history.cpp
上传用户:huahtool
上传日期:2015-12-10
资源大小:1089k
文件大小:41k
源码类别:

浏览器

开发平台:

Visual C++

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