Developer Documentation
TextBrowserWidget.cc
1 
2 /*===========================================================================*\
3 * *
4 * OpenFlipper *
5  * Copyright (c) 2001-2015, RWTH-Aachen University *
6  * Department of Computer Graphics and Multimedia *
7  * All rights reserved. *
8  * www.openflipper.org *
9  * *
10  *---------------------------------------------------------------------------*
11  * This file is part of OpenFlipper. *
12  *---------------------------------------------------------------------------*
13  * *
14  * Redistribution and use in source and binary forms, with or without *
15  * modification, are permitted provided that the following conditions *
16  * are met: *
17  * *
18  * 1. Redistributions of source code must retain the above copyright notice, *
19  * this list of conditions and the following disclaimer. *
20  * *
21  * 2. Redistributions in binary form must reproduce the above copyright *
22  * notice, this list of conditions and the following disclaimer in the *
23  * documentation and/or other materials provided with the distribution. *
24  * *
25  * 3. Neither the name of the copyright holder nor the names of its *
26  * contributors may be used to endorse or promote products derived from *
27  * this software without specific prior written permission. *
28  * *
29  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
30  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
31  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
32  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
33  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
34  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
35  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
36  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
37  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
38  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
39  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
40 * *
41 \*===========================================================================*/
42 
43 /*===========================================================================*\
44 * *
45 * $Revision: 17080 $ *
46 * $LastChangedBy: moeller $ *
47 * $Date: 2013-07-19 12:58:31 +0200 (Fri, 19 Jul 2013) $ *
48 * *
49 \*===========================================================================*/
50 
51 
52 #if QT_VERSION >= 0x050000
53  #include <QtWidgets>
54 #else
55  #include <QtGui>
56 #endif
57 
58 #include "TextBrowserWidget.hh"
59 
60 QString const TextBrowserWidget::startRenderObjectTag_ = "name:";
61 QString const TextBrowserWidget::startVertexShaderTag_ = "--vertex-shader--";
62 QString const TextBrowserWidget::endVertexShaderTag_ = "--end-vertex-shader--";
63 QString const TextBrowserWidget::startTessControlShaderTag_ = "---tesscontrol-shader--";
64 QString const TextBrowserWidget::endTessControlShaderTag_ = "--end-tesscontrol-shader--";
65 QString const TextBrowserWidget::startTessEvalShaderTag_ = "--tesseval-shader--";
66 QString const TextBrowserWidget::endTessEvalShaderTag_ = "--end-tesseval-shader--";
67 QString const TextBrowserWidget::startGeometryShaderTag_ = "--geometry-shader--";
68 QString const TextBrowserWidget::endGeometryShaderTag_ = "--end-geometry-shader--";
69 QString const TextBrowserWidget::startFragmentShaderTag_ = "--fragment-shader--";
70 QString const TextBrowserWidget::endFragmentShaderTag_ = "--end-fragment-shader--";
71 
72 
73 TextBrowserWidget::TextBrowserWidget(QWidget *parent) : QPlainTextEdit(parent) {
74  sideArea_ = new TextBrowserSideArea(this);
75  updateTextBrowserSideAreaWidth();
76 
77  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateTextBrowserSideAreaWidth()));
78  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateFolds()));
79  connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateTextBrowserSideArea(QRect,int)));
80 
81  setReadOnly(true);
82 }
83 
84 
85 
86 int TextBrowserWidget::sideAreaWidth() {
87  int digits = 1;
88  int max = qMax(1, blockCount());
89  while (max >= 10) {
90  max /= 10;
91  ++digits;
92  }
93 
94  int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
95 
96  return space;
97 }
98 
99 
100 
101 void TextBrowserWidget::updateTextBrowserSideAreaWidth() {
102  setViewportMargins(sideAreaWidth(), 0, 0, 0);
103 }
104 
105 void TextBrowserWidget::updateTextBrowserSideArea(const QRect &rect, int dy) {
106  if (dy)
107  sideArea_->scroll(0, dy);
108  else
109  sideArea_->update(0, rect.y(), sideArea_->width(), rect.height());
110 
111  if (rect.contains(viewport()->rect()))
112  updateTextBrowserSideAreaWidth();
113 }
114 
115 
116 
117 void TextBrowserWidget::resizeEvent(QResizeEvent *e) {
118  QPlainTextEdit::resizeEvent(e);
119 
120  QRect cr = contentsRect();
121  sideArea_->setGeometry(QRect(cr.left(), cr.top(), sideAreaWidth(), cr.height()));
122 }
123 
124 void TextBrowserWidget::sideAreaPaintEvent(QPaintEvent *event) {
125 
126  QPainter painter(sideArea_);
127  painter.fillRect(event->rect(), Qt::lightGray);
128  painter.setPen(Qt::black);
129 
130  QTextBlock block = firstVisibleBlock();
131 
132  int blockNumber = block.blockNumber();
133  int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
134  int bottom = top + (int) blockBoundingRect(block).height();
135 
136  Fold found_fold;
137  while (block.isValid() && top <= event->rect().bottom()) {
138  if (block.isVisible() && bottom >= event->rect().top()) {
139  if (getFold(block.position(), found_fold)) {
140  if (found_fold.type == SHADER) {
141  int fold_first_block = document()->findBlock(found_fold.start).blockNumber();
142  QString text = block.text();
143  // only draw line numbers on actual shader code
144  if (text.contains(TextBrowserWidget::startVertexShaderTag_) ||
145  text.contains(TextBrowserWidget::endVertexShaderTag_) ||
146  text.contains(TextBrowserWidget::startTessControlShaderTag_) ||
147  text.contains(TextBrowserWidget::endTessControlShaderTag_) ||
148  text.contains(TextBrowserWidget::startTessEvalShaderTag_) ||
149  text.contains(TextBrowserWidget::endTessEvalShaderTag_) ||
150  text.contains(TextBrowserWidget::startGeometryShaderTag_) ||
151  text.contains(TextBrowserWidget::endGeometryShaderTag_) ||
152  text.contains(TextBrowserWidget::startFragmentShaderTag_) ||
153  text.contains(TextBrowserWidget::endFragmentShaderTag_)) {
154  if (found_fold.folded)
155  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "+");
156  else
157  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "-");
158  } else {
159  QString number = QString::number(blockNumber - fold_first_block);
160  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, number);
161  }
162  } else {
163  if (found_fold.folded)
164  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "+");
165  else
166  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "-");
167  }
168  } else
169  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, " ");
170  }
171 
172  block = block.next();
173  top = bottom;
174  bottom = top + (int) blockBoundingRect(block).height();
175  ++blockNumber;
176  }
177 }
178 
179 bool TextBrowserWidget::getFold(int _position, Fold& _fold) {
180  std::map<int,size_t>::iterator it = blockPosToFold_.find(_position);
181  if (!folds_.empty() && it != blockPosToFold_.end()) {
182  _fold = folds_[it->second];
183  return true;
184  } else
185  return false;
186 }
187 
188 void TextBrowserWidget::mouseDoubleClickEvent(QMouseEvent* e) {
189  QTextBlock block = firstVisibleBlock();
190  int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
191  int bottom = top + (int) blockBoundingRect(block).height();
192  const int y = e->y();
193  // find the block that was clicked and toggle the folding
194  while (block.isValid()) {
195  if (top <= y && y <= bottom) {
196  toggleFold(block.position());
197  break;
198  }
199 
200  block = block.next();
201  top = bottom;
202  bottom = top + (int) blockBoundingRect(block).height();
203  }
204 }
205 
206 void TextBrowserWidget::foldAll() {
207  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
208  fold(*it);
209  }
210 }
211 
212 void TextBrowserWidget::unfoldAll() {
213  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
214  unfold(*it);
215  }
216 }
217 
218 void TextBrowserWidget::fold(Fold& _fold) {
219  if (_fold.folded)
220  return;
221 
222  QTextBlock startBlock = document()->findBlock(_fold.start);
223  QTextBlock endBlock = document()->findBlock(_fold.end);
224 
225  startBlock = startBlock.next();
226  while (startBlock.isValid() && startBlock != endBlock) {
227  startBlock.setVisible(false);
228  startBlock = startBlock.next();
229  }
230  if (_fold.type == RENDEROBJECT)
231  endBlock.setVisible(false);
232 
233  _fold.folded = true;
234  document()->markContentsDirty(_fold.start, _fold.end - _fold.start);
235 }
236 
237 void TextBrowserWidget::unfold(Fold& _fold) {
238  if (!_fold.folded)
239  return;
240 
241  QTextBlock startBlock = document()->findBlock(_fold.start);
242  QTextBlock endBlock = document()->findBlock(_fold.end);
243 
244  startBlock = startBlock.next();
245  while (startBlock.isValid() && startBlock != endBlock) {
246  startBlock.setVisible(true);
247  startBlock = startBlock.next();
248  }
249  if (_fold.type == RENDEROBJECT)
250  endBlock.setVisible(true);
251 
252  _fold.folded = false;
253  document()->markContentsDirty(_fold.start, _fold.end-_fold.start);
254 }
255 
256 void TextBrowserWidget::toggleFold(int _position) {
257  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
258  if (it->contains(_position)) {
259  if (it->folded)
260  unfold(*it);
261  else
262  fold(*it);
263 
264  break;
265  }
266  }
267 }
268 
269 void TextBrowserWidget::updateFolds() {
270  folds_.clear();
271 
272  // search for all vertex shader
273  QTextCursor startCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, 0, QTextDocument::FindWholeWords);
274  QTextCursor endCursor = document()->find(TextBrowserWidget::endVertexShaderTag_, 0, QTextDocument::FindWholeWords);
275 
276  while (!startCursor.isNull() && !endCursor.isNull()) {
277  startCursor.movePosition(QTextCursor::StartOfLine);
278  endCursor.movePosition(QTextCursor::EndOfLine);
279  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
280 
281  // map block position to fold
282  int startPos = startCursor.position();
283  const int endPos = endCursor.position();
284  for (; startPos < endPos; ++startPos) {
285  QTextBlock block = document()->findBlock(startPos);
286  blockPosToFold_[block.position()] = folds_.size() - 1;
287  }
288 
289  bool moved = startCursor.movePosition(QTextCursor::Down);
290  if (!moved)
291  break;
292  moved = endCursor.movePosition(QTextCursor::Down);
293  if (!moved)
294  break;
295 
296  startCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, startCursor, QTextDocument::FindWholeWords);
297  endCursor = document()->find(TextBrowserWidget::endVertexShaderTag_, endCursor, QTextDocument::FindWholeWords);
298  }
299 
300  // search for all tesscontrol shader
301  startCursor = document()->find(TextBrowserWidget::startTessControlShaderTag_, 0, QTextDocument::FindWholeWords);
302  endCursor = document()->find(TextBrowserWidget::endTessControlShaderTag_, 0, QTextDocument::FindWholeWords);
303 
304  while (!startCursor.isNull() && !endCursor.isNull()) {
305  startCursor.movePosition(QTextCursor::StartOfLine);
306  endCursor.movePosition(QTextCursor::EndOfLine);
307  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
308 
309  // map block position to fold
310  int startPos = startCursor.position();
311  const int endPos = endCursor.position();
312  for (; startPos < endPos; ++startPos) {
313  QTextBlock block = document()->findBlock(startPos);
314  blockPosToFold_[block.position()] = folds_.size() - 1;
315  }
316 
317  bool moved = startCursor.movePosition(QTextCursor::Down);
318  if (!moved)
319  break;
320  moved = endCursor.movePosition(QTextCursor::Down);
321  if (!moved)
322  break;
323 
324  startCursor = document()->find(TextBrowserWidget::startTessControlShaderTag_, startCursor, QTextDocument::FindWholeWords);
325  endCursor = document()->find(TextBrowserWidget::endTessControlShaderTag_, endCursor, QTextDocument::FindWholeWords);
326  }
327 
328  // search for all tesseval shader
329  startCursor = document()->find(TextBrowserWidget::startTessEvalShaderTag_, 0, QTextDocument::FindWholeWords);
330  endCursor = document()->find(TextBrowserWidget::endTessEvalShaderTag_, 0, QTextDocument::FindWholeWords);
331 
332  while (!startCursor.isNull() && !endCursor.isNull()) {
333  startCursor.movePosition(QTextCursor::StartOfLine);
334  endCursor.movePosition(QTextCursor::EndOfLine);
335  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
336 
337  // map block position to fold
338  int startPos = startCursor.position();
339  const int endPos = endCursor.position();
340  for (; startPos < endPos; ++startPos) {
341  QTextBlock block = document()->findBlock(startPos);
342  blockPosToFold_[block.position()] = folds_.size() - 1;
343  }
344 
345  bool moved = startCursor.movePosition(QTextCursor::Down);
346  if (!moved)
347  break;
348  moved = endCursor.movePosition(QTextCursor::Down);
349  if (!moved)
350  break;
351 
352  startCursor = document()->find(TextBrowserWidget::startTessEvalShaderTag_, startCursor, QTextDocument::FindWholeWords);
353  endCursor = document()->find(TextBrowserWidget::endTessEvalShaderTag_, endCursor, QTextDocument::FindWholeWords);
354  }
355 
356  // search for all geometry shader
357  startCursor = document()->find(TextBrowserWidget::startGeometryShaderTag_, 0, QTextDocument::FindWholeWords);
358  endCursor = document()->find(TextBrowserWidget::endGeometryShaderTag_, 0, QTextDocument::FindWholeWords);
359 
360  while (!startCursor.isNull() && !endCursor.isNull()) {
361  startCursor.movePosition(QTextCursor::StartOfLine);
362  endCursor.movePosition(QTextCursor::EndOfLine);
363  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
364 
365  // map block position to fold
366  int startPos = startCursor.position();
367  const int endPos = endCursor.position();
368  for (; startPos < endPos; ++startPos) {
369  QTextBlock block = document()->findBlock(startPos);
370  blockPosToFold_[block.position()] = folds_.size() - 1;
371  }
372 
373  bool moved = startCursor.movePosition(QTextCursor::Down);
374  if (!moved)
375  break;
376  moved = endCursor.movePosition(QTextCursor::Down);
377  if (!moved)
378  break;
379 
380  startCursor = document()->find(TextBrowserWidget::startGeometryShaderTag_, startCursor, QTextDocument::FindWholeWords);
381  endCursor = document()->find(TextBrowserWidget::endGeometryShaderTag_, endCursor, QTextDocument::FindWholeWords);
382  }
383 
384  // search for all fragment shader
385  startCursor = document()->find(TextBrowserWidget::startFragmentShaderTag_, 0, QTextDocument::FindWholeWords);
386  endCursor = document()->find(TextBrowserWidget::endFragmentShaderTag_, 0, QTextDocument::FindWholeWords);
387 
388  while (!startCursor.isNull() && !endCursor.isNull()) {
389  startCursor.movePosition(QTextCursor::StartOfLine);
390  endCursor.movePosition(QTextCursor::EndOfLine);
391  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
392 
393  // map block position to fold
394  int startPos = startCursor.position();
395  const int endPos = endCursor.position();
396  for (; startPos < endPos; ++startPos) {
397  QTextBlock block = document()->findBlock(startPos);
398  blockPosToFold_[block.position()] = folds_.size() - 1;
399  }
400 
401  bool moved = startCursor.movePosition(QTextCursor::Down);
402  if (!moved)
403  break;
404  moved = endCursor.movePosition(QTextCursor::Down);
405  if (!moved)
406  break;
407 
408  startCursor = document()->find(TextBrowserWidget::startFragmentShaderTag_, startCursor, QTextDocument::FindWholeWords);
409  endCursor = document()->find(TextBrowserWidget::endFragmentShaderTag_, endCursor, QTextDocument::FindWholeWords);
410  }
411 
412  // search for all render objects
413  startCursor = document()->find(TextBrowserWidget::startRenderObjectTag_, 0, QTextDocument::FindWholeWords);
414  endCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, 0, QTextDocument::FindWholeWords);
415 
416  while (!startCursor.isNull() && !endCursor.isNull()) {
417  startCursor.movePosition(QTextCursor::StartOfLine);
418  // vertex shader does not belong to this fold
419  endCursor.movePosition(QTextCursor::Up);
420  endCursor.movePosition(QTextCursor::EndOfLine);
421  folds_.push_back(Fold(startCursor.position(),endCursor.position(),RENDEROBJECT));
422 
423  // map block position to fold
424  int startPos = startCursor.position();
425  const int endPos = endCursor.position();
426  for (; startPos < endPos; ++startPos) {
427  QTextBlock block = document()->findBlock(startPos);
428  blockPosToFold_[block.position()] = folds_.size() - 1;
429  }
430 
431  bool moved = startCursor.movePosition(QTextCursor::Down);
432  if (!moved)
433  break;
434  // skip to after the vertex shader starts
435  moved = endCursor.movePosition(QTextCursor::Down);
436  if (!moved)
437  break;
438  moved = endCursor.movePosition(QTextCursor::Down);
439  if (!moved)
440  break;
441 
442  startCursor = document()->find(TextBrowserWidget::startRenderObjectTag_, startCursor, QTextDocument::FindWholeWords);
443  endCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, endCursor, QTextDocument::FindWholeWords);
444  }
445 
446  // fold shader blocks
447  foldAll();
448 }
bool getFold(int _position, Fold &_fold)
get the _fold corresponding to the document _position