Isis 3 Programmer Reference
AbstractTableModel.cpp
1
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(this, &AbstractTableModel::doSort,
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)(
273 AbstractTreeItem * const &);
275
276 if (start == item2) {
277 end = item1;
279 }
280
281 while (currentIndex < m_sortedItems->size() &&
282 m_sortedItems->at(currentIndex) != end) {
283 (sortedSubsetOfItems.*someKindaPend)(m_sortedItems->at(currentIndex));
284 currentIndex++;
285 }
286
287 if (currentIndex >= m_sortedItems->size()) {
288 IString msg = "Could not find the second item";
289 throw IException(IException::Programmer, msg, _FILEINFO_);
290 }
291
292 (sortedSubsetOfItems.*someKindaPend)(end);
293 }
294
295 return sortedSubsetOfItems;
296 }
297
298
299 void AbstractTableModel::handleTreeSelectionChanged(
300 QList< AbstractTreeItem * > newlySelectedItems,
301 AbstractTreeItem::InternalPointerType pointerType) {
302 QList< AbstractTreeItem * > interestingSelectedItems;
303 foreach (AbstractTreeItem * item, newlySelectedItems) {
304 if (item->getPointerType() == pointerType)
305 interestingSelectedItems.append(item);
306 }
307
308 if (interestingSelectedItems.size()) {
309 emit treeSelectionChanged(interestingSelectedItems);
310 }
311 }
312
313
314 void AbstractTableModel::sortStatusUpdated() {
315 if (m_lessThanFunctor)
316 emit sortProgressChanged(m_lessThanFunctor->getCompareCount());
317 }
318
319
320 void AbstractTableModel::sortFinished() {
321 bool interrupted = m_lessThanFunctor->interrupted();
322 delete m_lessThanFunctor;
323 m_lessThanFunctor = NULL;
324
325 if (!interrupted) {
326 QList< AbstractTreeItem * > newSortedItems = m_sortingWatcher->result();
327
328 if (!m_dataModel->isFiltering() && !m_dataModel->isRebuilding()) {
329 *m_sortedItems = newSortedItems;
330 emit modelModified();
331 }
332 }
333 else {
334 sort();
335 }
336 }
337
338
339 void AbstractTableModel::cancelSort() {
340 if (m_lessThanFunctor) {
341 m_lessThanFunctor->interrupt();
342 m_sortingWatcher->waitForFinished();
343 }
344 }
345
346
347 void AbstractTableModel::itemsLost() {
348 cancelSort();
349 m_sortedItems->clear();
350 }
351
352
353 QList< AbstractTreeItem * > AbstractTableModel::doSort(
354 QList< AbstractTreeItem * > itemsToSort) {
355 if (!isSorting()) {
356 setSorting(true);
357
358 QList< TableColumn * > columnsToSortOn = m_columns->getSortingOrder();
359 if (sortingOn()) {
360 // Reset the timer so that it will begin polling the status of the
361 // m_sort.
362 m_sortStatusPoller->start(SORT_UPDATE_FREQUENCY);
363
364 // Use n*log2(n) as our estimate of the number of comparisons that it
365 // should take to m_sort the list.
366 int numItems = itemsToSort.size();
367 double a = 1.0;
368 double b = 1.0;
369 emit sortProgressRangeChanged(0,
370 (int)((a * numItems) * (log2(b * numItems))));
371
372 try {
373 qStableSort(itemsToSort.begin(), itemsToSort.end(),
374 *m_lessThanFunctor);
375 }
376 catch (SortingCanceledException &e) {
377 m_sortStatusPoller->stop();
378 emit sortProgressRangeChanged(0, 0);
379 emit sortProgressChanged(0);
380 emit modelModified();
381
382 setSorting(false);
383 return QList< AbstractTreeItem * >();
384 }
385
386 // The m_sort is done, so stop emiting status updates and make sure we
387 // let the listeners know that the m_sort is done (since the status
388 // will not always reach 100% as we are estimating the progress).
389 m_sortStatusPoller->stop();
390 emit sortProgressRangeChanged(0, 0);
391 emit sortProgressChanged(0);
392 emit modelModified();
393 }
394
395 setSorting(false);
396 }
397
398 return itemsToSort;
399 }
400
401
402 void AbstractTableModel::nullify() {
403 m_dataModel = NULL;
404 m_delegate = NULL;
405 m_sortedItems = NULL;
406 m_busyItem = NULL;
407 m_sortStatusPoller = NULL;
408 m_lessThanFunctor = NULL;
409 m_columns = NULL;
410 m_sortingWatcher = NULL;
411 }
412
413
414 void AbstractTableModel::setSorting(bool isSorting) {
415 m_sorting = isSorting;
416 }
417
418
419 void AbstractTableModel::rebuildSort() {
420 m_sortedItems->clear();
421 cancelSort();
422
423 if (sortingOn()) {
424 m_sortingEnabled = false;
425 *m_sortedItems = getItems(0, -1);
426
427 foreach (AbstractTreeItem * item, *m_sortedItems) {
428 connect(item, SIGNAL(destroyed(QObject *)), this, SLOT(itemsLost()));
429 }
430
431 m_sortingEnabled = true;
432 sort();
433
434 emit userWarning(None);
435 }
436 else {
437 cancelSort();
438 emit modelModified();
439
440 if (!m_sortingEnabled)
441 emit userWarning(SortingDisabled);
442 else
443 emit userWarning(SortingTableSizeLimitReached);
444 }
445 }
446
447
448 // *********** LessThanFunctor implementation *************
449
450
451 AbstractTableModel::LessThanFunctor::LessThanFunctor(
452 TableColumn const *someColumn) : m_column(someColumn) {
453 m_sharedData = new LessThanFunctorData;
454 }
455
456
457 AbstractTableModel::LessThanFunctor::LessThanFunctor(
458 LessThanFunctor const &other) : m_sharedData(other.m_sharedData) {
459 m_column = other.m_column;
460 }
461
462
463 AbstractTableModel::LessThanFunctor::~LessThanFunctor() {
464 m_column = NULL;
465 }
466
467
468 int AbstractTableModel::LessThanFunctor::getCompareCount() const {
469 return m_sharedData->getCompareCount();
470 }
471
472
473 void AbstractTableModel::LessThanFunctor::interrupt() {
474 m_sharedData->setInterrupted(true);
475 }
476
477
478 bool AbstractTableModel::LessThanFunctor::interrupted() {
479 return m_sharedData->interrupted();
480 }
481
482
483 void AbstractTableModel::LessThanFunctor::reset() {
484 m_sharedData->setInterrupted(false);
485 }
486
487
488 bool AbstractTableModel::LessThanFunctor::operator()(
489 AbstractTreeItem *const &left, AbstractTreeItem *const &right) {
490 if (left->getPointerType() != right->getPointerType()) {
491 IString msg = "Tried to compare apples to oranges";
492 throw IException(IException::Programmer, msg, _FILEINFO_);
493 }
494
495 if (m_sharedData->interrupted()) {
496 throw SortingCanceledException();
497 }
498
499 m_sharedData->incrementCompareCount();
500
501 QVariant leftData = left->getData(m_column->getTitle());
502 QVariant rightData = right->getData(m_column->getTitle());
503 QString busy = BusyLeafItem().getData().toString();
504
505 bool lessThan;
506 if (leftData.type() == QVariant::String &&
507 rightData.type() == QVariant::String) {
508 lessThan = leftData.toString() < rightData.toString();
509 }
510 else if (leftData.type() == QVariant::Double &&
511 rightData.type() == QVariant::Double) {
512 lessThan = (leftData.toDouble() < rightData.toDouble());
513 }
514 else if (leftData.type() == QVariant::Double ||
515 rightData.type() == QVariant::Double) {
516 // We are comparing a BusyLeafItem to a double. BusyLeafItem's should
517 // always be less than the double.
518 lessThan = (leftData.toString() == busy);
519 }
520 else {
521 lessThan = leftData.toString() < rightData.toString();
522 }
523
524 return lessThan ^ m_column->sortAscending();
525 }
526
527
528 AbstractTableModel::LessThanFunctor &
529 AbstractTableModel::LessThanFunctor::operator=(
530 LessThanFunctor const &other) {
531 if (this != &other) {
532 m_column = other.m_column;
533 m_sharedData = other.m_sharedData;
534 }
535
536 return *this;
537 }
538
539
540 // *********** LessThanFunctorData implementation *************
541
542
543 AbstractTableModel::LessThanFunctorData::LessThanFunctorData() {
544 m_compareCount.fetchAndStoreRelaxed(0);
545 m_interruptFlag.fetchAndStoreRelaxed(0);
546 }
547
548
549 AbstractTableModel::LessThanFunctorData::LessThanFunctorData(
550 LessThanFunctorData const &other) : QSharedData(other),
551 m_compareCount(other.m_compareCount), m_interruptFlag(other.m_interruptFlag) {
552 }
553
554
555 AbstractTableModel::LessThanFunctorData::~LessThanFunctorData() {
556 }
557
558
559 int AbstractTableModel::LessThanFunctorData::getCompareCount() const {
560 return m_compareCount;
561 }
562
563
564 void AbstractTableModel::LessThanFunctorData::incrementCompareCount() {
565 m_compareCount.fetchAndAddRelaxed(1);
566 }
567
568
569 void AbstractTableModel::LessThanFunctorData::setInterrupted(bool newStatus) {
570 newStatus ? m_interruptFlag.fetchAndStoreRelaxed(1) :
571 m_interruptFlag.fetchAndStoreRelaxed(0);
572 }
573
574
575 bool AbstractTableModel::LessThanFunctorData::interrupted() {
576 return m_interruptFlag != 0;
577 }
578}
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition IException.h:146
This is free and unencumbered software released into the public domain.
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16