1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "history.h"
43
44 #include "autosaver.h"
45 #include "browserapplication.h"
46
47 #include <QtCore/QBuffer>
48 #include <QtCore/QDir>
49 #include <QtCore/QFile>
50 #include <QtCore/QFileInfo>
51 #include <QtCore/QSettings>
52 #include <QtCore/QTemporaryFile>
53 #include <QtCore/QTextStream>
54
55 #include <QtCore/QtAlgorithms>
56
57 #include <QtGui/QClipboard>
58 #include <QtGui/QDesktopServices>
59 #include <QtGui/QHeaderView>
60 #include <QtGui/QStyle>
61
62 #include <QtWebKit/QWebHistoryInterface>
63 #include <QtWebKit/QWebSettings>
64
65 #include <QtCore/QDebug>
66
67 static const unsigned int HISTORY_VERSION = 23;
68
HistoryManager(QObject * parent)69 HistoryManager::HistoryManager(QObject *parent)
70 : QWebHistoryInterface(parent)
71 , m_saveTimer(new AutoSaver(this))
72 , m_historyLimit(30)
73 , m_historyModel(0)
74 , m_historyFilterModel(0)
75 , m_historyTreeModel(0)
76 {
77 m_expiredTimer.setSingleShot(true);
78 connect(&m_expiredTimer, SIGNAL(timeout()),
79 this, SLOT(checkForExpired()));
80 connect(this, SIGNAL(entryAdded(HistoryItem)),
81 m_saveTimer, SLOT(changeOccurred()));
82 connect(this, SIGNAL(entryRemoved(HistoryItem)),
83 m_saveTimer, SLOT(changeOccurred()));
84 load();
85
86 m_historyModel = new HistoryModel(this, this);
87 m_historyFilterModel = new HistoryFilterModel(m_historyModel, this);
88 m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this);
89
90 // QWebHistoryInterface will delete the history manager
91 QWebHistoryInterface::setDefaultInterface(this);
92 }
93
~HistoryManager()94 HistoryManager::~HistoryManager()
95 {
96 m_saveTimer->saveIfNeccessary();
97 }
98
history() const99 QList<HistoryItem> HistoryManager::history() const
100 {
101 return m_history;
102 }
103
historyContains(const QString & url) const104 bool HistoryManager::historyContains(const QString &url) const
105 {
106 return m_historyFilterModel->historyContains(url);
107 }
108
addHistoryEntry(const QString & url)109 void HistoryManager::addHistoryEntry(const QString &url)
110 {
111 QUrl cleanUrl(url);
112 cleanUrl.setPassword(QString());
113 cleanUrl.setHost(cleanUrl.host().toLower());
114 HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime());
115 addHistoryItem(item);
116 }
117
setHistory(const QList<HistoryItem> & history,bool loadedAndSorted)118 void HistoryManager::setHistory(const QList<HistoryItem> &history, bool loadedAndSorted)
119 {
120 m_history = history;
121
122 // verify that it is sorted by date
123 if (!loadedAndSorted)
124 qSort(m_history.begin(), m_history.end());
125
126 checkForExpired();
127
128 if (loadedAndSorted) {
129 m_lastSavedUrl = m_history.value(0).url;
130 } else {
131 m_lastSavedUrl = QString();
132 m_saveTimer->changeOccurred();
133 }
134 emit historyReset();
135 }
136
historyModel() const137 HistoryModel *HistoryManager::historyModel() const
138 {
139 return m_historyModel;
140 }
141
historyFilterModel() const142 HistoryFilterModel *HistoryManager::historyFilterModel() const
143 {
144 return m_historyFilterModel;
145 }
146
historyTreeModel() const147 HistoryTreeModel *HistoryManager::historyTreeModel() const
148 {
149 return m_historyTreeModel;
150 }
151
checkForExpired()152 void HistoryManager::checkForExpired()
153 {
154 if (m_historyLimit < 0 || m_history.isEmpty())
155 return;
156
157 QDateTime now = QDateTime::currentDateTime();
158 int nextTimeout = 0;
159
160 while (!m_history.isEmpty()) {
161 QDateTime checkForExpired = m_history.last().dateTime;
162 checkForExpired.setDate(checkForExpired.date().addDays(m_historyLimit));
163 if (now.daysTo(checkForExpired) > 7) {
164 // check at most in a week to prevent int overflows on the timer
165 nextTimeout = 7 * 86400;
166 } else {
167 nextTimeout = now.secsTo(checkForExpired);
168 }
169 if (nextTimeout > 0)
170 break;
171 HistoryItem item = m_history.takeLast();
172 // remove from saved file also
173 m_lastSavedUrl = QString();
174 emit entryRemoved(item);
175 }
176
177 if (nextTimeout > 0)
178 m_expiredTimer.start(nextTimeout * 1000);
179 }
180
addHistoryItem(const HistoryItem & item)181 void HistoryManager::addHistoryItem(const HistoryItem &item)
182 {
183 QWebSettings *globalSettings = QWebSettings::globalSettings();
184 if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
185 return;
186
187 m_history.prepend(item);
188 emit entryAdded(item);
189 if (m_history.count() == 1)
190 checkForExpired();
191 }
192
updateHistoryItem(const QUrl & url,const QString & title)193 void HistoryManager::updateHistoryItem(const QUrl &url, const QString &title)
194 {
195 for (int i = 0; i < m_history.count(); ++i) {
196 if (url == m_history.at(i).url) {
197 m_history[i].title = title;
198 m_saveTimer->changeOccurred();
199 if (m_lastSavedUrl.isEmpty())
200 m_lastSavedUrl = m_history.at(i).url;
201 emit entryUpdated(i);
202 break;
203 }
204 }
205 }
206
historyLimit() const207 int HistoryManager::historyLimit() const
208 {
209 return m_historyLimit;
210 }
211
setHistoryLimit(int limit)212 void HistoryManager::setHistoryLimit(int limit)
213 {
214 if (m_historyLimit == limit)
215 return;
216 m_historyLimit = limit;
217 checkForExpired();
218 m_saveTimer->changeOccurred();
219 }
220
clear()221 void HistoryManager::clear()
222 {
223 m_history.clear();
224 m_lastSavedUrl = QString();
225 m_saveTimer->changeOccurred();
226 m_saveTimer->saveIfNeccessary();
227 historyReset();
228 }
229
loadSettings()230 void HistoryManager::loadSettings()
231 {
232 // load settings
233 QSettings settings;
234 settings.beginGroup(QLatin1String("history"));
235 m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt();
236 }
237
load()238 void HistoryManager::load()
239 {
240 loadSettings();
241
242 QFile historyFile(QDesktopServices::storageLocation(QDesktopServices::DataLocation)
243 + QLatin1String("/history"));
244 if (!historyFile.exists())
245 return;
246 if (!historyFile.open(QFile::ReadOnly)) {
247 qWarning() << "Unable to open history file" << historyFile.fileName();
248 return;
249 }
250
251 QList<HistoryItem> list;
252 QDataStream in(&historyFile);
253 // Double check that the history file is sorted as it is read in
254 bool needToSort = false;
255 HistoryItem lastInsertedItem;
256 QByteArray data;
257 QDataStream stream;
258 QBuffer buffer;
259 stream.setDevice(&buffer);
260 while (!historyFile.atEnd()) {
261 in >> data;
262 buffer.close();
263 buffer.setBuffer(&data);
264 buffer.open(QIODevice::ReadOnly);
265 quint32 ver;
266 stream >> ver;
267 if (ver != HISTORY_VERSION)
268 continue;
269 HistoryItem item;
270 stream >> item.url;
271 stream >> item.dateTime;
272 stream >> item.title;
273
274 if (!item.dateTime.isValid())
275 continue;
276
277 if (item == lastInsertedItem) {
278 if (lastInsertedItem.title.isEmpty() && !list.isEmpty())
279 list[0].title = item.title;
280 continue;
281 }
282
283 if (!needToSort && !list.isEmpty() && lastInsertedItem < item)
284 needToSort = true;
285
286 list.prepend(item);
287 lastInsertedItem = item;
288 }
289 if (needToSort)
290 qSort(list.begin(), list.end());
291
292 setHistory(list, true);
293
294 // If we had to sort re-write the whole history sorted
295 if (needToSort) {
296 m_lastSavedUrl = QString();
297 m_saveTimer->changeOccurred();
298 }
299 }
300
save()301 void HistoryManager::save()
302 {
303 QSettings settings;
304 settings.beginGroup(QLatin1String("history"));
305 settings.setValue(QLatin1String("historyLimit"), m_historyLimit);
306
307 bool saveAll = m_lastSavedUrl.isEmpty();
308 int first = m_history.count() - 1;
309 if (!saveAll) {
310 // find the first one to save
311 for (int i = 0; i < m_history.count(); ++i) {
312 if (m_history.at(i).url == m_lastSavedUrl) {
313 first = i - 1;
314 break;
315 }
316 }
317 }
318 if (first == m_history.count() - 1)
319 saveAll = true;
320
321 QString directory = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
322 if (directory.isEmpty())
323 directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
324 if (!QFile::exists(directory)) {
325 QDir dir;
326 dir.mkpath(directory);
327 }
328
329 QFile historyFile(directory + QLatin1String("/history"));
330 // When saving everything use a temporary file to prevent possible data loss.
331 QTemporaryFile tempFile;
332 tempFile.setAutoRemove(false);
333 bool open = false;
334 if (saveAll) {
335 open = tempFile.open();
336 } else {
337 open = historyFile.open(QFile::Append);
338 }
339
340 if (!open) {
341 qWarning() << "Unable to open history file for saving"
342 << (saveAll ? tempFile.fileName() : historyFile.fileName());
343 return;
344 }
345
346 QDataStream out(saveAll ? &tempFile : &historyFile);
347 for (int i = first; i >= 0; --i) {
348 QByteArray data;
349 QDataStream stream(&data, QIODevice::WriteOnly);
350 HistoryItem item = m_history.at(i);
351 stream << HISTORY_VERSION << item.url << item.dateTime << item.title;
352 out << data;
353 }
354 tempFile.close();
355
356 if (saveAll) {
357 if (historyFile.exists() && !historyFile.remove())
358 qWarning() << "History: error removing old history." << historyFile.errorString();
359 if (!tempFile.rename(historyFile.fileName()))
360 qWarning() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName();
361 }
362 m_lastSavedUrl = m_history.value(0).url;
363 }
364
HistoryModel(HistoryManager * history,QObject * parent)365 HistoryModel::HistoryModel(HistoryManager *history, QObject *parent)
366 : QAbstractTableModel(parent)
367 , m_history(history)
368 {
369 Q_ASSERT(m_history);
370 connect(m_history, SIGNAL(historyReset()),
371 this, SLOT(historyReset()));
372 connect(m_history, SIGNAL(entryRemoved(HistoryItem)),
373 this, SLOT(historyReset()));
374
375 connect(m_history, SIGNAL(entryAdded(HistoryItem)),
376 this, SLOT(entryAdded()));
377 connect(m_history, SIGNAL(entryUpdated(int)),
378 this, SLOT(entryUpdated(int)));
379 }
380
historyReset()381 void HistoryModel::historyReset()
382 {
383 reset();
384 }
385
entryAdded()386 void HistoryModel::entryAdded()
387 {
388 beginInsertRows(QModelIndex(), 0, 0);
389 endInsertRows();
390 }
391
entryUpdated(int offset)392 void HistoryModel::entryUpdated(int offset)
393 {
394 QModelIndex idx = index(offset, 0);
395 emit dataChanged(idx, idx);
396 }
397
headerData(int section,Qt::Orientation orientation,int role) const398 QVariant HistoryModel::headerData(int section, Qt::Orientation orientation, int role) const
399 {
400 if (orientation == Qt::Horizontal
401 && role == Qt::DisplayRole) {
402 switch (section) {
403 case 0: return tr("Title");
404 case 1: return tr("Address");
405 }
406 }
407 return QAbstractTableModel::headerData(section, orientation, role);
408 }
409
data(const QModelIndex & index,int role) const410 QVariant HistoryModel::data(const QModelIndex &index, int role) const
411 {
412 QList<HistoryItem> lst = m_history->history();
413 if (index.row() < 0 || index.row() >= lst.size())
414 return QVariant();
415
416 const HistoryItem &item = lst.at(index.row());
417 switch (role) {
418 case DateTimeRole:
419 return item.dateTime;
420 case DateRole:
421 return item.dateTime.date();
422 case UrlRole:
423 return QUrl(item.url);
424 case UrlStringRole:
425 return item.url;
426 case Qt::DisplayRole:
427 case Qt::EditRole: {
428 switch (index.column()) {
429 case 0:
430 // when there is no title try to generate one from the url
431 if (item.title.isEmpty()) {
432 QString page = QFileInfo(QUrl(item.url).path()).fileName();
433 if (!page.isEmpty())
434 return page;
435 return item.url;
436 }
437 return item.title;
438 case 1:
439 return item.url;
440 }
441 }
442 case Qt::DecorationRole:
443 if (index.column() == 0) {
444 return BrowserApplication::instance()->icon(item.url);
445 }
446 }
447 return QVariant();
448 }
449
columnCount(const QModelIndex & parent) const450 int HistoryModel::columnCount(const QModelIndex &parent) const
451 {
452 return (parent.isValid()) ? 0 : 2;
453 }
454
rowCount(const QModelIndex & parent) const455 int HistoryModel::rowCount(const QModelIndex &parent) const
456 {
457 return (parent.isValid()) ? 0 : m_history->history().count();
458 }
459
removeRows(int row,int count,const QModelIndex & parent)460 bool HistoryModel::removeRows(int row, int count, const QModelIndex &parent)
461 {
462 if (parent.isValid())
463 return false;
464 int lastRow = row + count - 1;
465 beginRemoveRows(parent, row, lastRow);
466 QList<HistoryItem> lst = m_history->history();
467 for (int i = lastRow; i >= row; --i)
468 lst.removeAt(i);
469 disconnect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
470 m_history->setHistory(lst);
471 connect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
472 endRemoveRows();
473 return true;
474 }
475
476 #define MOVEDROWS 15
477
478 /*
479 Maps the first bunch of items of the source model to the root
480 */
HistoryMenuModel(HistoryTreeModel * sourceModel,QObject * parent)481 HistoryMenuModel::HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent)
482 : QAbstractProxyModel(parent)
483 , m_treeModel(sourceModel)
484 {
485 setSourceModel(sourceModel);
486 }
487
bumpedRows() const488 int HistoryMenuModel::bumpedRows() const
489 {
490 QModelIndex first = m_treeModel->index(0, 0);
491 if (!first.isValid())
492 return 0;
493 return qMin(m_treeModel->rowCount(first), MOVEDROWS);
494 }
495
columnCount(const QModelIndex & parent) const496 int HistoryMenuModel::columnCount(const QModelIndex &parent) const
497 {
498 return m_treeModel->columnCount(mapToSource(parent));
499 }
500
rowCount(const QModelIndex & parent) const501 int HistoryMenuModel::rowCount(const QModelIndex &parent) const
502 {
503 if (parent.column() > 0)
504 return 0;
505
506 if (!parent.isValid()) {
507 int folders = sourceModel()->rowCount();
508 int bumpedItems = bumpedRows();
509 if (bumpedItems <= MOVEDROWS
510 && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
511 --folders;
512 return bumpedItems + folders;
513 }
514
515 if (parent.internalId() == -1) {
516 if (parent.row() < bumpedRows())
517 return 0;
518 }
519
520 QModelIndex idx = mapToSource(parent);
521 int defaultCount = sourceModel()->rowCount(idx);
522 if (idx == sourceModel()->index(0, 0))
523 return defaultCount - bumpedRows();
524 return defaultCount;
525 }
526
mapFromSource(const QModelIndex & sourceIndex) const527 QModelIndex HistoryMenuModel::mapFromSource(const QModelIndex &sourceIndex) const
528 {
529 // currently not used or autotested
530 Q_ASSERT(false);
531 int sr = m_treeModel->mapToSource(sourceIndex).row();
532 return createIndex(sourceIndex.row(), sourceIndex.column(), sr);
533 }
534
mapToSource(const QModelIndex & proxyIndex) const535 QModelIndex HistoryMenuModel::mapToSource(const QModelIndex &proxyIndex) const
536 {
537 if (!proxyIndex.isValid())
538 return QModelIndex();
539
540 if (proxyIndex.internalId() == -1) {
541 int bumpedItems = bumpedRows();
542 if (proxyIndex.row() < bumpedItems)
543 return m_treeModel->index(proxyIndex.row(), proxyIndex.column(), m_treeModel->index(0, 0));
544 if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(m_treeModel->index(0, 0)))
545 --bumpedItems;
546 return m_treeModel->index(proxyIndex.row() - bumpedItems, proxyIndex.column());
547 }
548
549 QModelIndex historyIndex = m_treeModel->sourceModel()->index(proxyIndex.internalId(), proxyIndex.column());
550 QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
551 return treeIndex;
552 }
553
index(int row,int column,const QModelIndex & parent) const554 QModelIndex HistoryMenuModel::index(int row, int column, const QModelIndex &parent) const
555 {
556 if (row < 0
557 || column < 0 || column >= columnCount(parent)
558 || parent.column() > 0)
559 return QModelIndex();
560 if (!parent.isValid())
561 return createIndex(row, column, -1);
562
563 QModelIndex treeIndexParent = mapToSource(parent);
564
565 int bumpedItems = 0;
566 if (treeIndexParent == m_treeModel->index(0, 0))
567 bumpedItems = bumpedRows();
568 QModelIndex treeIndex = m_treeModel->index(row + bumpedItems, column, treeIndexParent);
569 QModelIndex historyIndex = m_treeModel->mapToSource(treeIndex);
570 int historyRow = historyIndex.row();
571 if (historyRow == -1)
572 historyRow = treeIndex.row();
573 return createIndex(row, column, historyRow);
574 }
575
parent(const QModelIndex & index) const576 QModelIndex HistoryMenuModel::parent(const QModelIndex &index) const
577 {
578 int offset = index.internalId();
579 if (offset == -1 || !index.isValid())
580 return QModelIndex();
581
582 QModelIndex historyIndex = m_treeModel->sourceModel()->index(index.internalId(), 0);
583 QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
584 QModelIndex treeIndexParent = treeIndex.parent();
585
586 int sr = m_treeModel->mapToSource(treeIndexParent).row();
587 int bumpedItems = bumpedRows();
588 if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
589 --bumpedItems;
590 return createIndex(bumpedItems + treeIndexParent.row(), treeIndexParent.column(), sr);
591 }
592
593
HistoryMenu(QWidget * parent)594 HistoryMenu::HistoryMenu(QWidget *parent)
595 : ModelMenu(parent)
596 , m_history(0)
597 {
598 connect(this, SIGNAL(activated(QModelIndex)),
599 this, SLOT(activated(QModelIndex)));
600 setHoverRole(HistoryModel::UrlStringRole);
601 }
602
activated(const QModelIndex & index)603 void HistoryMenu::activated(const QModelIndex &index)
604 {
605 emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
606 }
607
prePopulated()608 bool HistoryMenu::prePopulated()
609 {
610 if (!m_history) {
611 m_history = BrowserApplication::historyManager();
612 m_historyMenuModel = new HistoryMenuModel(m_history->historyTreeModel(), this);
613 setModel(m_historyMenuModel);
614 }
615 // initial actions
616 for (int i = 0; i < m_initialActions.count(); ++i)
617 addAction(m_initialActions.at(i));
618 if (!m_initialActions.isEmpty())
619 addSeparator();
620 setFirstSeparator(m_historyMenuModel->bumpedRows());
621
622 return false;
623 }
624
postPopulated()625 void HistoryMenu::postPopulated()
626 {
627 if (m_history->history().count() > 0)
628 addSeparator();
629
630 QAction *showAllAction = new QAction(tr("Show All History"), this);
631 connect(showAllAction, SIGNAL(triggered()), this, SLOT(showHistoryDialog()));
632 addAction(showAllAction);
633
634 QAction *clearAction = new QAction(tr("Clear History"), this);
635 connect(clearAction, SIGNAL(triggered()), m_history, SLOT(clear()));
636 addAction(clearAction);
637 }
638
showHistoryDialog()639 void HistoryMenu::showHistoryDialog()
640 {
641 HistoryDialog *dialog = new HistoryDialog(this);
642 connect(dialog, SIGNAL(openUrl(QUrl)),
643 this, SIGNAL(openUrl(QUrl)));
644 dialog->show();
645 }
646
setInitialActions(QList<QAction * > actions)647 void HistoryMenu::setInitialActions(QList<QAction*> actions)
648 {
649 m_initialActions = actions;
650 for (int i = 0; i < m_initialActions.count(); ++i)
651 addAction(m_initialActions.at(i));
652 }
653
TreeProxyModel(QObject * parent)654 TreeProxyModel::TreeProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
655 {
656 setSortRole(HistoryModel::DateTimeRole);
657 setFilterCaseSensitivity(Qt::CaseInsensitive);
658 }
659
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const660 bool TreeProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
661 {
662 if (!source_parent.isValid())
663 return true;
664 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
665 }
666
HistoryDialog(QWidget * parent,HistoryManager * setHistory)667 HistoryDialog::HistoryDialog(QWidget *parent, HistoryManager *setHistory) : QDialog(parent)
668 {
669 HistoryManager *history = setHistory;
670 if (!history)
671 history = BrowserApplication::historyManager();
672 setupUi(this);
673 tree->setUniformRowHeights(true);
674 tree->setSelectionBehavior(QAbstractItemView::SelectRows);
675 tree->setTextElideMode(Qt::ElideMiddle);
676 QAbstractItemModel *model = history->historyTreeModel();
677 TreeProxyModel *proxyModel = new TreeProxyModel(this);
678 connect(search, SIGNAL(textChanged(QString)),
679 proxyModel, SLOT(setFilterFixedString(QString)));
680 connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
681 connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear()));
682 proxyModel->setSourceModel(model);
683 tree->setModel(proxyModel);
684 tree->setExpanded(proxyModel->index(0, 0), true);
685 tree->setAlternatingRowColors(true);
686 QFontMetrics fm(font());
687 int header = fm.width(QLatin1Char('m')) * 40;
688 tree->header()->resizeSection(0, header);
689 tree->header()->setStretchLastSection(true);
690 connect(tree, SIGNAL(activated(QModelIndex)),
691 this, SLOT(open()));
692 tree->setContextMenuPolicy(Qt::CustomContextMenu);
693 connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
694 this, SLOT(customContextMenuRequested(QPoint)));
695 }
696
customContextMenuRequested(const QPoint & pos)697 void HistoryDialog::customContextMenuRequested(const QPoint &pos)
698 {
699 QMenu menu;
700 QModelIndex index = tree->indexAt(pos);
701 index = index.sibling(index.row(), 0);
702 if (index.isValid() && !tree->model()->hasChildren(index)) {
703 menu.addAction(tr("Open"), this, SLOT(open()));
704 menu.addSeparator();
705 menu.addAction(tr("Copy"), this, SLOT(copy()));
706 }
707 menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
708 menu.exec(QCursor::pos());
709 }
710
open()711 void HistoryDialog::open()
712 {
713 QModelIndex index = tree->currentIndex();
714 if (!index.parent().isValid())
715 return;
716 emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
717 }
718
copy()719 void HistoryDialog::copy()
720 {
721 QModelIndex index = tree->currentIndex();
722 if (!index.parent().isValid())
723 return;
724 QString url = index.data(HistoryModel::UrlStringRole).toString();
725
726 QClipboard *clipboard = QApplication::clipboard();
727 clipboard->setText(url);
728 }
729
HistoryFilterModel(QAbstractItemModel * sourceModel,QObject * parent)730 HistoryFilterModel::HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent)
731 : QAbstractProxyModel(parent),
732 m_loaded(false)
733 {
734 setSourceModel(sourceModel);
735 }
736
historyLocation(const QString & url) const737 int HistoryFilterModel::historyLocation(const QString &url) const
738 {
739 load();
740 if (!m_historyHash.contains(url))
741 return 0;
742 return sourceModel()->rowCount() - m_historyHash.value(url);
743 }
744
data(const QModelIndex & index,int role) const745 QVariant HistoryFilterModel::data(const QModelIndex &index, int role) const
746 {
747 return QAbstractProxyModel::data(index, role);
748 }
749
setSourceModel(QAbstractItemModel * newSourceModel)750 void HistoryFilterModel::setSourceModel(QAbstractItemModel *newSourceModel)
751 {
752 if (sourceModel()) {
753 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
754 disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
755 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
756 disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
757 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
758 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
759 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
760 }
761
762 QAbstractProxyModel::setSourceModel(newSourceModel);
763
764 if (sourceModel()) {
765 m_loaded = false;
766 connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
767 connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
768 this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
769 connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
770 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
771 connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
772 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
773 }
774 }
775
sourceDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)776 void HistoryFilterModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
777 {
778 emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight));
779 }
780
headerData(int section,Qt::Orientation orientation,int role) const781 QVariant HistoryFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
782 {
783 return sourceModel()->headerData(section, orientation, role);
784 }
785
sourceReset()786 void HistoryFilterModel::sourceReset()
787 {
788 m_loaded = false;
789 reset();
790 }
791
rowCount(const QModelIndex & parent) const792 int HistoryFilterModel::rowCount(const QModelIndex &parent) const
793 {
794 load();
795 if (parent.isValid())
796 return 0;
797 return m_historyHash.count();
798 }
799
columnCount(const QModelIndex & parent) const800 int HistoryFilterModel::columnCount(const QModelIndex &parent) const
801 {
802 return (parent.isValid()) ? 0 : 2;
803 }
804
mapToSource(const QModelIndex & proxyIndex) const805 QModelIndex HistoryFilterModel::mapToSource(const QModelIndex &proxyIndex) const
806 {
807 load();
808 int sourceRow = sourceModel()->rowCount() - proxyIndex.internalId();
809 return sourceModel()->index(sourceRow, proxyIndex.column());
810 }
811
mapFromSource(const QModelIndex & sourceIndex) const812 QModelIndex HistoryFilterModel::mapFromSource(const QModelIndex &sourceIndex) const
813 {
814 load();
815 QString url = sourceIndex.data(HistoryModel::UrlStringRole).toString();
816 if (!m_historyHash.contains(url))
817 return QModelIndex();
818
819 // This can be done in a binary search, but we can't use qBinary find
820 // because it can't take: qBinaryFind(m_sourceRow.end(), m_sourceRow.begin(), v);
821 // so if this is a performance bottlneck then convert to binary search, until then
822 // the cleaner/easier to read code wins the day.
823 int realRow = -1;
824 int sourceModelRow = sourceModel()->rowCount() - sourceIndex.row();
825
826 for (int i = 0; i < m_sourceRow.count(); ++i) {
827 if (m_sourceRow.at(i) == sourceModelRow) {
828 realRow = i;
829 break;
830 }
831 }
832 if (realRow == -1)
833 return QModelIndex();
834
835 return createIndex(realRow, sourceIndex.column(), sourceModel()->rowCount() - sourceIndex.row());
836 }
837
index(int row,int column,const QModelIndex & parent) const838 QModelIndex HistoryFilterModel::index(int row, int column, const QModelIndex &parent) const
839 {
840 load();
841 if (row < 0 || row >= rowCount(parent)
842 || column < 0 || column >= columnCount(parent))
843 return QModelIndex();
844
845 return createIndex(row, column, m_sourceRow[row]);
846 }
847
parent(const QModelIndex &) const848 QModelIndex HistoryFilterModel::parent(const QModelIndex &) const
849 {
850 return QModelIndex();
851 }
852
load() const853 void HistoryFilterModel::load() const
854 {
855 if (m_loaded)
856 return;
857 m_sourceRow.clear();
858 m_historyHash.clear();
859 m_historyHash.reserve(sourceModel()->rowCount());
860 for (int i = 0; i < sourceModel()->rowCount(); ++i) {
861 QModelIndex idx = sourceModel()->index(i, 0);
862 QString url = idx.data(HistoryModel::UrlStringRole).toString();
863 if (!m_historyHash.contains(url)) {
864 m_sourceRow.append(sourceModel()->rowCount() - i);
865 m_historyHash[url] = sourceModel()->rowCount() - i;
866 }
867 }
868 m_loaded = true;
869 }
870
sourceRowsInserted(const QModelIndex & parent,int start,int end)871 void HistoryFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
872 {
873 Q_ASSERT(start == end && start == 0);
874 Q_UNUSED(end);
875 if (!m_loaded)
876 return;
877 QModelIndex idx = sourceModel()->index(start, 0, parent);
878 QString url = idx.data(HistoryModel::UrlStringRole).toString();
879 if (m_historyHash.contains(url)) {
880 int sourceRow = sourceModel()->rowCount() - m_historyHash[url];
881 int realRow = mapFromSource(sourceModel()->index(sourceRow, 0)).row();
882 beginRemoveRows(QModelIndex(), realRow, realRow);
883 m_sourceRow.removeAt(realRow);
884 m_historyHash.remove(url);
885 endRemoveRows();
886 }
887 beginInsertRows(QModelIndex(), 0, 0);
888 m_historyHash.insert(url, sourceModel()->rowCount() - start);
889 m_sourceRow.insert(0, sourceModel()->rowCount());
890 endInsertRows();
891 }
892
sourceRowsRemoved(const QModelIndex &,int start,int end)893 void HistoryFilterModel::sourceRowsRemoved(const QModelIndex &, int start, int end)
894 {
895 Q_UNUSED(start);
896 Q_UNUSED(end);
897 sourceReset();
898 }
899
900 /*
901 Removing a continuous block of rows will remove filtered rows too as this is
902 the users intention.
903 */
removeRows(int row,int count,const QModelIndex & parent)904 bool HistoryFilterModel::removeRows(int row, int count, const QModelIndex &parent)
905 {
906 if (row < 0 || count <= 0 || row + count > rowCount(parent) || parent.isValid())
907 return false;
908 int lastRow = row + count - 1;
909 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
910 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
911 beginRemoveRows(parent, row, lastRow);
912 int oldCount = rowCount();
913 int start = sourceModel()->rowCount() - m_sourceRow.value(row);
914 int end = sourceModel()->rowCount() - m_sourceRow.value(lastRow);
915 sourceModel()->removeRows(start, end - start + 1);
916 endRemoveRows();
917 connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
918 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
919 m_loaded = false;
920 if (oldCount - count != rowCount())
921 reset();
922 return true;
923 }
924
HistoryCompletionModel(QObject * parent)925 HistoryCompletionModel::HistoryCompletionModel(QObject *parent)
926 : QAbstractProxyModel(parent)
927 {
928 }
929
data(const QModelIndex & index,int role) const930 QVariant HistoryCompletionModel::data(const QModelIndex &index, int role) const
931 {
932 if (sourceModel()
933 && (role == Qt::EditRole || role == Qt::DisplayRole)
934 && index.isValid()) {
935 QModelIndex idx = mapToSource(index);
936 idx = idx.sibling(idx.row(), 1);
937 QString urlString = idx.data(HistoryModel::UrlStringRole).toString();
938 if (index.row() % 2) {
939 QUrl url = urlString;
940 QString s = url.toString(QUrl::RemoveScheme
941 | QUrl::RemoveUserInfo
942 | QUrl::StripTrailingSlash);
943 return s.mid(2); // strip // from the front
944 }
945 return urlString;
946 }
947 return QAbstractProxyModel::data(index, role);
948 }
949
rowCount(const QModelIndex & parent) const950 int HistoryCompletionModel::rowCount(const QModelIndex &parent) const
951 {
952 return (parent.isValid() || !sourceModel()) ? 0 : sourceModel()->rowCount(parent) * 2;
953 }
954
columnCount(const QModelIndex & parent) const955 int HistoryCompletionModel::columnCount(const QModelIndex &parent) const
956 {
957 return (parent.isValid()) ? 0 : 1;
958 }
959
mapFromSource(const QModelIndex & sourceIndex) const960 QModelIndex HistoryCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const
961 {
962 int row = sourceIndex.row() * 2;
963 return index(row, sourceIndex.column());
964 }
965
mapToSource(const QModelIndex & proxyIndex) const966 QModelIndex HistoryCompletionModel::mapToSource(const QModelIndex &proxyIndex) const
967 {
968 if (!sourceModel())
969 return QModelIndex();
970 int row = proxyIndex.row() / 2;
971 return sourceModel()->index(row, proxyIndex.column());
972 }
973
index(int row,int column,const QModelIndex & parent) const974 QModelIndex HistoryCompletionModel::index(int row, int column, const QModelIndex &parent) const
975 {
976 if (row < 0 || row >= rowCount(parent)
977 || column < 0 || column >= columnCount(parent))
978 return QModelIndex();
979 return createIndex(row, column, 0);
980 }
981
parent(const QModelIndex &) const982 QModelIndex HistoryCompletionModel::parent(const QModelIndex &) const
983 {
984 return QModelIndex();
985 }
986
setSourceModel(QAbstractItemModel * newSourceModel)987 void HistoryCompletionModel::setSourceModel(QAbstractItemModel *newSourceModel)
988 {
989 if (sourceModel()) {
990 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
991 disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
992 this, SLOT(sourceReset()));
993 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
994 this, SLOT(sourceReset()));
995 }
996
997 QAbstractProxyModel::setSourceModel(newSourceModel);
998
999 if (newSourceModel) {
1000 connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset()));
1001 connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1002 this, SLOT(sourceReset()));
1003 connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
1004 this, SLOT(sourceReset()));
1005 }
1006
1007 reset();
1008 }
1009
sourceReset()1010 void HistoryCompletionModel::sourceReset()
1011 {
1012 reset();
1013 }
1014
HistoryTreeModel(QAbstractItemModel * sourceModel,QObject * parent)1015 HistoryTreeModel::HistoryTreeModel(QAbstractItemModel *sourceModel, QObject *parent)
1016 : QAbstractProxyModel(parent)
1017 {
1018 setSourceModel(sourceModel);
1019 }
1020
headerData(int section,Qt::Orientation orientation,int role) const1021 QVariant HistoryTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
1022 {
1023 return sourceModel()->headerData(section, orientation, role);
1024 }
1025
data(const QModelIndex & index,int role) const1026 QVariant HistoryTreeModel::data(const QModelIndex &index, int role) const
1027 {
1028 if ((role == Qt::EditRole || role == Qt::DisplayRole)) {
1029 int start = index.internalId();
1030 if (start == 0) {
1031 int offset = sourceDateRow(index.row());
1032 if (index.column() == 0) {
1033 QModelIndex idx = sourceModel()->index(offset, 0);
1034 QDate date = idx.data(HistoryModel::DateRole).toDate();
1035 if (date == QDate::currentDate())
1036 return tr("Earlier Today");
1037 return date.toString(QLatin1String("dddd, MMMM d, yyyy"));
1038 }
1039 if (index.column() == 1) {
1040 return tr("%1 items").arg(rowCount(index.sibling(index.row(), 0)));
1041 }
1042 }
1043 }
1044 if (role == Qt::DecorationRole && index.column() == 0 && !index.parent().isValid())
1045 return QIcon(QLatin1String(":history.png"));
1046 if (role == HistoryModel::DateRole && index.column() == 0 && index.internalId() == 0) {
1047 int offset = sourceDateRow(index.row());
1048 QModelIndex idx = sourceModel()->index(offset, 0);
1049 return idx.data(HistoryModel::DateRole);
1050 }
1051
1052 return QAbstractProxyModel::data(index, role);
1053 }
1054
columnCount(const QModelIndex & parent) const1055 int HistoryTreeModel::columnCount(const QModelIndex &parent) const
1056 {
1057 return sourceModel()->columnCount(mapToSource(parent));
1058 }
1059
rowCount(const QModelIndex & parent) const1060 int HistoryTreeModel::rowCount(const QModelIndex &parent) const
1061 {
1062 if ( parent.internalId() != 0
1063 || parent.column() > 0
1064 || !sourceModel())
1065 return 0;
1066
1067 // row count OF dates
1068 if (!parent.isValid()) {
1069 if (!m_sourceRowCache.isEmpty())
1070 return m_sourceRowCache.count();
1071 QDate currentDate;
1072 int rows = 0;
1073 int totalRows = sourceModel()->rowCount();
1074
1075 for (int i = 0; i < totalRows; ++i) {
1076 QDate rowDate = sourceModel()->index(i, 0).data(HistoryModel::DateRole).toDate();
1077 if (rowDate != currentDate) {
1078 m_sourceRowCache.append(i);
1079 currentDate = rowDate;
1080 ++rows;
1081 }
1082 }
1083 Q_ASSERT(m_sourceRowCache.count() == rows);
1084 return rows;
1085 }
1086
1087 // row count FOR a date
1088 int start = sourceDateRow(parent.row());
1089 int end = sourceDateRow(parent.row() + 1);
1090 return (end - start);
1091 }
1092
1093 // Translate the top level date row into the offset where that date starts
sourceDateRow(int row) const1094 int HistoryTreeModel::sourceDateRow(int row) const
1095 {
1096 if (row <= 0)
1097 return 0;
1098
1099 if (m_sourceRowCache.isEmpty())
1100 rowCount(QModelIndex());
1101
1102 if (row >= m_sourceRowCache.count()) {
1103 if (!sourceModel())
1104 return 0;
1105 return sourceModel()->rowCount();
1106 }
1107 return m_sourceRowCache.at(row);
1108 }
1109
mapToSource(const QModelIndex & proxyIndex) const1110 QModelIndex HistoryTreeModel::mapToSource(const QModelIndex &proxyIndex) const
1111 {
1112 int offset = proxyIndex.internalId();
1113 if (offset == 0)
1114 return QModelIndex();
1115 int startDateRow = sourceDateRow(offset - 1);
1116 return sourceModel()->index(startDateRow + proxyIndex.row(), proxyIndex.column());
1117 }
1118
index(int row,int column,const QModelIndex & parent) const1119 QModelIndex HistoryTreeModel::index(int row, int column, const QModelIndex &parent) const
1120 {
1121 if (row < 0
1122 || column < 0 || column >= columnCount(parent)
1123 || parent.column() > 0)
1124 return QModelIndex();
1125
1126 if (!parent.isValid())
1127 return createIndex(row, column, 0);
1128 return createIndex(row, column, parent.row() + 1);
1129 }
1130
parent(const QModelIndex & index) const1131 QModelIndex HistoryTreeModel::parent(const QModelIndex &index) const
1132 {
1133 int offset = index.internalId();
1134 if (offset == 0 || !index.isValid())
1135 return QModelIndex();
1136 return createIndex(offset - 1, 0, 0);
1137 }
1138
hasChildren(const QModelIndex & parent) const1139 bool HistoryTreeModel::hasChildren(const QModelIndex &parent) const
1140 {
1141 QModelIndex grandparent = parent.parent();
1142 if (!grandparent.isValid())
1143 return true;
1144 return false;
1145 }
1146
flags(const QModelIndex & index) const1147 Qt::ItemFlags HistoryTreeModel::flags(const QModelIndex &index) const
1148 {
1149 if (!index.isValid())
1150 return Qt::NoItemFlags;
1151 return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
1152 }
1153
removeRows(int row,int count,const QModelIndex & parent)1154 bool HistoryTreeModel::removeRows(int row, int count, const QModelIndex &parent)
1155 {
1156 if (row < 0 || count <= 0 || row + count > rowCount(parent))
1157 return false;
1158
1159 if (parent.isValid()) {
1160 // removing pages
1161 int offset = sourceDateRow(parent.row());
1162 return sourceModel()->removeRows(offset + row, count);
1163 } else {
1164 // removing whole dates
1165 for (int i = row + count - 1; i >= row; --i) {
1166 QModelIndex dateParent = index(i, 0);
1167 int offset = sourceDateRow(dateParent.row());
1168 if (!sourceModel()->removeRows(offset, rowCount(dateParent)))
1169 return false;
1170 }
1171 }
1172 return true;
1173 }
1174
setSourceModel(QAbstractItemModel * newSourceModel)1175 void HistoryTreeModel::setSourceModel(QAbstractItemModel *newSourceModel)
1176 {
1177 if (sourceModel()) {
1178 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
1179 disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
1180 disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1181 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
1182 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
1183 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
1184 }
1185
1186 QAbstractProxyModel::setSourceModel(newSourceModel);
1187
1188 if (newSourceModel) {
1189 connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
1190 connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
1191 connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1192 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
1193 connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
1194 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
1195 }
1196
1197 reset();
1198 }
1199
sourceReset()1200 void HistoryTreeModel::sourceReset()
1201 {
1202 m_sourceRowCache.clear();
1203 reset();
1204 }
1205
sourceRowsInserted(const QModelIndex & parent,int start,int end)1206 void HistoryTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
1207 {
1208 Q_UNUSED(parent); // Avoid warnings when compiling release
1209 Q_ASSERT(!parent.isValid());
1210 if (start != 0 || start != end) {
1211 m_sourceRowCache.clear();
1212 reset();
1213 return;
1214 }
1215
1216 m_sourceRowCache.clear();
1217 QModelIndex treeIndex = mapFromSource(sourceModel()->index(start, 0));
1218 QModelIndex treeParent = treeIndex.parent();
1219 if (rowCount(treeParent) == 1) {
1220 beginInsertRows(QModelIndex(), 0, 0);
1221 endInsertRows();
1222 } else {
1223 beginInsertRows(treeParent, treeIndex.row(), treeIndex.row());
1224 endInsertRows();
1225 }
1226 }
1227
mapFromSource(const QModelIndex & sourceIndex) const1228 QModelIndex HistoryTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
1229 {
1230 if (!sourceIndex.isValid())
1231 return QModelIndex();
1232
1233 if (m_sourceRowCache.isEmpty())
1234 rowCount(QModelIndex());
1235
1236 QList<int>::iterator it;
1237 it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), sourceIndex.row());
1238 if (*it != sourceIndex.row())
1239 --it;
1240 int dateRow = qMax(0, it - m_sourceRowCache.begin());
1241 int row = sourceIndex.row() - m_sourceRowCache.at(dateRow);
1242 return createIndex(row, sourceIndex.column(), dateRow + 1);
1243 }
1244
sourceRowsRemoved(const QModelIndex & parent,int start,int end)1245 void HistoryTreeModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
1246 {
1247 Q_UNUSED(parent); // Avoid warnings when compiling release
1248 Q_ASSERT(!parent.isValid());
1249 if (m_sourceRowCache.isEmpty())
1250 return;
1251 for (int i = end; i >= start;) {
1252 QList<int>::iterator it;
1253 it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), i);
1254 // playing it safe
1255 if (it == m_sourceRowCache.end()) {
1256 m_sourceRowCache.clear();
1257 reset();
1258 return;
1259 }
1260
1261 if (*it != i)
1262 --it;
1263 int row = qMax(0, it - m_sourceRowCache.begin());
1264 int offset = m_sourceRowCache[row];
1265 QModelIndex dateParent = index(row, 0);
1266 // If we can remove all the rows in the date do that and skip over them
1267 int rc = rowCount(dateParent);
1268 if (i - rc + 1 == offset && start <= i - rc + 1) {
1269 beginRemoveRows(QModelIndex(), row, row);
1270 m_sourceRowCache.removeAt(row);
1271 i -= rc + 1;
1272 } else {
1273 beginRemoveRows(dateParent, i - offset, i - offset);
1274 ++row;
1275 --i;
1276 }
1277 for (int j = row; j < m_sourceRowCache.count(); ++j)
1278 --m_sourceRowCache[j];
1279 endRemoveRows();
1280 }
1281 }
1282
1283