history.cpp Example File
webkitwidgets/browser/history.cpp/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the demonstration applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "history.h" #include "autosaver.h" #include "browserapplication.h" #include <QtCore/QBuffer> #include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QSettings> #include <QtCore/QTemporaryFile> #include <QtCore/QTextStream> #include <QtCore/QtAlgorithms> #include <QtGui/QClipboard> #include <QtGui/QDesktopServices> #include <QtWidgets/QHeaderView> #include <QtWidgets/QStyle> #include <QWebHistoryInterface> #include <QWebSettings> #include <QtCore/QDebug> static const unsigned int HISTORY_VERSION = 23; HistoryManager::HistoryManager(QObject *parent) : QWebHistoryInterface(parent) , m_saveTimer(new AutoSaver(this)) , m_historyLimit(30) , m_historyModel(0) , m_historyFilterModel(0) , m_historyTreeModel(0) { m_expiredTimer.setSingleShot(true); connect(&m_expiredTimer, SIGNAL(timeout()), this, SLOT(checkForExpired())); connect(this, SIGNAL(entryAdded(HistoryItem)), m_saveTimer, SLOT(changeOccurred())); connect(this, SIGNAL(entryRemoved(HistoryItem)), m_saveTimer, SLOT(changeOccurred())); load(); m_historyModel = new HistoryModel(this, this); m_historyFilterModel = new HistoryFilterModel(m_historyModel, this); m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this); // QWebHistoryInterface will delete the history manager QWebHistoryInterface::setDefaultInterface(this); } HistoryManager::~HistoryManager() { m_saveTimer->saveIfNeccessary(); } QList<HistoryItem> HistoryManager::history() const { return m_history; } bool HistoryManager::historyContains(const QString &url) const { return m_historyFilterModel->historyContains(url); } void HistoryManager::addHistoryEntry(const QString &url) { QUrl cleanUrl(url); cleanUrl.setPassword(QString()); cleanUrl.setHost(cleanUrl.host().toLower()); HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime()); addHistoryItem(item); } void HistoryManager::setHistory(const QList<HistoryItem> &history, bool loadedAndSorted) { m_history = history; // verify that it is sorted by date if (!loadedAndSorted) qSort(m_history.begin(), m_history.end()); checkForExpired(); if (loadedAndSorted) { m_lastSavedUrl = m_history.value(0).url; } else { m_lastSavedUrl = QString(); m_saveTimer->changeOccurred(); } emit historyReset(); } HistoryModel *HistoryManager::historyModel() const { return m_historyModel; } HistoryFilterModel *HistoryManager::historyFilterModel() const { return m_historyFilterModel; } HistoryTreeModel *HistoryManager::historyTreeModel() const { return m_historyTreeModel; } void HistoryManager::checkForExpired() { if (m_historyLimit < 0 || m_history.isEmpty()) return; QDateTime now = QDateTime::currentDateTime(); int nextTimeout = 0; while (!m_history.isEmpty()) { QDateTime checkForExpired = m_history.last().dateTime; checkForExpired.setDate(checkForExpired.date().addDays(m_historyLimit)); if (now.daysTo(checkForExpired) > 7) { // check at most in a week to prevent int overflows on the timer nextTimeout = 7 * 86400; } else { nextTimeout = now.secsTo(checkForExpired); } if (nextTimeout > 0) break; HistoryItem item = m_history.takeLast(); // remove from saved file also m_lastSavedUrl = QString(); emit entryRemoved(item); } if (nextTimeout > 0) m_expiredTimer.start(nextTimeout * 1000); } void HistoryManager::addHistoryItem(const HistoryItem &item) { QWebSettings *globalSettings = QWebSettings::globalSettings(); if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled)) return; m_history.prepend(item); emit entryAdded(item); if (m_history.count() == 1) checkForExpired(); } void HistoryManager::updateHistoryItem(const QUrl &url, const QString &title) { for (int i = 0; i < m_history.count(); ++i) { if (url == m_history.at(i).url) { m_history[i].title = title; m_saveTimer->changeOccurred(); if (m_lastSavedUrl.isEmpty()) m_lastSavedUrl = m_history.at(i).url; emit entryUpdated(i); break; } } } int HistoryManager::historyLimit() const { return m_historyLimit; } void HistoryManager::setHistoryLimit(int limit) { if (m_historyLimit == limit) return; m_historyLimit = limit; checkForExpired(); m_saveTimer->changeOccurred(); } void HistoryManager::clear() { m_history.clear(); m_lastSavedUrl = QString(); m_saveTimer->changeOccurred(); m_saveTimer->saveIfNeccessary(); historyReset(); } void HistoryManager::loadSettings() { // load settings QSettings settings; settings.beginGroup(QLatin1String("history")); m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt(); } void HistoryManager::load() { loadSettings(); QFile historyFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/history")); if (!historyFile.exists()) return; if (!historyFile.open(QFile::ReadOnly)) { qWarning() << "Unable to open history file" << historyFile.fileName(); return; } QList<HistoryItem> list; QDataStream in(&historyFile); // Double check that the history file is sorted as it is read in bool needToSort = false; HistoryItem lastInsertedItem; QByteArray data; QDataStream stream; QBuffer buffer; stream.setDevice(&buffer); while (!historyFile.atEnd()) { in >> data; buffer.close(); buffer.setBuffer(&data); buffer.open(QIODevice::ReadOnly); quint32 ver; stream >> ver; if (ver != HISTORY_VERSION) continue; HistoryItem item; stream >> item.url; stream >> item.dateTime; stream >> item.title; if (!item.dateTime.isValid()) continue; if (item == lastInsertedItem) { if (lastInsertedItem.title.isEmpty() && !list.isEmpty()) list[0].title = item.title; continue; } if (!needToSort && !list.isEmpty() && lastInsertedItem < item) needToSort = true; list.prepend(item); lastInsertedItem = item; } if (needToSort) qSort(list.begin(), list.end()); setHistory(list, true); // If we had to sort re-write the whole history sorted if (needToSort) { m_lastSavedUrl = QString(); m_saveTimer->changeOccurred(); } } void HistoryManager::save() { QSettings settings; settings.beginGroup(QLatin1String("history")); settings.setValue(QLatin1String("historyLimit"), m_historyLimit); bool saveAll = m_lastSavedUrl.isEmpty(); int first = m_history.count() - 1; if (!saveAll) { // find the first one to save for (int i = 0; i < m_history.count(); ++i) { if (m_history.at(i).url == m_lastSavedUrl) { first = i - 1; break; } } } if (first == m_history.count() - 1) saveAll = true; QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation); if (directory.isEmpty()) directory = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName(); if (!QFile::exists(directory)) { QDir dir; dir.mkpath(directory); } QFile historyFile(directory + QLatin1String("/history")); // When saving everything use a temporary file to prevent possible data loss. QTemporaryFile tempFile; tempFile.setAutoRemove(false); bool open = false; if (saveAll) { open = tempFile.open(); } else { open = historyFile.open(QFile::Append); } if (!open) { qWarning() << "Unable to open history file for saving" << (saveAll ? tempFile.fileName() : historyFile.fileName()); return; } QDataStream out(saveAll ? &tempFile : &historyFile); for (int i = first; i >= 0; --i) { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); HistoryItem item = m_history.at(i); stream << HISTORY_VERSION << item.url << item.dateTime << item.title; out << data; } tempFile.close(); if (saveAll) { if (historyFile.exists() && !historyFile.remove()) qWarning() << "History: error removing old history." << historyFile.errorString(); if (!tempFile.rename(historyFile.fileName())) qWarning() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName(); } m_lastSavedUrl = m_history.value(0).url; } HistoryModel::HistoryModel(HistoryManager *history, QObject *parent) : QAbstractTableModel(parent) , m_history(history) { Q_ASSERT(m_history); connect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset())); connect(m_history, SIGNAL(entryRemoved(HistoryItem)), this, SLOT(historyReset())); connect(m_history, SIGNAL(entryAdded(HistoryItem)), this, SLOT(entryAdded())); connect(m_history, SIGNAL(entryUpdated(int)), this, SLOT(entryUpdated(int))); } void HistoryModel::historyReset() { beginResetModel(); endResetModel(); } void HistoryModel::entryAdded() { beginInsertRows(QModelIndex(), 0, 0); endInsertRows(); } void HistoryModel::entryUpdated(int offset) { QModelIndex idx = index(offset, 0); emit dataChanged(idx, idx); } QVariant HistoryModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return tr("Title"); case 1: return tr("Address"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant HistoryModel::data(const QModelIndex &index, int role) const { QList<HistoryItem> lst = m_history->history(); if (index.row() < 0 || index.row() >= lst.size()) return QVariant(); const HistoryItem &item = lst.at(index.row()); switch (role) { case DateTimeRole: return item.dateTime; case DateRole: return item.dateTime.date(); case UrlRole: return QUrl(item.url); case UrlStringRole: return item.url; case Qt::DisplayRole: case Qt::EditRole: { switch (index.column()) { case 0: // when there is no title try to generate one from the url if (item.title.isEmpty()) { QString page = QFileInfo(QUrl(item.url).path()).fileName(); if (!page.isEmpty()) return page; return item.url; } return item.title; case 1: return item.url; } } case Qt::DecorationRole: if (index.column() == 0) { return BrowserApplication::instance()->icon(item.url); } } return QVariant(); } int HistoryModel::columnCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : 2; } int HistoryModel::rowCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : m_history->history().count(); } bool HistoryModel::removeRows(int row, int count, const QModelIndex &parent) { if (parent.isValid()) return false; int lastRow = row + count - 1; beginRemoveRows(parent, row, lastRow); QList<HistoryItem> lst = m_history->history(); for (int i = lastRow; i >= row; --i) lst.removeAt(i); disconnect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset())); m_history->setHistory(lst); connect(m_history, SIGNAL(historyReset()), this, SLOT(historyReset())); endRemoveRows(); return true; } #define MOVEDROWS 15 /* Maps the first bunch of items of the source model to the root */ HistoryMenuModel::HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent) : QAbstractProxyModel(parent) , m_treeModel(sourceModel) { setSourceModel(sourceModel); } int HistoryMenuModel::bumpedRows() const { QModelIndex first = m_treeModel->index(0, 0); if (!first.isValid()) return 0; return qMin(m_treeModel->rowCount(first), MOVEDROWS); } int HistoryMenuModel::columnCount(const QModelIndex &parent) const { return m_treeModel->columnCount(mapToSource(parent)); } int HistoryMenuModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (!parent.isValid()) { int folders = sourceModel()->rowCount(); int bumpedItems = bumpedRows(); if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0))) --folders; return bumpedItems + folders; } if (parent.internalId() == quintptr(-1)) { if (parent.row() < bumpedRows()) return 0; } QModelIndex idx = mapToSource(parent); int defaultCount = sourceModel()->rowCount(idx); if (idx == sourceModel()->index(0, 0)) return defaultCount - bumpedRows(); return defaultCount; } QModelIndex HistoryMenuModel::mapFromSource(const QModelIndex &sourceIndex) const { // currently not used or autotested Q_ASSERT(false); int sr = m_treeModel->mapToSource(sourceIndex).row(); return createIndex(sourceIndex.row(), sourceIndex.column(), sr); } QModelIndex HistoryMenuModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) return QModelIndex(); if (proxyIndex.internalId() == quintptr(-1)) { int bumpedItems = bumpedRows(); if (proxyIndex.row() < bumpedItems) return m_treeModel->index(proxyIndex.row(), proxyIndex.column(), m_treeModel->index(0, 0)); if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(m_treeModel->index(0, 0))) --bumpedItems; return m_treeModel->index(proxyIndex.row() - bumpedItems, proxyIndex.column()); } QModelIndex historyIndex = m_treeModel->sourceModel()->index(proxyIndex.internalId(), proxyIndex.column()); QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex); return treeIndex; } QModelIndex HistoryMenuModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(parent) || parent.column() > 0) return QModelIndex(); if (!parent.isValid()) return createIndex(row, column, quintptr(-1)); QModelIndex treeIndexParent = mapToSource(parent); int bumpedItems = 0; if (treeIndexParent == m_treeModel->index(0, 0)) bumpedItems = bumpedRows(); QModelIndex treeIndex = m_treeModel->index(row + bumpedItems, column, treeIndexParent); QModelIndex historyIndex = m_treeModel->mapToSource(treeIndex); int historyRow = historyIndex.row(); if (historyRow == -1) historyRow = treeIndex.row(); return createIndex(row, column, historyRow); } QModelIndex HistoryMenuModel::parent(const QModelIndex &index) const { int offset = index.internalId(); if (offset == -1 || !index.isValid()) return QModelIndex(); QModelIndex historyIndex = m_treeModel->sourceModel()->index(index.internalId(), 0); QModelIndex treeIndex = m_treeModel->mapFromSource(historyIndex); QModelIndex treeIndexParent = treeIndex.parent(); int sr = m_treeModel->mapToSource(treeIndexParent).row(); int bumpedItems = bumpedRows(); if (bumpedItems <= MOVEDROWS && bumpedItems == sourceModel()->rowCount(sourceModel()->index(0, 0))) --bumpedItems; return createIndex(bumpedItems + treeIndexParent.row(), treeIndexParent.column(), sr); } HistoryMenu::HistoryMenu(QWidget *parent) : ModelMenu(parent) , m_history(0) { connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(activated(QModelIndex))); setHoverRole(HistoryModel::UrlStringRole); } void HistoryMenu::activated(const QModelIndex &index) { emit openUrl(index.data(HistoryModel::UrlRole).toUrl()); } bool HistoryMenu::prePopulated() { if (!m_history) { m_history = BrowserApplication::historyManager(); m_historyMenuModel = new HistoryMenuModel(m_history->historyTreeModel(), this); setModel(m_historyMenuModel); } // initial actions for (int i = 0; i < m_initialActions.count(); ++i) addAction(m_initialActions.at(i)); if (!m_initialActions.isEmpty()) addSeparator(); setFirstSeparator(m_historyMenuModel->bumpedRows()); return false; } void HistoryMenu::postPopulated() { if (m_history->history().count() > 0) addSeparator(); QAction *showAllAction = new QAction(tr("Show All History"), this); connect(showAllAction, SIGNAL(triggered()), this, SLOT(showHistoryDialog())); addAction(showAllAction); QAction *clearAction = new QAction(tr("Clear History"), this); connect(clearAction, SIGNAL(triggered()), m_history, SLOT(clear())); addAction(clearAction); } void HistoryMenu::showHistoryDialog() { HistoryDialog *dialog = new HistoryDialog(this); connect(dialog, SIGNAL(openUrl(QUrl)), this, SIGNAL(openUrl(QUrl))); dialog->show(); } void HistoryMenu::setInitialActions(QList<QAction*> actions) { m_initialActions = actions; for (int i = 0; i < m_initialActions.count(); ++i) addAction(m_initialActions.at(i)); } TreeProxyModel::TreeProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { setSortRole(HistoryModel::DateTimeRole); setFilterCaseSensitivity(Qt::CaseInsensitive); } bool TreeProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (!source_parent.isValid()) return true; return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } HistoryDialog::HistoryDialog(QWidget *parent, HistoryManager *setHistory) : QDialog(parent) { HistoryManager *history = setHistory; if (!history) history = BrowserApplication::historyManager(); setupUi(this); tree->setUniformRowHeights(true); tree->setSelectionBehavior(QAbstractItemView::SelectRows); tree->setTextElideMode(Qt::ElideMiddle); QAbstractItemModel *model = history->historyTreeModel(); TreeProxyModel *proxyModel = new TreeProxyModel(this); connect(search, SIGNAL(textChanged(QString)), proxyModel, SLOT(setFilterFixedString(QString))); connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne())); connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear())); proxyModel->setSourceModel(model); tree->setModel(proxyModel); tree->setExpanded(proxyModel->index(0, 0), true); tree->setAlternatingRowColors(true); QFontMetrics fm(font()); int header = fm.width(QLatin1Char('m')) * 40; tree->header()->resizeSection(0, header); tree->header()->setStretchLastSection(true); connect(tree, SIGNAL(activated(QModelIndex)), this, SLOT(open())); tree->setContextMenuPolicy(Qt::CustomContextMenu); connect(tree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); } void HistoryDialog::customContextMenuRequested(const QPoint &pos) { QMenu menu; QModelIndex index = tree->indexAt(pos); index = index.sibling(index.row(), 0); if (index.isValid() && !tree->model()->hasChildren(index)) { menu.addAction(tr("Open"), this, SLOT(open())); menu.addSeparator(); menu.addAction(tr("Copy"), this, SLOT(copy())); } menu.addAction(tr("Delete"), tree, SLOT(removeOne())); menu.exec(QCursor::pos()); } void HistoryDialog::open() { QModelIndex index = tree->currentIndex(); if (!index.parent().isValid()) return; emit openUrl(index.data(HistoryModel::UrlRole).toUrl()); } void HistoryDialog::copy() { #ifndef QT_NO_CLIPBOARD QModelIndex index = tree->currentIndex(); if (!index.parent().isValid()) return; QString url = index.data(HistoryModel::UrlStringRole).toString(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(url); #endif } HistoryFilterModel::HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent) : QAbstractProxyModel(parent), m_loaded(false) { setSourceModel(sourceModel); } int HistoryFilterModel::historyLocation(const QString &url) const { load(); if (!m_historyHash.contains(url)) return 0; return sourceModel()->rowCount() - m_historyHash.value(url); } QVariant HistoryFilterModel::data(const QModelIndex &index, int role) const { return QAbstractProxyModel::data(index, role); } void HistoryFilterModel::setSourceModel(QAbstractItemModel *newSourceModel) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceRowsInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); } QAbstractProxyModel::setSourceModel(newSourceModel); if (sourceModel()) { m_loaded = false; connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceRowsInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); } } void HistoryFilterModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight)); } QVariant HistoryFilterModel::headerData(int section, Qt::Orientation orientation, int role) const { return sourceModel()->headerData(section, orientation, role); } void HistoryFilterModel::sourceReset() { m_loaded = false; beginResetModel(); endResetModel(); } int HistoryFilterModel::rowCount(const QModelIndex &parent) const { load(); if (parent.isValid()) return 0; return m_historyHash.count(); } int HistoryFilterModel::columnCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : 2; } QModelIndex HistoryFilterModel::mapToSource(const QModelIndex &proxyIndex) const { load(); int sourceRow = sourceModel()->rowCount() - proxyIndex.internalId(); return sourceModel()->index(sourceRow, proxyIndex.column()); } QModelIndex HistoryFilterModel::mapFromSource(const QModelIndex &sourceIndex) const { load(); QString url = sourceIndex.data(HistoryModel::UrlStringRole).toString(); if (!m_historyHash.contains(url)) return QModelIndex(); // This can be done in a binary search, but we can't use qBinary find // because it can't take: qBinaryFind(m_sourceRow.end(), m_sourceRow.begin(), v); // so if this is a performance bottlneck then convert to binary search, until then // the cleaner/easier to read code wins the day. int realRow = -1; int sourceModelRow = sourceModel()->rowCount() - sourceIndex.row(); for (int i = 0; i < m_sourceRow.count(); ++i) { if (m_sourceRow.at(i) == sourceModelRow) { realRow = i; break; } } if (realRow == -1) return QModelIndex(); return createIndex(realRow, sourceIndex.column(), sourceModel()->rowCount() - sourceIndex.row()); } QModelIndex HistoryFilterModel::index(int row, int column, const QModelIndex &parent) const { load(); if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, m_sourceRow[row]); } QModelIndex HistoryFilterModel::parent(const QModelIndex &) const { return QModelIndex(); } void HistoryFilterModel::load() const { if (m_loaded) return; m_sourceRow.clear(); m_historyHash.clear(); m_historyHash.reserve(sourceModel()->rowCount()); for (int i = 0; i < sourceModel()->rowCount(); ++i) { QModelIndex idx = sourceModel()->index(i, 0); QString url = idx.data(HistoryModel::UrlStringRole).toString(); if (!m_historyHash.contains(url)) { m_sourceRow.append(sourceModel()->rowCount() - i); m_historyHash[url] = sourceModel()->rowCount() - i; } } m_loaded = true; } void HistoryFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end) { Q_ASSERT(start == end && start == 0); Q_UNUSED(end); if (!m_loaded) return; QModelIndex idx = sourceModel()->index(start, 0, parent); QString url = idx.data(HistoryModel::UrlStringRole).toString(); if (m_historyHash.contains(url)) { int sourceRow = sourceModel()->rowCount() - m_historyHash[url]; int realRow = mapFromSource(sourceModel()->index(sourceRow, 0)).row(); beginRemoveRows(QModelIndex(), realRow, realRow); m_sourceRow.removeAt(realRow); m_historyHash.remove(url); endRemoveRows(); } beginInsertRows(QModelIndex(), 0, 0); m_historyHash.insert(url, sourceModel()->rowCount() - start); m_sourceRow.insert(0, sourceModel()->rowCount()); endInsertRows(); } void HistoryFilterModel::sourceRowsRemoved(const QModelIndex &, int start, int end) { Q_UNUSED(start); Q_UNUSED(end); sourceReset(); } /* Removing a continuous block of rows will remove filtered rows too as this is the users intention. */ bool HistoryFilterModel::removeRows(int row, int count, const QModelIndex &parent) { if (row < 0 || count <= 0 || row + count > rowCount(parent) || parent.isValid()) return false; int lastRow = row + count - 1; disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); beginRemoveRows(parent, row, lastRow); int oldCount = rowCount(); int start = sourceModel()->rowCount() - m_sourceRow.value(row); int end = sourceModel()->rowCount() - m_sourceRow.value(lastRow); sourceModel()->removeRows(start, end - start + 1); endRemoveRows(); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); m_loaded = false; if (oldCount - count != rowCount()) { beginResetModel(); endResetModel(); } return true; } HistoryCompletionModel::HistoryCompletionModel(QObject *parent) : QAbstractProxyModel(parent) { } QVariant HistoryCompletionModel::data(const QModelIndex &index, int role) const { if (sourceModel() && (role == Qt::EditRole || role == Qt::DisplayRole) && index.isValid()) { QModelIndex idx = mapToSource(index); idx = idx.sibling(idx.row(), 1); QString urlString = idx.data(HistoryModel::UrlStringRole).toString(); if (index.row() % 2) { QUrl url = urlString; QString s = url.toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::StripTrailingSlash); return s.mid(2); // strip // from the front } return urlString; } return QAbstractProxyModel::data(index, role); } int HistoryCompletionModel::rowCount(const QModelIndex &parent) const { return (parent.isValid() || !sourceModel()) ? 0 : sourceModel()->rowCount(parent) * 2; } int HistoryCompletionModel::columnCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : 1; } QModelIndex HistoryCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const { int row = sourceIndex.row() * 2; return index(row, sourceIndex.column()); } QModelIndex HistoryCompletionModel::mapToSource(const QModelIndex &proxyIndex) const { if (!sourceModel()) return QModelIndex(); int row = proxyIndex.row() / 2; return sourceModel()->index(row, proxyIndex.column()); } QModelIndex HistoryCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column); } QModelIndex HistoryCompletionModel::parent(const QModelIndex &) const { return QModelIndex(); } void HistoryCompletionModel::setSourceModel(QAbstractItemModel *newSourceModel) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceReset())); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceReset())); } QAbstractProxyModel::setSourceModel(newSourceModel); if (newSourceModel) { connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset())); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceReset())); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceReset())); } beginResetModel(); endResetModel(); } void HistoryCompletionModel::sourceReset() { beginResetModel(); endResetModel(); } HistoryTreeModel::HistoryTreeModel(QAbstractItemModel *sourceModel, QObject *parent) : QAbstractProxyModel(parent) { setSourceModel(sourceModel); } QVariant HistoryTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { return sourceModel()->headerData(section, orientation, role); } QVariant HistoryTreeModel::data(const QModelIndex &index, int role) const { if ((role == Qt::EditRole || role == Qt::DisplayRole)) { int start = index.internalId(); if (start == 0) { int offset = sourceDateRow(index.row()); if (index.column() == 0) { QModelIndex idx = sourceModel()->index(offset, 0); QDate date = idx.data(HistoryModel::DateRole).toDate(); if (date == QDate::currentDate()) return tr("Earlier Today"); return date.toString(QLatin1String("dddd, MMMM d, yyyy")); } if (index.column() == 1) { return tr("%1 items").arg(rowCount(index.sibling(index.row(), 0))); } } } if (role == Qt::DecorationRole && index.column() == 0 && !index.parent().isValid()) return QIcon(QLatin1String(":history.png")); if (role == HistoryModel::DateRole && index.column() == 0 && index.internalId() == 0) { int offset = sourceDateRow(index.row()); QModelIndex idx = sourceModel()->index(offset, 0); return idx.data(HistoryModel::DateRole); } return QAbstractProxyModel::data(index, role); } int HistoryTreeModel::columnCount(const QModelIndex &parent) const { return sourceModel()->columnCount(mapToSource(parent)); } int HistoryTreeModel::rowCount(const QModelIndex &parent) const { if ( parent.internalId() != 0 || parent.column() > 0 || !sourceModel()) return 0; // row count OF dates if (!parent.isValid()) { if (!m_sourceRowCache.isEmpty()) return m_sourceRowCache.count(); QDate currentDate; int rows = 0; int totalRows = sourceModel()->rowCount(); for (int i = 0; i < totalRows; ++i) { QDate rowDate = sourceModel()->index(i, 0).data(HistoryModel::DateRole).toDate(); if (rowDate != currentDate) { m_sourceRowCache.append(i); currentDate = rowDate; ++rows; } } Q_ASSERT(m_sourceRowCache.count() == rows); return rows; } // row count FOR a date int start = sourceDateRow(parent.row()); int end = sourceDateRow(parent.row() + 1); return (end - start); } // Translate the top level date row into the offset where that date starts int HistoryTreeModel::sourceDateRow(int row) const { if (row <= 0) return 0; if (m_sourceRowCache.isEmpty()) rowCount(QModelIndex()); if (row >= m_sourceRowCache.count()) { if (!sourceModel()) return 0; return sourceModel()->rowCount(); } return m_sourceRowCache.at(row); } QModelIndex HistoryTreeModel::mapToSource(const QModelIndex &proxyIndex) const { int offset = proxyIndex.internalId(); if (offset == 0) return QModelIndex(); int startDateRow = sourceDateRow(offset - 1); return sourceModel()->index(startDateRow + proxyIndex.row(), proxyIndex.column()); } QModelIndex HistoryTreeModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(parent) || parent.column() > 0) return QModelIndex(); if (!parent.isValid()) return createIndex(row, column); return createIndex(row, column, parent.row() + 1); } QModelIndex HistoryTreeModel::parent(const QModelIndex &index) const { int offset = index.internalId(); if (offset == 0 || !index.isValid()) return QModelIndex(); return createIndex(offset - 1, 0); } bool HistoryTreeModel::hasChildren(const QModelIndex &parent) const { QModelIndex grandparent = parent.parent(); if (!grandparent.isValid()) return true; return false; } Qt::ItemFlags HistoryTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; } bool HistoryTreeModel::removeRows(int row, int count, const QModelIndex &parent) { if (row < 0 || count <= 0 || row + count > rowCount(parent)) return false; if (parent.isValid()) { // removing pages int offset = sourceDateRow(parent.row()); return sourceModel()->removeRows(offset + row, count); } else { // removing whole dates for (int i = row + count - 1; i >= row; --i) { QModelIndex dateParent = index(i, 0); int offset = sourceDateRow(dateParent.row()); if (!sourceModel()->removeRows(offset, rowCount(dateParent))) return false; } } return true; } void HistoryTreeModel::setSourceModel(QAbstractItemModel *newSourceModel) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset())); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceRowsInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); } QAbstractProxyModel::setSourceModel(newSourceModel); if (newSourceModel) { connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset())); connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset())); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(sourceRowsInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); } beginResetModel(); endResetModel(); } void HistoryTreeModel::sourceReset() { beginResetModel(); m_sourceRowCache.clear(); endResetModel(); } void HistoryTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); // Avoid warnings when compiling release Q_ASSERT(!parent.isValid()); if (start != 0 || start != end) { beginResetModel(); m_sourceRowCache.clear(); endResetModel(); return; } m_sourceRowCache.clear(); QModelIndex treeIndex = mapFromSource(sourceModel()->index(start, 0)); QModelIndex treeParent = treeIndex.parent(); if (rowCount(treeParent) == 1) { beginInsertRows(QModelIndex(), 0, 0); endInsertRows(); } else { beginInsertRows(treeParent, treeIndex.row(), treeIndex.row()); endInsertRows(); } } QModelIndex HistoryTreeModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) return QModelIndex(); if (m_sourceRowCache.isEmpty()) rowCount(QModelIndex()); QList<int>::iterator it; it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), sourceIndex.row()); if (*it != sourceIndex.row()) --it; int dateRow = qMax(0, it - m_sourceRowCache.begin()); int row = sourceIndex.row() - m_sourceRowCache.at(dateRow); return createIndex(row, sourceIndex.column(), dateRow + 1); } void HistoryTreeModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent); // Avoid warnings when compiling release Q_ASSERT(!parent.isValid()); if (m_sourceRowCache.isEmpty()) return; for (int i = end; i >= start;) { QList<int>::iterator it; it = qLowerBound(m_sourceRowCache.begin(), m_sourceRowCache.end(), i); // playing it safe if (it == m_sourceRowCache.end()) { beginResetModel(); m_sourceRowCache.clear(); endResetModel(); return; } if (*it != i) --it; int row = qMax(0, it - m_sourceRowCache.begin()); int offset = m_sourceRowCache[row]; QModelIndex dateParent = index(row, 0); // If we can remove all the rows in the date do that and skip over them int rc = rowCount(dateParent); if (i - rc + 1 == offset && start <= i - rc + 1) { beginRemoveRows(QModelIndex(), row, row); m_sourceRowCache.removeAt(row); i -= rc + 1; } else { beginRemoveRows(dateParent, i - offset, i - offset); ++row; --i; } for (int j = row; j < m_sourceRowCache.count(); ++j) --m_sourceRowCache[j]; endRemoveRows(); } }
© 2015 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.