Official websites use .gov
A .gov website belongs to an official government organization in the United States.

Secure .gov websites use HTTPS
A lock ( ) or https:// means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.

Isis 3 Programmer Reference
AbstractTableModel.cpp
1
6
7/* SPDX-License-Identifier: CC0-1.0 */
8
9#include <cmath>
10#include <iostream>
11
12#include <QCoreApplication>
13#include <QDateTime>
14#include <QDebug>
15#include <QFutureWatcher>
16#include <QSettings>
17#include <QtConcurrentRun>
18#include <QTimer>
19
20#include "IException.h"
21#include "IString.h"
22
23#include "AbstractTableDelegate.h"
24#include "AbstractTableModel.h"
25#include "BusyLeafItem.h"
26#include "TableColumn.h"
27#include "TableColumnList.h"
28#include "TableView.h"
29#include "AbstractTreeModel.h"
30
31
32namespace Isis {
33 AbstractTableModel::AbstractTableModel(AbstractTreeModel *model,
34 AbstractTableDelegate *someDelegate) {
35 nullify();
36
37 m_dataModel = model;
38 connect(model, SIGNAL(cancelSort()), this, SLOT(cancelSort()));
39
40 m_delegate = someDelegate;
41
42 m_sortingEnabled = false;
43 m_sortLimit = 10000;
44 m_sorting = false;
45
46 m_sortedItems = new QList<AbstractTreeItem *>;
47 m_busyItem = new BusyLeafItem;
48 m_sortStatusPoller = new QTimer;
49
50 m_sortingWatcher = new QFutureWatcher< QList< AbstractTreeItem * > >;
51 connect(m_sortingWatcher, SIGNAL(finished()), this, SLOT(sortFinished()));
52
53 connect(m_sortStatusPoller, SIGNAL(timeout()),
54 this, SLOT(sortStatusUpdated()));
55
56 // Signal forwarding
57 connect(model, SIGNAL(modelModified()), SLOT(rebuildSort()));
58
59 connect(model, SIGNAL(filterProgressChanged(int)),
60 this, SIGNAL(filterProgressChanged(int)));
61
62 connect(model, SIGNAL(rebuildProgressChanged(int)),
63 this, SIGNAL(rebuildProgressChanged(int)));
64
65 connect(model, SIGNAL(filterProgressRangeChanged(int, int)),
66 this, SIGNAL(filterProgressRangeChanged(int, int)));
67
68 connect(model, SIGNAL(rebuildProgressRangeChanged(int, int)),
69 this, SIGNAL(rebuildProgressRangeChanged(int, int)));
70
71 connect(this, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)),
72 model, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)));
73 connect(model, SIGNAL(filterCountsChanged(int, int)),
74 this, SIGNAL(filterCountsChanged(int, int)));
75 }
76
77
78 AbstractTableModel::~AbstractTableModel() {
79 cancelSort();
80
81 m_dataModel = NULL;
82
83 delete m_delegate;
84 m_delegate = NULL;
85
86 delete m_sortedItems;
87 m_sortedItems = NULL;
88
89 delete m_busyItem;
90 m_busyItem = NULL;
91
92 delete m_sortStatusPoller;
93 m_sortStatusPoller = NULL;
94
95 delete m_lessThanFunctor;
96 m_lessThanFunctor = NULL;
97
98 if (m_columns) {
99 for (int i = 0; i < m_columns->size(); i++)
100 delete(*m_columns)[i];
101
102 delete m_columns;
103 m_columns = NULL;
104 }
105
106 delete m_sortingWatcher;
107 m_sortingWatcher = NULL;
108 }
109
110
111 bool AbstractTableModel::isSorting() const {
112 return m_sorting;
113 }
114
115
116 bool AbstractTableModel::isFiltering() const {
117 return m_dataModel && m_dataModel->isFiltering();
118 }
119
120
121 bool AbstractTableModel::sortingIsEnabled() const {
122 return m_sortingEnabled;
123 }
124
125
126 void AbstractTableModel::setSortingEnabled(bool enabled) {
127 if (m_sortingEnabled != enabled) {
128 m_sortingEnabled = enabled;
129 rebuildSort();
130 }
131 }
132
133
134 int AbstractTableModel::sortLimit() const {
135 return m_sortLimit;
136 }
137
138
139 void AbstractTableModel::setSortLimit(int limit) {
140 if (m_sortLimit != limit) {
141 m_sortLimit = limit;
142 rebuildSort();
143 }
144 }
145
146
147 bool AbstractTableModel::sortingOn() const {
148 return (sortingIsEnabled() && (getVisibleRowCount() <= sortLimit()));
149 }
150
151
152 TableColumnList *AbstractTableModel::getColumns() {
153 if (!m_columns) {
154 m_columns = createColumns();
155 connect(m_columns, SIGNAL(sortOutDated()), this, SLOT(sort()));
156 }
157
158 return m_columns;
159 }
160
161
162 const AbstractTableDelegate *AbstractTableModel::getDelegate() const {
163 return m_delegate;
164 }
165
166
167 void AbstractTableModel::applyFilter() {
168 getDataModel()->applyFilter();
169 }
170
171
172 void AbstractTableModel::sort() {
173 if (sortingOn() && m_sortedItems->size() && !m_dataModel->isFiltering() &&
174 !m_dataModel->isRebuilding()) {
175 if (isSorting()) {
176 cancelSort();
177 }
178 else if (!m_lessThanFunctor) {
179 // Create a new comparison functor to be used in the m_sort. It will
180 // keep track of the number of comparisons made so that we can make a
181 // guess at the progress of the m_sort.
182 m_lessThanFunctor = new LessThanFunctor(
183 m_columns->getSortingOrder().first());
184
185 // Sorting is always done on a COPY of the items list.
186 QFuture< QList< AbstractTreeItem * > > future =
187 QtConcurrent::run(&AbstractTableModel::doSort, this,
188 *m_sortedItems);
189 m_sortingWatcher->setFuture(future);
190
191 emit modelModified();
192 }
193 }
194 }
195
196
197 void AbstractTableModel::reverseOrder(TableColumn *column) {
198 }
199
200
201 void AbstractTableModel::updateSort() {
202 }
203
204
205 AbstractTreeModel *AbstractTableModel::getDataModel() {
206 return m_dataModel;
207 }
208
209
210 const AbstractTreeModel *AbstractTableModel::getDataModel() const {
211 return m_dataModel;
212 }
213
214
215 QList< AbstractTreeItem * > AbstractTableModel::getSortedItems(
216 int start, int end, AbstractTreeModel::InterestingItems flags) {
217 QList< AbstractTreeItem * > sortedSubsetOfItems;
218
219 if (sortingOn()) {
220 while (start <= end) {
221 if (start < m_sortedItems->size())
222 sortedSubsetOfItems.append(m_sortedItems->at(start));
223 else if (isFiltering())
224 sortedSubsetOfItems.append(m_busyItem);
225
226 start++;
227 }
228 }
229 else {
230 sortedSubsetOfItems = getDataModel()->getItems(start, end, flags, true);
231 }
232
233 return sortedSubsetOfItems;
234 }
235
236
237 QList< AbstractTreeItem * > AbstractTableModel::getSortedItems(
238 AbstractTreeItem *item1, AbstractTreeItem *item2,
239 AbstractTreeModel::InterestingItems flags) {
240 QList< AbstractTreeItem * > sortedSubsetOfItems;
241
242 if (!sortingOn()) {
243 sortedSubsetOfItems = getDataModel()->getItems(item1, item2, flags, true);
244 }
245 else {
246 AbstractTreeItem *start = NULL;
247
248 int currentIndex = 0;
249
250 while (!start && currentIndex < m_sortedItems->size()) {
251 AbstractTreeItem *current = m_sortedItems->at(currentIndex);
252 if (current == item1)
253 start = item1;
254 else if (current == item2)
255 start = item2;
256
257 if (!start)
258 currentIndex++;
259 }
260
261 if (!start) {
262 IString msg = "Could not find the first item";
263 throw IException(IException::Programmer, msg, _FILEINFO_);
264 }
265
266 AbstractTreeItem *end = item2;
267
268 // Sometimes we need to build the list forwards and sometimes backwards.
269 // This is accomplished by using either append or prepend. We abstract
270 // away which of these we should use (why should we care) by using the
271 // variable "someKindaPend" to store the appropriate method.
272 void (QList< AbstractTreeItem * >::*someKindaPend)(AbstractTreeItem *);
273 someKindaPend = &QList< AbstractTreeItem * >::append;
274
275 if (start == item2) {
276 end = item1;
277 someKindaPend = &QList< AbstractTreeItem * >::prepend;
278 }
279
280 while (currentIndex < m_sortedItems->size() &&
281 m_sortedItems->at(currentIndex) != end) {
282 (sortedSubsetOfItems.*someKindaPend)(m_sortedItems->at(currentIndex));
283 currentIndex++;
284 }
285
286 if (currentIndex >= m_sortedItems->size()) {
287 IString msg = "Could not find the second item";
288 throw IException(IException::Programmer, msg, _FILEINFO_);
289 }
290
291 (sortedSubsetOfItems.*someKindaPend)(end);
292 }
293
294 return sortedSubsetOfItems;
295 }
296
297
298 void AbstractTableModel::handleTreeSelectionChanged(
299 QList< AbstractTreeItem * > newlySelectedItems,
300 AbstractTreeItem::InternalPointerType pointerType) {
301 QList< AbstractTreeItem * > interestingSelectedItems;
302 foreach (AbstractTreeItem * item, newlySelectedItems) {
303 if (item->getPointerType() == pointerType)
304 interestingSelectedItems.append(item);
305 }
306
307 if (interestingSelectedItems.size()) {
308 emit treeSelectionChanged(interestingSelectedItems);
309 }
310 }
311
312
313 void AbstractTableModel::sortStatusUpdated() {
314 if (m_lessThanFunctor)
315 emit sortProgressChanged(m_lessThanFunctor->getCompareCount());
316 }
317
318
319 void AbstractTableModel::sortFinished() {
320 bool interrupted = m_lessThanFunctor->interrupted();
321 delete m_lessThanFunctor;
322 m_lessThanFunctor = NULL;
323
324 if (!interrupted) {
325 QList< AbstractTreeItem * > newSortedItems = m_sortingWatcher->result();
326
327 if (!m_dataModel->isFiltering() && !m_dataModel->isRebuilding()) {
328 *m_sortedItems = newSortedItems;
329 emit modelModified();
330 }
331 }
332 else {
333 sort();
334 }
335 }
336
337
338 void AbstractTableModel::cancelSort() {
339 if (m_lessThanFunctor) {
340 m_lessThanFunctor->interrupt();
341 m_sortingWatcher->waitForFinished();
342 }
343 }
344
345
346 void AbstractTableModel::itemsLost() {
347 cancelSort();
348 m_sortedItems->clear();
349 }
350
351
352 QList< AbstractTreeItem * > AbstractTableModel::doSort(
353 QList< AbstractTreeItem * > itemsToSort) {
354 if (!isSorting()) {
355 setSorting(true);
356
357 QList< TableColumn * > columnsToSortOn = m_columns->getSortingOrder();
358 if (sortingOn()) {
359 // Reset the timer so that it will begin polling the status of the
360 // m_sort.
361 m_sortStatusPoller->start(SORT_UPDATE_FREQUENCY);
362
363 // Use n*log2(n) as our estimate of the number of comparisons that it
364 // should take to m_sort the list.
365 int numItems = itemsToSort.size();
366 double a = 1.0;
367 double b = 1.0;
368 emit sortProgressRangeChanged(0,
369 (int)((a * numItems) * (log2(b * numItems))));
370
371 try {
372 std::stable_sort(itemsToSort.begin(), itemsToSort.end(),
373 *m_lessThanFunctor);
374 }
375 catch (SortingCanceledException &e) {
376 m_sortStatusPoller->stop();
377 emit sortProgressRangeChanged(0, 0);
378 emit sortProgressChanged(0);
379 emit modelModified();
380
381 setSorting(false);
382 return QList< AbstractTreeItem * >();
383 }
384
385 // The m_sort is done, so stop emiting status updates and make sure we
386 // let the listeners know that the m_sort is done (since the status
387 // will not always reach 100% as we are estimating the progress).
388 m_sortStatusPoller->stop();
389 emit sortProgressRangeChanged(0, 0);
390 emit sortProgressChanged(0);
391 emit modelModified();
392 }
393
394 setSorting(false);
395 }
396
397 return itemsToSort;
398 }
399
400
401 void AbstractTableModel::nullify() {
402 m_dataModel = NULL;
403 m_delegate = NULL;
404 m_sortedItems = NULL;
405 m_busyItem = NULL;
406 m_sortStatusPoller = NULL;
407 m_lessThanFunctor = NULL;
408 m_columns = NULL;
409 m_sortingWatcher = NULL;
410 }
411
412
413 void AbstractTableModel::setSorting(bool isSorting) {
414 m_sorting = isSorting;
415 }
416
417
418 void AbstractTableModel::rebuildSort() {
419 m_sortedItems->clear();
420 cancelSort();
421
422 if (sortingOn()) {
423 m_sortingEnabled = false;
424 *m_sortedItems = getItems(0, -1);
425
426 foreach (AbstractTreeItem * item, *m_sortedItems) {
427 connect(item, SIGNAL(destroyed(QObject *)), this, SLOT(itemsLost()));
428 }
429
430 m_sortingEnabled = true;
431 sort();
432
433 emit userWarning(None);
434 }
435 else {
436 cancelSort();
437 emit modelModified();
438
439 if (!m_sortingEnabled)
440 emit userWarning(SortingDisabled);
441 else
442 emit userWarning(SortingTableSizeLimitReached);
443 }
444 }
445
446
447 // *********** LessThanFunctor implementation *************
448
449
450 AbstractTableModel::LessThanFunctor::LessThanFunctor(
451 TableColumn const *someColumn) : m_column(someColumn) {
452 m_sharedData = new LessThanFunctorData;
453 }
454
455
456 AbstractTableModel::LessThanFunctor::LessThanFunctor(
457 LessThanFunctor const &other) : m_sharedData(other.m_sharedData) {
458 m_column = other.m_column;
459 }
460
461
462 AbstractTableModel::LessThanFunctor::~LessThanFunctor() {
463 m_column = NULL;
464 }
465
466
467 int AbstractTableModel::LessThanFunctor::getCompareCount() const {
468 return m_sharedData->getCompareCount();
469 }
470
471
472 void AbstractTableModel::LessThanFunctor::interrupt() {
473 m_sharedData->setInterrupted(true);
474 }
475
476
477 bool AbstractTableModel::LessThanFunctor::interrupted() {
478 return m_sharedData->interrupted();
479 }
480
481
482 void AbstractTableModel::LessThanFunctor::reset() {
483 m_sharedData->setInterrupted(false);
484 }
485
486
487 bool AbstractTableModel::LessThanFunctor::operator()(
488 AbstractTreeItem *const &left, AbstractTreeItem *const &right) {
489 if (left->getPointerType() != right->getPointerType()) {
490 IString msg = "Tried to compare apples to oranges";
491 throw IException(IException::Programmer, msg, _FILEINFO_);
492 }
493
494 if (m_sharedData->interrupted()) {
495 throw SortingCanceledException();
496 }
497
498 m_sharedData->incrementCompareCount();
499
500 QVariant leftData = left->getData(m_column->getTitle());
501 QVariant rightData = right->getData(m_column->getTitle());
502 QString busy = BusyLeafItem().getData().toString();
503
504 bool lessThan;
505 if (leftData.type() == QVariant::String &&
506 rightData.type() == QVariant::String) {
507 lessThan = leftData.toString() < rightData.toString();
508 }
509 else if (leftData.type() == QVariant::Double &&
510 rightData.type() == QVariant::Double) {
511 lessThan = (leftData.toDouble() < rightData.toDouble());
512 }
513 else if (leftData.type() == QVariant::Double ||
514 rightData.type() == QVariant::Double) {
515 // We are comparing a BusyLeafItem to a double. BusyLeafItem's should
516 // always be less than the double.
517 lessThan = (leftData.toString() == busy);
518 }
519 else {
520 lessThan = leftData.toString() < rightData.toString();
521 }
522
523 return lessThan ^ m_column->sortAscending();
524 }
525
526
527 AbstractTableModel::LessThanFunctor &
528 AbstractTableModel::LessThanFunctor::operator=(
529 LessThanFunctor const &other) {
530 if (this != &other) {
531 m_column = other.m_column;
532 m_sharedData = other.m_sharedData;
533 }
534
535 return *this;
536 }
537
538
539 // *********** LessThanFunctorData implementation *************
540
541
542 AbstractTableModel::LessThanFunctorData::LessThanFunctorData() {
543 m_compareCount.fetchAndStoreRelaxed(0);
544 m_interruptFlag.fetchAndStoreRelaxed(0);
545 }
546
547
548 AbstractTableModel::LessThanFunctorData::LessThanFunctorData(
549 LessThanFunctorData const &other) : QSharedData(other),
550 m_compareCount(other.m_compareCount), m_interruptFlag(other.m_interruptFlag) {
551 }
552
553
554 AbstractTableModel::LessThanFunctorData::~LessThanFunctorData() {
555 }
556
557
558 int AbstractTableModel::LessThanFunctorData::getCompareCount() const {
559 return m_compareCount;
560 }
561
562
563 void AbstractTableModel::LessThanFunctorData::incrementCompareCount() {
564 m_compareCount.fetchAndAddRelaxed(1);
565 }
566
567
568 void AbstractTableModel::LessThanFunctorData::setInterrupted(bool newStatus) {
569 newStatus ? m_interruptFlag.fetchAndStoreRelaxed(1) :
570 m_interruptFlag.fetchAndStoreRelaxed(0);
571 }
572
573
574 bool AbstractTableModel::LessThanFunctorData::interrupted() {
575 return m_interruptFlag != 0;
576 }
577}
Base class for delegates which create, read, and save data in the tables.
Thread-safe exception for cancelling sorting.
Base class for an item in the tree.
Base class for tree models.
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16