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 #include <QtWidgets>
44 
45 
46 #include "TextBrowserWidget.hh"
47 
48 QString const TextBrowserWidget::startRenderObjectTag_ = "name:";
49 QString const TextBrowserWidget::startVertexShaderTag_ = "--vertex-shader--";
50 QString const TextBrowserWidget::endVertexShaderTag_ = "--end-vertex-shader--";
51 QString const TextBrowserWidget::startTessControlShaderTag_ = "---tesscontrol-shader--";
52 QString const TextBrowserWidget::endTessControlShaderTag_ = "--end-tesscontrol-shader--";
53 QString const TextBrowserWidget::startTessEvalShaderTag_ = "--tesseval-shader--";
54 QString const TextBrowserWidget::endTessEvalShaderTag_ = "--end-tesseval-shader--";
55 QString const TextBrowserWidget::startGeometryShaderTag_ = "--geometry-shader--";
56 QString const TextBrowserWidget::endGeometryShaderTag_ = "--end-geometry-shader--";
57 QString const TextBrowserWidget::startFragmentShaderTag_ = "--fragment-shader--";
58 QString const TextBrowserWidget::endFragmentShaderTag_ = "--end-fragment-shader--";
59 
60 
61 TextBrowserWidget::TextBrowserWidget(QWidget *parent) : QPlainTextEdit(parent) {
62  sideArea_ = new TextBrowserSideArea(this);
63  updateTextBrowserSideAreaWidth();
64 
65  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateTextBrowserSideAreaWidth()));
66  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateFolds()));
67  connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateTextBrowserSideArea(QRect,int)));
68 
69  setReadOnly(true);
70 }
71 
72 
73 
74 int TextBrowserWidget::sideAreaWidth() {
75  int digits = 1;
76  int max = qMax(1, blockCount());
77  while (max >= 10) {
78  max /= 10;
79  ++digits;
80  }
81 
82  int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
83 
84  return space;
85 }
86 
87 
88 
89 void TextBrowserWidget::updateTextBrowserSideAreaWidth() {
90  setViewportMargins(sideAreaWidth(), 0, 0, 0);
91 }
92 
93 void TextBrowserWidget::updateTextBrowserSideArea(const QRect &rect, int dy) {
94  if (dy)
95  sideArea_->scroll(0, dy);
96  else
97  sideArea_->update(0, rect.y(), sideArea_->width(), rect.height());
98 
99  if (rect.contains(viewport()->rect()))
100  updateTextBrowserSideAreaWidth();
101 }
102 
103 
104 
105 void TextBrowserWidget::resizeEvent(QResizeEvent *e) {
106  QPlainTextEdit::resizeEvent(e);
107 
108  QRect cr = contentsRect();
109  sideArea_->setGeometry(QRect(cr.left(), cr.top(), sideAreaWidth(), cr.height()));
110 }
111 
112 void TextBrowserWidget::sideAreaPaintEvent(QPaintEvent *event) {
113 
114  QPainter painter(sideArea_);
115  painter.fillRect(event->rect(), Qt::lightGray);
116  painter.setPen(Qt::black);
117 
118  QTextBlock block = firstVisibleBlock();
119 
120  int blockNumber = block.blockNumber();
121  int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
122  int bottom = top + (int) blockBoundingRect(block).height();
123 
124  Fold found_fold;
125  while (block.isValid() && top <= event->rect().bottom()) {
126  if (block.isVisible() && bottom >= event->rect().top()) {
127  if (getFold(block.position(), found_fold)) {
128  if (found_fold.type == SHADER) {
129  int fold_first_block = document()->findBlock(found_fold.start).blockNumber();
130  QString text = block.text();
131  // only draw line numbers on actual shader code
132  if (text.contains(TextBrowserWidget::startVertexShaderTag_) ||
133  text.contains(TextBrowserWidget::endVertexShaderTag_) ||
134  text.contains(TextBrowserWidget::startTessControlShaderTag_) ||
135  text.contains(TextBrowserWidget::endTessControlShaderTag_) ||
136  text.contains(TextBrowserWidget::startTessEvalShaderTag_) ||
137  text.contains(TextBrowserWidget::endTessEvalShaderTag_) ||
138  text.contains(TextBrowserWidget::startGeometryShaderTag_) ||
139  text.contains(TextBrowserWidget::endGeometryShaderTag_) ||
140  text.contains(TextBrowserWidget::startFragmentShaderTag_) ||
141  text.contains(TextBrowserWidget::endFragmentShaderTag_)) {
142  if (found_fold.folded)
143  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "+");
144  else
145  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "-");
146  } else {
147  QString number = QString::number(blockNumber - fold_first_block);
148  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, number);
149  }
150  } else {
151  if (found_fold.folded)
152  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "+");
153  else
154  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, "-");
155  }
156  } else
157  painter.drawText(0, top, sideArea_->width(), fontMetrics().height(),Qt::AlignRight, " ");
158  }
159 
160  block = block.next();
161  top = bottom;
162  bottom = top + (int) blockBoundingRect(block).height();
163  ++blockNumber;
164  }
165 }
166 
167 bool TextBrowserWidget::getFold(int _position, Fold& _fold) {
168  std::map<int,size_t>::iterator it = blockPosToFold_.find(_position);
169  if (!folds_.empty() && it != blockPosToFold_.end()) {
170  _fold = folds_[it->second];
171  return true;
172  } else
173  return false;
174 }
175 
176 void TextBrowserWidget::mouseDoubleClickEvent(QMouseEvent* e) {
177  QTextBlock block = firstVisibleBlock();
178  int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
179  int bottom = top + (int) blockBoundingRect(block).height();
180  const int y = e->y();
181  // find the block that was clicked and toggle the folding
182  while (block.isValid()) {
183  if (top <= y && y <= bottom) {
184  toggleFold(block.position());
185  break;
186  }
187 
188  block = block.next();
189  top = bottom;
190  bottom = top + (int) blockBoundingRect(block).height();
191  }
192 }
193 
194 void TextBrowserWidget::foldAll() {
195  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
196  fold(*it);
197  }
198 }
199 
200 void TextBrowserWidget::unfoldAll() {
201  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
202  unfold(*it);
203  }
204 }
205 
206 void TextBrowserWidget::fold(Fold& _fold) {
207  if (_fold.folded)
208  return;
209 
210  QTextBlock startBlock = document()->findBlock(_fold.start);
211  QTextBlock endBlock = document()->findBlock(_fold.end);
212 
213  startBlock = startBlock.next();
214  while (startBlock.isValid() && startBlock != endBlock) {
215  startBlock.setVisible(false);
216  startBlock = startBlock.next();
217  }
218  if (_fold.type == RENDEROBJECT)
219  endBlock.setVisible(false);
220 
221  _fold.folded = true;
222  document()->markContentsDirty(_fold.start, _fold.end - _fold.start);
223 }
224 
225 void TextBrowserWidget::unfold(Fold& _fold) {
226  if (!_fold.folded)
227  return;
228 
229  QTextBlock startBlock = document()->findBlock(_fold.start);
230  QTextBlock endBlock = document()->findBlock(_fold.end);
231 
232  startBlock = startBlock.next();
233  while (startBlock.isValid() && startBlock != endBlock) {
234  startBlock.setVisible(true);
235  startBlock = startBlock.next();
236  }
237  if (_fold.type == RENDEROBJECT)
238  endBlock.setVisible(true);
239 
240  _fold.folded = false;
241  document()->markContentsDirty(_fold.start, _fold.end-_fold.start);
242 }
243 
244 void TextBrowserWidget::toggleFold(int _position) {
245  for (std::vector<Fold>::iterator it = folds_.begin(); it != folds_.end(); ++it) {
246  if (it->contains(_position)) {
247  if (it->folded)
248  unfold(*it);
249  else
250  fold(*it);
251 
252  break;
253  }
254  }
255 }
256 
257 void TextBrowserWidget::updateFolds() {
258  folds_.clear();
259 
260  // search for all vertex shader
261  QTextCursor startCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, 0, QTextDocument::FindWholeWords);
262  QTextCursor endCursor = document()->find(TextBrowserWidget::endVertexShaderTag_, 0, QTextDocument::FindWholeWords);
263 
264  while (!startCursor.isNull() && !endCursor.isNull()) {
265  startCursor.movePosition(QTextCursor::StartOfLine);
266  endCursor.movePosition(QTextCursor::EndOfLine);
267  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
268 
269  // map block position to fold
270  int startPos = startCursor.position();
271  const int endPos = endCursor.position();
272  for (; startPos < endPos; ++startPos) {
273  QTextBlock block = document()->findBlock(startPos);
274  blockPosToFold_[block.position()] = folds_.size() - 1;
275  }
276 
277  bool moved = startCursor.movePosition(QTextCursor::Down);
278  if (!moved)
279  break;
280  moved = endCursor.movePosition(QTextCursor::Down);
281  if (!moved)
282  break;
283 
284  startCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, startCursor, QTextDocument::FindWholeWords);
285  endCursor = document()->find(TextBrowserWidget::endVertexShaderTag_, endCursor, QTextDocument::FindWholeWords);
286  }
287 
288  // search for all tesscontrol shader
289  startCursor = document()->find(TextBrowserWidget::startTessControlShaderTag_, 0, QTextDocument::FindWholeWords);
290  endCursor = document()->find(TextBrowserWidget::endTessControlShaderTag_, 0, QTextDocument::FindWholeWords);
291 
292  while (!startCursor.isNull() && !endCursor.isNull()) {
293  startCursor.movePosition(QTextCursor::StartOfLine);
294  endCursor.movePosition(QTextCursor::EndOfLine);
295  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
296 
297  // map block position to fold
298  int startPos = startCursor.position();
299  const int endPos = endCursor.position();
300  for (; startPos < endPos; ++startPos) {
301  QTextBlock block = document()->findBlock(startPos);
302  blockPosToFold_[block.position()] = folds_.size() - 1;
303  }
304 
305  bool moved = startCursor.movePosition(QTextCursor::Down);
306  if (!moved)
307  break;
308  moved = endCursor.movePosition(QTextCursor::Down);
309  if (!moved)
310  break;
311 
312  startCursor = document()->find(TextBrowserWidget::startTessControlShaderTag_, startCursor, QTextDocument::FindWholeWords);
313  endCursor = document()->find(TextBrowserWidget::endTessControlShaderTag_, endCursor, QTextDocument::FindWholeWords);
314  }
315 
316  // search for all tesseval shader
317  startCursor = document()->find(TextBrowserWidget::startTessEvalShaderTag_, 0, QTextDocument::FindWholeWords);
318  endCursor = document()->find(TextBrowserWidget::endTessEvalShaderTag_, 0, QTextDocument::FindWholeWords);
319 
320  while (!startCursor.isNull() && !endCursor.isNull()) {
321  startCursor.movePosition(QTextCursor::StartOfLine);
322  endCursor.movePosition(QTextCursor::EndOfLine);
323  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
324 
325  // map block position to fold
326  int startPos = startCursor.position();
327  const int endPos = endCursor.position();
328  for (; startPos < endPos; ++startPos) {
329  QTextBlock block = document()->findBlock(startPos);
330  blockPosToFold_[block.position()] = folds_.size() - 1;
331  }
332 
333  bool moved = startCursor.movePosition(QTextCursor::Down);
334  if (!moved)
335  break;
336  moved = endCursor.movePosition(QTextCursor::Down);
337  if (!moved)
338  break;
339 
340  startCursor = document()->find(TextBrowserWidget::startTessEvalShaderTag_, startCursor, QTextDocument::FindWholeWords);
341  endCursor = document()->find(TextBrowserWidget::endTessEvalShaderTag_, endCursor, QTextDocument::FindWholeWords);
342  }
343 
344  // search for all geometry shader
345  startCursor = document()->find(TextBrowserWidget::startGeometryShaderTag_, 0, QTextDocument::FindWholeWords);
346  endCursor = document()->find(TextBrowserWidget::endGeometryShaderTag_, 0, QTextDocument::FindWholeWords);
347 
348  while (!startCursor.isNull() && !endCursor.isNull()) {
349  startCursor.movePosition(QTextCursor::StartOfLine);
350  endCursor.movePosition(QTextCursor::EndOfLine);
351  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
352 
353  // map block position to fold
354  int startPos = startCursor.position();
355  const int endPos = endCursor.position();
356  for (; startPos < endPos; ++startPos) {
357  QTextBlock block = document()->findBlock(startPos);
358  blockPosToFold_[block.position()] = folds_.size() - 1;
359  }
360 
361  bool moved = startCursor.movePosition(QTextCursor::Down);
362  if (!moved)
363  break;
364  moved = endCursor.movePosition(QTextCursor::Down);
365  if (!moved)
366  break;
367 
368  startCursor = document()->find(TextBrowserWidget::startGeometryShaderTag_, startCursor, QTextDocument::FindWholeWords);
369  endCursor = document()->find(TextBrowserWidget::endGeometryShaderTag_, endCursor, QTextDocument::FindWholeWords);
370  }
371 
372  // search for all fragment shader
373  startCursor = document()->find(TextBrowserWidget::startFragmentShaderTag_, 0, QTextDocument::FindWholeWords);
374  endCursor = document()->find(TextBrowserWidget::endFragmentShaderTag_, 0, QTextDocument::FindWholeWords);
375 
376  while (!startCursor.isNull() && !endCursor.isNull()) {
377  startCursor.movePosition(QTextCursor::StartOfLine);
378  endCursor.movePosition(QTextCursor::EndOfLine);
379  folds_.push_back(Fold(startCursor.position(),endCursor.position(),SHADER));
380 
381  // map block position to fold
382  int startPos = startCursor.position();
383  const int endPos = endCursor.position();
384  for (; startPos < endPos; ++startPos) {
385  QTextBlock block = document()->findBlock(startPos);
386  blockPosToFold_[block.position()] = folds_.size() - 1;
387  }
388 
389  bool moved = startCursor.movePosition(QTextCursor::Down);
390  if (!moved)
391  break;
392  moved = endCursor.movePosition(QTextCursor::Down);
393  if (!moved)
394  break;
395 
396  startCursor = document()->find(TextBrowserWidget::startFragmentShaderTag_, startCursor, QTextDocument::FindWholeWords);
397  endCursor = document()->find(TextBrowserWidget::endFragmentShaderTag_, endCursor, QTextDocument::FindWholeWords);
398  }
399 
400  // search for all render objects
401  startCursor = document()->find(TextBrowserWidget::startRenderObjectTag_, 0, QTextDocument::FindWholeWords);
402  endCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, 0, QTextDocument::FindWholeWords);
403 
404  while (!startCursor.isNull() && !endCursor.isNull()) {
405  startCursor.movePosition(QTextCursor::StartOfLine);
406  // vertex shader does not belong to this fold
407  endCursor.movePosition(QTextCursor::Up);
408  endCursor.movePosition(QTextCursor::EndOfLine);
409  folds_.push_back(Fold(startCursor.position(),endCursor.position(),RENDEROBJECT));
410 
411  // map block position to fold
412  int startPos = startCursor.position();
413  const int endPos = endCursor.position();
414  for (; startPos < endPos; ++startPos) {
415  QTextBlock block = document()->findBlock(startPos);
416  blockPosToFold_[block.position()] = folds_.size() - 1;
417  }
418 
419  bool moved = startCursor.movePosition(QTextCursor::Down);
420  if (!moved)
421  break;
422  // skip to after the vertex shader starts
423  moved = endCursor.movePosition(QTextCursor::Down);
424  if (!moved)
425  break;
426  moved = endCursor.movePosition(QTextCursor::Down);
427  if (!moved)
428  break;
429 
430  startCursor = document()->find(TextBrowserWidget::startRenderObjectTag_, startCursor, QTextDocument::FindWholeWords);
431  endCursor = document()->find(TextBrowserWidget::startVertexShaderTag_, endCursor, QTextDocument::FindWholeWords);
432  }
433 
434  // fold shader blocks
435  foldAll();
436 }
bool getFold(int _position, Fold &_fold)
get the _fold corresponding to the document _position