Home · All Classes · Main Classes · Grouped Classes · Modules · Functions

history.cpp Example File
demos/browser/history.cpp

 /****************************************************************************
 **
 ** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
 **
 ** This file is part of the documentation of the Qt Toolkit.
 **
 ** This file may be used under the terms of the GNU General Public
** License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the files LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file.  Alternatively you may (at
** your option) use any later version of the GNU General Public
** License if such license has been publicly approved by Trolltech ASA
** (or its successors, if any) and the KDE Free Qt Foundation. In
** addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.2, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/. If
** you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech, as the sole
** copyright holder for Qt Designer, grants users of the Qt/Eclipse
** Integration plug-in the right for the Qt/Eclipse Integration to
** link to functionality provided by Qt Designer and its related
** libraries.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not expressly
** granted herein.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

 #include "history.h"
 #include "autosave.h"

 #include <QtCore/QSettings>
 #include <QtCore/QFile>
 #include <QtCore/QFileInfo>
 #include <QtCore/QTextStream>
 #include <QtCore/QTemporaryFile>
 #include <QtAlgorithms>
 #include <QtGui/QStyle>
 #include <QtGui/QHeaderView>
 #include <QtGui/QDesktopServices>
 #include <QtWebKit/QWebSettings>
 #include <qdebug.h>

 static const unsigned int HISTORY_VERSION = 23;

 History::History(QObject *parent)
     : QWebHistoryInterface(parent)
     , m_saveTimer(new AutoSave(this))
     , m_historyLimit(30)
 {
     m_expiredTimer.setSingleShot(true);
     connect(&m_expiredTimer, SIGNAL(timeout()),
             this, SLOT(checkForExpired()));
     connect(this, SIGNAL(entryAdded(const HistoryItem &)),
             m_saveTimer, SLOT(changed()));
     load();

     m_historyModel = new HistoryModel(this, this);
     m_historyFilterModel = new HistoryFilterModel(m_historyModel, this);
     m_historyTreeModel = new HistoryTreeModel(m_historyFilterModel, this);
 }

 History::~History()
 {
     m_saveTimer->saveNow();
 }

 QList<HistoryItem> History::history() const
 {
     return m_history;
 }

 bool History::historyContains(const QString &url) const
 {
     return m_historyFilterModel->historyContains(url);
 }

 void History::addHistoryEntry(const QString &url)
 {
     QUrl cleanUrl(url);
     cleanUrl.setPassword(QString());
     cleanUrl.setHost(cleanUrl.host().toLower());
     HistoryItem item(cleanUrl.toString(), QDateTime::currentDateTime());
     addHistoryItem(item);
 }

 void History::setHistory(const QList<HistoryItem> &history, bool loaded)
 {
     m_history = history;

     // verify sorted by date
     if (!loaded)
         qSort(m_history.begin(), m_history.end());

     checkForExpired();

     if (loaded) {
         m_lastSavedUrl = m_history.value(0).url;
     } else {
         m_lastSavedUrl = QString();
         m_saveTimer->changed();
     }
     emit historyReset();
 }

 QAbstractItemModel *History::historyModel() const
 {
     return m_historyModel;
 }

 QAbstractItemModel *History::historyFilterModel() const
 {
     return m_historyFilterModel;
 }

 QAbstractItemModel *History::historyTreeModel() const
 {
     return m_historyTreeModel;
 }

 void History::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();
         emit entryRemoved(item);
         // remove from saved file also
         m_lastSavedUrl = QString();
     }

     if (nextTimeout > 0)
         m_expiredTimer.start(nextTimeout * 1000);
 }

 void History::addHistoryItem(const HistoryItem &item)
 {
     m_history.prepend(item);
     emit entryAdded(item);
     if (m_history.count() == 1)
         checkForExpired();
 }

 void History::updateHistoryItem(const QUrl &url, const QString &title)
 {
     for (int i = 0; i < m_history.count(); ++i) {
         if (url == m_history.at(i).url) {
             m_history[i].title = title;
             m_saveTimer->changed();
             emit entryUpdated(i);
             break;
         }
     }
 }

 int History::historyLimit() const
 {
     return m_historyLimit;
 }

 void History::setHistoryLimit(int limit)
 {
     if (m_historyLimit == limit)
         return;
     m_historyLimit = limit;
     checkForExpired();
     m_saveTimer->changed();
 }

 void History::clear()
 {
     m_history.clear();
     m_lastSavedUrl = QString();
     m_saveTimer->changed();
     m_saveTimer->saveNow();
     historyReset();
 }

 void History::load()
 {
     // load settings
     QSettings settings;
     settings.beginGroup("history");
     m_historyLimit = settings.value(QLatin1String("historyLimit"), 30).toInt();

     QFile historyFile(QDesktopServices::storageLocation(QDesktopServices::DataLocation) + "/history");
     if (!historyFile.exists())
         return;
     if (!historyFile.open(QFile::ReadOnly)) {
         qWarning() << "Unable to open history file" << historyFile.fileName();
         return;
     }
     QList<HistoryItem> list;
     QDataStream in(&historyFile);

     // Double check that the history file is sorted as it is read in
     bool sort = false;
     HistoryItem last;
     QByteArray data;
     while (!historyFile.atEnd()) {
         in >> data;
         QDataStream stream(&data, 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()
             || item == last)
             continue;

         if (!sort && !list.isEmpty() && last < item)
             sort = true;

         list.prepend(item);
         last = item;
     }
     if (sort)
         qSort(list.begin(), list.end());

     setHistory(list, true);

     // If we had to sort re-write the whole history sorted
     if (sort) {
         m_lastSavedUrl = QString();
         m_saveTimer->changed();
     }
 }

 void History::save()
 {
     QSettings settings;
     settings.beginGroup("history");
     settings.setValue(QLatin1String("historyLimit"), m_historyLimit);

     bool saveAll = m_lastSavedUrl.isEmpty();
     int first = m_history.count() - 1;
     if (!saveAll) {
         // find the first one to save
         for (int i = 0; i < m_history.count(); ++i) {
             if (m_history.at(i).url == m_lastSavedUrl) {
                 first = i - 1;
                 break;
             }
         }
     }
     if (first == m_history.count() - 1)
         saveAll = true;

     QFile historyFile(QDesktopServices::storageLocation(QDesktopServices::DataLocation) + "/history");
     // when saving everything use a temporary file to prevent possible data loss
     QTemporaryFile tempFile;
     tempFile.setAutoRemove(false);
     bool open = false;
     if (saveAll) {
         open = tempFile.open();
     } else {
         open = historyFile.open(QFile::Append);
     }

     if (!open) {
         qWarning() << "Unable to open history file for saving"
                    << (saveAll ? tempFile.fileName() : historyFile.fileName());
         return;
     }

     QDataStream out(saveAll ? &tempFile : &historyFile);
     for (int i = first; i >= 0; --i) {
         QByteArray data;
         QDataStream stream(&data, QIODevice::WriteOnly);
         HistoryItem item = m_history.at(i);
         stream << HISTORY_VERSION << item.url << item.dateTime << item.title;
         out << data;
     }

     if (saveAll) {
         if (historyFile.exists() && !historyFile.remove())
             qWarning() << "History: error removing old history.";
         if (!tempFile.rename(historyFile.fileName()))
             qWarning() << "History: error moving new history over old.";
     }
     m_lastSavedUrl = m_history.value(0).url;
 }

 HistoryModel::HistoryModel(History *h, QObject *parent)
     : QAbstractTableModel(parent), history(h)
 {
     if (history) {
         connect(history, SIGNAL(historyReset()), this, SLOT(historyReset()));
         connect(history, SIGNAL(entryRemoved(const HistoryItem &)),
                 this, SLOT(historyReset()));

         connect(history, SIGNAL(entryAdded(const HistoryItem &)),
                 this, SLOT(entryAdded()));
         connect(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 tr("Title");
             case 1: return tr("Address");
         }
     }
     return QAbstractTableModel::headerData(section, orientation, role);
 }

 QVariant HistoryModel::data(const QModelIndex &index, int role) const
 {
     QList<HistoryItem> lst = 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:
                 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) {
             QIcon icon = QWebSettings::iconForUrl(item.url);
             if (!icon.isNull())
                 return icon;
             if (defaultIcon.isNull())
                 defaultIcon = QApplication::style()->standardIcon(QStyle::SP_FileIcon);
             return defaultIcon;
         }
     }
     return QVariant();
 }

 int HistoryModel::columnCount(const QModelIndex &parent) const
 {
     return (parent.isValid()) ? 0 : 2;
 }

 int HistoryModel::rowCount(const QModelIndex &parent) const
 {
     return (parent.isValid() || !history) ? 0 : 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 = history->history();
     for (int i = lastRow; i >= row; --i)
         lst.removeAt(i);
     disconnect(history, SIGNAL(historyReset()), this, SLOT(historyReset()));
     history->setHistory(lst);
     connect(history, SIGNAL(historyReset()), this, SLOT(historyReset()));
     endRemoveRows();
     return true;
 }

 HistoryMenu::HistoryMenu(History *history, QWidget *parent)
     : ModelMenu(history->historyTreeModel(), parent),
      m_history(history)
 {
     connect(this, SIGNAL(activated(const QModelIndex &)),
             this, SLOT(activated(const QModelIndex &)));
 }

 void HistoryMenu::activated(const QModelIndex &index)
 {
     emit openUrl(index.data(HistoryModel::UrlRole).toUrl());
 }

 bool HistoryMenu::prePopulated()
 {
     // initial actions
     for (int i = 0; i < initialActions.count(); ++i)
         addAction(initialActions.at(i));
     if (!initialActions.isEmpty())
         addSeparator();

     createMenu(model()->index(0, 0), 20, this);
     return true;
 }

 void HistoryMenu::postPopulated()
 {
     if (m_history->history().count() > 0)
         addSeparator();

     QAction *showAllAction = new QAction(tr("Show All History"), this);
     connect(showAllAction, SIGNAL(triggered()), this, SLOT(showHistoryDialog()));
     addAction(showAllAction);

     QAction *clearAction = new QAction(tr("Clear History"), this);
     connect(clearAction, SIGNAL(triggered()), m_history, SLOT(clear()));
     addAction(clearAction);
 }

 void HistoryMenu::showHistoryDialog()
 {
     HistoryDialog *dialog = new HistoryDialog(m_history, this);
     connect(dialog, SIGNAL(openUrl(const QUrl&)), this, SIGNAL(openUrl(const QUrl&)));
     dialog->show();
 }

 void HistoryMenu::setInitialActions(QList<QAction*> actions)
 {
     initialActions = actions;
 }

 TreeProxyModel::TreeProxyModel(QObject * parent) : QSortFilterProxyModel(parent)
 {
     setSortRole(HistoryModel::DateTimeRole);
     setFilterCaseSensitivity(Qt::CaseInsensitive);
 }

 bool TreeProxyModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
 {
     if (!source_parent.isValid())
         return true;
     return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
 }

 HistoryDialog::HistoryDialog(History *history, QWidget *parent) : QDialog(parent)
 {
     setupUi(this);
     tree->setUniformRowHeights(true);
     tree->setSelectionBehavior(QAbstractItemView::SelectRows);
     tree->setTextElideMode(Qt::ElideMiddle);
     QAbstractItemModel *model = history->historyTreeModel();
     TreeProxyModel *proxyModel = new TreeProxyModel(this);
     connect(search, SIGNAL(textChanged(QString)),
             proxyModel, SLOT(setFilterFixedString(QString)));
     connect(removeButton, SIGNAL(clicked()), tree, SLOT(removeOne()));
     connect(removeAllButton, SIGNAL(clicked()), history, SLOT(clear()));
     proxyModel->setSourceModel(model);
     tree->setModel(proxyModel);
     tree->setExpanded(proxyModel->index(0, 0), true);
     tree->setAlternatingRowColors(true);
     QFontMetrics fm(font());
     int header = fm.width("m") * 40;
     tree->header()->resizeSection(0, header);
     tree->header()->setStretchLastSection(true);
     connect(tree, SIGNAL(activated(const QModelIndex&)),
             this, SLOT(open()));
     tree->setContextMenuPolicy(Qt::CustomContextMenu);
     connect(tree, SIGNAL(customContextMenuRequested(const QPoint &)),
             this, SLOT(customContextMenuRequested(const QPoint &)));
 }

 void HistoryDialog::customContextMenuRequested(const QPoint &pos)
 {
     QMenu menu;
     QModelIndex index = tree->indexAt(pos);
     index = index.sibling(index.row(), 0);
     if (index.isValid() && !tree->model()->hasChildren(index)) {
         menu.addAction(tr("Open"), this, SLOT(open()));
         menu.addSeparator();
     }
     menu.addAction(tr("Delete"), tree, SLOT(removeOne()));
     menu.exec(QCursor::pos());
 }

 void HistoryDialog::open()
 {
     QModelIndex index = tree->currentIndex();
     if (!index.parent().isValid())
         return;
     emit openUrl(index.sibling(index.row(), 1).data(HistoryModel::UrlRole).toUrl());
 }

 HistoryFilterModel::HistoryFilterModel(QAbstractItemModel *sourceModel, QObject *parent)
     : QAbstractProxyModel(parent),
     loaded(false)
 {
     setSourceModel(sourceModel);
 }

 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()) {
         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);
 }

 int HistoryFilterModel::historyLocation(const QString &url) const
 {
     load();
     if (!m_historyHash.contains(url))
         return 0;
     return sourceModel()->rowCount() - m_historyHash.value(url);
 }

 void HistoryFilterModel::sourceReset()
 {
     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 sourceModel()->columnCount(parent);
 }

 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[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 (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;
         }
     }
     loaded = true;
 }

 void HistoryFilterModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
 {
     Q_ASSERT(start == end && start == 0);
     Q_UNUSED(end);
     if (!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 hidden 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)));
     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);
         if (index.row() % 2) {
             QUrl url = idx.data(HistoryModel::UrlRole).toUrl();
             QString s = url.toString(QUrl::RemoveScheme
                                      | QUrl::RemoveUserInfo
                                      | QUrl::StripTrailingSlash);
             return s.mid(2);  // strip // from the front
         }
         return idx.data(role);
     }
     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 (sourceModel()) {
         if ((role == Qt::EditRole || role == Qt::DisplayRole)) {
             int start = index.internalId();
             if (start == 0) {
                 int offset = sourceDateRow(index.row());
                 if (index.column() == 0) {
                     QModelIndex idx = sourceModel()->index(offset, 0);
                     QDate date = idx.data(HistoryModel::DateRole).toDate();
                     if (date == QDate::currentDate())
                         return tr("Earlier Today");
                     return date.toString("dddd, MMMM d, yyyy");
                 }
                 if (index.column() == 1) {
                     return tr("%1 items").arg(rowCount(index.sibling(index.row(), 0)));
                 }
             }
         }
         if (role == Qt::DecorationRole && index.column() == 0 && !index.parent().isValid())
             return QIcon(":history.png");
     }

     return QAbstractProxyModel::data(index, role);
 }

 int HistoryTreeModel::columnCount(const QModelIndex &parent) const
 {
     if (!sourceModel())
         return 0;
     return sourceModel()->columnCount(mapToSource(parent));
 }

 int HistoryTreeModel::rowCount(const QModelIndex &parent) const
 {
     if (!sourceModel()
         || parent.internalId() != 0
         || parent.column() > 0)
         return 0;

     // row count OF dates
     if (!parent.isValid()) {
         if (!sourceRowCache.isEmpty())
             return sourceRowCache.count() - 1;
         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) {
                 currentDate = rowDate;
                 ++rows;
             }
         }
         sourceRowCache.append(0);
         while (sourceRowCache.count() <= rows)
             sourceRowCache.append(-1);
         return rows;
     }

     // row count FOR a date
     int start = sourceDateRow(parent.row());
     int end = sourceDateRow(parent.row() + 1);
     return (end - start);
 }

 QModelIndex HistoryTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
 {
     int dateRow = 0;
     int row = 0;
     QDate currentDate;
     for (int i = 0; i < sourceIndex.row(); ++i) {
         QDate rowDate = sourceModel()->index(i, 0).data(HistoryModel::DateRole).toDate();
         if (rowDate != currentDate) {
             currentDate = rowDate;
             row = 0;
             ++dateRow;
         }
         ++row;
     }

     return createIndex(row, sourceIndex.column(), dateRow + 1);
 }

 // Translate the top level date row into the offset where that date starts
 int HistoryTreeModel::sourceDateRow(int row) const
 {
     if (!sourceModel() || row <= 0)
         return 0;

     if (!sourceRowCache.isEmpty()) {
         if (row >= sourceRowCache.count())
             return sourceModel()->rowCount();
         if (sourceRowCache.at(row) != -1)
             return sourceRowCache.at(row);
     }

     QDate currentDate;
     int currentRow = row - 1;
     int start = sourceDateRow(row - 1);
     int totalSourceRows = sourceModel()->rowCount();
     int i;
     for (i = start; i < totalSourceRows; ++i) {
         QDate rowDate = sourceModel()->index(i, 0).data(HistoryModel::DateRole).toDate();
         if (rowDate != currentDate) {
             if (!currentDate.isNull())
                 ++currentRow;
             if (currentRow == row)
                 break;
             currentDate = rowDate;
         }
     }

     int offset = i;
     if (sourceRowCache.isEmpty())
         rowCount(QModelIndex());
     if (row < sourceRowCache.count())
         sourceRowCache[row] = offset;
     return offset;
 }

 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;
 }

 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());
         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());
             sourceModel()->removeRows(offset, rowCount(dateParent));
         }
     }
     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()
 {
     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) {
         sourceRowCache.clear();
         reset();
         return;
     }

     sourceRowCache.clear();
     QModelIndex idx = mapFromSource(sourceModel()->index(start, 0));
     QModelIndex p = idx.parent();
     if (rowCount(p) == 1) {
         beginInsertRows(QModelIndex(), 0, 0);
         endInsertRows();
     } else {
         beginInsertRows(p, idx.row(), idx.row());
         endInsertRows();
     }
 }

 void HistoryTreeModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
 {
     Q_UNUSED(parent); // Avoid warnings when compiling release
     Q_ASSERT(!parent.isValid());
     if (sourceRowCache.isEmpty())
         return;
     for (int i = end; i >= start;) {
         QList<int>::iterator it;
         it = qLowerBound(sourceRowCache.begin(), sourceRowCache.end(), i);
         if (*it != i)
             --it;
         int row = qMax(0, it - sourceRowCache.begin());
         int offset = 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);
             sourceRowCache.removeAt(row);
             i -= rc + 1;
         } else {
             beginRemoveRows(dateParent, i - offset, i - offset);
             ++row;
             --i;
         }
         for (int j = row; j < sourceRowCache.count(); ++j)
             --sourceRowCache[j];
         endRemoveRows();
     }
 }


Copyright © 2008 Trolltech Trademarks
Qt 4.4.0-beta1