summaryrefslogtreecommitdiff
path: root/src/history
diff options
context:
space:
mode:
authorAndrea Diamantini <adjam7@gmail.com>2012-07-29 11:22:24 +0200
committerAndrea Diamantini <adjam7@gmail.com>2012-12-10 02:48:04 +0100
commitfe7c06fffa370f8f04a3035f3ae92975ea39b960 (patch)
treef60ad5a2bd0c72ccac0c195dc5630c15071e35f2 /src/history
parentClass Application Import, first (important) part (diff)
downloadrekonq-fe7c06fffa370f8f04a3035f3ae92975ea39b960.tar.xz
Insert back HistoryManager
There are a few notable changes here: - rekonq_defines.h clean up, removing the enums from there - let HistoryManager be accessible by a self function
Diffstat (limited to 'src/history')
-rw-r--r--src/history/autosaver.cpp96
-rw-r--r--src/history/autosaver.h77
-rw-r--r--src/history/historymanager.cpp473
-rw-r--r--src/history/historymanager.h175
-rw-r--r--src/history/historymodels.cpp743
-rw-r--r--src/history/historymodels.h179
6 files changed, 1743 insertions, 0 deletions
diff --git a/src/history/autosaver.cpp b/src/history/autosaver.cpp
new file mode 100644
index 00000000..ee84e299
--- /dev/null
+++ b/src/history/autosaver.cpp
@@ -0,0 +1,96 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008-2011 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 "autosaver.h"
+#include "autosaver.moc"
+
+// Qt Includes
+#include <QMetaObject>
+#include <QTimerEvent>
+#include <QBasicTimer>
+#include <QTime>
+
+
+const int AUTOSAVE_TIME = 1000 * 3; // seconds
+const int MAX_TIME_LIMIT = 1000 * 15; // seconds
+
+
+AutoSaver::AutoSaver(QObject *parent)
+ : QObject(parent)
+ , m_timer(new QBasicTimer)
+ , m_firstChange(new QTime)
+{
+}
+
+
+AutoSaver::~AutoSaver()
+{
+ if (m_timer->isActive())
+ kDebug() << "AutoSaver: still active when destroyed, changes not saved.";
+
+ delete m_firstChange;
+ delete m_timer;
+}
+
+
+void AutoSaver::saveIfNeccessary()
+{
+ if (m_timer->isActive())
+ save();
+}
+
+
+void AutoSaver::changeOccurred()
+{
+ if (m_firstChange->isNull())
+ m_firstChange->start();
+
+ if (m_firstChange->elapsed() > MAX_TIME_LIMIT)
+ save();
+ else
+ m_timer->start(AUTOSAVE_TIME, this);
+}
+
+
+void AutoSaver::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == m_timer->timerId())
+ save();
+ else
+ QObject::timerEvent(event);
+}
+
+
+void AutoSaver::save()
+{
+ m_timer->stop();
+ delete m_firstChange;
+ m_firstChange = new QTime;
+
+ emit saveNeeded();
+}
diff --git a/src/history/autosaver.h b/src/history/autosaver.h
new file mode 100644
index 00000000..cb1add2f
--- /dev/null
+++ b/src/history/autosaver.h
@@ -0,0 +1,77 @@
+/* ============================================================
+*
+* This file is a part of the rekonq project
+*
+* Copyright (C) 2007-2008 Trolltech ASA. All rights reserved
+* Copyright (C) 2008-2011 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 AUTOSAVER_H
+#define AUTOSAVER_H
+
+
+// Rekonq Includes
+#include "rekonq_defines.h"
+
+// Qt Includes
+#include <QObject>
+
+// Forward Declarations
+class QBasicTimer;
+class QTime;
+
+
+/**
+ * This class emits the saveNeeded() signal.
+ * It will wait several seconds after changeOccurred() to combine
+ * multiple changes preventing continuous writing to disk.
+ */
+class AutoSaver : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit AutoSaver(QObject *parent);
+ virtual ~AutoSaver();
+
+ /**
+ * Emits the saveNeeded() signal if there's been any change since we last saved.
+ */
+ void saveIfNeccessary();
+
+Q_SIGNALS:
+ void saveNeeded();
+
+public Q_SLOTS:
+ void changeOccurred();
+
+protected:
+ virtual void timerEvent(QTimerEvent *event);
+
+private:
+ void save();
+
+ QBasicTimer *m_timer;
+ QTime *m_firstChange;
+};
+
+#endif // AUTOSAVER_H
diff --git a/src/history/historymanager.cpp b/src/history/historymanager.cpp
new file mode 100644
index 00000000..6fe13b73
--- /dev/null
+++ b/src/history/historymanager.cpp
@@ -0,0 +1,473 @@
+/* ============================================================
+*
+* 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-2012 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 "historymanager.h"
+#include "historymanager.moc"
+
+// Auto Includes
+#include "rekonq.h"
+
+// Local Includes
+#include "historymodels.h"
+#include "autosaver.h"
+
+// KDE Includes
+#include <KStandardDirs>
+#include <KLocale>
+#include <KCompletion>
+
+// Qt Includes
+#include <QList>
+#include <QUrl>
+#include <QDate>
+#include <QDateTime>
+#include <QString>
+#include <QFile>
+#include <QDataStream>
+#include <QBuffer>
+#include <QTemporaryFile>
+#include <QTimer>
+
+#include <QClipboard>
+
+// generic algorithms
+#include <QtAlgorithms>
+
+
+static const unsigned int HISTORY_VERSION = 25;
+
+
+QWeakPointer<HistoryManager> HistoryManager::s_historyManager;
+
+
+HistoryManager *HistoryManager::self()
+{
+ if (s_historyManager.isNull())
+ {
+ s_historyManager = new HistoryManager(qApp);
+ }
+ return s_historyManager.data();
+}
+
+
+// ----------------------------------------------------------------------------------------------
+
+
+HistoryManager::HistoryManager(QObject *parent)
+ : QObject(parent)
+ , m_saveTimer(new AutoSaver(this))
+ , m_historyLimit(0)
+ , m_historyTreeModel(0)
+{
+ kDebug() << "loading history";
+ connect(this, SIGNAL(entryAdded(HistoryItem)), m_saveTimer, SLOT(changeOccurred()));
+ connect(this, SIGNAL(entryRemoved(HistoryItem)), m_saveTimer, SLOT(changeOccurred()));
+ connect(m_saveTimer, SIGNAL(saveNeeded()), this, SLOT(save()));
+
+ load();
+
+ HistoryModel *historyModel = new HistoryModel(this, this);
+ m_historyFilterModel = new HistoryFilterModel(historyModel, this);
+ m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this);
+}
+
+
+HistoryManager::~HistoryManager()
+{
+ if (ReKonfig::expireHistory() == 4)
+ {
+ m_history.clear();
+ save();
+ return;
+ }
+ m_saveTimer->saveIfNeccessary();
+
+ kDebug() << "bye bye history...";
+}
+
+
+bool HistoryManager::historyContains(const QString &url) const
+{
+ return m_historyFilterModel->historyContains(url);
+}
+
+
+void HistoryManager::addHistoryEntry(const KUrl &url, const QString &title)
+{
+ if (ReKonfig::expireHistory() == 5) // DON'T STORE HISTORY!
+ return;
+
+ QWebSettings *globalSettings = QWebSettings::globalSettings();
+ if (globalSettings->testAttribute(QWebSettings::PrivateBrowsingEnabled))
+ return;
+
+ if (url.isEmpty())
+ return;
+
+ QUrl urlToClean(url);
+
+ // don't store about: urls (home page related)
+ if (urlToClean.scheme() == QString("about"))
+ return;
+
+ urlToClean.setPassword(QString());
+ urlToClean.setHost(urlToClean.host().toLower());
+ QString urlString = urlToClean.toString();
+
+ HistoryItem item;
+
+ // NOTE
+ // check if the url has just been visited.
+ // if so, remove previous entry from history, update and prepend it
+ if (historyContains(urlString))
+ {
+ int index = m_historyFilterModel->historyLocation(urlString);
+ item = m_history.at(index);
+ m_history.removeOne(item);
+ emit entryRemoved(item);
+
+ item.lastDateTimeVisit = QDateTime::currentDateTime();
+ item.visitCount++;
+ }
+ else
+ {
+ item = HistoryItem(urlString, QDateTime::currentDateTime(), title);
+ }
+
+ m_history.prepend(item);
+ emit entryAdded(item);
+
+ if (m_history.count() == 1)
+ checkForExpired();
+}
+
+
+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();
+}
+
+
+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().lastDateTimeVisit;
+ 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)
+ QTimer::singleShot(nextTimeout * 1000, this, SLOT(checkForExpired()));
+}
+
+
+void HistoryManager::removeHistoryEntry(const KUrl &url, const QString &title)
+{
+ HistoryItem item;
+ for (int i = 0; i < m_history.count(); ++i)
+ {
+ if (url == m_history.at(i).url
+ && (title.isEmpty() || title == m_history.at(i).title))
+ {
+ item = m_history.at(i);
+ m_lastSavedUrl.clear();
+ m_history.removeOne(item);
+ emit entryRemoved(item);
+ break;
+ }
+ }
+}
+
+
+QList<HistoryItem> HistoryManager::find(const QString &text)
+{
+ QList<HistoryItem> list;
+
+ QStringList urlKeys = m_historyFilterModel->keys();
+ Q_FOREACH(const QString & url, urlKeys)
+ {
+ int index = m_historyFilterModel->historyLocation(url);
+ HistoryItem item = m_history.at(index);
+
+ QStringList words = text.split(' ');
+ bool matches = true;
+ Q_FOREACH(const QString & word, words)
+ {
+ if (!url.contains(word, Qt::CaseInsensitive)
+ && !item.title.contains(word, Qt::CaseInsensitive))
+ {
+ matches = false;
+ break;
+ }
+ }
+ if (matches)
+ list << item;
+ }
+
+ return list;
+}
+
+
+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 1:
+ days = 90;
+ break;
+ case 2:
+ days = 30;
+ break;
+ case 3:
+ days = 1;
+ break;
+ case 0:
+ case 4:
+ case 5:
+ default:
+ days = -1;
+ break;
+ }
+ 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))
+ {
+ kDebug() << "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 version;
+ stream >> version;
+
+ HistoryItem item;
+
+ switch (version)
+ {
+ case HISTORY_VERSION: // default case
+ stream >> item.url;
+ stream >> item.firstDateTimeVisit;
+ stream >> item.lastDateTimeVisit;
+ stream >> item.title;
+ stream >> item.visitCount;
+ break;
+
+ case 24: // this was history structure for rekonq < 0.8
+ stream >> item.url;
+ stream >> item.lastDateTimeVisit;
+ stream >> item.title;
+ stream >> item.visitCount;
+ item.firstDateTimeVisit = item.lastDateTimeVisit;
+ break;
+
+ case 23: // this will be used to upgrade previous structure...
+ stream >> item.url;
+ stream >> item.lastDateTimeVisit;
+ stream >> item.title;
+ item.visitCount = 1;
+ item.firstDateTimeVisit = item.lastDateTimeVisit;
+ break;
+
+ default:
+ continue;
+ };
+
+ if (!item.lastDateTimeVisit.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.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)
+ {
+ kDebug() << "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.firstDateTimeVisit << item.lastDateTimeVisit << item.title << item.visitCount;
+ out << data;
+ }
+ tempFile.close();
+
+ if (saveAll)
+ {
+ if (historyFile.exists() && !historyFile.remove())
+ {
+ kDebug() << "History: error removing old history." << historyFile.errorString();
+ }
+ if (!tempFile.rename(historyFile.fileName()))
+ {
+ kDebug() << "History: error moving new history over old." << tempFile.errorString() << historyFile.fileName();
+ }
+ }
+ m_lastSavedUrl = m_history.value(0).url;
+
+ emit historySaved();
+}
diff --git a/src/history/historymanager.h b/src/history/historymanager.h
new file mode 100644
index 00000000..96de8ab8
--- /dev/null
+++ b/src/history/historymanager.h
@@ -0,0 +1,175 @@
+/* ============================================================
+*
+* 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-2012 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
+
+
+// KDE Includes
+#include <KUrl>
+
+// Qt Includes
+#include <QDateTime>
+#include <QHash>
+#include <QObject>
+#include <QWebHistory>
+
+#include <math.h>
+
+// Forward Declarations
+class AutoSaver;
+class HistoryFilterModel;
+class HistoryTreeModel;
+
+class QWebHistory;
+
+
+/**
+ * Elements in this class represent an history item
+ *
+ */
+class HistoryItem
+{
+public:
+ HistoryItem() : visitCount(1)
+ {}
+
+ explicit HistoryItem(const QString &u,
+ const QDateTime &d = QDateTime(),
+ const QString &t = QString()
+ )
+ : title(t)
+ , url(u)
+ , firstDateTimeVisit(d)
+ , lastDateTimeVisit(d)
+ , visitCount(1)
+ {}
+
+ inline bool operator==(const HistoryItem &other) const
+ {
+ return other.title == title
+ && other.url == url
+ && other.firstDateTimeVisit == firstDateTimeVisit
+ && other.lastDateTimeVisit == lastDateTimeVisit;
+ }
+
+ inline qreal relevance() const
+ {
+ return log(visitCount) - log(lastDateTimeVisit.daysTo(QDateTime::currentDateTime()) + 1);
+ }
+
+ // history is sorted in reverse
+ inline bool operator <(const HistoryItem &other) const
+ {
+ return lastDateTimeVisit > other.lastDateTimeVisit;
+ }
+
+ QString title;
+ QString url;
+ QDateTime firstDateTimeVisit;
+ QDateTime lastDateTimeVisit;
+ int visitCount;
+};
+
+
+// ---------------------------------------------------------------------------------------------------------------
+
+
+/**
+ * THE History Manager:
+ * It manages rekonq history
+ *
+ */
+class HistoryManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Entry point.
+ * Access to HistoryManager class by using
+ * HistoryManager::self()->thePublicMethodYouNeed()
+ */
+ static HistoryManager *self();
+
+ ~HistoryManager();
+
+ bool historyContains(const QString &url) const;
+ void addHistoryEntry(const KUrl &url, const QString &title);
+ void removeHistoryEntry(const KUrl &url, const QString &title = QString());
+
+ QList<HistoryItem> find(const QString &text);
+
+ QList<HistoryItem> history() const
+ {
+ return m_history;
+ };
+ void setHistory(const QList<HistoryItem> &history, bool loadedAndSorted = false);
+
+ // History manager keeps around these models for use by the completer and other classes
+ HistoryFilterModel *historyFilterModel() const
+ {
+ return m_historyFilterModel;
+ };
+ HistoryTreeModel *historyTreeModel() const
+ {
+ return m_historyTreeModel;
+ };
+
+Q_SIGNALS:
+ void historyReset();
+ void entryAdded(const HistoryItem &item);
+ void entryRemoved(const HistoryItem &item);
+
+ void historySaved();
+
+public Q_SLOTS:
+ void clear();
+ void loadSettings();
+
+private Q_SLOTS:
+ void save();
+ void checkForExpired();
+
+private:
+ HistoryManager(QObject *parent = 0);
+ void load();
+
+ AutoSaver *m_saveTimer;
+ int m_historyLimit;
+ QList<HistoryItem> m_history;
+ QString m_lastSavedUrl;
+
+ HistoryFilterModel *m_historyFilterModel;
+ HistoryTreeModel *m_historyTreeModel;
+
+ static QWeakPointer<HistoryManager> s_historyManager;
+};
+
+
+#endif // HISTORY_H
diff --git a/src/history/historymodels.cpp b/src/history/historymodels.cpp
new file mode 100644
index 00000000..76c00a52
--- /dev/null
+++ b/src/history/historymodels.cpp
@@ -0,0 +1,743 @@
+/* ============================================================
+*
+* 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-2012 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 "historymanager.h"
+// #include "iconmanager.h"
+
+// KDE Includes
+#include <KStandardDirs>
+#include <KLocale>
+#include <KIcon>
+
+// Qt Includes
+#include <QList>
+#include <QUrl>
+#include <QDate>
+#include <QDateTime>
+#include <QString>
+#include <QFile>
+#include <QDataStream>
+#include <QBuffer>
+
+#include <QClipboard>
+#include <QFileInfo>
+
+// generic algorithms
+#include <QtAlgorithms>
+
+
+HistoryModel::HistoryModel(HistoryManager *history, QObject *parent)
+ : QAbstractTableModel(parent)
+ , m_historyManager(history)
+{
+ Q_ASSERT(m_historyManager);
+ connect(m_historyManager, SIGNAL(historyReset()), this, SLOT(historyReset()));
+ connect(m_historyManager, SIGNAL(entryRemoved(HistoryItem)), this, SLOT(historyReset()));
+ connect(m_historyManager, SIGNAL(entryAdded(HistoryItem)), this, SLOT(entryAdded()));
+}
+
+
+void HistoryModel::historyReset()
+{
+ reset();
+}
+
+
+void HistoryModel::entryAdded()
+{
+ beginInsertRows(QModelIndex(), 0, 0);
+ endInsertRows();
+}
+
+
+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_historyManager->history();
+ if (index.row() < 0 || index.row() >= lst.size())
+ return QVariant();
+
+ const HistoryItem &item = lst.at(index.row());
+ switch (role)
+ {
+ case DateTimeRole:
+ return item.lastDateTimeVisit;
+ case DateRole:
+ return item.lastDateTimeVisit.date();
+ case FirstDateTimeVisitRole:
+ return item.firstDateTimeVisit;
+ case UrlRole:
+ return QUrl(item.url);
+ case Qt::UserRole:
+ return KUrl(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 QIcon(); //FIXME rApp->iconManager()->iconForUrl(item.url);
+ }
+ case Qt::ToolTipRole:
+ QString tooltip = "";
+ if (!item.title.isEmpty())
+ tooltip = item.title + "<br/>";
+
+ QString lastVisit = item.firstDateTimeVisit.toString(Qt::SystemLocaleDate);
+ QString firstVisit = item.lastDateTimeVisit.toString(Qt::SystemLocaleDate);
+ int visitCount = item.visitCount;
+ tooltip += "<center> <b>" + item.url + "</b> </center>";
+ tooltip += "<hr/>";
+ tooltip += i18n("First Visit: ") + firstVisit + "<br/>";
+ tooltip += i18n("Last Visit: ") + lastVisit + "<br/>";
+ tooltip += i18n("Number of Visits: ") + QString::number(visitCount);
+
+ return tooltip;
+ }
+ 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_historyManager->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_historyManager->history();
+ for (int i = lastRow; i >= row; --i)
+ lst.removeAt(i);
+ disconnect(m_historyManager, SIGNAL(historyReset()), this, SLOT(historyReset()));
+ m_historyManager->setHistory(lst);
+ connect(m_historyManager, SIGNAL(historyReset()), this, SLOT(historyReset()));
+ endRemoveRows();
+ return true;
+}
+
+
+// ------------------------------------------------------------------------------------------------------------------
+
+
+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;
+ 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_UNUSED(end);
+
+ if (start != 0)
+ {
+ kDebug() << "STARTING from a NON zero position...";
+ return;
+ }
+
+ 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())
+ reset();
+ return true;
+}
+
+
+// ----------------------------------------------------------------------------------------------------------
+
+
+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(QL1S("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);
+ }
+
+ if (role == HistoryModel::FirstDateTimeVisitRole && index.column() == 0 && index.internalId() == 0)
+ {
+ int offset = sourceDateRow(index.row());
+ QModelIndex idx = sourceModel()->index(offset, 0);
+ return idx.data(HistoryModel::FirstDateTimeVisitRole);
+ }
+
+ 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(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)));
+ }
+
+ 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..44569fa2
--- /dev/null
+++ b/src/history/historymodels.h
@@ -0,0 +1,179 @@
+/* ============================================================
+*
+* 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-2012 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
+
+
+// Rekonq Includes
+#include "rekonq_defines.h"
+
+// KDE Includes
+#include <KUrl>
+
+// Qt Includes
+#include <QHash>
+#include <QAbstractTableModel>
+#include <QAbstractProxyModel>
+
+// Forward Declarations
+class HistoryManager;
+
+
+class HistoryModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ enum Roles
+ {
+ DateRole = Qt::UserRole + 1,
+ DateTimeRole = Qt::UserRole + 2,
+ UrlRole = Qt::UserRole + 3,
+ UrlStringRole = Qt::UserRole + 4,
+ FirstDateTimeVisitRole = Qt::UserRole + 5
+ };
+
+ explicit HistoryModel(HistoryManager *history, QObject *parent = 0);
+
+public Q_SLOTS:
+ void historyReset();
+ void entryAdded();
+
+public:
+ 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_historyManager;
+};
+
+
+// ----------------------------------------------------------------------------------------------------
+
+
+/**
+ * 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);
+ }
+
+ inline QList<QString> keys() const
+ {
+ load();
+ return m_historyHash.keys();
+ }
+
+ 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 Q_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;
+};
+
+
+// ----------------------------------------------------------------------------------------------------------------------
+
+
+/**
+ * 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 Q_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;
+};
+
+
+#endif // HISTORYMODELS_H