blob: 79f4a16b073a22d5069fa7f592c5906f30fd88b3 [file] [log] [blame]
Zack Rusinfbd67a02011-04-11 23:35:02 -04001/*
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
José Fonseca3f4cd302015-01-14 12:02:06 +000032#include <QtWidgets>
Zack Rusinfbd67a02011-04-11 23:35:02 -040033
34class GLSLBlockData: public QTextBlockUserData
35{
36public:
37 QList<int> bracketPositions;
38};
39
40class GLSLHighlighter : public QSyntaxHighlighter
41{
42public:
43 GLSLHighlighter(QTextDocument *parent = 0);
44 void setColor(GLSLEdit::ColorComponent component, const QColor &color);
45 void mark(const QString &str, Qt::CaseSensitivity caseSensitivity);
46
47protected:
Jose Fonseca010f9962016-03-05 14:45:41 +000048 void highlightBlock(const QString &text) override;
Zack Rusinfbd67a02011-04-11 23:35:02 -040049
50private:
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
59GLSLHighlighter::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
197void GLSLHighlighter::setColor(GLSLEdit::ColorComponent component, const QColor &color)
198{
199 m_colors[component] = color;
200 rehighlight();
201}
202
203void 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
373void GLSLHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
374{
375 m_markString = str;
376 m_markCaseSensitivity = caseSensitivity;
377 rehighlight();
378}
379
380struct BlockInfo {
381 int position;
382 int number;
383 bool foldable: 1;
384 bool folded : 1;
385};
386
387Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE);
388
389class SidebarWidget : public QWidget
390{
391public:
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;
402protected:
Jose Fonseca010f9962016-03-05 14:45:41 +0000403 void mousePressEvent(QMouseEvent *event) override;
404 void paintEvent(QPaintEvent *event) override;
Zack Rusinfbd67a02011-04-11 23:35:02 -0400405};
406
407SidebarWidget::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
417void 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
440void 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
496static 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
523static 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
550class GLSLDocLayout: public QPlainTextDocumentLayout
551{
552public:
553 GLSLDocLayout(QTextDocument *doc);
554 void forceUpdate();
555};
556
557GLSLDocLayout::GLSLDocLayout(QTextDocument *doc)
558 : QPlainTextDocumentLayout(doc)
559{
560}
561
562void GLSLDocLayout::forceUpdate()
563{
564 emit documentSizeChanged(documentSize());
565}
566
567class GLSLEditPrivate
568{
569public:
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
585GLSLEdit::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
619GLSLEdit::~GLSLEdit()
620{
621 delete d_ptr->layout;
622}
623
624void 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
662bool GLSLEdit::isLineNumbersVisible() const
663{
664 return d_ptr->showLineNumbers;
665}
666
667void GLSLEdit::setLineNumbersVisible(bool visible)
668{
669 d_ptr->showLineNumbers = visible;
670 updateSidebar();
671}
672
673bool GLSLEdit::isTextWrapEnabled() const
674{
675 return d_ptr->textWrap;
676}
677
678void GLSLEdit::setTextWrapEnabled(bool enable)
679{
680 d_ptr->textWrap = enable;
681 setLineWrapMode(enable ? WidgetWidth : NoWrap);
682}
683
684bool GLSLEdit::isBracketsMatchingEnabled() const
685{
686 return d_ptr->bracketsMatching;
687}
688
689void GLSLEdit::setBracketsMatchingEnabled(bool enable)
690{
691 d_ptr->bracketsMatching = enable;
692 updateCursor();
693}
694
695bool GLSLEdit::isCodeFoldingEnabled() const
696{
697 return d_ptr->codeFolding;
698}
699
700void GLSLEdit::setCodeFoldingEnabled(bool enable)
701{
702 d_ptr->codeFolding = enable;
703 updateSidebar();
704}
705
706static 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
728bool 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
739bool 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
750void 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
773void 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
794void GLSLEdit::toggleFold(int line)
795{
796 if (isFolded(line))
797 unfold(line);
798 else
799 fold(line);
800}
801
802void GLSLEdit::resizeEvent(QResizeEvent *e)
803{
804 QPlainTextEdit::resizeEvent(e);
805 updateSidebar();
806}
807
808void 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
826void 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
895void GLSLEdit::updateSidebar(const QRect &rect, int d)
896{
897 Q_UNUSED(rect)
898 if (d != 0)
899 updateSidebar();
900}
901
902void 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
959void GLSLEdit::mark(const QString &str, Qt::CaseSensitivity sens)
960{
961 d_ptr->highlighter->mark(str, sens);
962}
963
Zack Rusinab347b02011-09-24 19:39:59 -0400964void 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!";
Zack Rusin50049de2011-09-26 23:54:21 -0400982 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);
Zack Rusinab347b02011-09-24 19:39:59 -0400988 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
1000void 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
Zack Rusinfbd67a02011-04-11 23:35:02 -04001010#include "glsledit.moc"