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