summaryrefslogtreecommitdiff
path: root/src/history
diff options
context:
space:
mode:
Diffstat (limited to 'src/history')
-rw-r--r--src/history/history.cpp454
-rw-r--r--src/history/history.h164
-rw-r--r--src/history/historymodels.cpp971
-rw-r--r--src/history/historymodels.h256
-rw-r--r--src/history/historypanel.cpp101
-rw-r--r--src/history/historypanel.h64
6 files changed, 2010 insertions, 0 deletions
diff --git a/src/history/history.cpp b/src/history/history.cpp
new file mode 100644
index 00000000..07580433
--- /dev/null
+++ b/src/history/history.cpp
@@ -0,0 +1,454 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008 Benjamin C. Meyer <ben@meyerhome.net>
+* Copyright (C) 2008-2009 by Andrea Diamantini <adjam7 at gmail dot com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+// Self Includes
+#include "history.h"
+#include "history.moc"
+
+// Auto Includes
+#include "rekonq.h"
+
+// Local Includes
+#include "historymodels.h"
+#include "autosaver.h"
+#include "application.h"
+
+// KDE Includes
+#include <KStandardDirs>
+#include <KLocale>
+#include <KCompletion>
+
+// Qt Includes
+#include <QtCore/QList>
+#include <QtCore/QUrl>
+#include <QtCore/QDate>
+#include <QtCore/QDateTime>
+#include <QtCore/QString>
+#include <QtCore/QFile>
+#include <QtCore/QDataStream>
+#include <QtCore/QBuffer>
+
+#include <QtGui/QClipboard>
+
+// generic algorithms
+#include <QtAlgorithms>
+
+
+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_completion(0)
+{
+ // take care of the completion object
+ m_completion = new KCompletion;
+ m_completion->setOrder( KCompletion::Weighted );
+
+ m_expiredTimer.setSingleShot(true);
+ connect(&m_expiredTimer, SIGNAL(timeout()), this, SLOT(checkForExpired()));
+ connect(this, SIGNAL(entryAdded(const HistoryItem &)), m_saveTimer, SLOT(changeOccurred()));
+ connect(this, SIGNAL(entryRemoved(const 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();
+ delete m_completion;
+}
+
+
+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);
+
+ // don't store about: urls (home page related)
+ if(cleanUrl.scheme() == QString("about"))
+ return;
+
+ cleanUrl.setPassword(QString());
+ cleanUrl.setHost(cleanUrl.host().toLower());
+ HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime());
+ addHistoryEntry(item);
+
+ // Add item to completion object
+ QString _url(url);
+ _url.remove(QRegExp("^http://|/$"));
+ m_completion->addItem(_url);
+}
+
+
+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.clear();
+ 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.clear();
+ emit entryRemoved(item);
+ }
+
+ if (nextTimeout > 0)
+ m_expiredTimer.start(nextTimeout * 1000);
+}
+
+
+void HistoryManager::addHistoryEntry(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::updateHistoryEntry(const KUrl &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;
+ }
+ }
+}
+
+
+void HistoryManager::removeHistoryEntry(const HistoryItem &item)
+{
+ m_lastSavedUrl.clear();
+ m_history.removeOne(item);
+ emit entryRemoved(item);
+}
+
+
+void HistoryManager::removeHistoryEntry(const KUrl &url, const QString &title)
+{
+ for (int i = 0; i < m_history.count(); ++i)
+ {
+ if (url == m_history.at(i).url
+ && (title.isEmpty() || title == m_history.at(i).title))
+ {
+ removeHistoryEntry(m_history.at(i));
+ break;
+ }
+ }
+
+ // Remove item from completion object
+ QString _url = url.path();
+ _url.remove(QRegExp("^http://|/$"));
+ m_completion->removeItem(_url);
+}
+
+
+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.clear();
+ m_saveTimer->changeOccurred();
+ m_saveTimer->saveIfNeccessary();
+ historyReset();
+}
+
+
+void HistoryManager::loadSettings()
+{
+ int historyExpire = ReKonfig::expireHistory();
+ int days;
+ switch (historyExpire)
+ {
+ case 0: days = 1; break;
+ case 1: days = 7; break;
+ case 2: days = 14; break;
+ case 3: days = 30; break;
+ case 4: days = 365; break;
+ case 5: days = -1; break;
+ default: days = -1;
+ }
+ m_historyLimit = days;
+}
+
+
+void HistoryManager::load()
+{
+ loadSettings();
+
+ QString historyFilePath = KStandardDirs::locateLocal("appdata" , "history");
+ QFile historyFile(historyFilePath);
+ if (!historyFile.exists())
+ return;
+ if (!historyFile.open(QFile::ReadOnly))
+ {
+ kWarning() << "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;
+
+ // Add item to completion object
+ QString _url = item.url;
+ _url.remove(QRegExp("^http://|/$"));
+ m_completion->addItem(_url);
+ }
+ 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.clear();
+ m_saveTimer->changeOccurred();
+ }
+}
+
+
+void HistoryManager::save()
+{
+ 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 historyFilePath = KStandardDirs::locateLocal("appdata" , "history");
+ QFile historyFile(historyFilePath);
+
+ // 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)
+ {
+ kWarning() << "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())
+ {
+ kWarning() << "History: error removing old history." << historyFile.errorString();
+ }
+ if (!tempFile.rename(historyFile.fileName()))
+ {
+ kWarning() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName();
+ }
+ }
+ m_lastSavedUrl = m_history.value(0).url;
+}
+
+
+KCompletion * HistoryManager::completionObject() const
+{
+ return m_completion;
+}
diff --git a/src/history/history.h b/src/history/history.h
new file mode 100644
index 00000000..2ccf8cb4
--- /dev/null
+++ b/src/history/history.h
@@ -0,0 +1,164 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008 Benjamin C. Meyer <ben@meyerhome.net>
+* Copyright (C) 2008-2009 by Andrea Diamantini <adjam7 at gmail dot com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+#ifndef HISTORY_H
+#define HISTORY_H
+
+
+// Local Includes
+#include "modelmenu.h"
+
+// KDE Includes
+#include <KUrl>
+
+// Qt Includes
+#include <QDateTime>
+#include <QHash>
+#include <QObject>
+#include <QTimer>
+#include <QSortFilterProxyModel>
+#include <QWebHistoryInterface>
+
+
+/**
+ * Elements in this class represent an history item
+ *
+ */
+class HistoryItem
+{
+public:
+ HistoryItem() {}
+ explicit HistoryItem(const QString &u,
+ const QDateTime &d = QDateTime(),
+ const QString &t = QString()
+ )
+ : title(t), url(u), dateTime(d) {}
+
+ inline bool operator==(const HistoryItem &other) const
+ {
+ return other.title == title
+ && other.url == url && other.dateTime == dateTime;
+ }
+
+ // history is sorted in reverse
+ inline bool operator <(const HistoryItem &other) const
+ {
+ return dateTime > other.dateTime;
+ }
+
+ QString title;
+ QString url;
+ QDateTime dateTime;
+};
+
+
+
+// ---------------------------------------------------------------------------------------------------------------
+
+
+// Forward Declarations
+class AutoSaver;
+class HistoryModel;
+class HistoryFilterModel;
+class HistoryTreeModel;
+
+class KCompletion;
+
+
+/**
+ * THE History Manager:
+ * It manages rekonq history
+ *
+ */
+class HistoryManager : public QWebHistoryInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(int historyLimit READ historyLimit WRITE setHistoryLimit)
+
+signals:
+ void historyReset();
+ void entryAdded(const HistoryItem &item);
+ void entryRemoved(const HistoryItem &item);
+ void entryUpdated(int offset);
+
+public:
+ HistoryManager(QObject *parent = 0);
+ ~HistoryManager();
+
+ bool historyContains(const QString &url) const;
+ void addHistoryEntry(const QString &url);
+ void updateHistoryEntry(const KUrl &url, const QString &title);
+ void removeHistoryEntry(const KUrl &url, const QString &title = QString());
+
+ int historyLimit() const;
+ void setHistoryLimit(int limit);
+
+ QList<HistoryItem> history() const;
+ void setHistory(const QList<HistoryItem> &history, bool loadedAndSorted = false);
+
+ // History manager keeps around these models for use by the completer and other classes
+ HistoryModel *historyModel() const;
+ HistoryFilterModel *historyFilterModel() const;
+ HistoryTreeModel *historyTreeModel() const;
+
+ /**
+ * @returns the KCompletion object.
+ */
+ KCompletion *completionObject() const;
+
+public slots:
+ void clear();
+ void loadSettings();
+
+private slots:
+ void save();
+ void checkForExpired();
+
+protected:
+ void addHistoryEntry(const HistoryItem &item);
+ void removeHistoryEntry(const HistoryItem &item);
+
+private:
+ void load();
+
+ AutoSaver *m_saveTimer;
+ int m_historyLimit;
+ QTimer m_expiredTimer;
+ QList<HistoryItem> m_history;
+ QString m_lastSavedUrl;
+
+ HistoryModel *m_historyModel;
+ HistoryFilterModel *m_historyFilterModel;
+ HistoryTreeModel *m_historyTreeModel;
+
+ // the completion object we sync with
+ KCompletion *m_completion;
+};
+
+
+#endif // HISTORY_H
diff --git a/src/history/historymodels.cpp b/src/history/historymodels.cpp
new file mode 100644
index 00000000..cf8d1aed
--- /dev/null
+++ b/src/history/historymodels.cpp
@@ -0,0 +1,971 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008 Benjamin C. Meyer <ben@meyerhome.net>
+* Copyright (C) 2008-2009 by Andrea Diamantini <adjam7 at gmail dot com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+// Self Includes
+#include "historymodels.h"
+#include "historymodels.moc"
+
+// Auto Includes
+#include "rekonq.h"
+
+// Local Includes
+#include "application.h"
+
+// KDE Includes
+#include <KStandardDirs>
+#include <KLocale>
+
+// Qt Includes
+#include <QtCore/QList>
+#include <QtCore/QUrl>
+#include <QtCore/QDate>
+#include <QtCore/QDateTime>
+#include <QtCore/QString>
+#include <QtCore/QFile>
+#include <QtCore/QDataStream>
+#include <QtCore/QBuffer>
+
+#include <QtGui/QClipboard>
+
+// generic algorithms
+#include <QtAlgorithms>
+
+
+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(const HistoryItem &)), this, SLOT(historyReset()));
+ connect(m_history, SIGNAL(entryAdded(const HistoryItem &)), this, SLOT(entryAdded()));
+ connect(m_history, SIGNAL(entryUpdated(int)), this, SLOT(entryUpdated(int)));
+}
+
+
+void HistoryModel::historyReset()
+{
+ reset();
+}
+
+
+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 i18n("Title");
+ case 1: return i18n("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 Application::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 20
+
+
+/*
+ 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() == -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() == -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, -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);
+}
+
+
+// --------------------------------------------------------------------------------------------------------------
+
+
+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);
+}
+
+
+// -------------------------------------------------------------------------------------------------------------
+
+
+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(const QModelIndex &, const QModelIndex &)),
+ this, SLOT(dataChanged(const QModelIndex &, const QModelIndex &)));
+ disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
+ }
+
+ QAbstractProxyModel::setSourceModel(newSourceModel);
+
+ if (sourceModel())
+ {
+ m_loaded = false;
+ connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
+ this, SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &)));
+ connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
+ connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const 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;
+ reset();
+}
+
+
+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(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const 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(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
+ m_loaded = false;
+ if (oldCount - count != rowCount())
+ reset();
+ 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, 0);
+}
+
+
+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(const QModelIndex &, int, int)),
+ this, SLOT(sourceReset()));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceReset()));
+ }
+
+ QAbstractProxyModel::setSourceModel(newSourceModel);
+
+ if (newSourceModel)
+ {
+ connect(newSourceModel, SIGNAL(modelReset()), this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(sourceReset()));
+ connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceReset()));
+ }
+
+ reset();
+}
+
+
+void HistoryCompletionModel::sourceReset()
+{
+ reset();
+}
+
+
+// ------------------------------------------------------------------------------------------------------
+
+
+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 i18n("Earlier Today");
+ return date.toString(QLatin1String("dddd, MMMM d, yyyy"));
+ }
+ if (index.column() == 1)
+ {
+ return i18np("1 item", "%1 items", rowCount(index.sibling(index.row(), 0)));
+ }
+ }
+ }
+ if (role == Qt::DecorationRole && index.column() == 0 && !index.parent().isValid())
+ return KIcon("view-history");
+ 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, 0);
+ 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, 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(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
+ disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const 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(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
+ connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+ this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
+ }
+
+ reset();
+}
+
+
+void HistoryTreeModel::sourceReset()
+{
+ m_sourceRowCache.clear();
+ reset();
+}
+
+
+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)
+ {
+ m_sourceRowCache.clear();
+ reset();
+ 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())
+ {
+ m_sourceRowCache.clear();
+ reset();
+ 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();
+ }
+}
diff --git a/src/history/historymodels.h b/src/history/historymodels.h
new file mode 100644
index 00000000..93e72c6a
--- /dev/null
+++ b/src/history/historymodels.h
@@ -0,0 +1,256 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008 Benjamin C. Meyer <ben@meyerhome.net>
+* Copyright (C) 2008-2009 by Andrea Diamantini <adjam7 at gmail dot com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+#ifndef HISTORYMODELS_H
+#define HISTORYMODELS_H
+
+
+// Local Includes
+#include "history.h"
+#include "modelmenu.h"
+
+// KDE Includes
+#include <KUrl>
+
+// Qt Includes
+#include <QDateTime>
+#include <QHash>
+#include <QObject>
+#include <QTimer>
+#include <QSortFilterProxyModel>
+#include <QWebHistoryInterface>
+
+
+class HistoryModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public slots:
+ void historyReset();
+ void entryAdded();
+ void entryUpdated(int offset);
+
+public:
+ enum Roles
+ {
+ DateRole = Qt::UserRole + 1,
+ DateTimeRole = Qt::UserRole + 2,
+ UrlRole = Qt::UserRole + 3,
+ UrlStringRole = Qt::UserRole + 4
+ };
+
+ explicit HistoryModel(HistoryManager *history, QObject *parent = 0);
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+private:
+ HistoryManager *m_history;
+};
+
+
+// ----------------------------------------------------------------------------------------------------
+
+/**
+ * Proxy model that will remove any duplicate entries.
+ * Both m_sourceRow and m_historyHash store their offsets not from
+ * the front of the list, but as offsets from the back.
+ *
+ */
+
+class HistoryFilterModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent = 0);
+
+ inline bool historyContains(const QString &url) const
+ {
+ load(); return m_historyHash.contains(url);
+ }
+ int historyLocation(const QString &url) const;
+
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ void setSourceModel(QAbstractItemModel *sourceModel);
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index = QModelIndex()) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+private slots:
+ void sourceReset();
+ void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+ void sourceRowsInserted(const QModelIndex &parent, int start, int end);
+ void sourceRowsRemoved(const QModelIndex &, int, int);
+
+private:
+ void load() const;
+
+ mutable QList<int> m_sourceRow;
+ mutable QHash<QString, int> m_historyHash;
+ mutable bool m_loaded;
+};
+
+
+// ----------------------------------------------------------------------------------------------------------------------
+
+/**
+ * The history menu
+ * - Removes the first twenty entries and puts them as children of the top level.
+ * - If there are less then twenty entries then the first folder is also removed.
+ *
+ * The mapping is done by knowing that HistoryTreeModel is over a table
+ * We store that row offset in our index's private data.
+ *
+ */
+
+class HistoryMenuModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit HistoryMenuModel(HistoryTreeModel *sourceModel, QObject *parent = 0);
+
+ int columnCount(const QModelIndex &parent) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex & sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex & proxyIndex) const;
+ QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &index = QModelIndex()) const;
+
+ int bumpedRows() const;
+
+private:
+ HistoryTreeModel *m_treeModel;
+};
+
+
+// ----------------------------------------------------------------------------------------
+
+/**
+ * Proxy model for the history model that
+ * exposes each url http://www.foo.com and
+ * it url starting at the host www.foo.com
+ *
+ */
+
+class HistoryCompletionModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ HistoryCompletionModel(QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex index(int, int, const QModelIndex& = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex& index = QModelIndex()) const;
+ void setSourceModel(QAbstractItemModel *sourceModel);
+
+private slots:
+ void sourceReset();
+
+};
+
+
+// ---------------------------------------------------------------------------------------
+
+/**
+ * Proxy model for the history model that converts the list
+ * into a tree, one top level node per day.
+ * Used in the HistoryDialog.
+ *
+ */
+
+class HistoryTreeModel : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit HistoryTreeModel(QAbstractItemModel *sourceModel, QObject *parent = 0);
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &index = QModelIndex()) const;
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ void setSourceModel(QAbstractItemModel *sourceModel);
+
+private slots:
+ void sourceReset();
+ void sourceRowsInserted(const QModelIndex &parent, int start, int end);
+ void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
+
+private:
+ int sourceDateRow(int row) const;
+ mutable QList<int> m_sourceRowCache;
+
+};
+
+
+// -----------------------------------------------------------------------------------------------------------------
+
+/**
+ * A modified QSortFilterProxyModel that always accepts
+ * the root nodes in the tree
+ * so filtering is only done on the children.
+ *
+ * Used in the HistoryDialog.
+ *
+ */
+
+class TreeProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ TreeProxyModel(QObject *parent = 0);
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+};
+
+
+#endif // HISTORYMODELS_H
diff --git a/src/history/historypanel.cpp b/src/history/historypanel.cpp
new file mode 100644
index 00000000..42497f9e
--- /dev/null
+++ b/src/history/historypanel.cpp
@@ -0,0 +1,101 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2009 by Andrea Diamantini <adjam7 at gmail dot com>*
+* Copyright (C) 2009 by Domrachev Alexandr <alexandr.domrachev@gmail.com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+// Self Includes
+#include "historypanel.h"
+#include "historypanel.moc"
+
+// Qt Includes
+#include <QtGui/QLabel>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QHeaderView>
+
+// KDE Includes
+#include <KLineEdit>
+#include <KLocalizedString>
+
+
+HistoryPanel::HistoryPanel(QWidget *parent)
+ : QWidget(parent)
+ , m_historyTreeView(new QTreeView)
+ , m_treeProxyModel(new TreeProxyModel(this))
+{
+ m_historyTreeView->setUniformRowHeights(true);
+ m_historyTreeView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_historyTreeView->setTextElideMode(Qt::ElideMiddle);
+ m_historyTreeView->setAlternatingRowColors(true);
+
+ // add search bar
+ QHBoxLayout *hBoxLayout = new QHBoxLayout;
+ hBoxLayout->setContentsMargins(5, 0, 0, 0);
+ QLabel *searchLabel = new QLabel(i18n("Search:"));
+ hBoxLayout->addWidget(searchLabel);
+ KLineEdit *search = new KLineEdit;
+ search->setClearButtonShown(true);
+ hBoxLayout->addWidget(search);
+ QWidget *searchBar = new QWidget;
+ searchBar->setLayout(hBoxLayout);
+
+ // setup view
+ QVBoxLayout *vBoxLayout = new QVBoxLayout;
+ vBoxLayout->setContentsMargins(0, 0, 0, 0);
+ vBoxLayout->addWidget(searchBar);
+ vBoxLayout->addWidget(m_historyTreeView);
+ setLayout(vBoxLayout);
+
+ //-
+ HistoryManager *historyManager = Application::historyManager();
+ QAbstractItemModel *model = historyManager->historyTreeModel();
+
+ m_treeProxyModel->setSourceModel(model);
+ m_historyTreeView->setModel(m_treeProxyModel);
+ m_historyTreeView->setExpanded(m_treeProxyModel->index(0, 0), true);
+ m_historyTreeView->header()->hideSection(1);
+ QFontMetrics fm(font());
+ int header = fm.width(QLatin1Char('m')) * 40;
+ m_historyTreeView->header()->resizeSection(0, header);
+
+ connect(search, SIGNAL(textChanged(QString)), m_treeProxyModel, SLOT(setFilterFixedString(QString)));
+ connect(m_historyTreeView, SIGNAL(activated(const QModelIndex&)), this, SLOT(open()));
+}
+
+
+HistoryPanel::~HistoryPanel()
+{
+ delete m_treeProxyModel;
+ delete m_historyTreeView;
+}
+
+
+void HistoryPanel::open()
+{
+ QModelIndex index = m_historyTreeView->currentIndex();
+ if (!index.parent().isValid())
+ return;
+ emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
+}
+
diff --git a/src/history/historypanel.h b/src/history/historypanel.h
new file mode 100644
index 00000000..083b2741
--- /dev/null
+++ b/src/history/historypanel.h
@@ -0,0 +1,64 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2009 by Andrea Diamantini <adjam7 at gmail dot com>*
+* Copyright (C) 2009 by Domrachev Alexandr <alexandr.domrachev@gmail.com>
+*
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of
+* the License or (at your option) version 3 or any later version
+* accepted by the membership of KDE e.V. (or its successor approved
+* by the membership of KDE e.V.), which shall act as a proxy
+* defined in Section 14 of version 3 of the license.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* ============================================================ */
+
+
+#ifndef HISTORYPANEL_H
+#define HISTORYPANEL_H
+
+
+// Local Includes
+#include "application.h"
+#include "historymodels.h"
+
+// Qt Includes
+#include <QtGui/QWidget>
+#include <QtGui/QTreeView>
+
+// Forward Declarations
+class KUrl;
+
+
+class HistoryPanel : public QWidget
+{
+Q_OBJECT
+
+public:
+ explicit HistoryPanel(QWidget *parent = 0);
+ virtual ~HistoryPanel();
+
+signals:
+ void openUrl(const KUrl &);
+
+private slots:
+ void open();
+
+private:
+ QTreeView *m_historyTreeView;
+ TreeProxyModel *m_treeProxyModel;
+
+};
+
+#endif // HISTORYPANEL_H