Isis 3 Programmer Reference
TreeViewContent.cpp
1 #include "IsisDebug.h"
2 
3 #include "TreeViewContent.h"
4 
5 #include <cmath>
6 #include <iostream>
7 
8 #include <QAction>
9 #include <QLabel>
10 #include <QMutex>
11 #include <QPainter>
12 #include <QPaintEvent>
13 #include <QScrollBar>
14 #include <QSize>
15 #include <QtCore/qtextstream.h>
16 #include <QVariant>
17 #include <QVBoxLayout>
18 
19 #include "IException.h"
20 #include "IString.h"
21 
22 #include "AbstractTreeItem.h"
23 #include "TableColumn.h"
24 #include "AbstractTreeModel.h"
25 
26 
27 namespace Isis {
28  TreeViewContent::TreeViewContent(QWidget *parent) :
29  QAbstractScrollArea(parent) {
30  nullify();
31 
32  m_parentView = (TreeView *) parent;
33 
34  m_items = new QList< AbstractTreeItem * >;
35  m_mousePressPos = new QPoint;
36  m_pressedItem = new QPair< AbstractTreeItem *, bool >(NULL, false);
37  m_hoveredItem = new QPair< AbstractTreeItem *, bool >(NULL, false);
38  m_lastShiftSelection = new QList<AbstractTreeItem *>;
39 
40  verticalScrollBar()->setSingleStep(1);
41  horizontalScrollBar()->setSingleStep(10);
42  m_rowHeight = QFontMetrics(font()).height() + ITEM_PADDING;
43  m_contentWidth = 0;
44  ASSERT(m_rowHeight > 0);
45 
46  setMouseTracking(true);
47  setContextMenuPolicy(Qt::ActionsContextMenu);
48  QAction *alternateRowsAct = new QAction("&Alternate row colors", this);
49  alternateRowsAct->setCheckable(true);
50  connect(alternateRowsAct, SIGNAL(toggled(bool)),
51  this, SLOT(setAlternatingRowColors(bool)));
52  addAction(alternateRowsAct);
53  alternateRowsAct->setChecked(true);
54  }
55 
56 
57  TreeViewContent::~TreeViewContent() {
58  delete m_items;
59  m_items = NULL;
60 
61  delete m_mousePressPos;
62  m_mousePressPos = NULL;
63 
64  delete m_pressedItem;
65  m_pressedItem = NULL;
66 
67  delete m_hoveredItem;
68  m_hoveredItem = NULL;
69 
70  delete m_lastShiftSelection;
71  m_lastShiftSelection = NULL;
72  }
73 
74 
75  QSize TreeViewContent::minimumSizeHint() const {
76  return QWidget::minimumSizeHint();
77  }
78 
79 
80  QSize TreeViewContent::sizeHint() const {
81  return minimumSizeHint();
82  }
83 
84 
85  AbstractTreeModel *TreeViewContent::getModel() {
86  return m_model;
87  }
88 
89 
90  void TreeViewContent::setModel(AbstractTreeModel *someModel) {
91  if (!someModel) {
92  IString msg = "Attempted to set a NULL model!";
93  throw IException(IException::Programmer, msg, _FILEINFO_);
94  }
95 
96  if (m_model) {
97  disconnect(m_model, SIGNAL(modelModified()), this, SLOT(refresh()));
98  disconnect(m_model, SIGNAL(filterProgressChanged(int)),
99  this, SLOT(updateItemList()));
100  disconnect(this,
101  SIGNAL(treeSelectionChanged(QList< AbstractTreeItem * >)),
102  m_model,
103  SIGNAL(treeSelectionChanged(QList< AbstractTreeItem * >)));
104  disconnect(m_model, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)),
105  this, SLOT(scrollTo(QList<AbstractTreeItem *>)));
106  }
107 
108  m_model = someModel;
109  connect(m_model, SIGNAL(modelModified()), this, SLOT(refresh()));
110  connect(m_model, SIGNAL(filterProgressChanged(int)),
111  this, SLOT(updateItemList()));
112  connect(this, SIGNAL(treeSelectionChanged(QList< AbstractTreeItem * >)),
113  m_model, SIGNAL(treeSelectionChanged(QList< AbstractTreeItem * >)));
114  connect(m_model, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)),
115  this, SLOT(scrollTo(QList<AbstractTreeItem *>)));
116 
117  refresh();
118  }
119 
120 
121  void TreeViewContent::refresh() {
122  ASSERT(m_model);
123  if (m_model) {
124  if (!m_model->isFiltering()) {
125  QSize modelVisibleSize =
126  m_model->getVisibleSize(ITEM_INDENTATION);
127  int rowCount = modelVisibleSize.height();
128  m_contentWidth = modelVisibleSize.width() + ITEM_INDENTATION;
129  verticalScrollBar()->setRange(0, qMax(rowCount - 1, 0));
130  horizontalScrollBar()->setRange(0, m_contentWidth - viewport()->width()
131  + horizontalScrollBar()->singleStep());
132  }
133 
134  updateItemList();
135  viewport()->update();
136  }
137  }
138 
139 
140  bool TreeViewContent::eventFilter(QObject *target, QEvent *event) {
141  return QObject::eventFilter(target, event);
142  }
143 
144 
145  void TreeViewContent::mouseDoubleClickEvent(QMouseEvent *event) {
146  QPoint pressPos = event->pos();
147  int index = pressPos.y() / m_rowHeight;
148 
149  if (index < m_items->size()) {
150  AbstractTreeItem *item = (*m_items)[index];
151  item->setExpanded(!item->isExpanded());
152  refresh();
153  }
154  }
155 
156  void TreeViewContent::mousePressEvent(QMouseEvent *event) {
157  QPoint pressPos = event->pos();
158  int index = pressPos.y() / m_rowHeight;
159 
160  m_pressedItem->first = NULL;
161  m_pressedItem->second = false;
162 
163  if (index < m_items->size()) {
164  AbstractTreeItem *item = (*m_items)[index];
165  if (item->isSelectable() ||
166  (item->getFirstVisibleChild() &&
167  getArrowRect(item).contains(pressPos))) {
168  m_pressedItem->first = item;
169 
170  if (item->getFirstVisibleChild()) {
171  QRect arrowRect(getArrowRect(item));
172  m_pressedItem->second = arrowRect.contains(pressPos);
173  }
174 
175  QList< AbstractTreeItem * > newlySelectedItems;
176  if (!m_pressedItem->second) {
177  if (event->modifiers() & Qt::ControlModifier) {
178  foreach (AbstractTreeItem * child, item->getChildren()) {
179  child->setSelected(!item->isSelected());
180  if (child->isSelected())
181  newlySelectedItems.append(child);
182  }
183 
184  item->setSelected(!item->isSelected());
185  if (item->isSelected())
186  newlySelectedItems.append(item);
187 
188  m_lastDirectlySelectedItem = item;
189  m_lastShiftSelection->clear();
190  }
191  else {
192  if (event->modifiers() & Qt::ShiftModifier) {
193  foreach (AbstractTreeItem * i, *m_lastShiftSelection)
194  i->setSelected(false);
195 
196  if (m_lastDirectlySelectedItem) {
197  // gets the new shift selection without selecting children
199  m_model->getItems(m_lastDirectlySelectedItem, item);
200 
201  // use tmp to create a new m_lastShiftSelection with children
202  // selected as well
203  foreach (AbstractTreeItem * i, tmp) {
204  m_lastShiftSelection->append(i);
205 
206  // if this item is a point item then select its children
207  if (i->getPointerType() == AbstractTreeItem::Point) {
208  foreach (AbstractTreeItem * child, i->getChildren()) {
209  child->setSelected(true);
210  m_lastShiftSelection->append(child);
211  }
212  }
213  }
214  }
215  else {
216  m_lastShiftSelection->clear();
217  }
218 
219  foreach (AbstractTreeItem * i, *m_lastShiftSelection) {
220  i->setSelected(true);
221  newlySelectedItems.append(i);
222  }
223  }
224  else {
225  m_model->setGlobalSelection(false);
226  item->setSelected(true);
227  newlySelectedItems.append(item);
228  m_lastDirectlySelectedItem = item;
229 
230  if (item->getPointerType() == AbstractTreeItem::Point) {
231  foreach (AbstractTreeItem * child, item->getChildren()) {
232  child->setSelected(true);
233  newlySelectedItems.append(child);
234  }
235  }
236 
237  m_lastShiftSelection->clear();
238  }
239  }
240 
241  emit treeSelectionChanged(newlySelectedItems);
242  }
243  }
244  }
245  else {
246  m_model->setGlobalSelection(false);
247  }
248 
249  viewport()->update();
250  }
251 
252 
253  void TreeViewContent::mouseReleaseEvent(QMouseEvent *event) {
254  AbstractTreeItem *item = m_pressedItem->first;
255  if (item && getArrowRect(item).contains(event->pos())) {
256  item->setExpanded(!item->isExpanded());
257  refresh();
258  }
259 
260  m_pressedItem->first = NULL;
261  m_pressedItem->second = false;
262  viewport()->update();
263 
264  QWidget::mousePressEvent(event);
265  }
266 
267 
268  void TreeViewContent::mouseMoveEvent(QMouseEvent *event) {
269  QPoint cursorPos = event->pos();
270  int index = cursorPos.y() / m_rowHeight;
271 
272  m_hoveredItem->first = NULL;
273  m_hoveredItem->second = false;
274 
275  if (index < m_items->size() && index >= 0) {
276  AbstractTreeItem *item = (*m_items)[index];
277  if (item->isSelectable() ||
278  (item->getFirstVisibleChild() &&
279  getArrowRect(item).contains(cursorPos))) {
280  m_hoveredItem->first = item;
281 
282  if (item->getFirstVisibleChild()) {
283  QRect arrowRect = getArrowRect(item);
284  m_hoveredItem->second = arrowRect.contains(cursorPos);
285  }
286  }
287  }
288 
289  viewport()->update();
290  }
291 
292 
293  void TreeViewContent::leaveEvent(QEvent *event) {
294  m_hoveredItem->first = NULL;
295  m_hoveredItem->second = false;
296  viewport()->update();
297  }
298 
299 
300  void TreeViewContent::keyPressEvent(QKeyEvent *event) {
301  if (event->key() == Qt::Key_A &&
302  event->modifiers() == Qt::ControlModifier) {
303  m_model->setGlobalSelection(true);
304  viewport()->update();
305  emit treeSelectionChanged();
306  }
307  else {
308  QWidget::keyPressEvent(event);
309  }
310  }
311 
312 
313  void TreeViewContent::paintEvent(QPaintEvent *event) {
314  if (m_model) {
315  int startRow = verticalScrollBar()->value();
316  int rowCount = (int) ceil(viewport()->height() / (double) m_rowHeight);
317 
318  QPainter painter(viewport());
319  painter.setRenderHints(QPainter::Antialiasing |
320  QPainter::TextAntialiasing);
321 
322  for (int i = 0; i < rowCount; i++) {
323  // Assume the background color should be the base. Then set odd rows
324  // to be the alternate row color if m_alternatingRowColors is set to
325  // true.
326  QColor backgroundColor = palette().base().color();
327 
328  if (i < m_items->size()) {
329  if (m_alternatingRowColors && (startRow + i) % 2 == 1)
330  backgroundColor = palette().alternateBase().color();
331 
332  ASSERT(m_items->at(i));
333  if (m_items->at(i)->isSelected())
334  backgroundColor = palette().highlight().color();
335  }
336 
337  // define the top left corner of the row and also how big the row is
338  QPoint relativeTopLeft(0, i * m_rowHeight);
339  QPoint scrollBarPos(horizontalScrollBar()->value(),
340  verticalScrollBar()->value());
341  QPoint absoluteTopLeft(relativeTopLeft + scrollBarPos);
342  QSize rowSize(viewport()->width(), (int) m_rowHeight);
343 
344  // Fill in the background with the background color
345  painter.fillRect(QRect(relativeTopLeft, rowSize), backgroundColor);
346 
347  // if the mouse is hovering over this item, then also draw a rect
348  // around this item.
349  if (i < m_items->size() && m_hoveredItem->first == (*m_items)[i] &&
350  m_hoveredItem->first->isSelectable()) {
351  QPen prevPen(painter.pen());
352  QPen borderPen(prevPen);
353  borderPen.setWidth(1);
354  borderPen.setColor(palette().highlight().color());
355  painter.setPen(borderPen);
356  QPoint borderTopLeft(relativeTopLeft.x() - absoluteTopLeft.x(),
357  relativeTopLeft.y() + 1);
358 
359  int rectWidth = qMax(m_contentWidth +
360  horizontalScrollBar()->singleStep(), viewport()->width());
361  QSize borderSize(rectWidth, rowSize.height() - 2);
362  painter.drawRect(QRect(borderTopLeft, borderSize));
363  painter.setPen(prevPen);
364  }
365 
366  // if this row has text then draw it
367  if (i < m_items->size())
368  paintItemText(&painter, i, absoluteTopLeft, relativeTopLeft);
369  }
370  }
371  else {
372  QWidget::paintEvent(event);
373  }
374  }
375 
376 
377  void TreeViewContent::resizeEvent(QResizeEvent *event) {
378  QAbstractScrollArea::resizeEvent(event);
379  horizontalScrollBar()->setRange(0, m_contentWidth - viewport()->width()
380  + horizontalScrollBar()->singleStep());
381  updateItemList();
382  }
383 
384 
385  void TreeViewContent::scrollContentsBy(int dx, int dy) {
386  QAbstractScrollArea::scrollContentsBy(dx, dy);
387  updateItemList();
388  }
389 
390 
391  void TreeViewContent::nullify() {
392  m_parentView = NULL;
393  m_model = NULL;
394  m_items = NULL;
395  m_pressedItem = NULL;
396  m_hoveredItem = NULL;
397  m_lastDirectlySelectedItem = NULL;
398  m_lastShiftSelection = NULL;
399  m_mousePressPos = NULL;
400  }
401 
402 
403  void TreeViewContent::paintItemText(QPainter *painter,
404  int index, QPoint absolutePosition, QPoint relativePosition) {
405  ASSERT(m_items);
406  ASSERT(index >= 0 && index < m_items->size());
407 
408  QPoint point(-absolutePosition.x(), relativePosition.y());
409 
410  AbstractTreeItem *item = (*m_items)[index];
411 
412  // should always be true, but prevents segfault in case of bug
413  if (item) {
414  // the parameter called point is given to us as the top left corner of
415  // the row where the text should go. We adjust this point until it can
416  // be used to draw the text in the middle of the row. First the x
417  // component is adjusted. How far the x component needs to be adjusted
418  // is directly related to how many parents this item has, hence the
419  // following while loop. Note that even top level items have a parent
420  // (the invisible root item). Also note that top level items do not get
421  // any adjustment from this while. This is because all items need
422  // exactly one adjustment in the x direction after the arrow is
423  // potentially drawn.
424  AbstractTreeItem *iteratorItem = item;
425  while (iteratorItem->parent() && iteratorItem->parent()->parent()) {
426  point.setX(point.x() + ITEM_INDENTATION);
427  iteratorItem = iteratorItem->parent();
428  }
429 
430  QPen originalPen = painter->pen();
431  if (item->isSelected()) {
432  painter->setPen(QPen(palette().highlightedText().color()));
433  }
434 
435  // now that the x component has all but its last adjustment taken care
436  // of, we then consider items with children. These items need to have
437  // an arrow drawn next to them, before the text is drawn
438  if (item->getFirstVisibleChild()) {
439  // if the user is hovering over the arrow with the mouse, then draw
440  // a box around where the arrow will be drawn
441  QRect itemArrowRect(getArrowRect(item));
442  if (item == m_hoveredItem->first && item == m_pressedItem->first) {
443  if (m_pressedItem->second && m_hoveredItem->second) {
444  QPainter::CompositionMode prevMode = painter->compositionMode();
445  painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
446  QColor color = palette().button().color().darker(160);
447  color.setAlpha(100);
448  painter->fillRect(itemArrowRect, color);
449  painter->setCompositionMode(prevMode);
450  }
451  }
452 
453  // if the user has pressed the mouse over the arrow but has not yet
454  // released it, then darken the background behind it
455  if ((item == m_hoveredItem->first && m_hoveredItem->second) ||
456  (item == m_pressedItem->first && m_pressedItem->second)) {
457  if (!m_pressedItem->first ||
458  (item == m_pressedItem->first && m_pressedItem->second)) {
459  painter->drawRect(itemArrowRect);
460  }
461  }
462 
463  // draw the appropriate arrow based on the items expandedness
464  if (item->isExpanded())
465  drawExpandedArrow(painter, itemArrowRect);
466  else
467  drawCollapsedArrow(painter, itemArrowRect);
468  }
469 
470  // the final x component adjustment is the same whether an arrow was
471  // drawn or not
472  point.setX(point.x() + ITEM_INDENTATION);
473 
474  // adjust the y component to center the text vertically in the row
475  point.setY(point.y() + ITEM_PADDING / 2);
476 
477  // finally draw the text
478  int textHeight = m_rowHeight - ITEM_PADDING;
479  QRect rect(point, QSize(viewport()->width() - point.x(), textHeight));
480  painter->drawText(rect, Qt::TextDontClip, item->getData().toString());
481  painter->setPen(originalPen);
482  }
483  }
484 
485 
486  void TreeViewContent::drawCollapsedArrow(QPainter *painter, QRect rect) {
487  rect.setTopLeft(rect.topLeft() + QPoint(4, 3));
488  rect.setBottomRight(rect.bottomRight() - QPoint(4, 2));
489 
490  QPoint top(rect.topLeft());
491  QPoint bottom(rect.bottomLeft());
492  QPoint right(rect.right(), rect.center().y());
493 
494  QPen prevPen = painter->pen();
495  QPen arrowPen(prevPen);
496  arrowPen.setCapStyle(Qt::RoundCap);
497  arrowPen.setJoinStyle(Qt::RoundJoin);
498  arrowPen.setWidth(2);
499  painter->setPen(arrowPen);
500  painter->drawLine(top, right);
501  painter->drawLine(bottom, right);
502  painter->setPen(prevPen);
503  }
504 
505 
506  void TreeViewContent::drawExpandedArrow(QPainter *painter, QRect rect) {
507  rect.setTopLeft(rect.topLeft() + QPoint(3, 4));
508  rect.setBottomRight(rect.bottomRight() - QPoint(2, 4));
509 
510  QPoint left(rect.topLeft());
511  QPoint right(rect.topRight());
512  QPoint bottom(rect.center().x(), rect.bottom());
513 
514  QPen prevPen = painter->pen();
515  QPen arrowPen(prevPen);
516  arrowPen.setCapStyle(Qt::RoundCap);
517  arrowPen.setJoinStyle(Qt::RoundJoin);
518  arrowPen.setWidth(2);
519  painter->setPen(arrowPen);
520  painter->drawLine(left, bottom);
521  painter->drawLine(right, bottom);
522  painter->setPen(prevPen);
523  }
524 
525 
526  void TreeViewContent::setAlternatingRowColors(bool newStatus) {
527  m_alternatingRowColors = newStatus;
528  viewport()->update();
529  }
530 
531 
532  void TreeViewContent::updateItemList() {
533  int startRow = verticalScrollBar()->value();
534  int rowCount = (int) ceil(viewport()->height() / (double) m_rowHeight);
535  *m_items = m_model->getItems(startRow, startRow + rowCount,
536  AbstractTreeModel::AllItems, false);
537 
538  viewport()->update();
539  }
540 
541 
542  QRect TreeViewContent::getArrowRect(AbstractTreeItem *item) const {
543  QRect arrowRect;
544  if (item) {
545  int index = m_items->indexOf(item);
546  QPoint centerOfArrow(12 - horizontalScrollBar()->value(),
547  (index * m_rowHeight) + (m_rowHeight / 2));
548  int depth = item->getDepth() - 1;
549  centerOfArrow.setX(centerOfArrow.x() + (depth * ITEM_INDENTATION));
550 
551  arrowRect = QRect(centerOfArrow.x() - 6, centerOfArrow.y() - 6, 12, 12);
552  }
553 
554  return arrowRect;
555  }
556 
557  void TreeViewContent::scrollTo(
558  QList< AbstractTreeItem * > newlySelectedItems) {
559  if (newlySelectedItems.size())
560  scrollTo(newlySelectedItems.last());
561  }
562 
563 
564  void TreeViewContent::scrollTo(AbstractTreeItem *newlySelectedItem) {
565  if (newlySelectedItem->getPointerType() == AbstractTreeItem::Measure)
566  newlySelectedItem->parent()->setExpanded(true);
567 
568  int row = getModel()->indexOfVisibleItem(newlySelectedItem);
569 
570  if (row >= 0) {
571  int topRow = verticalScrollBar()->value();
572 
573  if (row < topRow) {
574  verticalScrollBar()->setValue(row);
575  }
576  else {
577  int wholeVisibleRowCount = viewport()->height() / m_rowHeight;
578  int bottomRow = topRow + wholeVisibleRowCount;
579  if (row > bottomRow)
580  verticalScrollBar()->setValue(row - wholeVisibleRowCount + 1);
581  }
582  }
583 
584  viewport()->update();
585  }
586 }
QSize getVisibleSize(int indentation) const
indentation is in pixels
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:162
QPair< AbstractTreeItem *, bool > * m_pressedItem
The bool is true if the arrow in the item was pressed.
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:40
QPair< AbstractTreeItem *, bool > * m_hoveredItem
The bool is true if the mouse is hovering over the arrow.
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31