]> git.cworth.org Git - apitrace/commitdiff
Add a very nice glsl editor code.
authorZack Rusin <zack@kde.org>
Tue, 12 Apr 2011 03:35:02 +0000 (23:35 -0400)
committerZack Rusin <zack@kde.org>
Tue, 12 Apr 2011 03:35:02 +0000 (23:35 -0400)
port of Ariya's bsd licensed javascript editor to glsl.

gui/CMakeLists.txt
gui/glsledit.cpp [new file with mode: 0644]
gui/glsledit.h [new file with mode: 0644]
gui/shaderssourcewidget.cpp
gui/shaderssourcewidget.h
gui/ui/mainwindow.ui
gui/ui/shaderssourcewidget.ui

index d12c9b3da8966b60eaf2cf6cf986c59bf887a7fc..15d14ce6edaf22bd46a72de003b274162a9964fb 100644 (file)
@@ -8,6 +8,7 @@ set(qapitrace_SRCS
    apitracecall.cpp
    apitracefilter.cpp
    apitracemodel.cpp
+   glsledit.cpp
    imageviewer.cpp
    jumpwidget.cpp
    loaderthread.cpp
diff --git a/gui/glsledit.cpp b/gui/glsledit.cpp
new file mode 100644 (file)
index 0000000..5c77882
--- /dev/null
@@ -0,0 +1,964 @@
+/*
+  This file is part of the Ofi Labs X2 project.
+
+  Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "glsledit.h"
+
+#include <QtGui>
+
+class GLSLBlockData: public QTextBlockUserData
+{
+public:
+    QList<int> bracketPositions;
+};
+
+class GLSLHighlighter : public QSyntaxHighlighter
+{
+public:
+    GLSLHighlighter(QTextDocument *parent = 0);
+    void setColor(GLSLEdit::ColorComponent component, const QColor &color);
+    void mark(const QString &str, Qt::CaseSensitivity caseSensitivity);
+
+protected:
+    void highlightBlock(const QString &text);
+
+private:
+    QSet<QString> m_keywords;
+    QSet<QString> m_types;
+    QSet<QString> m_builtins;
+    QHash<GLSLEdit::ColorComponent, QColor> m_colors;
+    QString m_markString;
+    Qt::CaseSensitivity m_markCaseSensitivity;
+};
+
+GLSLHighlighter::GLSLHighlighter(QTextDocument *parent)
+    : QSyntaxHighlighter(parent)
+    , m_markCaseSensitivity(Qt::CaseInsensitive)
+{
+    // default color scheme
+    m_colors[GLSLEdit::Normal]     = QColor("#000000");
+    m_colors[GLSLEdit::Comment]    = QColor("#808080");
+    m_colors[GLSLEdit::Number]     = QColor("#008000");
+    m_colors[GLSLEdit::String]     = QColor("#800000");
+    m_colors[GLSLEdit::Operator]   = QColor("#808000");
+    m_colors[GLSLEdit::Identifier] = QColor("#000020");
+    m_colors[GLSLEdit::Keyword]    = QColor("#000080");
+    m_colors[GLSLEdit::BuiltIn]    = QColor("#008080");
+    m_colors[GLSLEdit::Marker]     = QColor("#ffff00");
+
+    m_keywords << "attribute";
+    m_keywords << "const";
+    m_keywords << "uniform";
+    m_keywords << "varying";
+    m_keywords << "layout";
+    m_keywords << "centroid";
+    m_keywords << "flat";
+    m_keywords << "smooth";
+    m_keywords << "noperspective";
+    m_keywords << "patch";
+    m_keywords << "sample";
+    m_keywords << "break";
+    m_keywords << "continue";
+    m_keywords << "do";
+    m_keywords << "for";
+    m_keywords << "while";
+    m_keywords << "switch";
+    m_keywords << "case";
+    m_keywords << "default";
+    m_keywords << "if";
+    m_keywords << "else";
+    m_keywords << "subroutine";
+    m_keywords << "in";
+    m_keywords << "out";
+    m_keywords << "inout";
+    m_keywords << "true";
+    m_keywords << "false";
+    m_keywords << "invariant";
+    m_keywords << "discard";
+    m_keywords << "return";
+    m_keywords << "lowp";
+    m_keywords << "mediump";
+    m_keywords << "highp";
+    m_keywords << "precision";
+    m_keywords << "struct";
+
+    m_types << "float";
+    m_types << "double";
+    m_types << "int";
+    m_types << "void";
+    m_types << "bool";
+    m_types << "mat2";
+    m_types << "mat3";
+    m_types << "mat4";
+    m_types << "dmat2";
+    m_types << "dmat3";
+    m_types << "dmat4";
+    m_types << "mat2x2";
+    m_types << "mat2x3";
+    m_types << "mat2x4";
+    m_types << "dmat2x2";
+    m_types << "dmat2x3";
+    m_types << "dmat2x4";
+    m_types << "mat3x2";
+    m_types << "mat3x3";
+    m_types << "mat3x4";
+    m_types << "dmat3x2";
+    m_types << "dmat3x3";
+    m_types << "dmat3x4";
+    m_types << "mat4x2";
+    m_types << "mat4x3";
+    m_types << "mat4x4";
+    m_types << "dmat4x2";
+    m_types << "dmat4x3";
+    m_types << "dmat4x4";
+    m_types << "vec2";
+    m_types << "vec3";
+    m_types << "vec4";
+    m_types << "ivec2";
+    m_types << "ivec3";
+    m_types << "ivec4";
+    m_types << "bvec2";
+    m_types << "bvec3";
+    m_types << "bvec4";
+    m_types << "dvec2";
+    m_types << "dvec3";
+    m_types << "dvec4";
+    m_types << "uint";
+    m_types << "uvec2";
+    m_types << "uvec3";
+    m_types << "uvec4";
+    m_types << "sampler1D";
+    m_types << "sampler2D";
+    m_types << "sampler3D";
+    m_types << "samplerCube";
+    m_types << "sampler1DShadow";
+    m_types << "sampler2DShadow";
+    m_types << "samplerCubeShadow";
+    m_types << "sampler1DArray";
+    m_types << "sampler2DArray";
+    m_types << "sampler1DArrayShadow";
+    m_types << "sampler2DArrayShadow";
+    m_types << "isampler1D";
+    m_types << "isampler2D";
+    m_types << "isampler3D";
+    m_types << "isamplerCube";
+    m_types << "isampler1DArray";
+    m_types << "isampler2DArray";
+    m_types << "usampler1D";
+    m_types << "usampler2D";
+    m_types << "usampler3D";
+    m_types << "usamplerCube";
+    m_types << "usampler1DArray";
+    m_types << "usampler2DArray";
+    m_types << "sampler2DRect";
+    m_types << "sampler2DRectShadow";
+    m_types << "isampler2DRect";
+    m_types << "usampler2DRect";
+    m_types << "samplerBuffer";
+    m_types << "isamplerBuffer";
+    m_types << "usamplerBuffer";
+    m_types << "sampler2DMS";
+    m_types << "isampler2DMS";
+    m_types << "usampler2DMS";
+    m_types << "sampler2DMSArray";
+    m_types << "isampler2DMSArray";
+    m_types << "usampler2DMSArray";
+    m_types << "samplerCubeArray";
+    m_types << "samplerCubeArrayShadow";
+    m_types << "isamplerCubeArray";
+    m_types << "usamplerCubeArray";
+}
+
+void GLSLHighlighter::setColor(GLSLEdit::ColorComponent component, const QColor &color)
+{
+    m_colors[component] = color;
+    rehighlight();
+}
+
+void GLSLHighlighter::highlightBlock(const QString &text)
+{
+    // parsing state
+    enum {
+        Start = 0,
+        Number = 1,
+        Identifier = 2,
+        String = 3,
+        Comment = 4,
+        Regex = 5
+    };
+
+    QList<int> bracketPositions;
+
+    int blockState = previousBlockState();
+    int bracketLevel = blockState >> 4;
+    int state = blockState & 15;
+    if (blockState < 0) {
+        bracketLevel = 0;
+        state = Start;
+    }
+
+    int start = 0;
+    int i = 0;
+    while (i <= text.length()) {
+        QChar ch = (i < text.length()) ? text.at(i) : QChar();
+        QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar();
+
+        switch (state) {
+
+        case Start:
+            start = i;
+            if (ch.isSpace()) {
+                ++i;
+            } else if (ch.isDigit()) {
+                ++i;
+                state = Number;
+            } else if (ch.isLetter() || ch == '_') {
+                ++i;
+                state = Identifier;
+            } else if (ch == '\'' || ch == '\"') {
+                ++i;
+                state = String;
+            } else if (ch == '/' && next == '*') {
+                ++i;
+                ++i;
+                state = Comment;
+            } else if (ch == '/' && next == '/') {
+                i = text.length();
+                setFormat(start, text.length(), m_colors[GLSLEdit::Comment]);
+            } else if (ch == '/' && next != '*') {
+                ++i;
+                state = Regex;
+            } else {
+                if (!QString("(){}[]").contains(ch))
+                    setFormat(start, 1, m_colors[GLSLEdit::Operator]);
+                if (ch =='{' || ch == '}') {
+                    bracketPositions += i;
+                    if (ch == '{')
+                        bracketLevel++;
+                    else
+                        bracketLevel--;
+                }
+                ++i;
+                state = Start;
+            }
+            break;
+
+        case Number:
+            if (ch.isSpace() || !ch.isDigit()) {
+                setFormat(start, i - start, m_colors[GLSLEdit::Number]);
+                state = Start;
+            } else {
+                ++i;
+            }
+            break;
+
+        case Identifier:
+            if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) {
+                QString token = text.mid(start, i - start).trimmed();
+                if (m_keywords.contains(token))
+                    setFormat(start, i - start, m_colors[GLSLEdit::Keyword]);
+                else if (m_types.contains(token) || m_builtins.contains(token))
+                    setFormat(start, i - start, m_colors[GLSLEdit::BuiltIn]);
+                state = Start;
+            } else {
+                ++i;
+            }
+            break;
+
+        case String:
+            if (ch == text.at(start)) {
+                QChar prev = (i > 0) ? text.at(i - 1) : QChar();
+                if (prev != '\\') {
+                    ++i;
+                    setFormat(start, i - start, m_colors[GLSLEdit::String]);
+                    state = Start;
+                } else {
+                    ++i;
+                }
+            } else {
+                ++i;
+            }
+            break;
+
+        case Comment:
+            if (ch == '*' && next == '/') {
+                ++i;
+                ++i;
+                setFormat(start, i - start, m_colors[GLSLEdit::Comment]);
+                state = Start;
+            } else {
+                ++i;
+            }
+            break;
+
+        case Regex:
+            if (ch == '/') {
+                QChar prev = (i > 0) ? text.at(i - 1) : QChar();
+                if (prev != '\\') {
+                    ++i;
+                    setFormat(start, i - start, m_colors[GLSLEdit::String]);
+                    state = Start;
+                } else {
+                    ++i;
+                }
+            } else {
+                ++i;
+            }
+            break;
+
+        default:
+            state = Start;
+            break;
+        }
+    }
+
+    if (state == Comment)
+        setFormat(start, text.length(), m_colors[GLSLEdit::Comment]);
+    else
+        state = Start;
+
+    if (!m_markString.isEmpty()) {
+        int pos = 0;
+        int len = m_markString.length();
+        QTextCharFormat markerFormat;
+        markerFormat.setBackground(m_colors[GLSLEdit::Marker]);
+        markerFormat.setForeground(m_colors[GLSLEdit::Normal]);
+        for (;;) {
+            pos = text.indexOf(m_markString, pos, m_markCaseSensitivity);
+            if (pos < 0)
+                break;
+            setFormat(pos, len, markerFormat);
+            ++pos;
+        }
+    }
+
+    if (!bracketPositions.isEmpty()) {
+        GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(currentBlock().userData());
+        if (!blockData) {
+            blockData = new GLSLBlockData;
+            currentBlock().setUserData(blockData);
+        }
+        blockData->bracketPositions = bracketPositions;
+    }
+
+    blockState = (state & 15) | (bracketLevel << 4);
+    setCurrentBlockState(blockState);
+}
+
+void GLSLHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
+{
+    m_markString = str;
+    m_markCaseSensitivity = caseSensitivity;
+    rehighlight();
+}
+
+struct BlockInfo {
+    int position;
+    int number;
+    bool foldable: 1;
+    bool folded : 1;
+};
+
+Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE);
+
+class SidebarWidget : public QWidget
+{
+public:
+    SidebarWidget(GLSLEdit *editor);
+    QVector<BlockInfo> lineNumbers;
+    QColor backgroundColor;
+    QColor lineNumberColor;
+    QColor indicatorColor;
+    QColor foldIndicatorColor;
+    QFont font;
+    int foldIndicatorWidth;
+    QPixmap rightArrowIcon;
+    QPixmap downArrowIcon;
+protected:
+    void mousePressEvent(QMouseEvent *event);
+    void paintEvent(QPaintEvent *event);
+};
+
+SidebarWidget::SidebarWidget(GLSLEdit *editor)
+    : QWidget(editor)
+    , foldIndicatorWidth(0)
+{
+    backgroundColor = Qt::lightGray;
+    lineNumberColor = Qt::black;
+    indicatorColor = Qt::white;
+    foldIndicatorColor = Qt::lightGray;
+}
+
+void SidebarWidget::mousePressEvent(QMouseEvent *event)
+{
+    if (foldIndicatorWidth > 0) {
+        int xofs = width() - foldIndicatorWidth;
+        int lineNo = -1;
+        int fh = fontMetrics().lineSpacing();
+        int ys = event->pos().y();
+        if (event->pos().x() > xofs) {
+            foreach (BlockInfo ln, lineNumbers)
+                if (ln.position < ys && (ln.position + fh) > ys) {
+                    if (ln.foldable)
+                        lineNo = ln.number;
+                    break;
+                }
+        }
+        if (lineNo >= 0) {
+            GLSLEdit *editor = qobject_cast<GLSLEdit*>(parent());
+            if (editor)
+                editor->toggleFold(lineNo);
+        }
+    }
+}
+
+void SidebarWidget::paintEvent(QPaintEvent *event)
+{
+    QPainter p(this);
+    p.fillRect(event->rect(), backgroundColor);
+    p.setPen(lineNumberColor);
+    p.setFont(font);
+    int fh = QFontMetrics(font).height();
+    foreach (BlockInfo ln, lineNumbers)
+        p.drawText(0, ln.position, width() - 4 - foldIndicatorWidth, fh, Qt::AlignRight, QString::number(ln.number));
+
+    if (foldIndicatorWidth > 0) {
+        int xofs = width() - foldIndicatorWidth;
+        p.fillRect(xofs, 0, foldIndicatorWidth, height(), indicatorColor);
+
+        // initialize (or recreate) the arrow icons whenever necessary
+        if (foldIndicatorWidth != rightArrowIcon.width()) {
+            QPainter iconPainter;
+            QPolygonF polygon;
+
+            int dim = foldIndicatorWidth;
+            rightArrowIcon = QPixmap(dim, dim);
+            rightArrowIcon.fill(Qt::transparent);
+            downArrowIcon = rightArrowIcon;
+
+            polygon << QPointF(dim * 0.4, dim * 0.25);
+            polygon << QPointF(dim * 0.4, dim * 0.75);
+            polygon << QPointF(dim * 0.8, dim * 0.5);
+            iconPainter.begin(&rightArrowIcon);
+            iconPainter.setRenderHint(QPainter::Antialiasing);
+            iconPainter.setPen(Qt::NoPen);
+            iconPainter.setBrush(foldIndicatorColor);
+            iconPainter.drawPolygon(polygon);
+            iconPainter.end();
+
+            polygon.clear();
+            polygon << QPointF(dim * 0.25, dim * 0.4);
+            polygon << QPointF(dim * 0.75, dim * 0.4);
+            polygon << QPointF(dim * 0.5, dim * 0.8);
+            iconPainter.begin(&downArrowIcon);
+            iconPainter.setRenderHint(QPainter::Antialiasing);
+            iconPainter.setPen(Qt::NoPen);
+            iconPainter.setBrush(foldIndicatorColor);
+            iconPainter.drawPolygon(polygon);
+            iconPainter.end();
+        }
+
+        foreach (BlockInfo ln, lineNumbers)
+            if (ln.foldable) {
+                if (ln.folded)
+                    p.drawPixmap(xofs, ln.position, rightArrowIcon);
+                else
+                    p.drawPixmap(xofs, ln.position, downArrowIcon);
+            }
+    }
+}
+
+static int findClosingMatch(const QTextDocument *doc, int cursorPosition)
+{
+    QTextBlock block = doc->findBlock(cursorPosition);
+    GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
+    if (!blockData->bracketPositions.isEmpty()) {
+        int depth = 1;
+        while (block.isValid()) {
+            blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
+            if (blockData && !blockData->bracketPositions.isEmpty()) {
+                for (int c = 0; c < blockData->bracketPositions.count(); ++c) {
+                    int absPos = block.position() + blockData->bracketPositions.at(c);
+                    if (absPos <= cursorPosition)
+                        continue;
+                    if (doc->characterAt(absPos) == '{')
+                        depth++;
+                    else
+                        depth--;
+                    if (depth == 0)
+                        return absPos;
+                }
+            }
+            block = block.next();
+        }
+    }
+    return -1;
+}
+
+static int findOpeningMatch(const QTextDocument *doc, int cursorPosition)
+{
+    QTextBlock block = doc->findBlock(cursorPosition);
+    GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
+    if (!blockData->bracketPositions.isEmpty()) {
+        int depth = 1;
+        while (block.isValid()) {
+            blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
+            if (blockData && !blockData->bracketPositions.isEmpty()) {
+                for (int c = blockData->bracketPositions.count() - 1; c >= 0; --c) {
+                    int absPos = block.position() + blockData->bracketPositions.at(c);
+                    if (absPos >= cursorPosition - 1)
+                        continue;
+                    if (doc->characterAt(absPos) == '}')
+                        depth++;
+                    else
+                        depth--;
+                    if (depth == 0)
+                        return absPos;
+                }
+            }
+            block = block.previous();
+        }
+    }
+    return -1;
+}
+
+class GLSLDocLayout: public QPlainTextDocumentLayout
+{
+public:
+    GLSLDocLayout(QTextDocument *doc);
+    void forceUpdate();
+};
+
+GLSLDocLayout::GLSLDocLayout(QTextDocument *doc)
+    : QPlainTextDocumentLayout(doc)
+{
+}
+
+void GLSLDocLayout::forceUpdate()
+{
+    emit documentSizeChanged(documentSize());
+}
+
+class GLSLEditPrivate
+{
+public:
+    GLSLEdit *editor;
+    GLSLDocLayout *layout;
+    GLSLHighlighter *highlighter;
+    SidebarWidget *sidebar;
+    bool showLineNumbers;
+    bool textWrap;
+    QColor cursorColor;
+    bool bracketsMatching;
+    QList<int> matchPositions;
+    QColor bracketMatchColor;
+    QList<int> errorPositions;
+    QColor bracketErrorColor;
+    bool codeFolding : 1;
+};
+
+GLSLEdit::GLSLEdit(QWidget *parent)
+    : QPlainTextEdit(parent)
+    , d_ptr(new GLSLEditPrivate)
+{
+    d_ptr->editor = this;
+    d_ptr->layout = new GLSLDocLayout(document());
+    d_ptr->highlighter = new GLSLHighlighter(document());
+    d_ptr->sidebar = new SidebarWidget(this);
+    d_ptr->showLineNumbers = true;
+    d_ptr->textWrap = true;
+    d_ptr->bracketsMatching = true;
+    d_ptr->cursorColor = QColor(255, 255, 192);
+    d_ptr->bracketMatchColor = QColor(180, 238, 180);
+    d_ptr->bracketErrorColor = QColor(224, 128, 128);
+    d_ptr->codeFolding = true;
+
+    document()->setDocumentLayout(d_ptr->layout);
+
+    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursor()));
+    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateSidebar()));
+    connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateSidebar(QRect, int)));
+
+#if defined(Q_OS_MAC)
+    QFont textFont = font();
+    textFont.setPointSize(12);
+    textFont.setFamily("Monaco");
+    setFont(textFont);
+#elif defined(Q_OS_UNIX)
+    QFont textFont = font();
+    textFont.setFamily("Monospace");
+    setFont(textFont);
+#endif
+}
+
+GLSLEdit::~GLSLEdit()
+{
+    delete d_ptr->layout;
+}
+
+void GLSLEdit::setColor(ColorComponent component, const QColor &color)
+{
+    Q_D(GLSLEdit);
+
+    if (component == Background) {
+        QPalette pal = palette();
+        pal.setColor(QPalette::Base, color);
+        setPalette(pal);
+        d->sidebar->indicatorColor = color;
+        updateSidebar();
+    } else if (component == Normal) {
+        QPalette pal = palette();
+        pal.setColor(QPalette::Text, color);
+        setPalette(pal);
+    } else if (component == Sidebar) {
+        d->sidebar->backgroundColor = color;
+        updateSidebar();
+    } else if (component == LineNumber) {
+        d->sidebar->lineNumberColor = color;
+        updateSidebar();
+    } else if (component == Cursor) {
+        d->cursorColor = color;
+        updateCursor();
+    } else if (component == BracketMatch) {
+        d->bracketMatchColor = color;
+        updateCursor();
+    } else if (component == BracketError) {
+        d->bracketErrorColor = color;
+        updateCursor();
+    } else if (component == FoldIndicator) {
+        d->sidebar->foldIndicatorColor = color;
+        updateSidebar();
+    } else {
+        d->highlighter->setColor(component, color);
+        updateCursor();
+    }
+}
+
+bool GLSLEdit::isLineNumbersVisible() const
+{
+    return d_ptr->showLineNumbers;
+}
+
+void GLSLEdit::setLineNumbersVisible(bool visible)
+{
+    d_ptr->showLineNumbers = visible;
+    updateSidebar();
+}
+
+bool GLSLEdit::isTextWrapEnabled() const
+{
+    return d_ptr->textWrap;
+}
+
+void GLSLEdit::setTextWrapEnabled(bool enable)
+{
+    d_ptr->textWrap = enable;
+    setLineWrapMode(enable ? WidgetWidth : NoWrap);
+}
+
+bool GLSLEdit::isBracketsMatchingEnabled() const
+{
+    return d_ptr->bracketsMatching;
+}
+
+void GLSLEdit::setBracketsMatchingEnabled(bool enable)
+{
+    d_ptr->bracketsMatching = enable;
+    updateCursor();
+}
+
+bool GLSLEdit::isCodeFoldingEnabled() const
+{
+    return d_ptr->codeFolding;
+}
+
+void GLSLEdit::setCodeFoldingEnabled(bool enable)
+{
+    d_ptr->codeFolding = enable;
+    updateSidebar();
+}
+
+static int findClosingConstruct(const QTextBlock &block)
+{
+    if (!block.isValid())
+        return -1;
+    GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
+    if (!blockData)
+        return -1;
+    if (blockData->bracketPositions.isEmpty())
+        return -1;
+    const QTextDocument *doc = block.document();
+    int offset = block.position();
+    foreach (int pos, blockData->bracketPositions) {
+        int absPos = offset + pos;
+        if (doc->characterAt(absPos) == '{') {
+            int matchPos = findClosingMatch(doc, absPos);
+            if (matchPos >= 0)
+                return matchPos;
+        }
+    }
+    return -1;
+}
+
+bool GLSLEdit::isFoldable(int line) const
+{
+    int matchPos = findClosingConstruct(document()->findBlockByNumber(line - 1));
+    if (matchPos >= 0) {
+        QTextBlock matchBlock = document()->findBlock(matchPos);
+        if (matchBlock.isValid() && matchBlock.blockNumber() > line)
+            return true;
+    }
+    return false;
+}
+
+bool GLSLEdit::isFolded(int line) const
+{
+    QTextBlock block = document()->findBlockByNumber(line - 1);
+    if (!block.isValid())
+        return false;
+    block = block.next();
+    if (!block.isValid())
+        return false;
+    return !block.isVisible();
+}
+
+void GLSLEdit::fold(int line)
+{
+    QTextBlock startBlock = document()->findBlockByNumber(line - 1);
+    int endPos = findClosingConstruct(startBlock);
+    if (endPos < 0)
+        return;
+    QTextBlock endBlock = document()->findBlock(endPos);
+
+    QTextBlock block = startBlock.next();
+    while (block.isValid() && block != endBlock) {
+        block.setVisible(false);
+        block.setLineCount(0);
+        block = block.next();
+    }
+
+    document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
+    updateSidebar();
+    update();
+
+    GLSLDocLayout *layout = reinterpret_cast<GLSLDocLayout*>(document()->documentLayout());
+    layout->forceUpdate();
+}
+
+void GLSLEdit::unfold(int line)
+{
+    QTextBlock startBlock = document()->findBlockByNumber(line - 1);
+    int endPos = findClosingConstruct(startBlock);
+
+    QTextBlock block = startBlock.next();
+    while (block.isValid() && !block.isVisible()) {
+        block.setVisible(true);
+        block.setLineCount(block.layout()->lineCount());
+        endPos = block.position() + block.length();
+        block = block.next();
+    }
+
+    document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
+    updateSidebar();
+    update();
+
+    GLSLDocLayout *layout = reinterpret_cast<GLSLDocLayout*>(document()->documentLayout());
+    layout->forceUpdate();
+}
+
+void GLSLEdit::toggleFold(int line)
+{
+    if (isFolded(line))
+        unfold(line);
+    else
+        fold(line);
+}
+
+void GLSLEdit::resizeEvent(QResizeEvent *e)
+{
+    QPlainTextEdit::resizeEvent(e);
+    updateSidebar();
+}
+
+void GLSLEdit::wheelEvent(QWheelEvent *e)
+{
+    if (e->modifiers() == Qt::ControlModifier) {
+        int steps = e->delta() / 20;
+        steps = qBound(-3, steps, 3);
+        QFont textFont = font();
+        int pointSize = textFont.pointSize() + steps;
+        pointSize = qBound(10, pointSize, 40);
+        textFont.setPointSize(pointSize);
+        setFont(textFont);
+        updateSidebar();
+        e->accept();
+        return;
+    }
+    QPlainTextEdit::wheelEvent(e);
+}
+
+
+void GLSLEdit::updateCursor()
+{
+    Q_D(GLSLEdit);
+
+    if (isReadOnly()) {
+        setExtraSelections(QList<QTextEdit::ExtraSelection>());
+    } else {
+
+        d->matchPositions.clear();
+        d->errorPositions.clear();
+
+        if (d->bracketsMatching && textCursor().block().userData()) {
+            QTextCursor cursor = textCursor();
+            int cursorPosition = cursor.position();
+
+            if (document()->characterAt(cursorPosition) == '{') {
+                int matchPos = findClosingMatch(document(), cursorPosition);
+                if (matchPos < 0) {
+                    d->errorPositions += cursorPosition;
+                } else {
+                    d->matchPositions += cursorPosition;
+                    d->matchPositions += matchPos;
+                }
+            }
+
+            if (document()->characterAt(cursorPosition - 1) == '}') {
+                int matchPos = findOpeningMatch(document(), cursorPosition);
+                if (matchPos < 0) {
+                    d->errorPositions += cursorPosition - 1;
+                } else {
+                    d->matchPositions += cursorPosition - 1;
+                    d->matchPositions += matchPos;
+                }
+            }
+        }
+
+        QTextEdit::ExtraSelection highlight;
+        highlight.format.setBackground(d->cursorColor);
+        highlight.format.setProperty(QTextFormat::FullWidthSelection, true);
+        highlight.cursor = textCursor();
+        highlight.cursor.clearSelection();
+
+        QList<QTextEdit::ExtraSelection> extraSelections;
+        extraSelections.append(highlight);
+
+        for (int i = 0; i < d->matchPositions.count(); ++i) {
+            int pos = d->matchPositions.at(i);
+            QTextEdit::ExtraSelection matchHighlight;
+            matchHighlight.format.setBackground(d->bracketMatchColor);
+            matchHighlight.cursor = textCursor();
+            matchHighlight.cursor.setPosition(pos);
+            matchHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
+            extraSelections.append(matchHighlight);
+        }
+
+        for (int i = 0; i < d->errorPositions.count(); ++i) {
+            int pos = d->errorPositions.at(i);
+            QTextEdit::ExtraSelection errorHighlight;
+            errorHighlight.format.setBackground(d->bracketErrorColor);
+            errorHighlight.cursor = textCursor();
+            errorHighlight.cursor.setPosition(pos);
+            errorHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
+            extraSelections.append(errorHighlight);
+        }
+
+        setExtraSelections(extraSelections);
+    }
+}
+
+void GLSLEdit::updateSidebar(const QRect &rect, int d)
+{
+    Q_UNUSED(rect)
+    if (d != 0)
+        updateSidebar();
+}
+
+void GLSLEdit::updateSidebar()
+{
+    Q_D(GLSLEdit);
+
+    if (!d->showLineNumbers && !d->codeFolding) {
+        d->sidebar->hide();
+        setViewportMargins(0, 0, 0, 0);
+        d->sidebar->setGeometry(3, 0, 0, height());
+        return;
+    }
+
+    d->sidebar->foldIndicatorWidth = 0;
+    d->sidebar->font = this->font();
+    d->sidebar->show();
+
+    int sw = 0;
+    if (d->showLineNumbers) {
+        int digits = 2;
+        int maxLines = blockCount();
+        for (int number = 10; number < maxLines; number *= 10)
+            ++digits;
+        sw += fontMetrics().width('w') * digits;
+    }
+    if (d->codeFolding) {
+        int fh = fontMetrics().lineSpacing();
+        int fw = fontMetrics().width('w');
+        d->sidebar->foldIndicatorWidth = qMax(fw, fh);
+        sw += d->sidebar->foldIndicatorWidth;
+    }
+    setViewportMargins(sw, 0, 0, 0);
+
+    d->sidebar->setGeometry(0, 0, sw, height());
+    QRectF sidebarRect(0, 0, sw, height());
+
+    QTextBlock block = firstVisibleBlock();
+    int index = 0;
+    while (block.isValid()) {
+        if (block.isVisible()) {
+            QRectF rect = blockBoundingGeometry(block).translated(contentOffset());
+            if (sidebarRect.intersects(rect)) {
+                if (d->sidebar->lineNumbers.count() >= index)
+                    d->sidebar->lineNumbers.resize(index + 1);
+                d->sidebar->lineNumbers[index].position = rect.top();
+                d->sidebar->lineNumbers[index].number = block.blockNumber() + 1;
+                d->sidebar->lineNumbers[index].foldable = d->codeFolding ? isFoldable(block.blockNumber() + 1) : false;
+                d->sidebar->lineNumbers[index].folded = d->codeFolding ? isFolded(block.blockNumber() + 1) : false;
+                ++index;
+            }
+            if (rect.top() > sidebarRect.bottom())
+                break;
+        }
+        block = block.next();
+    }
+    d->sidebar->lineNumbers.resize(index);
+    d->sidebar->update();
+}
+
+void GLSLEdit::mark(const QString &str, Qt::CaseSensitivity sens)
+{
+    d_ptr->highlighter->mark(str, sens);
+}
+
+#include "glsledit.moc"
diff --git a/gui/glsledit.h b/gui/glsledit.h
new file mode 100644 (file)
index 0000000..e4412a2
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+  This file is part of the Ofi Labs X2 project.
+
+  Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef OFILABS_GLSLEDIT
+#define OFILABS_GLSLEDIT
+
+#include <QColor>
+#include <QPlainTextEdit>
+#include <QScopedPointer>
+
+class GLSLEditPrivate;
+
+class GLSLEdit: public QPlainTextEdit
+{
+    Q_OBJECT
+    Q_PROPERTY(bool bracketsMatchingEnabled READ isBracketsMatchingEnabled WRITE setBracketsMatchingEnabled)
+    Q_PROPERTY(bool codeFoldingEnabled READ isCodeFoldingEnabled WRITE setCodeFoldingEnabled)
+    Q_PROPERTY(bool lineNumbersVisible READ isLineNumbersVisible WRITE setLineNumbersVisible)
+    Q_PROPERTY(bool textWrapEnabled READ isTextWrapEnabled WRITE setTextWrapEnabled)
+
+public:
+
+    typedef enum {
+        Background,
+        Normal,
+        Comment,
+        Number,
+        String,
+        Operator,
+        Identifier,
+        Keyword,
+        BuiltIn,
+        Sidebar,
+        LineNumber,
+        Cursor,
+        Marker,
+        BracketMatch,
+        BracketError,
+        FoldIndicator
+    } ColorComponent;
+
+    GLSLEdit(QWidget *parent = 0);
+    ~GLSLEdit();
+
+    void setColor(ColorComponent component, const QColor &color);
+
+    bool isBracketsMatchingEnabled() const;
+    bool isCodeFoldingEnabled() const;
+    bool isLineNumbersVisible() const;
+    bool isTextWrapEnabled() const;
+
+    bool isFoldable(int line) const;
+    bool isFolded(int line) const;
+
+public slots:
+    void updateSidebar();
+    void mark(const QString &str, Qt::CaseSensitivity sens = Qt::CaseInsensitive);
+    void setBracketsMatchingEnabled(bool enable);
+    void setCodeFoldingEnabled(bool enable);
+    void setLineNumbersVisible(bool visible);
+    void setTextWrapEnabled(bool enable);
+
+    void fold(int line);
+    void unfold(int line);
+    void toggleFold(int line);
+
+protected:
+    void resizeEvent(QResizeEvent *e);
+    void wheelEvent(QWheelEvent *e);
+
+private slots:
+    void updateCursor();
+    void updateSidebar(const QRect &rect, int d);
+
+private:
+    QScopedPointer<GLSLEditPrivate> d_ptr;
+    Q_DECLARE_PRIVATE(GLSLEdit);
+    Q_DISABLE_COPY(GLSLEdit);
+};
+
+#endif // OFILABS_GLSLEDIT
index 0bec7a7a83651804de58d9ed77e8fadfce476f48..5003dda11752d8bf435982cc854df39c7552007b 100644 (file)
@@ -1,9 +1,15 @@
 #include "shaderssourcewidget.h"
 
+#include "glsledit.h"
+
 ShadersSourceWidget::ShadersSourceWidget(QWidget *parent)
     : QWidget(parent)
 {
     m_ui.setupUi(this);
+    m_edit = new GLSLEdit(this);
+    m_edit->setReadOnly(true);
+
+    m_ui.verticalLayout->addWidget(m_edit);
 
     connect(m_ui.shadersCB, SIGNAL(currentIndexChanged(int)),
             SLOT(changeShader(int)));
@@ -14,18 +20,17 @@ void ShadersSourceWidget::setShaders(const QStringList &sources)
     m_sources = sources;
 
     m_ui.shadersCB->clear();
-    m_ui.shadersTextEdit->clear();
+    m_edit->clear();
 
     if (m_sources.isEmpty()) {
         m_ui.shadersCB->setDisabled(true);
-        m_ui.shadersTextEdit->setPlainText(
-            tr("No bound shaders."));
-        m_ui.shadersTextEdit->setDisabled(true);
+        m_edit->setPlainText(tr("No bound shaders."));
+        m_edit->setDisabled(true);
         return;
     }
 
     m_ui.shadersCB->setEnabled(true);
-    m_ui.shadersTextEdit->setEnabled(true);
+    m_edit->setEnabled(true);
 
     for (int i = 0; i < m_sources.count(); ++i) {
         QString source = m_sources[i];
@@ -38,7 +43,7 @@ void ShadersSourceWidget::setShaders(const QStringList &sources)
 
 void ShadersSourceWidget::changeShader(int idx)
 {
-    m_ui.shadersTextEdit->setPlainText(m_sources.value(idx));
+    m_edit->setPlainText(m_sources.value(idx));
 }
 
 #include "shaderssourcewidget.moc"
index 4d7876d6b31cb66b11e8d2b11e277c271c62667d..c4bc3eb39ad784d235b84750703cab28105ee4c3 100644 (file)
@@ -4,6 +4,8 @@
 #include "ui_shaderssourcewidget.h"
 #include <QWidget>
 
+class GLSLEdit;
+
 class ShadersSourceWidget : public QWidget
 {
     Q_OBJECT
@@ -18,6 +20,7 @@ private slots:
 private:
     Ui::ShadersSourceWidget m_ui;
     QStringList m_sources;
+    GLSLEdit *m_edit;
 };
 
 #endif
index 9bf49936da16385f3e46ceff7b42d887ec710a32..09c164c2f7be6c4c1659dee5cea6464ed0664f70 100644 (file)
      <height>21</height>
     </rect>
    </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>&amp;File</string>
+    </property>
+    <addaction name="actionNew"/>
+    <addaction name="actionOpen"/>
+    <addaction name="separator"/>
+    <addaction name="actionQuit"/>
+   </widget>
    <widget class="QMenu" name="menu_Trace">
     <property name="title">
      <string>&amp;Trace</string>
     <addaction name="separator"/>
     <addaction name="actionOptions"/>
    </widget>
-   <widget class="QMenu" name="menuFile">
-    <property name="title">
-     <string>&amp;File</string>
-    </property>
-    <addaction name="actionNew"/>
-    <addaction name="actionOpen"/>
-    <addaction name="separator"/>
-    <addaction name="actionQuit"/>
-   </widget>
    <addaction name="menuFile"/>
    <addaction name="menu_Trace"/>
   </widget>
index 698eebfbb64d0b382d23fee725e2b71a801bff36..6ca7b8f819e210e35d8543eeacc418e1875c392c 100644 (file)
      </item>
     </layout>
    </item>
-   <item>
-    <widget class="QPlainTextEdit" name="shadersTextEdit">
-     <property name="readOnly">
-      <bool>true</bool>
-     </property>
-     <property name="textInteractionFlags">
-      <set>Qt::TextSelectableByMouse</set>
-     </property>
-    </widget>
-   </item>
   </layout>
  </widget>
  <resources/>