]> git.cworth.org Git - apitrace/blob - gui/glsledit.cpp
Use skiplist-based FastCallSet within trace::CallSet
[apitrace] / gui / glsledit.cpp
1 /*
2   This file is part of the Ofi Labs X2 project.
3
4   Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
5
6   Redistribution and use in source and binary forms, with or without
7   modification, are permitted provided that the following conditions are met:
8
9     * Redistributions of source code must retain the above copyright
10       notice, this list of conditions and the following disclaimer.
11     * Redistributions in binary form must reproduce the above copyright
12       notice, this list of conditions and the following disclaimer in the
13       documentation and/or other materials provided with the distribution.
14     * Neither the name of the <organization> nor the
15       names of its contributors may be used to endorse or promote products
16       derived from this software without specific prior written permission.
17
18   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21   ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "glsledit.h"
31
32 #include <QtGui>
33
34 class GLSLBlockData: public QTextBlockUserData
35 {
36 public:
37     QList<int> bracketPositions;
38 };
39
40 class GLSLHighlighter : public QSyntaxHighlighter
41 {
42 public:
43     GLSLHighlighter(QTextDocument *parent = 0);
44     void setColor(GLSLEdit::ColorComponent component, const QColor &color);
45     void mark(const QString &str, Qt::CaseSensitivity caseSensitivity);
46
47 protected:
48     void highlightBlock(const QString &text);
49
50 private:
51     QSet<QString> m_keywords;
52     QSet<QString> m_types;
53     QSet<QString> m_builtins;
54     QHash<GLSLEdit::ColorComponent, QColor> m_colors;
55     QString m_markString;
56     Qt::CaseSensitivity m_markCaseSensitivity;
57 };
58
59 GLSLHighlighter::GLSLHighlighter(QTextDocument *parent)
60     : QSyntaxHighlighter(parent)
61     , m_markCaseSensitivity(Qt::CaseInsensitive)
62 {
63     // default color scheme
64     m_colors[GLSLEdit::Normal]     = QColor("#000000");
65     m_colors[GLSLEdit::Comment]    = QColor("#808080");
66     m_colors[GLSLEdit::Number]     = QColor("#008000");
67     m_colors[GLSLEdit::String]     = QColor("#800000");
68     m_colors[GLSLEdit::Operator]   = QColor("#808000");
69     m_colors[GLSLEdit::Identifier] = QColor("#000020");
70     m_colors[GLSLEdit::Keyword]    = QColor("#000080");
71     m_colors[GLSLEdit::BuiltIn]    = QColor("#008080");
72     m_colors[GLSLEdit::Marker]     = QColor("#ffff00");
73
74     m_keywords << "attribute";
75     m_keywords << "const";
76     m_keywords << "uniform";
77     m_keywords << "varying";
78     m_keywords << "layout";
79     m_keywords << "centroid";
80     m_keywords << "flat";
81     m_keywords << "smooth";
82     m_keywords << "noperspective";
83     m_keywords << "patch";
84     m_keywords << "sample";
85     m_keywords << "break";
86     m_keywords << "continue";
87     m_keywords << "do";
88     m_keywords << "for";
89     m_keywords << "while";
90     m_keywords << "switch";
91     m_keywords << "case";
92     m_keywords << "default";
93     m_keywords << "if";
94     m_keywords << "else";
95     m_keywords << "subroutine";
96     m_keywords << "in";
97     m_keywords << "out";
98     m_keywords << "inout";
99     m_keywords << "true";
100     m_keywords << "false";
101     m_keywords << "invariant";
102     m_keywords << "discard";
103     m_keywords << "return";
104     m_keywords << "lowp";
105     m_keywords << "mediump";
106     m_keywords << "highp";
107     m_keywords << "precision";
108     m_keywords << "struct";
109
110     m_types << "float";
111     m_types << "double";
112     m_types << "int";
113     m_types << "void";
114     m_types << "bool";
115     m_types << "mat2";
116     m_types << "mat3";
117     m_types << "mat4";
118     m_types << "dmat2";
119     m_types << "dmat3";
120     m_types << "dmat4";
121     m_types << "mat2x2";
122     m_types << "mat2x3";
123     m_types << "mat2x4";
124     m_types << "dmat2x2";
125     m_types << "dmat2x3";
126     m_types << "dmat2x4";
127     m_types << "mat3x2";
128     m_types << "mat3x3";
129     m_types << "mat3x4";
130     m_types << "dmat3x2";
131     m_types << "dmat3x3";
132     m_types << "dmat3x4";
133     m_types << "mat4x2";
134     m_types << "mat4x3";
135     m_types << "mat4x4";
136     m_types << "dmat4x2";
137     m_types << "dmat4x3";
138     m_types << "dmat4x4";
139     m_types << "vec2";
140     m_types << "vec3";
141     m_types << "vec4";
142     m_types << "ivec2";
143     m_types << "ivec3";
144     m_types << "ivec4";
145     m_types << "bvec2";
146     m_types << "bvec3";
147     m_types << "bvec4";
148     m_types << "dvec2";
149     m_types << "dvec3";
150     m_types << "dvec4";
151     m_types << "uint";
152     m_types << "uvec2";
153     m_types << "uvec3";
154     m_types << "uvec4";
155     m_types << "sampler1D";
156     m_types << "sampler2D";
157     m_types << "sampler3D";
158     m_types << "samplerCube";
159     m_types << "sampler1DShadow";
160     m_types << "sampler2DShadow";
161     m_types << "samplerCubeShadow";
162     m_types << "sampler1DArray";
163     m_types << "sampler2DArray";
164     m_types << "sampler1DArrayShadow";
165     m_types << "sampler2DArrayShadow";
166     m_types << "isampler1D";
167     m_types << "isampler2D";
168     m_types << "isampler3D";
169     m_types << "isamplerCube";
170     m_types << "isampler1DArray";
171     m_types << "isampler2DArray";
172     m_types << "usampler1D";
173     m_types << "usampler2D";
174     m_types << "usampler3D";
175     m_types << "usamplerCube";
176     m_types << "usampler1DArray";
177     m_types << "usampler2DArray";
178     m_types << "sampler2DRect";
179     m_types << "sampler2DRectShadow";
180     m_types << "isampler2DRect";
181     m_types << "usampler2DRect";
182     m_types << "samplerBuffer";
183     m_types << "isamplerBuffer";
184     m_types << "usamplerBuffer";
185     m_types << "sampler2DMS";
186     m_types << "isampler2DMS";
187     m_types << "usampler2DMS";
188     m_types << "sampler2DMSArray";
189     m_types << "isampler2DMSArray";
190     m_types << "usampler2DMSArray";
191     m_types << "samplerCubeArray";
192     m_types << "samplerCubeArrayShadow";
193     m_types << "isamplerCubeArray";
194     m_types << "usamplerCubeArray";
195 }
196
197 void GLSLHighlighter::setColor(GLSLEdit::ColorComponent component, const QColor &color)
198 {
199     m_colors[component] = color;
200     rehighlight();
201 }
202
203 void GLSLHighlighter::highlightBlock(const QString &text)
204 {
205     // parsing state
206     enum {
207         Start = 0,
208         Number = 1,
209         Identifier = 2,
210         String = 3,
211         Comment = 4,
212         Regex = 5
213     };
214
215     QList<int> bracketPositions;
216
217     int blockState = previousBlockState();
218     int bracketLevel = blockState >> 4;
219     int state = blockState & 15;
220     if (blockState < 0) {
221         bracketLevel = 0;
222         state = Start;
223     }
224
225     int start = 0;
226     int i = 0;
227     while (i <= text.length()) {
228         QChar ch = (i < text.length()) ? text.at(i) : QChar();
229         QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar();
230
231         switch (state) {
232
233         case Start:
234             start = i;
235             if (ch.isSpace()) {
236                 ++i;
237             } else if (ch.isDigit()) {
238                 ++i;
239                 state = Number;
240             } else if (ch.isLetter() || ch == '_') {
241                 ++i;
242                 state = Identifier;
243             } else if (ch == '\'' || ch == '\"') {
244                 ++i;
245                 state = String;
246             } else if (ch == '/' && next == '*') {
247                 ++i;
248                 ++i;
249                 state = Comment;
250             } else if (ch == '/' && next == '/') {
251                 i = text.length();
252                 setFormat(start, text.length(), m_colors[GLSLEdit::Comment]);
253             } else if (ch == '/' && next != '*') {
254                 ++i;
255                 state = Regex;
256             } else {
257                 if (!QString("(){}[]").contains(ch))
258                     setFormat(start, 1, m_colors[GLSLEdit::Operator]);
259                 if (ch =='{' || ch == '}') {
260                     bracketPositions += i;
261                     if (ch == '{')
262                         bracketLevel++;
263                     else
264                         bracketLevel--;
265                 }
266                 ++i;
267                 state = Start;
268             }
269             break;
270
271         case Number:
272             if (ch.isSpace() || !ch.isDigit()) {
273                 setFormat(start, i - start, m_colors[GLSLEdit::Number]);
274                 state = Start;
275             } else {
276                 ++i;
277             }
278             break;
279
280         case Identifier:
281             if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) {
282                 QString token = text.mid(start, i - start).trimmed();
283                 if (m_keywords.contains(token))
284                     setFormat(start, i - start, m_colors[GLSLEdit::Keyword]);
285                 else if (m_types.contains(token) || m_builtins.contains(token))
286                     setFormat(start, i - start, m_colors[GLSLEdit::BuiltIn]);
287                 state = Start;
288             } else {
289                 ++i;
290             }
291             break;
292
293         case String:
294             if (ch == text.at(start)) {
295                 QChar prev = (i > 0) ? text.at(i - 1) : QChar();
296                 if (prev != '\\') {
297                     ++i;
298                     setFormat(start, i - start, m_colors[GLSLEdit::String]);
299                     state = Start;
300                 } else {
301                     ++i;
302                 }
303             } else {
304                 ++i;
305             }
306             break;
307
308         case Comment:
309             if (ch == '*' && next == '/') {
310                 ++i;
311                 ++i;
312                 setFormat(start, i - start, m_colors[GLSLEdit::Comment]);
313                 state = Start;
314             } else {
315                 ++i;
316             }
317             break;
318
319         case Regex:
320             if (ch == '/') {
321                 QChar prev = (i > 0) ? text.at(i - 1) : QChar();
322                 if (prev != '\\') {
323                     ++i;
324                     setFormat(start, i - start, m_colors[GLSLEdit::String]);
325                     state = Start;
326                 } else {
327                     ++i;
328                 }
329             } else {
330                 ++i;
331             }
332             break;
333
334         default:
335             state = Start;
336             break;
337         }
338     }
339
340     if (state == Comment)
341         setFormat(start, text.length(), m_colors[GLSLEdit::Comment]);
342     else
343         state = Start;
344
345     if (!m_markString.isEmpty()) {
346         int pos = 0;
347         int len = m_markString.length();
348         QTextCharFormat markerFormat;
349         markerFormat.setBackground(m_colors[GLSLEdit::Marker]);
350         markerFormat.setForeground(m_colors[GLSLEdit::Normal]);
351         for (;;) {
352             pos = text.indexOf(m_markString, pos, m_markCaseSensitivity);
353             if (pos < 0)
354                 break;
355             setFormat(pos, len, markerFormat);
356             ++pos;
357         }
358     }
359
360     if (!bracketPositions.isEmpty()) {
361         GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(currentBlock().userData());
362         if (!blockData) {
363             blockData = new GLSLBlockData;
364             currentBlock().setUserData(blockData);
365         }
366         blockData->bracketPositions = bracketPositions;
367     }
368
369     blockState = (state & 15) | (bracketLevel << 4);
370     setCurrentBlockState(blockState);
371 }
372
373 void GLSLHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
374 {
375     m_markString = str;
376     m_markCaseSensitivity = caseSensitivity;
377     rehighlight();
378 }
379
380 struct BlockInfo {
381     int position;
382     int number;
383     bool foldable: 1;
384     bool folded : 1;
385 };
386
387 Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE);
388
389 class SidebarWidget : public QWidget
390 {
391 public:
392     SidebarWidget(GLSLEdit *editor);
393     QVector<BlockInfo> lineNumbers;
394     QColor backgroundColor;
395     QColor lineNumberColor;
396     QColor indicatorColor;
397     QColor foldIndicatorColor;
398     QFont font;
399     int foldIndicatorWidth;
400     QPixmap rightArrowIcon;
401     QPixmap downArrowIcon;
402 protected:
403     void mousePressEvent(QMouseEvent *event);
404     void paintEvent(QPaintEvent *event);
405 };
406
407 SidebarWidget::SidebarWidget(GLSLEdit *editor)
408     : QWidget(editor)
409     , foldIndicatorWidth(0)
410 {
411     backgroundColor = Qt::lightGray;
412     lineNumberColor = Qt::black;
413     indicatorColor = Qt::white;
414     foldIndicatorColor = Qt::lightGray;
415 }
416
417 void SidebarWidget::mousePressEvent(QMouseEvent *event)
418 {
419     if (foldIndicatorWidth > 0) {
420         int xofs = width() - foldIndicatorWidth;
421         int lineNo = -1;
422         int fh = fontMetrics().lineSpacing();
423         int ys = event->pos().y();
424         if (event->pos().x() > xofs) {
425             foreach (BlockInfo ln, lineNumbers)
426                 if (ln.position < ys && (ln.position + fh) > ys) {
427                     if (ln.foldable)
428                         lineNo = ln.number;
429                     break;
430                 }
431         }
432         if (lineNo >= 0) {
433             GLSLEdit *editor = qobject_cast<GLSLEdit*>(parent());
434             if (editor)
435                 editor->toggleFold(lineNo);
436         }
437     }
438 }
439
440 void SidebarWidget::paintEvent(QPaintEvent *event)
441 {
442     QPainter p(this);
443     p.fillRect(event->rect(), backgroundColor);
444     p.setPen(lineNumberColor);
445     p.setFont(font);
446     int fh = QFontMetrics(font).height();
447     foreach (BlockInfo ln, lineNumbers)
448         p.drawText(0, ln.position, width() - 4 - foldIndicatorWidth, fh, Qt::AlignRight, QString::number(ln.number));
449
450     if (foldIndicatorWidth > 0) {
451         int xofs = width() - foldIndicatorWidth;
452         p.fillRect(xofs, 0, foldIndicatorWidth, height(), indicatorColor);
453
454         // initialize (or recreate) the arrow icons whenever necessary
455         if (foldIndicatorWidth != rightArrowIcon.width()) {
456             QPainter iconPainter;
457             QPolygonF polygon;
458
459             int dim = foldIndicatorWidth;
460             rightArrowIcon = QPixmap(dim, dim);
461             rightArrowIcon.fill(Qt::transparent);
462             downArrowIcon = rightArrowIcon;
463
464             polygon << QPointF(dim * 0.4, dim * 0.25);
465             polygon << QPointF(dim * 0.4, dim * 0.75);
466             polygon << QPointF(dim * 0.8, dim * 0.5);
467             iconPainter.begin(&rightArrowIcon);
468             iconPainter.setRenderHint(QPainter::Antialiasing);
469             iconPainter.setPen(Qt::NoPen);
470             iconPainter.setBrush(foldIndicatorColor);
471             iconPainter.drawPolygon(polygon);
472             iconPainter.end();
473
474             polygon.clear();
475             polygon << QPointF(dim * 0.25, dim * 0.4);
476             polygon << QPointF(dim * 0.75, dim * 0.4);
477             polygon << QPointF(dim * 0.5, dim * 0.8);
478             iconPainter.begin(&downArrowIcon);
479             iconPainter.setRenderHint(QPainter::Antialiasing);
480             iconPainter.setPen(Qt::NoPen);
481             iconPainter.setBrush(foldIndicatorColor);
482             iconPainter.drawPolygon(polygon);
483             iconPainter.end();
484         }
485
486         foreach (BlockInfo ln, lineNumbers)
487             if (ln.foldable) {
488                 if (ln.folded)
489                     p.drawPixmap(xofs, ln.position, rightArrowIcon);
490                 else
491                     p.drawPixmap(xofs, ln.position, downArrowIcon);
492             }
493     }
494 }
495
496 static int findClosingMatch(const QTextDocument *doc, int cursorPosition)
497 {
498     QTextBlock block = doc->findBlock(cursorPosition);
499     GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
500     if (!blockData->bracketPositions.isEmpty()) {
501         int depth = 1;
502         while (block.isValid()) {
503             blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
504             if (blockData && !blockData->bracketPositions.isEmpty()) {
505                 for (int c = 0; c < blockData->bracketPositions.count(); ++c) {
506                     int absPos = block.position() + blockData->bracketPositions.at(c);
507                     if (absPos <= cursorPosition)
508                         continue;
509                     if (doc->characterAt(absPos) == '{')
510                         depth++;
511                     else
512                         depth--;
513                     if (depth == 0)
514                         return absPos;
515                 }
516             }
517             block = block.next();
518         }
519     }
520     return -1;
521 }
522
523 static int findOpeningMatch(const QTextDocument *doc, int cursorPosition)
524 {
525     QTextBlock block = doc->findBlock(cursorPosition);
526     GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
527     if (!blockData->bracketPositions.isEmpty()) {
528         int depth = 1;
529         while (block.isValid()) {
530             blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
531             if (blockData && !blockData->bracketPositions.isEmpty()) {
532                 for (int c = blockData->bracketPositions.count() - 1; c >= 0; --c) {
533                     int absPos = block.position() + blockData->bracketPositions.at(c);
534                     if (absPos >= cursorPosition - 1)
535                         continue;
536                     if (doc->characterAt(absPos) == '}')
537                         depth++;
538                     else
539                         depth--;
540                     if (depth == 0)
541                         return absPos;
542                 }
543             }
544             block = block.previous();
545         }
546     }
547     return -1;
548 }
549
550 class GLSLDocLayout: public QPlainTextDocumentLayout
551 {
552 public:
553     GLSLDocLayout(QTextDocument *doc);
554     void forceUpdate();
555 };
556
557 GLSLDocLayout::GLSLDocLayout(QTextDocument *doc)
558     : QPlainTextDocumentLayout(doc)
559 {
560 }
561
562 void GLSLDocLayout::forceUpdate()
563 {
564     emit documentSizeChanged(documentSize());
565 }
566
567 class GLSLEditPrivate
568 {
569 public:
570     GLSLEdit *editor;
571     GLSLDocLayout *layout;
572     GLSLHighlighter *highlighter;
573     SidebarWidget *sidebar;
574     bool showLineNumbers;
575     bool textWrap;
576     QColor cursorColor;
577     bool bracketsMatching;
578     QList<int> matchPositions;
579     QColor bracketMatchColor;
580     QList<int> errorPositions;
581     QColor bracketErrorColor;
582     bool codeFolding : 1;
583 };
584
585 GLSLEdit::GLSLEdit(QWidget *parent)
586     : QPlainTextEdit(parent)
587     , d_ptr(new GLSLEditPrivate)
588 {
589     d_ptr->editor = this;
590     d_ptr->layout = new GLSLDocLayout(document());
591     d_ptr->highlighter = new GLSLHighlighter(document());
592     d_ptr->sidebar = new SidebarWidget(this);
593     d_ptr->showLineNumbers = true;
594     d_ptr->textWrap = true;
595     d_ptr->bracketsMatching = true;
596     d_ptr->cursorColor = QColor(255, 255, 192);
597     d_ptr->bracketMatchColor = QColor(180, 238, 180);
598     d_ptr->bracketErrorColor = QColor(224, 128, 128);
599     d_ptr->codeFolding = true;
600
601     document()->setDocumentLayout(d_ptr->layout);
602
603     connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursor()));
604     connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateSidebar()));
605     connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateSidebar(QRect, int)));
606
607 #if defined(Q_OS_MAC)
608     QFont textFont = font();
609     textFont.setPointSize(12);
610     textFont.setFamily("Monaco");
611     setFont(textFont);
612 #elif defined(Q_OS_UNIX)
613     QFont textFont = font();
614     textFont.setFamily("Monospace");
615     setFont(textFont);
616 #endif
617 }
618
619 GLSLEdit::~GLSLEdit()
620 {
621     delete d_ptr->layout;
622 }
623
624 void GLSLEdit::setColor(ColorComponent component, const QColor &color)
625 {
626     Q_D(GLSLEdit);
627
628     if (component == Background) {
629         QPalette pal = palette();
630         pal.setColor(QPalette::Base, color);
631         setPalette(pal);
632         d->sidebar->indicatorColor = color;
633         updateSidebar();
634     } else if (component == Normal) {
635         QPalette pal = palette();
636         pal.setColor(QPalette::Text, color);
637         setPalette(pal);
638     } else if (component == Sidebar) {
639         d->sidebar->backgroundColor = color;
640         updateSidebar();
641     } else if (component == LineNumber) {
642         d->sidebar->lineNumberColor = color;
643         updateSidebar();
644     } else if (component == Cursor) {
645         d->cursorColor = color;
646         updateCursor();
647     } else if (component == BracketMatch) {
648         d->bracketMatchColor = color;
649         updateCursor();
650     } else if (component == BracketError) {
651         d->bracketErrorColor = color;
652         updateCursor();
653     } else if (component == FoldIndicator) {
654         d->sidebar->foldIndicatorColor = color;
655         updateSidebar();
656     } else {
657         d->highlighter->setColor(component, color);
658         updateCursor();
659     }
660 }
661
662 bool GLSLEdit::isLineNumbersVisible() const
663 {
664     return d_ptr->showLineNumbers;
665 }
666
667 void GLSLEdit::setLineNumbersVisible(bool visible)
668 {
669     d_ptr->showLineNumbers = visible;
670     updateSidebar();
671 }
672
673 bool GLSLEdit::isTextWrapEnabled() const
674 {
675     return d_ptr->textWrap;
676 }
677
678 void GLSLEdit::setTextWrapEnabled(bool enable)
679 {
680     d_ptr->textWrap = enable;
681     setLineWrapMode(enable ? WidgetWidth : NoWrap);
682 }
683
684 bool GLSLEdit::isBracketsMatchingEnabled() const
685 {
686     return d_ptr->bracketsMatching;
687 }
688
689 void GLSLEdit::setBracketsMatchingEnabled(bool enable)
690 {
691     d_ptr->bracketsMatching = enable;
692     updateCursor();
693 }
694
695 bool GLSLEdit::isCodeFoldingEnabled() const
696 {
697     return d_ptr->codeFolding;
698 }
699
700 void GLSLEdit::setCodeFoldingEnabled(bool enable)
701 {
702     d_ptr->codeFolding = enable;
703     updateSidebar();
704 }
705
706 static int findClosingConstruct(const QTextBlock &block)
707 {
708     if (!block.isValid())
709         return -1;
710     GLSLBlockData *blockData = reinterpret_cast<GLSLBlockData*>(block.userData());
711     if (!blockData)
712         return -1;
713     if (blockData->bracketPositions.isEmpty())
714         return -1;
715     const QTextDocument *doc = block.document();
716     int offset = block.position();
717     foreach (int pos, blockData->bracketPositions) {
718         int absPos = offset + pos;
719         if (doc->characterAt(absPos) == '{') {
720             int matchPos = findClosingMatch(doc, absPos);
721             if (matchPos >= 0)
722                 return matchPos;
723         }
724     }
725     return -1;
726 }
727
728 bool GLSLEdit::isFoldable(int line) const
729 {
730     int matchPos = findClosingConstruct(document()->findBlockByNumber(line - 1));
731     if (matchPos >= 0) {
732         QTextBlock matchBlock = document()->findBlock(matchPos);
733         if (matchBlock.isValid() && matchBlock.blockNumber() > line)
734             return true;
735     }
736     return false;
737 }
738
739 bool GLSLEdit::isFolded(int line) const
740 {
741     QTextBlock block = document()->findBlockByNumber(line - 1);
742     if (!block.isValid())
743         return false;
744     block = block.next();
745     if (!block.isValid())
746         return false;
747     return !block.isVisible();
748 }
749
750 void GLSLEdit::fold(int line)
751 {
752     QTextBlock startBlock = document()->findBlockByNumber(line - 1);
753     int endPos = findClosingConstruct(startBlock);
754     if (endPos < 0)
755         return;
756     QTextBlock endBlock = document()->findBlock(endPos);
757
758     QTextBlock block = startBlock.next();
759     while (block.isValid() && block != endBlock) {
760         block.setVisible(false);
761         block.setLineCount(0);
762         block = block.next();
763     }
764
765     document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
766     updateSidebar();
767     update();
768
769     GLSLDocLayout *layout = reinterpret_cast<GLSLDocLayout*>(document()->documentLayout());
770     layout->forceUpdate();
771 }
772
773 void GLSLEdit::unfold(int line)
774 {
775     QTextBlock startBlock = document()->findBlockByNumber(line - 1);
776     int endPos = findClosingConstruct(startBlock);
777
778     QTextBlock block = startBlock.next();
779     while (block.isValid() && !block.isVisible()) {
780         block.setVisible(true);
781         block.setLineCount(block.layout()->lineCount());
782         endPos = block.position() + block.length();
783         block = block.next();
784     }
785
786     document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
787     updateSidebar();
788     update();
789
790     GLSLDocLayout *layout = reinterpret_cast<GLSLDocLayout*>(document()->documentLayout());
791     layout->forceUpdate();
792 }
793
794 void GLSLEdit::toggleFold(int line)
795 {
796     if (isFolded(line))
797         unfold(line);
798     else
799         fold(line);
800 }
801
802 void GLSLEdit::resizeEvent(QResizeEvent *e)
803 {
804     QPlainTextEdit::resizeEvent(e);
805     updateSidebar();
806 }
807
808 void GLSLEdit::wheelEvent(QWheelEvent *e)
809 {
810     if (e->modifiers() == Qt::ControlModifier) {
811         int steps = e->delta() / 20;
812         steps = qBound(-3, steps, 3);
813         QFont textFont = font();
814         int pointSize = textFont.pointSize() + steps;
815         pointSize = qBound(10, pointSize, 40);
816         textFont.setPointSize(pointSize);
817         setFont(textFont);
818         updateSidebar();
819         e->accept();
820         return;
821     }
822     QPlainTextEdit::wheelEvent(e);
823 }
824
825
826 void GLSLEdit::updateCursor()
827 {
828     Q_D(GLSLEdit);
829
830     if (isReadOnly()) {
831         setExtraSelections(QList<QTextEdit::ExtraSelection>());
832     } else {
833
834         d->matchPositions.clear();
835         d->errorPositions.clear();
836
837         if (d->bracketsMatching && textCursor().block().userData()) {
838             QTextCursor cursor = textCursor();
839             int cursorPosition = cursor.position();
840
841             if (document()->characterAt(cursorPosition) == '{') {
842                 int matchPos = findClosingMatch(document(), cursorPosition);
843                 if (matchPos < 0) {
844                     d->errorPositions += cursorPosition;
845                 } else {
846                     d->matchPositions += cursorPosition;
847                     d->matchPositions += matchPos;
848                 }
849             }
850
851             if (document()->characterAt(cursorPosition - 1) == '}') {
852                 int matchPos = findOpeningMatch(document(), cursorPosition);
853                 if (matchPos < 0) {
854                     d->errorPositions += cursorPosition - 1;
855                 } else {
856                     d->matchPositions += cursorPosition - 1;
857                     d->matchPositions += matchPos;
858                 }
859             }
860         }
861
862         QTextEdit::ExtraSelection highlight;
863         highlight.format.setBackground(d->cursorColor);
864         highlight.format.setProperty(QTextFormat::FullWidthSelection, true);
865         highlight.cursor = textCursor();
866         highlight.cursor.clearSelection();
867
868         QList<QTextEdit::ExtraSelection> extraSelections;
869         extraSelections.append(highlight);
870
871         for (int i = 0; i < d->matchPositions.count(); ++i) {
872             int pos = d->matchPositions.at(i);
873             QTextEdit::ExtraSelection matchHighlight;
874             matchHighlight.format.setBackground(d->bracketMatchColor);
875             matchHighlight.cursor = textCursor();
876             matchHighlight.cursor.setPosition(pos);
877             matchHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
878             extraSelections.append(matchHighlight);
879         }
880
881         for (int i = 0; i < d->errorPositions.count(); ++i) {
882             int pos = d->errorPositions.at(i);
883             QTextEdit::ExtraSelection errorHighlight;
884             errorHighlight.format.setBackground(d->bracketErrorColor);
885             errorHighlight.cursor = textCursor();
886             errorHighlight.cursor.setPosition(pos);
887             errorHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
888             extraSelections.append(errorHighlight);
889         }
890
891         setExtraSelections(extraSelections);
892     }
893 }
894
895 void GLSLEdit::updateSidebar(const QRect &rect, int d)
896 {
897     Q_UNUSED(rect)
898     if (d != 0)
899         updateSidebar();
900 }
901
902 void GLSLEdit::updateSidebar()
903 {
904     Q_D(GLSLEdit);
905
906     if (!d->showLineNumbers && !d->codeFolding) {
907         d->sidebar->hide();
908         setViewportMargins(0, 0, 0, 0);
909         d->sidebar->setGeometry(3, 0, 0, height());
910         return;
911     }
912
913     d->sidebar->foldIndicatorWidth = 0;
914     d->sidebar->font = this->font();
915     d->sidebar->show();
916
917     int sw = 0;
918     if (d->showLineNumbers) {
919         int digits = 2;
920         int maxLines = blockCount();
921         for (int number = 10; number < maxLines; number *= 10)
922             ++digits;
923         sw += fontMetrics().width('w') * digits;
924     }
925     if (d->codeFolding) {
926         int fh = fontMetrics().lineSpacing();
927         int fw = fontMetrics().width('w');
928         d->sidebar->foldIndicatorWidth = qMax(fw, fh);
929         sw += d->sidebar->foldIndicatorWidth;
930     }
931     setViewportMargins(sw, 0, 0, 0);
932
933     d->sidebar->setGeometry(0, 0, sw, height());
934     QRectF sidebarRect(0, 0, sw, height());
935
936     QTextBlock block = firstVisibleBlock();
937     int index = 0;
938     while (block.isValid()) {
939         if (block.isVisible()) {
940             QRectF rect = blockBoundingGeometry(block).translated(contentOffset());
941             if (sidebarRect.intersects(rect)) {
942                 if (d->sidebar->lineNumbers.count() >= index)
943                     d->sidebar->lineNumbers.resize(index + 1);
944                 d->sidebar->lineNumbers[index].position = rect.top();
945                 d->sidebar->lineNumbers[index].number = block.blockNumber() + 1;
946                 d->sidebar->lineNumbers[index].foldable = d->codeFolding ? isFoldable(block.blockNumber() + 1) : false;
947                 d->sidebar->lineNumbers[index].folded = d->codeFolding ? isFolded(block.blockNumber() + 1) : false;
948                 ++index;
949             }
950             if (rect.top() > sidebarRect.bottom())
951                 break;
952         }
953         block = block.next();
954     }
955     d->sidebar->lineNumbers.resize(index);
956     d->sidebar->update();
957 }
958
959 void GLSLEdit::mark(const QString &str, Qt::CaseSensitivity sens)
960 {
961     d_ptr->highlighter->mark(str, sens);
962 }
963
964 void GLSLEdit::indent()
965 {
966     QTemporaryFile file(QLatin1String("shader.glsl"));
967     if (!file.open()) {
968         qDebug()<<"Couldn't create temporary file "<<file.fileName();
969         return;
970     }
971     file.write(toPlainText().toUtf8());
972     file.flush();
973
974     QString tempFileName =
975             QDir::toNativeSeparators(QFileInfo(file).canonicalFilePath());
976
977     QProcess astyle;
978     astyle.setStandardInputFile(tempFileName);
979     astyle.start("astyle");
980     if (!astyle.waitForStarted()) {
981         qDebug()<<"Couldn't start the 'astyle' process!";
982         QMessageBox::warning(this,
983                              tr("QApiTrace"),
984                              tr("QApiTrace could not locate the 'astyle'\n"
985                                 "binary. Make sure 'astyle' is installed\n"
986                                 "and in the PATH."),
987                              QMessageBox::Ok);
988         return;
989     }
990
991     if (!astyle.waitForFinished()) {
992         qDebug()<<"Couldn't finish the 'astyle' process";
993         return;
994     }
995
996     QByteArray result = astyle.readAll();
997     setPlainText(QString::fromUtf8(result));
998 }
999
1000 void GLSLEdit::contextMenuEvent(QContextMenuEvent *e)
1001 {
1002     QMenu *menu = createStandardContextMenu();
1003
1004     menu->addAction(tr("Indent Code"), this, SLOT(indent()));
1005
1006     menu->exec(e->globalPos());
1007     delete menu;
1008 }
1009
1010 #include "glsledit.moc"