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