xref: /OK3568_Linux_fs/app/forlinx/flapp/src/plugins/allwinner/browser/history.cpp (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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:LGPL21$
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 ** $QT_END_LICENSE$
31 **
32 ****************************************************************************/
33 
34 #include "history.h"
35 
36 #include "autosaver.h"
37 #include "browserapplication.h"
38 
39 #include <QtCore/QBuffer>
40 #include <QtCore/QDir>
41 #include <QtCore/QFile>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QSettings>
44 #include <QtCore/QTemporaryFile>
45 #include <QtCore/QTextStream>
46 
47 #include <QtCore/QtAlgorithms>
48 
49 #include <QtGui/QClipboard>
50 #include <QtGui/QDesktopServices>
51 #include <QtWidgets/QHeaderView>
52 #include <QtWidgets/QStyle>
53 
54 #include <QWebHistoryInterface>
55 #include <QWebSettings>
56 
57 #include <QtCore/QDebug>
58 
59 static const unsigned int HISTORY_VERSION = 23;
60 
HistoryManager(QObject * parent)61 HistoryManager::HistoryManager(QObject *parent)
62     : QWebHistoryInterface(parent)
63     , m_saveTimer(new AutoSaver(this))
64     , m_historyLimit(30)
65     , m_historyModel(0)
66     , m_historyFilterModel(0)
67     , m_historyTreeModel(0)
68 {
69     m_expiredTimer.setSingleShot(true);
70     connect(&m_expiredTimer, SIGNAL(timeout()),
71             this, SLOT(checkForExpired()));
72     connect(this, SIGNAL(entryAdded(HistoryItem)),
73             m_saveTimer, SLOT(changeOccurred()));
74     connect(this, SIGNAL(entryRemoved(HistoryItem)),
75             m_saveTimer, SLOT(changeOccurred()));
76     load();
77 
78     m_historyModel = new HistoryModel(this, this);
79     m_historyFilterModel = new HistoryFilterModel(m_historyModel, this);
80     m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this);
81 
82     // QWebHistoryInterface will delete the history manager
83     QWebHistoryInterface::setDefaultInterface(this);
84 }
85 
~HistoryManager()86 HistoryManager::~HistoryManager()
87 {
88     m_saveTimer->saveIfNeccessary();
89 }
90 
history() const91 QList<HistoryItem> HistoryManager::history() const
92 {
93     return m_history;
94 }
95 
historyContains(const QString & url) const96 bool HistoryManager::historyContains(const QString &url) const
97 {
98     return m_historyFilterModel->historyContains(url);
99 }
100 
addHistoryEntry(const QString & url)101 void HistoryManager::addHistoryEntry(const QString &url)
102 {
103     QUrl cleanUrl(url);
104     cleanUrl.setPassword(QString());
105     cleanUrl.setHost(cleanUrl.host().toLower());
106     HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime());
107     addHistoryItem(item);
108 }
109 
setHistory(const QList<HistoryItem> & history,bool loadedAndSorted)110 void HistoryManager::setHistory(const QList<HistoryItem> &history, bool loadedAndSorted)
111 {
112     m_history = history;
113 
114     // verify that it is sorted by date
115     if (!loadedAndSorted)
116         qSort(m_history.begin(), m_history.end());
117 
118     checkForExpired();
119 
120     if (loadedAndSorted) {
121         m_lastSavedUrl = m_history.value(0).url;
122     } else {
123         m_lastSavedUrl = QString();
124         m_saveTimer->changeOccurred();
125     }
126     emit historyReset();
127 }
128 
historyModel() const129 HistoryModel *HistoryManager::historyModel() const
130 {
131     return m_historyModel;
132 }
133 
historyFilterModel() const134 HistoryFilterModel *HistoryManager::historyFilterModel() const
135 {
136     return m_historyFilterModel;
137 }
138 
historyTreeModel() const139 HistoryTreeModel *HistoryManager::historyTreeModel() const
140 {
141     return m_historyTreeModel;
142 }
143 
checkForExpired()144 void HistoryManager::checkForExpired()
145 {
146     if (m_historyLimit < 0 || m_history.isEmpty())
147         return;
148 
149     QDateTime now = QDateTime::currentDateTime();
150     int nextTimeout = 0;
151 
152     while (!m_history.isEmpty()) {
153         QDateTime checkForExpired = m_history.last().dateTime;
154         checkForExpired.setDate(checkForExpired.date().addDays(m_historyLimit));
155         if (now.daysTo(checkForExpired) > 7) {
156             // check at most in a week to prevent int overflows on the timer
157             nextTimeout = 7 * 86400;
158         } else {
159             nextTimeout = now.secsTo(checkForExpired);
160         }
161         if (nextTimeout > 0)
162             break;
163         HistoryItem item = m_history.takeLast();
164         // remove from saved file also
165         m_lastSavedUrl = QString();
166         emit entryRemoved(item);
167     }
168 
169     if (nextTimeout > 0)
170         m_expiredTimer.start(nextTimeout * 1000);
171 }
172 
addHistoryItem(const HistoryItem & item)173 void HistoryManager::addHistoryItem(const HistoryItem &item)
174 {
175     QWebSettings *globalSettings = QWebSettings::globalSettings();
176     if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
177         return;
178 
179     m_history.prepend(item);
180     emit entryAdded(item);
181     if (m_history.count() == 1)
182         checkForExpired();
183 }
184 
updateHistoryItem(const QUrl & url,const QString & title)185 void HistoryManager::updateHistoryItem(const QUrl &url, const QString &title)
186 {
187     for (int i = 0; i < m_history.count(); ++i) {
188         if (url == m_history.at(i).url) {
189             m_history[i].title = title;
190             m_saveTimer->changeOccurred();
191             if (m_lastSavedUrl.isEmpty())
192                 m_lastSavedUrl = m_history.at(i).url;
193             emit entryUpdated(i);
194             break;
195         }
196     }
197 }
198 
historyLimit() const199 int HistoryManager::historyLimit() const
200 {
201     return m_historyLimit;
202 }
203 
setHistoryLimit(int limit)204 void HistoryManager::setHistoryLimit(int limit)
205 {
206     if (m_historyLimit == limit)
207         return;
208     m_historyLimit = limit;
209     checkForExpired();
210     m_saveTimer->changeOccurred();
211 }
212 
clear()213 void HistoryManager::clear()
214 {
215     m_history.clear();
216     m_lastSavedUrl = QString();
217     m_saveTimer->changeOccurred();
218     m_saveTimer->saveIfNeccessary();
219     historyReset();
220 }
221 
loadSettings()222 void HistoryManager::loadSettings()
223 {
224     // load settings
225     QSettings settings;
226     settings.beginGroup(QLatin1String("history"));
227     m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt();
228 }
229 
load()230 void HistoryManager::load()
231 {
232     loadSettings();
233 
234     QFile historyFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation)
235         + QLatin1String("/history"));
236     if (!historyFile.exists())
237         return;
238     if (!historyFile.open(QFile::ReadOnly)) {
239         qWarning() << "Unable to open history file" << historyFile.fileName();
240         return;
241     }
242 
243     QList<HistoryItem> list;
244     QDataStream in(&historyFile);
245     // Double check that the history file is sorted as it is read in
246     bool needToSort = false;
247     HistoryItem lastInsertedItem;
248     QByteArray data;
249     QDataStream stream;
250     QBuffer buffer;
251     stream.setDevice(&buffer);
252     while (!historyFile.atEnd()) {
253         in >> data;
254         buffer.close();
255         buffer.setBuffer(&data);
256         buffer.open(QIODevice::ReadOnly);
257         quint32 ver;
258         stream >> ver;
259         if (ver != HISTORY_VERSION)
260             continue;
261         HistoryItem item;
262         stream >> item.url;
263         stream >> item.dateTime;
264         stream >> item.title;
265 
266         if (!item.dateTime.isValid())
267             continue;
268 
269         if (item == lastInsertedItem) {
270             if (lastInsertedItem.title.isEmpty() && !list.isEmpty())
271                 list[0].title = item.title;
272             continue;
273         }
274 
275         if (!needToSort && !list.isEmpty() && lastInsertedItem < item)
276             needToSort = true;
277 
278         list.prepend(item);
279         lastInsertedItem = item;
280     }
281     if (needToSort)
282         qSort(list.begin(), list.end());
283 
284     setHistory(list, true);
285 
286     // If we had to sort re-write the whole history sorted
287     if (needToSort) {
288         m_lastSavedUrl = QString();
289         m_saveTimer->changeOccurred();
290     }
291 }
292 
save()293 void HistoryManager::save()
294 {
295     QSettings settings;
296     settings.beginGroup(QLatin1String("history"));
297     settings.setValue(QLatin1String("historyLimit"), m_historyLimit);
298 
299     bool saveAll = m_lastSavedUrl.isEmpty();
300     int first = m_history.count() - 1;
301     if (!saveAll) {
302         // find the first one to save
303         for (int i = 0; i < m_history.count(); ++i) {
304             if (m_history.at(i).url == m_lastSavedUrl) {
305                 first = i - 1;
306                 break;
307             }
308         }
309     }
310     if (first == m_history.count() - 1)
311         saveAll = true;
312 
313     QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
314     if (directory.isEmpty())
315         directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
316     if (!QFile::exists(directory)) {
317         QDir dir;
318         dir.mkpath(directory);
319     }
320 
321     QFile historyFile(directory + QLatin1String("/history"));
322     // When saving everything use a temporary file to prevent possible data loss.
323     QTemporaryFile tempFile;
324     tempFile.setAutoRemove(false);
325     bool open = false;
326     if (saveAll) {
327         open = tempFile.open();
328     } else {
329         open = historyFile.open(QFile::Append);
330     }
331 
332     if (!open) {
333         qWarning() << "Unable to open history file for saving"
334                    << (saveAll ? tempFile.fileName() : historyFile.fileName());
335         return;
336     }
337 
338     QDataStream out(saveAll ? &tempFile : &historyFile);
339     for (int i = first; i >= 0; --i) {
340         QByteArray data;
341         QDataStream stream(&data, QIODevice::WriteOnly);
342         HistoryItem item = m_history.at(i);
343         stream << HISTORY_VERSION << item.url << item.dateTime << item.title;
344         out << data;
345     }
346     tempFile.close();
347 
348     if (saveAll) {
349         if (historyFile.exists() && !historyFile.remove())
350             qWarning() << "History: error removing old history." << historyFile.errorString();
351         if (!tempFile.rename(historyFile.fileName()))
352             qWarning() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName();
353     }
354     m_lastSavedUrl = m_history.value(0).url;
355 }
356 
HistoryModel(HistoryManager * history,QObject * parent)357 HistoryModel::HistoryModel(HistoryManager *history, QObject *parent)
358     : QAbstractTableModel(parent)
359     , m_history(history)
360 {
361     Q_ASSERT(m_history);
362     connect(m_history, SIGNAL(historyReset()),
363             this, SLOT(historyReset()));
364     connect(m_history, SIGNAL(entryRemoved(HistoryItem)),
365             this, SLOT(historyReset()));
366 
367     connect(m_history, SIGNAL(entryAdded(HistoryItem)),
368             this, SLOT(entryAdded()));
369     connect(m_history, SIGNAL(entryUpdated(int)),
370             this, SLOT(entryUpdated(int)));
371 }
372 
historyReset()373 void HistoryModel::historyReset()
374 {
375     beginResetModel();
376     endResetModel();
377 }
378 
entryAdded()379 void HistoryModel::entryAdded()
380 {
381     beginInsertRows(QModelIndex(), 0, 0);
382     endInsertRows();
383 }
384 
entryUpdated(int offset)385 void HistoryModel::entryUpdated(int offset)
386 {
387     QModelIndex idx = index(offset, 0);
388     emit dataChanged(idx, idx);
389 }
390 
headerData(int section,Qt::Orientation orientation,int role) const391 QVariant HistoryModel::headerData(int section, Qt::Orientation orientation, int role) const
392 {
393     if (orientation == Qt::Horizontal
394         && role == Qt::DisplayRole) {
395         switch (section) {
396             case 0: return tr("Title");
397             case 1: return tr("Address");
398         }
399     }
400     return QAbstractTableModel::headerData(section, orientation, role);
401 }
402 
data(const QModelIndex & index,int role) const403 QVariant HistoryModel::data(const QModelIndex &index, int role) const
404 {
405     QList<HistoryItem> lst = m_history->history();
406     if (index.row() < 0 || index.row() >= lst.size())
407         return QVariant();
408 
409     const HistoryItem &item = lst.at(index.row());
410     switch (role) {
411     case DateTimeRole:
412         return item.dateTime;
413     case DateRole:
414         return item.dateTime.date();
415     case UrlRole:
416         return QUrl(item.url);
417     case UrlStringRole:
418         return item.url;
419     case Qt::DisplayRole:
420     case Qt::EditRole: {
421         switch (index.column()) {
422             case 0:
423                 // when there is no title try to generate one from the url
424                 if (item.title.isEmpty()) {
425                     QString page = QFileInfo(QUrl(item.url).path()).fileName();
426                     if (!page.isEmpty())
427                         return page;
428                     return item.url;
429                 }
430                 return item.title;
431             case 1:
432                 return item.url;
433         }
434         }
435     case Qt::DecorationRole:
436         if (index.column() == 0) {
437             return BrowserApplication::instance()->icon(item.url);
438         }
439     }
440     return QVariant();
441 }
442 
columnCount(const QModelIndex & parent) const443 int HistoryModel::columnCount(const QModelIndex &parent) const
444 {
445     return (parent.isValid()) ? 0 : 2;
446 }
447 
rowCount(const QModelIndex & parent) const448 int HistoryModel::rowCount(const QModelIndex &parent) const
449 {
450     return (parent.isValid()) ? 0 : m_history->history().count();
451 }
452 
removeRows(int row,int count,const QModelIndex & parent)453 bool HistoryModel::removeRows(int row, int count, const QModelIndex &parent)
454 {
455     if (parent.isValid())
456         return false;
457     int lastRow = row + count - 1;
458     beginRemoveRows(parent, row, lastRow);
459     QList<HistoryItem> lst = m_history->history();
460     for (int i = lastRow; i >= row; --i)
461         lst.removeAt(i);
462     disconnect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
463     m_history->setHistory(lst);
464     connect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset()));
465     endRemoveRows();
466     return true;
467 }
468 
469 #define MOVEDROWS 15
470 
471 /*
472     Maps the first bunch of items of the source model to the root
473 */
HistoryMenuModel(HistoryTreeModel * sourceModel,QObject * parent)474 HistoryMenuModel::HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent)
475     : QAbstractProxyModel(parent)
476     , m_treeModel(sourceModel)
477 {
478     setSourceModel(sourceModel);
479 }
480 
bumpedRows() const481 int HistoryMenuModel::bumpedRows() const
482 {
483     QModelIndex first = m_treeModel->index(0, 0);
484     if (!first.isValid())
485         return 0;
486     return qMin(m_treeModel->rowCount(first), MOVEDROWS);
487 }
488 
columnCount(const QModelIndex & parent) const489 int HistoryMenuModel::columnCount(const QModelIndex &parent) const
490 {
491     return m_treeModel->columnCount(mapToSource(parent));
492 }
493 
rowCount(const QModelIndex & parent) const494 int HistoryMenuModel::rowCount(const QModelIndex &parent) const
495 {
496     if (parent.column() > 0)
497         return 0;
498 
499     if (!parent.isValid()) {
500         int folders = sourceModel()->rowCount();
501         int bumpedItems = bumpedRows();
502         if (bumpedItems <= MOVEDROWS
503             && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
504             --folders;
505         return bumpedItems + folders;
506     }
507 
508     if (parent.internalId() == quintptr(-1)) {
509         if (parent.row() < bumpedRows())
510             return 0;
511     }
512 
513     QModelIndex idx = mapToSource(parent);
514     int defaultCount = sourceModel()->rowCount(idx);
515     if (idx == sourceModel()->index(0, 0))
516         return defaultCount - bumpedRows();
517     return defaultCount;
518 }
519 
mapFromSource(const QModelIndex & sourceIndex) const520 QModelIndex HistoryMenuModel::mapFromSource(const QModelIndex &sourceIndex) const
521 {
522     // currently not used or autotested
523     Q_ASSERT(false);
524     int sr = m_treeModel->mapToSource(sourceIndex).row();
525     return createIndex(sourceIndex.row(), sourceIndex.column(), sr);
526 }
527 
mapToSource(const QModelIndex & proxyIndex) const528 QModelIndex HistoryMenuModel::mapToSource(const QModelIndex &proxyIndex) const
529 {
530     if (!proxyIndex.isValid())
531         return QModelIndex();
532 
533     if (proxyIndex.internalId() == quintptr(-1)) {
534         int bumpedItems = bumpedRows();
535         if (proxyIndex.row() < bumpedItems)
536             return m_treeModel->index(proxyIndex.row(), proxyIndex.column(), m_treeModel->index(0, 0));
537         if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(m_treeModel->index(0, 0)))
538             --bumpedItems;
539         return m_treeModel->index(proxyIndex.row() - bumpedItems, proxyIndex.column());
540     }
541 
542     QModelIndex historyIndex = m_treeModel->sourceModel()->index(proxyIndex.internalId(), proxyIndex.column());
543     QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
544     return treeIndex;
545 }
546 
index(int row,int column,const QModelIndex & parent) const547 QModelIndex HistoryMenuModel::index(int row, int column, const QModelIndex &parent) const
548 {
549     if (row < 0
550         || column < 0 || column >= columnCount(parent)
551         || parent.column() > 0)
552         return QModelIndex();
553     if (!parent.isValid())
554         return createIndex(row, column, quintptr(-1));
555 
556     QModelIndex treeIndexParent = mapToSource(parent);
557 
558     int bumpedItems = 0;
559     if (treeIndexParent == m_treeModel->index(0, 0))
560         bumpedItems = bumpedRows();
561     QModelIndex treeIndex = m_treeModel->index(row + bumpedItems, column, treeIndexParent);
562     QModelIndex historyIndex = m_treeModel->mapToSource(treeIndex);
563     int historyRow = historyIndex.row();
564     if (historyRow == -1)
565         historyRow = treeIndex.row();
566     return createIndex(row, column, historyRow);
567 }
568 
parent(const QModelIndex & index) const569 QModelIndex HistoryMenuModel::parent(const QModelIndex &index) const
570 {
571     int offset = index.internalId();
572     if (offset == -1 || !index.isValid())
573         return QModelIndex();
574 
575     QModelIndex historyIndex = m_treeModel->sourceModel()->index(index.internalId(), 0);
576     QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex);
577     QModelIndex treeIndexParent = treeIndex.parent();
578 
579     int sr = m_treeModel->mapToSource(treeIndexParent).row();
580     int bumpedItems = bumpedRows();
581     if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0)))
582         --bumpedItems;
583     return createIndex(bumpedItems + treeIndexParent.row(), treeIndexParent.column(), sr);
584 }
585 
586 
HistoryMenu(QWidget * parent)587 HistoryMenu::HistoryMenu(QWidget *parent)
588     : ModelMenu(parent)
589     , m_history(0)
590 {
591     connect(this, SIGNAL(activated(QModelIndex)),
592             this, SLOT(activated(QModelIndex)));
593     setHoverRole(HistoryModel::UrlStringRole);
594 }
595 
activated(const QModelIndex & index)596 void HistoryMenu::activated(const QModelIndex &index)
597 {
598     emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
599 }
600 
prePopulated()601 bool HistoryMenu::prePopulated()
602 {
603     if (!m_history) {
604         m_history = BrowserApplication::historyManager();
605         m_historyMenuModel = new HistoryMenuModel(m_history->historyTreeModel(), this);
606         setModel(m_historyMenuModel);
607     }
608     // initial actions
609     for (int i = 0; i < m_initialActions.count(); ++i)
610         addAction(m_initialActions.at(i));
611     if (!m_initialActions.isEmpty())
612         addSeparator();
613     setFirstSeparator(m_historyMenuModel->bumpedRows());
614 
615     return false;
616 }
617 
postPopulated()618 void HistoryMenu::postPopulated()
619 {
620     if (m_history->history().count() > 0)
621         addSeparator();
622 
623     QAction *showAllAction = new QAction(tr("Show All History"), this);
624     connect(showAllAction, SIGNAL(triggered()), this, SLOT(showHistoryDialog()));
625     addAction(showAllAction);
626 
627     QAction *clearAction = new QAction(tr("Clear History"), this);
628     connect(clearAction, SIGNAL(triggered()), m_history, SLOT(clear()));
629     addAction(clearAction);
630 }
631 
showHistoryDialog()632 void HistoryMenu::showHistoryDialog()
633 {
634     HistoryDialog *dialog = new HistoryDialog(this);
635     connect(dialog, SIGNAL(openUrl(QUrl)),
636             this, SIGNAL(openUrl(QUrl)));
637     dialog->show();
638 }
639 
setInitialActions(QList<QAction * > actions)640 void HistoryMenu::setInitialActions(QList<QAction*> actions)
641 {
642     m_initialActions = actions;
643     for (int i = 0; i < m_initialActions.count(); ++i)
644         addAction(m_initialActions.at(i));
645 }
646 
TreeProxyModel(QObject * parent)647 TreeProxyModel::TreeProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
648 {
649     setSortRole(HistoryModel::DateTimeRole);
650     setFilterCaseSensitivity(Qt::CaseInsensitive);
651 }
652 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const653 bool TreeProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
654 {
655     if (!source_parent.isValid())
656         return true;
657     return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
658 }
659 
HistoryDialog(QWidget * parent,HistoryManager * setHistory)660 HistoryDialog::HistoryDialog(QWidget *parent, HistoryManager *setHistory) : QDialog(parent)
661 {
662     HistoryManager *history = setHistory;
663     if (!history)
664         history = BrowserApplication::historyManager();
665     setupUi(this);
666     tree->setUniformRowHeights(true);
667     tree->setSelectionBehavior(QAbstractItemView::SelectRows);
668     tree->setTextElideMode(Qt::ElideMiddle);
669     QAbstractItemModel *model = history->historyTreeModel();
670     TreeProxyModel *proxyModel = new TreeProxyModel(this);
671     connect(search, SIGNAL(textChanged(QString)),
672             proxyModel, SLOT(setFilterFixedString(QString)));
673     connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
674     connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear()));
675     proxyModel->setSourceModel(model);
676     tree->setModel(proxyModel);
677     tree->setExpanded(proxyModel->index(0, 0), true);
678     tree->setAlternatingRowColors(true);
679     QFontMetrics fm(font());
680     int header = fm.width(QLatin1Char('m')) * 40;
681     tree->header()->resizeSection(0, header);
682     tree->header()->setStretchLastSection(true);
683     connect(tree, SIGNAL(activated(QModelIndex)),
684             this, SLOT(open()));
685     tree->setContextMenuPolicy(Qt::CustomContextMenu);
686     connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
687             this, SLOT(customContextMenuRequested(QPoint)));
688 }
689 
customContextMenuRequested(const QPoint & pos)690 void HistoryDialog::customContextMenuRequested(const QPoint &pos)
691 {
692     QMenu menu;
693     QModelIndex index = tree->indexAt(pos);
694     index = index.sibling(index.row(), 0);
695     if (index.isValid() && !tree->model()->hasChildren(index)) {
696         menu.addAction(tr("Open"), this, SLOT(open()));
697         menu.addSeparator();
698         menu.addAction(tr("Copy"), this, SLOT(copy()));
699     }
700     menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
701     menu.exec(QCursor::pos());
702 }
703 
open()704 void HistoryDialog::open()
705 {
706     QModelIndex index = tree->currentIndex();
707     if (!index.parent().isValid())
708         return;
709     emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
710 }
711 
copy()712 void HistoryDialog::copy()
713 {
714 #ifndef QT_NO_CLIPBOARD
715     QModelIndex index = tree->currentIndex();
716     if (!index.parent().isValid())
717         return;
718     QString url = index.data(HistoryModel::UrlStringRole).toString();
719 
720     QClipboard *clipboard = QApplication::clipboard();
721     clipboard->setText(url);
722 #endif
723 }
724 
HistoryFilterModel(QAbstractItemModel * sourceModel,QObject * parent)725 HistoryFilterModel::HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent)
726     : QAbstractProxyModel(parent),
727     m_loaded(false)
728 {
729     setSourceModel(sourceModel);
730 }
731 
historyLocation(const QString & url) const732 int HistoryFilterModel::historyLocation(const QString &url) const
733 {
734     load();
735     if (!m_historyHash.contains(url))
736         return 0;
737     return sourceModel()->rowCount() - m_historyHash.value(url);
738 }
739 
data(const QModelIndex & index,int role) const740 QVariant HistoryFilterModel::data(const QModelIndex &index, int role) const
741 {
742     return QAbstractProxyModel::data(index, role);
743 }
744 
setSourceModel(QAbstractItemModel * newSourceModel)745 void HistoryFilterModel::setSourceModel(QAbstractItemModel *newSourceModel)
746 {
747     if (sourceModel()) {
748         disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
749         disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
750                    this, SLOT(dataChanged(QModelIndex,QModelIndex)));
751         disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
752                 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
753         disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
754                 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
755     }
756 
757     QAbstractProxyModel::setSourceModel(newSourceModel);
758 
759     if (sourceModel()) {
760         m_loaded = false;
761         connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
762         connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
763                    this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
764         connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
765                 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
766         connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
767                 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
768     }
769 }
770 
sourceDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)771 void HistoryFilterModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
772 {
773     emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight));
774 }
775 
headerData(int section,Qt::Orientation orientation,int role) const776 QVariant HistoryFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
777 {
778     return sourceModel()->headerData(section, orientation, role);
779 }
780 
sourceReset()781 void HistoryFilterModel::sourceReset()
782 {
783     m_loaded = false;
784     beginResetModel();
785     endResetModel();
786 }
787 
rowCount(const QModelIndex & parent) const788 int HistoryFilterModel::rowCount(const QModelIndex &parent) const
789 {
790     load();
791     if (parent.isValid())
792         return 0;
793     return m_historyHash.count();
794 }
795 
columnCount(const QModelIndex & parent) const796 int HistoryFilterModel::columnCount(const QModelIndex &parent) const
797 {
798     return (parent.isValid()) ? 0 : 2;
799 }
800 
mapToSource(const QModelIndex & proxyIndex) const801 QModelIndex HistoryFilterModel::mapToSource(const QModelIndex &proxyIndex) const
802 {
803     load();
804     int sourceRow = sourceModel()->rowCount() - proxyIndex.internalId();
805     return sourceModel()->index(sourceRow, proxyIndex.column());
806 }
807 
mapFromSource(const QModelIndex & sourceIndex) const808 QModelIndex HistoryFilterModel::mapFromSource(const QModelIndex &sourceIndex) const
809 {
810     load();
811     QString url = sourceIndex.data(HistoryModel::UrlStringRole).toString();
812     if (!m_historyHash.contains(url))
813         return QModelIndex();
814 
815     // This can be done in a binary search, but we can't use qBinary find
816     // because it can't take: qBinaryFind(m_sourceRow.end(), m_sourceRow.begin(), v);
817     // so if this is a performance bottlneck then convert to binary search, until then
818     // the cleaner/easier to read code wins the day.
819     int realRow = -1;
820     int sourceModelRow = sourceModel()->rowCount() - sourceIndex.row();
821 
822     for (int i = 0; i < m_sourceRow.count(); ++i) {
823         if (m_sourceRow.at(i) == sourceModelRow) {
824             realRow = i;
825             break;
826         }
827     }
828     if (realRow == -1)
829         return QModelIndex();
830 
831     return createIndex(realRow, sourceIndex.column(), sourceModel()->rowCount() - sourceIndex.row());
832 }
833 
index(int row,int column,const QModelIndex & parent) const834 QModelIndex HistoryFilterModel::index(int row, int column, const QModelIndex &parent) const
835 {
836     load();
837     if (row < 0 || row >= rowCount(parent)
838         || column < 0 || column >= columnCount(parent))
839         return QModelIndex();
840 
841     return createIndex(row, column, m_sourceRow[row]);
842 }
843 
parent(const QModelIndex &) const844 QModelIndex HistoryFilterModel::parent(const QModelIndex &) const
845 {
846     return QModelIndex();
847 }
848 
load() const849 void HistoryFilterModel::load() const
850 {
851     if (m_loaded)
852         return;
853     m_sourceRow.clear();
854     m_historyHash.clear();
855     m_historyHash.reserve(sourceModel()->rowCount());
856     for (int i = 0; i < sourceModel()->rowCount(); ++i) {
857         QModelIndex idx = sourceModel()->index(i, 0);
858         QString url = idx.data(HistoryModel::UrlStringRole).toString();
859         if (!m_historyHash.contains(url)) {
860             m_sourceRow.append(sourceModel()->rowCount() - i);
861             m_historyHash[url] = sourceModel()->rowCount() - i;
862         }
863     }
864     m_loaded = true;
865 }
866 
sourceRowsInserted(const QModelIndex & parent,int start,int end)867 void HistoryFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
868 {
869     Q_ASSERT(start == end && start == 0);
870     Q_UNUSED(end);
871     if (!m_loaded)
872         return;
873     QModelIndex idx = sourceModel()->index(start, 0, parent);
874     QString url = idx.data(HistoryModel::UrlStringRole).toString();
875     if (m_historyHash.contains(url)) {
876         int sourceRow = sourceModel()->rowCount() - m_historyHash[url];
877         int realRow = mapFromSource(sourceModel()->index(sourceRow, 0)).row();
878         beginRemoveRows(QModelIndex(), realRow, realRow);
879         m_sourceRow.removeAt(realRow);
880         m_historyHash.remove(url);
881         endRemoveRows();
882     }
883     beginInsertRows(QModelIndex(), 0, 0);
884     m_historyHash.insert(url, sourceModel()->rowCount() - start);
885     m_sourceRow.insert(0, sourceModel()->rowCount());
886     endInsertRows();
887 }
888 
sourceRowsRemoved(const QModelIndex &,int start,int end)889 void HistoryFilterModel::sourceRowsRemoved(const QModelIndex &, int start, int end)
890 {
891     Q_UNUSED(start);
892     Q_UNUSED(end);
893     sourceReset();
894 }
895 
896 /*
897     Removing a continuous block of rows will remove filtered rows too as this is
898     the users intention.
899 */
removeRows(int row,int count,const QModelIndex & parent)900 bool HistoryFilterModel::removeRows(int row, int count, const QModelIndex &parent)
901 {
902     if (row < 0 || count <= 0 || row + count > rowCount(parent) || parent.isValid())
903         return false;
904     int lastRow = row + count - 1;
905     disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
906                 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
907     beginRemoveRows(parent, row, lastRow);
908     int oldCount = rowCount();
909     int start = sourceModel()->rowCount() - m_sourceRow.value(row);
910     int end = sourceModel()->rowCount() - m_sourceRow.value(lastRow);
911     sourceModel()->removeRows(start, end - start + 1);
912     endRemoveRows();
913     connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
914                 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
915     m_loaded = false;
916     if (oldCount - count != rowCount()) {
917         beginResetModel();
918         endResetModel();
919     }
920     return true;
921 }
922 
HistoryCompletionModel(QObject * parent)923 HistoryCompletionModel::HistoryCompletionModel(QObject *parent)
924     : QAbstractProxyModel(parent)
925 {
926 }
927 
data(const QModelIndex & index,int role) const928 QVariant HistoryCompletionModel::data(const QModelIndex &index, int role) const
929 {
930     if (sourceModel()
931         && (role == Qt::EditRole || role == Qt::DisplayRole)
932         && index.isValid()) {
933         QModelIndex idx = mapToSource(index);
934         idx = idx.sibling(idx.row(), 1);
935         QString urlString = idx.data(HistoryModel::UrlStringRole).toString();
936         if (index.row() % 2) {
937             QUrl url = urlString;
938             QString s = url.toString(QUrl::RemoveScheme
939                                      | QUrl::RemoveUserInfo
940                                      | QUrl::StripTrailingSlash);
941             return s.mid(2);  // strip // from the front
942         }
943         return urlString;
944     }
945     return QAbstractProxyModel::data(index, role);
946 }
947 
rowCount(const QModelIndex & parent) const948 int HistoryCompletionModel::rowCount(const QModelIndex &parent) const
949 {
950     return (parent.isValid() || !sourceModel()) ? 0 : sourceModel()->rowCount(parent) * 2;
951 }
952 
columnCount(const QModelIndex & parent) const953 int HistoryCompletionModel::columnCount(const QModelIndex &parent) const
954 {
955     return (parent.isValid()) ? 0 : 1;
956 }
957 
mapFromSource(const QModelIndex & sourceIndex) const958 QModelIndex HistoryCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const
959 {
960     int row = sourceIndex.row() * 2;
961     return index(row, sourceIndex.column());
962 }
963 
mapToSource(const QModelIndex & proxyIndex) const964 QModelIndex HistoryCompletionModel::mapToSource(const QModelIndex &proxyIndex) const
965 {
966     if (!sourceModel())
967         return QModelIndex();
968     int row = proxyIndex.row() / 2;
969     return sourceModel()->index(row, proxyIndex.column());
970 }
971 
index(int row,int column,const QModelIndex & parent) const972 QModelIndex HistoryCompletionModel::index(int row, int column, const QModelIndex &parent) const
973 {
974     if (row < 0 || row >= rowCount(parent)
975         || column < 0 || column >= columnCount(parent))
976         return QModelIndex();
977     return createIndex(row, column);
978 }
979 
parent(const QModelIndex &) const980 QModelIndex HistoryCompletionModel::parent(const QModelIndex &) const
981 {
982     return QModelIndex();
983 }
984 
setSourceModel(QAbstractItemModel * newSourceModel)985 void HistoryCompletionModel::setSourceModel(QAbstractItemModel *newSourceModel)
986 {
987     if (sourceModel()) {
988         disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
989         disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
990                 this, SLOT(sourceReset()));
991         disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
992                 this, SLOT(sourceReset()));
993     }
994 
995     QAbstractProxyModel::setSourceModel(newSourceModel);
996 
997     if (newSourceModel) {
998         connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset()));
999         connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1000                 this, SLOT(sourceReset()));
1001         connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
1002                 this, SLOT(sourceReset()));
1003     }
1004 
1005     beginResetModel();
1006     endResetModel();
1007 }
1008 
sourceReset()1009 void HistoryCompletionModel::sourceReset()
1010 {
1011     beginResetModel();
1012     endResetModel();
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);
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);
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     beginResetModel();
1198     endResetModel();
1199 }
1200 
sourceReset()1201 void HistoryTreeModel::sourceReset()
1202 {
1203     beginResetModel();
1204     m_sourceRowCache.clear();
1205     endResetModel();
1206 }
1207 
sourceRowsInserted(const QModelIndex & parent,int start,int end)1208 void HistoryTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
1209 {
1210     Q_UNUSED(parent); // Avoid warnings when compiling release
1211     Q_ASSERT(!parent.isValid());
1212     if (start != 0 || start != end) {
1213         beginResetModel();
1214         m_sourceRowCache.clear();
1215         endResetModel();
1216         return;
1217     }
1218 
1219     m_sourceRowCache.clear();
1220     QModelIndex treeIndex = mapFromSource(sourceModel()->index(start, 0));
1221     QModelIndex treeParent = treeIndex.parent();
1222     if (rowCount(treeParent) == 1) {
1223         beginInsertRows(QModelIndex(), 0, 0);
1224         endInsertRows();
1225     } else {
1226         beginInsertRows(treeParent, treeIndex.row(), treeIndex.row());
1227         endInsertRows();
1228     }
1229 }
1230 
mapFromSource(const QModelIndex & sourceIndex) const1231 QModelIndex HistoryTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
1232 {
1233     if (!sourceIndex.isValid())
1234         return QModelIndex();
1235 
1236     if (m_sourceRowCache.isEmpty())
1237         rowCount(QModelIndex());
1238 
1239     QList<int>::iterator it;
1240     it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), sourceIndex.row());
1241     if (*it != sourceIndex.row())
1242         --it;
1243     int dateRow = qMax(0, it - m_sourceRowCache.begin());
1244     int row = sourceIndex.row() - m_sourceRowCache.at(dateRow);
1245     return createIndex(row, sourceIndex.column(), dateRow + 1);
1246 }
1247 
sourceRowsRemoved(const QModelIndex & parent,int start,int end)1248 void HistoryTreeModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
1249 {
1250     Q_UNUSED(parent); // Avoid warnings when compiling release
1251     Q_ASSERT(!parent.isValid());
1252     if (m_sourceRowCache.isEmpty())
1253         return;
1254     for (int i = end; i >= start;) {
1255         QList<int>::iterator it;
1256         it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), i);
1257         // playing it safe
1258         if (it == m_sourceRowCache.end()) {
1259             beginResetModel();
1260             m_sourceRowCache.clear();
1261             endResetModel();
1262             return;
1263         }
1264 
1265         if (*it != i)
1266             --it;
1267         int row = qMax(0, it - m_sourceRowCache.begin());
1268         int offset = m_sourceRowCache[row];
1269         QModelIndex dateParent = index(row, 0);
1270         // If we can remove all the rows in the date do that and skip over them
1271         int rc = rowCount(dateParent);
1272         if (i - rc + 1 == offset && start <= i - rc + 1) {
1273             beginRemoveRows(QModelIndex(), row, row);
1274             m_sourceRowCache.removeAt(row);
1275             i -= rc + 1;
1276         } else {
1277             beginRemoveRows(dateParent, i - offset, i - offset);
1278             ++row;
1279             --i;
1280         }
1281         for (int j = row; j < m_sourceRowCache.count(); ++j)
1282             --m_sourceRowCache[j];
1283         endRemoveRows();
1284     }
1285 }
1286 
1287