Vuo  2.0.3
VuoCodeEditor.cc
Go to the documentation of this file.
1 
10 #include "VuoCodeEditor.hh"
11 
12 #include "VuoCodeGutter.hh"
14 #include "VuoCodeIssueList.hh"
15 #include "VuoCodeWindow.hh"
16 #include "VuoEditor.hh"
17 #include "VuoInputEditorIcon.hh"
18 #include "VuoRendererColors.hh"
19 
23 VuoCodeEditor::VuoCodeEditor(QString initialSourceCode):
24  QTextEdit(),
25  highlighter(nullptr)
26 {
27  setAcceptRichText(false);
28 
29  setPlainText(initialSourceCode);
30 
31  VuoEditor *editor = static_cast<VuoEditor *>(qApp);
32  isDark = editor->isInterfaceDark();
33  updateColor(isDark);
34 
35  gutter = new VuoCodeGutter(this);
36 
37  // VuoCodeGutter doesn't yet support wrapping.
38  setLineWrapMode(QTextEdit::NoWrap);
39 
40  fontSizes << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 16 << 20 << 28 << 36 << 48;
41  zoom11();
42 
43  connect(this, &QTextEdit::cursorPositionChanged, this, &VuoCodeEditor::cursorPositionChanged);
44  cursorPositionChanged();
45 
46  highlighter = new VuoCodeHighlighterGLSL(document(), this);
47 }
48 
49 void VuoCodeEditor::setFontSize(int fontSize)
50 {
51  currentFontSize = fontSize;
52 
53  // Menlo is included with Mac OS 10.7+, and includes italic and bold variants (unlike Monaco).
54  QFont font("Menlo", fontSize);
55 
56  int spacesPerTab = 4;
57  QFontMetricsF fontMetrics(font);
58  qreal spaceWidth = fontMetrics.width(' ');
59  qreal tabWidth = spacesPerTab * spaceWidth;
60  setTabStopWidth(ceil(tabWidth));
61 
62  // Align characters to pixels.
63  qreal pitch = (ceil(tabWidth) - tabWidth) / spacesPerTab;
64  font.setLetterSpacing(QFont::AbsoluteSpacing, pitch);
65  setFont(font);
66 
67  // A little thicker / more visible than the default 1px.
68  setCursorWidth(fontSize/5.);
69 
71 }
72 
77 {
78  setFontSize(11);
79 }
80 
85 {
86  foreach (int fontSize, fontSizes)
87  if (fontSize > currentFontSize)
88  {
89  setFontSize(fontSize);
90  break;
91  }
92 }
93 
98 {
99  QList<int>::reverse_iterator i;
100  for (i = fontSizes.rbegin(); i != fontSizes.rend(); ++i)
101  if (*i < currentFontSize)
102  {
103  setFontSize(*i);
104  break;
105  }
106 }
107 
112 {
113  return currentFontSize == 11;
114 }
115 
119 void VuoCodeEditor::selectLine(int lineNumber)
120 {
121  QTextCursor cursor = textCursor();
122  cursor.movePosition(QTextCursor::Start);
123  cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineNumber - 1);
124 
125  // Select the line, placing the cursor at the beginning of the line.
126  cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
127  cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
128 
129  setTextCursor(cursor);
130  setFocus();
131 }
132 
133 void VuoCodeEditor::keyPressEvent(QKeyEvent *event)
134 {
135  int key = event->key();
136  Qt::KeyboardModifiers modifiers = event->modifiers();
137 
138  if (key == Qt::Key_Tab)
139  handleTab(true);
140  else if (key == Qt::Key_BracketRight
141  && modifiers & Qt::ControlModifier
142  && !(modifiers & Qt::ShiftModifier))
143  handleTab(true);
144  else if (key == Qt::Key_BracketLeft
145  && modifiers & Qt::ControlModifier
146  && !(modifiers & Qt::ShiftModifier))
147  handleTab(false);
148  else if (key == Qt::Key_Backtab)
149  handleTab(false);
150  else if (key == Qt::Key_Return
151  && !((modifiers & Qt::AltModifier) || (modifiers & Qt::ControlModifier))) // bubble up to VuoCodeWindow
152  handleLinebreak(false);
153  else if (key == Qt::Key_BraceRight)
154  handleBlockEnd();
155  else if (key == Qt::Key_Slash && modifiers & Qt::ControlModifier)
156  handleComment();
157  else
158  QTextEdit::keyPressEvent(event);
159 }
160 
165 void VuoCodeEditor::handleTab(bool forward)
166 {
167  QTextCursor cursor = textCursor();
168 
169  if (!cursor.hasSelection())
170  {
171  if (forward)
172  cursor.insertText("\t");
173  return;
174  }
175 
176  cursor.beginEditBlock();
177 
178  int selectionEnd = cursor.selectionEnd();
179  cursor.setPosition(cursor.selectionStart());
180  cursor.movePosition(QTextCursor::StartOfBlock);
181  do {
182  if (forward)
183  {
184  cursor.insertText("\t");
185  ++selectionEnd;
186  }
187  else
188  if (toPlainText().at(cursor.position()) == '\t')
189  {
190  cursor.deleteChar();
191  --selectionEnd;
192  cursor.movePosition(QTextCursor::NextCharacter);
193  }
194 
195  if (!cursor.movePosition(QTextCursor::NextBlock))
196  break;
197  } while (cursor.position() < selectionEnd);
198 
199  cursor.endEditBlock();
200 }
201 
206 void VuoCodeEditor::handleLinebreak(bool dontBreakAtCursor)
207 {
208  QTextCursor cursor = textCursor();
209  cursor.beginEditBlock();
210 
211  if (dontBreakAtCursor)
212  cursor.movePosition(QTextCursor::EndOfLine);
213  cursor.insertText("\n");
214  QTextCursor insertionCursor = cursor;
215 
216  // Get previous line's indent level.
217  int indentLevel = 0;
218  {
219  cursor.movePosition(QTextCursor::PreviousBlock);
220  while (toPlainText().at(cursor.position()) == '\t')
221  {
222  ++indentLevel;
223  if (!cursor.movePosition(QTextCursor::NextCharacter))
224  break;
225  }
226 
227  // Adjust indentation level if the line ends with a brace (excluding trailing whitespace).
228  cursor.movePosition(QTextCursor::StartOfBlock);
229  int startPos = cursor.position();
230  cursor.movePosition(QTextCursor::EndOfBlock);
231  while (toPlainText().at(cursor.position()).isSpace() && cursor.position() >= startPos)
232  if (!cursor.movePosition(QTextCursor::PreviousCharacter))
233  break;
234  if (toPlainText().at(cursor.position()) == '{')
235  ++indentLevel;
236  }
237 
238  for (int i = 0; i < indentLevel; ++i)
239  insertionCursor.insertText("\t");
240 
241  cursor.endEditBlock();
242 
243  // Actually move the visible cursor.
244  // If we do this before endEditBlock(), the viewport resets its scroll position.
245  setTextCursor(insertionCursor);
246 }
247 
251 void VuoCodeEditor::handleBlockEnd()
252 {
253  QTextCursor cursor = textCursor();
254  cursor.beginEditBlock();
255 
256  if (toPlainText().length()
257  && toPlainText().at(fmax(0, cursor.position() - 1)) == '\t')
258  cursor.deletePreviousChar();
259 
260  cursor.insertText("}");
261 
262  cursor.endEditBlock();
263 }
264 
269 int VuoCodeEditor::toggleLineComment(QTextCursor &cursor)
270 {
271  cursor.movePosition(QTextCursor::StartOfBlock);
272 
273  while (cursor.position() < toPlainText().length()
274  && toPlainText().at(cursor.position()).isSpace())
275  cursor.movePosition(QTextCursor::NextCharacter);
276 
277  if (toPlainText().mid(cursor.position(), 2) == "//")
278  {
279  cursor.deleteChar();
280  cursor.deleteChar();
281  return -2;
282  }
283  else
284  {
285  cursor.movePosition(QTextCursor::StartOfBlock);
286  cursor.insertText("//");
287  return 2;
288  }
289 }
290 
294 void VuoCodeEditor::handleComment()
295 {
296  QTextCursor cursor = textCursor();
297  cursor.beginEditBlock();
298 
299  if (cursor.hasSelection())
300  {
301  int selectionEnd = cursor.selectionEnd();
302  cursor.setPosition(cursor.selectionStart());
303  do {
304  int delta = toggleLineComment(cursor);
305  selectionEnd += delta;
306  if (!cursor.movePosition(QTextCursor::NextBlock))
307  break;
308  } while (cursor.position() < selectionEnd);
309  }
310  else
311  toggleLineComment(cursor);
312 
313  cursor.endEditBlock();
314 }
315 
316 void VuoCodeEditor::resizeEvent(QResizeEvent *event)
317 {
318  QTextEdit::resizeEvent(event);
319  gutter->resize(event->size());
320 }
321 
322 int VuoCodeEditor::getCurrentLineNumber()
323 {
324  QTextCursor cursor = textCursor();
325  cursor.movePosition(QTextCursor::StartOfLine);
326 
327  int lines = 1;
328  while (cursor.movePosition(QTextCursor::PreviousBlock))
329  ++lines;
330 
331  return lines;
332 }
333 
334 void VuoCodeEditor::cursorPositionChanged()
335 {
336  // Highlight the current line.
337  QTextEdit::ExtraSelection selection;
338  selection.format.setBackground(currentLineColor);
339  selection.format.setProperty(QTextFormat::FullWidthSelection, true);
340  selection.cursor = textCursor();
341  selection.cursor.clearSelection();
342  QList<QTextEdit::ExtraSelection> extraSelections;
343  extraSelections.append(selection);
344  setExtraSelections(extraSelections);
345 
346 
347  if (parent())
348  {
349  VuoCodeWindow *codeWindow = static_cast<VuoCodeWindow *>(parent()->parent()->parent());
350  if (codeWindow->issueList)
351  codeWindow->issueList->selectIssueForLine(getCurrentLineNumber());
352  }
353 }
354 
358 void VuoCodeEditor::updateColor(bool isDark)
359 {
361  c.setDark(isDark);
362 
363  QPalette p;
364 
365  QColor background = c.canvasFill();
366  p.setColor(QPalette::Base, background);
367 
368  p.setColor(QPalette::Active, QPalette::Highlight, isDark ? "#12418c" : "#74acec");
369  p.setColor(QPalette::Inactive, QPalette::Highlight, isDark ? "#606060" : "#e0e0e0");
370  p.setColor(QPalette::Active, QPalette::HighlightedText, isDark ? "#c0c0c0" : "#404040");
371  p.setColor(QPalette::Inactive, QPalette::HighlightedText, isDark ? "#c0c0c0" : "#404040");
372 
373  setPalette(p);
374 
375  currentLineColor = c.canvasFill().lighter(isDark ? 110 : 97);
376  gutterColor = c.canvasFill().lighter(isDark ? 150 : 90);
377  gutterTextColor = c.canvasFill().lighter(isDark ? 250 : 70);
378  operatorColor = isDark ? "#c0c0c0" : "#000000";
379  commentColor = isDark ? "#606060" : "#c0c0c0";
380 
381  {
382  VuoRendererColors c(VuoNode::TintMagenta);
383  c.setDark(isDark);
384  keywordColor = c.nodeFrame();
385  }
386 
387  {
388  VuoRendererColors c(VuoNode::TintCyan);
389  c.setDark(isDark);
391  }
392 
393  {
394  VuoRendererColors c(VuoNode::TintOrange);
395  c.setDark(isDark);
397  }
398 
399  {
400  VuoRendererColors c(VuoNode::TintBlue);
401  c.setDark(isDark);
402  constantColor = c.nodeFrame();
403  }
404 
405  {
406  VuoRendererColors c(VuoNode::TintYellow);
407  c.setDark(isDark);
409  }
410 
412 
413  setStyleSheet(VUO_QSTRINGIFY(
414  QTextEdit {
415  background: %1;
416  color: %2;
417  }
418  ).arg(background.name())
419  // Fade out normal text a little, so keywords and operators stand out more.
420  .arg(isDark ? "#a0a0a0" : "#606060"));
421 
422  if (highlighter && isDark != this->isDark)
423  {
424  this->isDark = isDark;
425 
426  highlighter->rehighlight();
427  cursorPositionChanged();
428  }
429 }