USGS

Isis 3.0 Object Programmers' Reference

Home

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