1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the demonstration applications of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
44 #include "autosaver.h"
45 #include "browserapplication.h"
47 #include <QtCore/QBuffer>
48 #include <QtCore/QDir>
49 #include <QtCore/QFile>
50 #include <QtCore/QFileInfo>
51 #include <QtCore/QSettings>
52 #include <QtCore/QTemporaryFile>
53 #include <QtCore/QTextStream>
55 #include <QtCore/QtAlgorithms>
57 #include <QtGui/QClipboard>
58 #include <QtGui/QDesktopServices>
59 #include <QtGui/QHeaderView>
60 #include <QtGui/QStyle>
62 #include <QtWebKit/QWebHistoryInterface>
63 #include <QtWebKit/QWebSettings>
65 #include <QtCore/QDebug>
67 static const unsigned int HISTORY_VERSION
= 23;
69 HistoryManager::HistoryManager(QObject
*parent
)
70 : QWebHistoryInterface(parent
)
71 , m_saveTimer(new AutoSaver(this))
74 , m_historyFilterModel(0)
75 , m_historyTreeModel(0)
77 m_expiredTimer
.setSingleShot(true);
78 connect(&m_expiredTimer
, SIGNAL(timeout()),
79 this, SLOT(checkForExpired()));
80 connect(this, SIGNAL(entryAdded(const HistoryItem
&)),
81 m_saveTimer
, SLOT(changeOccurred()));
82 connect(this, SIGNAL(entryRemoved(const HistoryItem
&)),
83 m_saveTimer
, SLOT(changeOccurred()));
86 m_historyModel
= new HistoryModel(this, this);
87 m_historyFilterModel
= new HistoryFilterModel(m_historyModel
, this);
88 m_historyTreeModel
= new HistoryTreeModel(m_historyFilterModel
, this);
90 // QWebHistoryInterface will delete the history manager
91 QWebHistoryInterface::setDefaultInterface(this);
94 HistoryManager::~HistoryManager()
96 m_saveTimer
->saveIfNeccessary();
99 QList
<HistoryItem
> HistoryManager::history() const
104 bool HistoryManager::historyContains(const QString
&url
) const
106 return m_historyFilterModel
->historyContains(url
);
109 void HistoryManager::addHistoryEntry(const QString
&url
)
112 cleanUrl
.setPassword(QString());
113 cleanUrl
.setHost(cleanUrl
.host().toLower());
114 HistoryItem
item(cleanUrl
.toString(), QDateTime::currentDateTime());
115 addHistoryItem(item
);
118 void HistoryManager::setHistory(const QList
<HistoryItem
> &history
, bool loadedAndSorted
)
122 // verify that it is sorted by date
123 if (!loadedAndSorted
)
124 qSort(m_history
.begin(), m_history
.end());
128 if (loadedAndSorted
) {
129 m_lastSavedUrl
= m_history
.value(0).url
;
131 m_lastSavedUrl
= QString();
132 m_saveTimer
->changeOccurred();
137 HistoryModel
*HistoryManager::historyModel() const
139 return m_historyModel
;
142 HistoryFilterModel
*HistoryManager::historyFilterModel() const
144 return m_historyFilterModel
;
147 HistoryTreeModel
*HistoryManager::historyTreeModel() const
149 return m_historyTreeModel
;
152 void HistoryManager::checkForExpired()
154 if (m_historyLimit
< 0 || m_history
.isEmpty())
157 QDateTime now
= QDateTime::currentDateTime();
160 while (!m_history
.isEmpty()) {
161 QDateTime checkForExpired
= m_history
.last().dateTime
;
162 checkForExpired
.setDate(checkForExpired
.date().addDays(m_historyLimit
));
163 if (now
.daysTo(checkForExpired
) > 7) {
164 // check at most in a week to prevent int overflows on the timer
165 nextTimeout
= 7 * 86400;
167 nextTimeout
= now
.secsTo(checkForExpired
);
171 HistoryItem item
= m_history
.takeLast();
172 // remove from saved file also
173 m_lastSavedUrl
= QString();
174 emit
entryRemoved(item
);
178 m_expiredTimer
.start(nextTimeout
* 1000);
181 void HistoryManager::addHistoryItem(const HistoryItem
&item
)
183 QWebSettings
*globalSettings
= QWebSettings::globalSettings();
184 if (globalSettings
->testAttribute(QWebSettings::PrivateBrowsingEnabled
))
187 m_history
.prepend(item
);
188 emit
entryAdded(item
);
189 if (m_history
.count() == 1)
193 void HistoryManager::updateHistoryItem(const QUrl
&url
, const QString
&title
)
195 for (int i
= 0; i
< m_history
.count(); ++i
) {
196 if (url
== m_history
.at(i
).url
) {
197 m_history
[i
].title
= title
;
198 m_saveTimer
->changeOccurred();
199 if (m_lastSavedUrl
.isEmpty())
200 m_lastSavedUrl
= m_history
.at(i
).url
;
201 emit
entryUpdated(i
);
207 int HistoryManager::historyLimit() const
209 return m_historyLimit
;
212 void HistoryManager::setHistoryLimit(int limit
)
214 if (m_historyLimit
== limit
)
216 m_historyLimit
= limit
;
218 m_saveTimer
->changeOccurred();
221 void HistoryManager::clear()
224 m_lastSavedUrl
= QString();
225 m_saveTimer
->changeOccurred();
226 m_saveTimer
->saveIfNeccessary();
230 void HistoryManager::loadSettings()
234 settings
.beginGroup(QLatin1String("history"));
235 m_historyLimit
= settings
.value(QLatin1String("historyLimit"), 30).toInt();
238 void HistoryManager::load()
242 QFile
historyFile(QDesktopServices::storageLocation(QDesktopServices::DataLocation
)
243 + QLatin1String("/history"));
244 if (!historyFile
.exists())
246 if (!historyFile
.open(QFile::ReadOnly
)) {
247 qWarning() << "Unable to open history file" << historyFile
.fileName();
251 QList
<HistoryItem
> list
;
252 QDataStream
in(&historyFile
);
253 // Double check that the history file is sorted as it is read in
254 bool needToSort
= false;
255 HistoryItem lastInsertedItem
;
259 stream
.setDevice(&buffer
);
260 while (!historyFile
.atEnd()) {
263 buffer
.setBuffer(&data
);
264 buffer
.open(QIODevice::ReadOnly
);
267 if (ver
!= HISTORY_VERSION
)
271 stream
>> item
.dateTime
;
272 stream
>> item
.title
;
274 if (!item
.dateTime
.isValid())
277 if (item
== lastInsertedItem
) {
278 if (lastInsertedItem
.title
.isEmpty() && !list
.isEmpty())
279 list
[0].title
= item
.title
;
283 if (!needToSort
&& !list
.isEmpty() && lastInsertedItem
< item
)
287 lastInsertedItem
= item
;
290 qSort(list
.begin(), list
.end());
292 setHistory(list
, true);
294 // If we had to sort re-write the whole history sorted
296 m_lastSavedUrl
= QString();
297 m_saveTimer
->changeOccurred();
301 void HistoryManager::save()
304 settings
.beginGroup(QLatin1String("history"));
305 settings
.setValue(QLatin1String("historyLimit"), m_historyLimit
);
307 bool saveAll
= m_lastSavedUrl
.isEmpty();
308 int first
= m_history
.count() - 1;
310 // find the first one to save
311 for (int i
= 0; i
< m_history
.count(); ++i
) {
312 if (m_history
.at(i
).url
== m_lastSavedUrl
) {
318 if (first
== m_history
.count() - 1)
321 QString directory
= QDesktopServices::storageLocation(QDesktopServices::DataLocation
);
322 if (directory
.isEmpty())
323 directory
= QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
324 if (!QFile::exists(directory
)) {
326 dir
.mkpath(directory
);
329 QFile
historyFile(directory
+ QLatin1String("/history"));
330 // When saving everything use a temporary file to prevent possible data loss.
331 QTemporaryFile tempFile
;
332 tempFile
.setAutoRemove(false);
335 open
= tempFile
.open();
337 open
= historyFile
.open(QFile::Append
);
341 qWarning() << "Unable to open history file for saving"
342 << (saveAll
? tempFile
.fileName() : historyFile
.fileName());
346 QDataStream
out(saveAll
? &tempFile
: &historyFile
);
347 for (int i
= first
; i
>= 0; --i
) {
349 QDataStream
stream(&data
, QIODevice::WriteOnly
);
350 HistoryItem item
= m_history
.at(i
);
351 stream
<< HISTORY_VERSION
<< item
.url
<< item
.dateTime
<< item
.title
;
357 if (historyFile
.exists() && !historyFile
.remove())
358 qWarning() << "History: error removing old history." << historyFile
.errorString();
359 if (!tempFile
.rename(historyFile
.fileName()))
360 qWarning() << "History: error moving new history over old." << tempFile
.errorString() << historyFile
.fileName();
362 m_lastSavedUrl
= m_history
.value(0).url
;
365 HistoryModel::HistoryModel(HistoryManager
*history
, QObject
*parent
)
366 : QAbstractTableModel(parent
)
370 connect(m_history
, SIGNAL(historyReset()),
371 this, SLOT(historyReset()));
372 connect(m_history
, SIGNAL(entryRemoved(const HistoryItem
&)),
373 this, SLOT(historyReset()));
375 connect(m_history
, SIGNAL(entryAdded(const HistoryItem
&)),
376 this, SLOT(entryAdded()));
377 connect(m_history
, SIGNAL(entryUpdated(int)),
378 this, SLOT(entryUpdated(int)));
381 void HistoryModel::historyReset()
386 void HistoryModel::entryAdded()
388 beginInsertRows(QModelIndex(), 0, 0);
392 void HistoryModel::entryUpdated(int offset
)
394 QModelIndex idx
= index(offset
, 0);
395 emit
dataChanged(idx
, idx
);
398 QVariant
HistoryModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
400 if (orientation
== Qt::Horizontal
401 && role
== Qt::DisplayRole
) {
403 case 0: return tr("Title");
404 case 1: return tr("Address");
407 return QAbstractTableModel::headerData(section
, orientation
, role
);
410 QVariant
HistoryModel::data(const QModelIndex
&index
, int role
) const
412 QList
<HistoryItem
> lst
= m_history
->history();
413 if (index
.row() < 0 || index
.row() >= lst
.size())
416 const HistoryItem
&item
= lst
.at(index
.row());
419 return item
.dateTime
;
421 return item
.dateTime
.date();
423 return QUrl(item
.url
);
426 case Qt::DisplayRole
:
428 switch (index
.column()) {
430 // when there is no title try to generate one from the url
431 if (item
.title
.isEmpty()) {
432 QString page
= QFileInfo(QUrl(item
.url
).path()).fileName();
442 case Qt::DecorationRole
:
443 if (index
.column() == 0) {
444 return BrowserApplication::instance()->icon(item
.url
);
450 int HistoryModel::columnCount(const QModelIndex
&parent
) const
452 return (parent
.isValid()) ? 0 : 2;
455 int HistoryModel::rowCount(const QModelIndex
&parent
) const
457 return (parent
.isValid()) ? 0 : m_history
->history().count();
460 bool HistoryModel::removeRows(int row
, int count
, const QModelIndex
&parent
)
462 if (parent
.isValid())
464 int lastRow
= row
+ count
- 1;
465 beginRemoveRows(parent
, row
, lastRow
);
466 QList
<HistoryItem
> lst
= m_history
->history();
467 for (int i
= lastRow
; i
>= row
; --i
)
469 disconnect(m_history
, SIGNAL(historyReset()), this, SLOT(historyReset()));
470 m_history
->setHistory(lst
);
471 connect(m_history
, SIGNAL(historyReset()), this, SLOT(historyReset()));
479 Maps the first bunch of items of the source model to the root
481 HistoryMenuModel::HistoryMenuModel(HistoryTreeModel
*sourceModel
, QObject
*parent
)
482 : QAbstractProxyModel(parent
)
483 , m_treeModel(sourceModel
)
485 setSourceModel(sourceModel
);
488 int HistoryMenuModel::bumpedRows() const
490 QModelIndex first
= m_treeModel
->index(0, 0);
491 if (!first
.isValid())
493 return qMin(m_treeModel
->rowCount(first
), MOVEDROWS
);
496 int HistoryMenuModel::columnCount(const QModelIndex
&parent
) const
498 return m_treeModel
->columnCount(mapToSource(parent
));
501 int HistoryMenuModel::rowCount(const QModelIndex
&parent
) const
503 if (parent
.column() > 0)
506 if (!parent
.isValid()) {
507 int folders
= sourceModel()->rowCount();
508 int bumpedItems
= bumpedRows();
509 if (bumpedItems
<= MOVEDROWS
510 && bumpedItems
== sourceModel()->rowCount(sourceModel()->index(0, 0)))
512 return bumpedItems
+ folders
;
515 if (parent
.internalId() == -1) {
516 if (parent
.row() < bumpedRows())
520 QModelIndex idx
= mapToSource(parent
);
521 int defaultCount
= sourceModel()->rowCount(idx
);
522 if (idx
== sourceModel()->index(0, 0))
523 return defaultCount
- bumpedRows();
527 QModelIndex
HistoryMenuModel::mapFromSource(const QModelIndex
&sourceIndex
) const
529 // currently not used or autotested
531 int sr
= m_treeModel
->mapToSource(sourceIndex
).row();
532 return createIndex(sourceIndex
.row(), sourceIndex
.column(), sr
);
535 QModelIndex
HistoryMenuModel::mapToSource(const QModelIndex
&proxyIndex
) const
537 if (!proxyIndex
.isValid())
538 return QModelIndex();
540 if (proxyIndex
.internalId() == -1) {
541 int bumpedItems
= bumpedRows();
542 if (proxyIndex
.row() < bumpedItems
)
543 return m_treeModel
->index(proxyIndex
.row(), proxyIndex
.column(), m_treeModel
->index(0, 0));
544 if (bumpedItems
<= MOVEDROWS
&& bumpedItems
== sourceModel()->rowCount(m_treeModel
->index(0, 0)))
546 return m_treeModel
->index(proxyIndex
.row() - bumpedItems
, proxyIndex
.column());
549 QModelIndex historyIndex
= m_treeModel
->sourceModel()->index(proxyIndex
.internalId(), proxyIndex
.column());
550 QModelIndex treeIndex
= m_treeModel
->mapFromSource(historyIndex
);
554 QModelIndex
HistoryMenuModel::index(int row
, int column
, const QModelIndex
&parent
) const
557 || column
< 0 || column
>= columnCount(parent
)
558 || parent
.column() > 0)
559 return QModelIndex();
560 if (!parent
.isValid())
561 return createIndex(row
, column
, -1);
563 QModelIndex treeIndexParent
= mapToSource(parent
);
566 if (treeIndexParent
== m_treeModel
->index(0, 0))
567 bumpedItems
= bumpedRows();
568 QModelIndex treeIndex
= m_treeModel
->index(row
+ bumpedItems
, column
, treeIndexParent
);
569 QModelIndex historyIndex
= m_treeModel
->mapToSource(treeIndex
);
570 int historyRow
= historyIndex
.row();
571 if (historyRow
== -1)
572 historyRow
= treeIndex
.row();
573 return createIndex(row
, column
, historyRow
);
576 QModelIndex
HistoryMenuModel::parent(const QModelIndex
&index
) const
578 int offset
= index
.internalId();
579 if (offset
== -1 || !index
.isValid())
580 return QModelIndex();
582 QModelIndex historyIndex
= m_treeModel
->sourceModel()->index(index
.internalId(), 0);
583 QModelIndex treeIndex
= m_treeModel
->mapFromSource(historyIndex
);
584 QModelIndex treeIndexParent
= treeIndex
.parent();
586 int sr
= m_treeModel
->mapToSource(treeIndexParent
).row();
587 int bumpedItems
= bumpedRows();
588 if (bumpedItems
<= MOVEDROWS
&& bumpedItems
== sourceModel()->rowCount(sourceModel()->index(0, 0)))
590 return createIndex(bumpedItems
+ treeIndexParent
.row(), treeIndexParent
.column(), sr
);
594 HistoryMenu::HistoryMenu(QWidget
*parent
)
598 connect(this, SIGNAL(activated(const QModelIndex
&)),
599 this, SLOT(activated(const QModelIndex
&)));
600 setHoverRole(HistoryModel::UrlStringRole
);
603 void HistoryMenu::activated(const QModelIndex
&index
)
605 emit
openUrl(index
.data(HistoryModel::UrlRole
).toUrl());
608 bool HistoryMenu::prePopulated()
611 m_history
= BrowserApplication::historyManager();
612 m_historyMenuModel
= new HistoryMenuModel(m_history
->historyTreeModel(), this);
613 setModel(m_historyMenuModel
);
616 for (int i
= 0; i
< m_initialActions
.count(); ++i
)
617 addAction(m_initialActions
.at(i
));
618 if (!m_initialActions
.isEmpty())
620 setFirstSeparator(m_historyMenuModel
->bumpedRows());
625 void HistoryMenu::postPopulated()
627 if (m_history
->history().count() > 0)
630 QAction
*showAllAction
= new QAction(tr("Show All History"), this);
631 connect(showAllAction
, SIGNAL(triggered()), this, SLOT(showHistoryDialog()));
632 addAction(showAllAction
);
634 QAction
*clearAction
= new QAction(tr("Clear History"), this);
635 connect(clearAction
, SIGNAL(triggered()), m_history
, SLOT(clear()));
636 addAction(clearAction
);
639 void HistoryMenu::showHistoryDialog()
641 HistoryDialog
*dialog
= new HistoryDialog(this);
642 connect(dialog
, SIGNAL(openUrl(const QUrl
&)),
643 this, SIGNAL(openUrl(const QUrl
&)));
647 void HistoryMenu::setInitialActions(QList
<QAction
*> actions
)
649 m_initialActions
= actions
;
650 for (int i
= 0; i
< m_initialActions
.count(); ++i
)
651 addAction(m_initialActions
.at(i
));
654 TreeProxyModel::TreeProxyModel(QObject
*parent
) : QSortFilterProxyModel(parent
)
656 setSortRole(HistoryModel::DateTimeRole
);
657 setFilterCaseSensitivity(Qt::CaseInsensitive
);
660 bool TreeProxyModel::filterAcceptsRow(int source_row
, const QModelIndex
&source_parent
) const
662 if (!source_parent
.isValid())
664 return QSortFilterProxyModel::filterAcceptsRow(source_row
, source_parent
);
667 HistoryDialog::HistoryDialog(QWidget
*parent
, HistoryManager
*setHistory
) : QDialog(parent
)
669 HistoryManager
*history
= setHistory
;
671 history
= BrowserApplication::historyManager();
673 tree
->setUniformRowHeights(true);
674 tree
->setSelectionBehavior(QAbstractItemView::SelectRows
);
675 tree
->setTextElideMode(Qt::ElideMiddle
);
676 QAbstractItemModel
*model
= history
->historyTreeModel();
677 TreeProxyModel
*proxyModel
= new TreeProxyModel(this);
678 connect(search
, SIGNAL(textChanged(QString
)),
679 proxyModel
, SLOT(setFilterFixedString(QString
)));
680 connect(removeButton
, SIGNAL(clicked()), tree
, SLOT(removeOne()));
681 connect(removeAllButton
, SIGNAL(clicked()), history
, SLOT(clear()));
682 proxyModel
->setSourceModel(model
);
683 tree
->setModel(proxyModel
);
684 tree
->setExpanded(proxyModel
->index(0, 0), true);
685 tree
->setAlternatingRowColors(true);
686 QFontMetrics
fm(font());
687 int header
= fm
.width(QLatin1Char('m')) * 40;
688 tree
->header()->resizeSection(0, header
);
689 tree
->header()->setStretchLastSection(true);
690 connect(tree
, SIGNAL(activated(const QModelIndex
&)),
692 tree
->setContextMenuPolicy(Qt::CustomContextMenu
);
693 connect(tree
, SIGNAL(customContextMenuRequested(const QPoint
&)),
694 this, SLOT(customContextMenuRequested(const QPoint
&)));
697 void HistoryDialog::customContextMenuRequested(const QPoint
&pos
)
700 QModelIndex index
= tree
->indexAt(pos
);
701 index
= index
.sibling(index
.row(), 0);
702 if (index
.isValid() && !tree
->model()->hasChildren(index
)) {
703 menu
.addAction(tr("Open"), this, SLOT(open()));
705 menu
.addAction(tr("Copy"), this, SLOT(copy()));
707 menu
.addAction(tr("Delete"), tree
, SLOT(removeOne()));
708 menu
.exec(QCursor::pos());
711 void HistoryDialog::open()
713 QModelIndex index
= tree
->currentIndex();
714 if (!index
.parent().isValid())
716 emit
openUrl(index
.data(HistoryModel::UrlRole
).toUrl());
719 void HistoryDialog::copy()
721 QModelIndex index
= tree
->currentIndex();
722 if (!index
.parent().isValid())
724 QString url
= index
.data(HistoryModel::UrlStringRole
).toString();
726 QClipboard
*clipboard
= QApplication::clipboard();
727 clipboard
->setText(url
);
730 HistoryFilterModel::HistoryFilterModel(QAbstractItemModel
*sourceModel
, QObject
*parent
)
731 : QAbstractProxyModel(parent
),
734 setSourceModel(sourceModel
);
737 int HistoryFilterModel::historyLocation(const QString
&url
) const
740 if (!m_historyHash
.contains(url
))
742 return sourceModel()->rowCount() - m_historyHash
.value(url
);
745 QVariant
HistoryFilterModel::data(const QModelIndex
&index
, int role
) const
747 return QAbstractProxyModel::data(index
, role
);
750 void HistoryFilterModel::setSourceModel(QAbstractItemModel
*newSourceModel
)
753 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
754 disconnect(sourceModel(), SIGNAL(dataChanged(const QModelIndex
&, const QModelIndex
&)),
755 this, SLOT(dataChanged(const QModelIndex
&, const QModelIndex
&)));
756 disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
757 this, SLOT(sourceRowsInserted(const QModelIndex
&, int, int)));
758 disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
759 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
762 QAbstractProxyModel::setSourceModel(newSourceModel
);
766 connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
767 connect(sourceModel(), SIGNAL(dataChanged(const QModelIndex
&, const QModelIndex
&)),
768 this, SLOT(sourceDataChanged(const QModelIndex
&, const QModelIndex
&)));
769 connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
770 this, SLOT(sourceRowsInserted(const QModelIndex
&, int, int)));
771 connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
772 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
776 void HistoryFilterModel::sourceDataChanged(const QModelIndex
&topLeft
, const QModelIndex
&bottomRight
)
778 emit
dataChanged(mapFromSource(topLeft
), mapFromSource(bottomRight
));
781 QVariant
HistoryFilterModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
783 return sourceModel()->headerData(section
, orientation
, role
);
786 void HistoryFilterModel::sourceReset()
792 int HistoryFilterModel::rowCount(const QModelIndex
&parent
) const
795 if (parent
.isValid())
797 return m_historyHash
.count();
800 int HistoryFilterModel::columnCount(const QModelIndex
&parent
) const
802 return (parent
.isValid()) ? 0 : 2;
805 QModelIndex
HistoryFilterModel::mapToSource(const QModelIndex
&proxyIndex
) const
808 int sourceRow
= sourceModel()->rowCount() - proxyIndex
.internalId();
809 return sourceModel()->index(sourceRow
, proxyIndex
.column());
812 QModelIndex
HistoryFilterModel::mapFromSource(const QModelIndex
&sourceIndex
) const
815 QString url
= sourceIndex
.data(HistoryModel::UrlStringRole
).toString();
816 if (!m_historyHash
.contains(url
))
817 return QModelIndex();
819 // This can be done in a binary search, but we can't use qBinary find
820 // because it can't take: qBinaryFind(m_sourceRow.end(), m_sourceRow.begin(), v);
821 // so if this is a performance bottlneck then convert to binary search, until then
822 // the cleaner/easier to read code wins the day.
824 int sourceModelRow
= sourceModel()->rowCount() - sourceIndex
.row();
826 for (int i
= 0; i
< m_sourceRow
.count(); ++i
) {
827 if (m_sourceRow
.at(i
) == sourceModelRow
) {
833 return QModelIndex();
835 return createIndex(realRow
, sourceIndex
.column(), sourceModel()->rowCount() - sourceIndex
.row());
838 QModelIndex
HistoryFilterModel::index(int row
, int column
, const QModelIndex
&parent
) const
841 if (row
< 0 || row
>= rowCount(parent
)
842 || column
< 0 || column
>= columnCount(parent
))
843 return QModelIndex();
845 return createIndex(row
, column
, m_sourceRow
[row
]);
848 QModelIndex
HistoryFilterModel::parent(const QModelIndex
&) const
850 return QModelIndex();
853 void HistoryFilterModel::load() const
858 m_historyHash
.clear();
859 m_historyHash
.reserve(sourceModel()->rowCount());
860 for (int i
= 0; i
< sourceModel()->rowCount(); ++i
) {
861 QModelIndex idx
= sourceModel()->index(i
, 0);
862 QString url
= idx
.data(HistoryModel::UrlStringRole
).toString();
863 if (!m_historyHash
.contains(url
)) {
864 m_sourceRow
.append(sourceModel()->rowCount() - i
);
865 m_historyHash
[url
] = sourceModel()->rowCount() - i
;
871 void HistoryFilterModel::sourceRowsInserted(const QModelIndex
&parent
, int start
, int end
)
873 Q_ASSERT(start
== end
&& start
== 0);
877 QModelIndex idx
= sourceModel()->index(start
, 0, parent
);
878 QString url
= idx
.data(HistoryModel::UrlStringRole
).toString();
879 if (m_historyHash
.contains(url
)) {
880 int sourceRow
= sourceModel()->rowCount() - m_historyHash
[url
];
881 int realRow
= mapFromSource(sourceModel()->index(sourceRow
, 0)).row();
882 beginRemoveRows(QModelIndex(), realRow
, realRow
);
883 m_sourceRow
.removeAt(realRow
);
884 m_historyHash
.remove(url
);
887 beginInsertRows(QModelIndex(), 0, 0);
888 m_historyHash
.insert(url
, sourceModel()->rowCount() - start
);
889 m_sourceRow
.insert(0, sourceModel()->rowCount());
893 void HistoryFilterModel::sourceRowsRemoved(const QModelIndex
&, int start
, int end
)
901 Removing a continuous block of rows will remove filtered rows too as this is
904 bool HistoryFilterModel::removeRows(int row
, int count
, const QModelIndex
&parent
)
906 if (row
< 0 || count
<= 0 || row
+ count
> rowCount(parent
) || parent
.isValid())
908 int lastRow
= row
+ count
- 1;
909 disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
910 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
911 beginRemoveRows(parent
, row
, lastRow
);
912 int oldCount
= rowCount();
913 int start
= sourceModel()->rowCount() - m_sourceRow
.value(row
);
914 int end
= sourceModel()->rowCount() - m_sourceRow
.value(lastRow
);
915 sourceModel()->removeRows(start
, end
- start
+ 1);
917 connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
918 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
920 if (oldCount
- count
!= rowCount())
925 HistoryCompletionModel::HistoryCompletionModel(QObject
*parent
)
926 : QAbstractProxyModel(parent
)
930 QVariant
HistoryCompletionModel::data(const QModelIndex
&index
, int role
) const
933 && (role
== Qt::EditRole
|| role
== Qt::DisplayRole
)
934 && index
.isValid()) {
935 QModelIndex idx
= mapToSource(index
);
936 idx
= idx
.sibling(idx
.row(), 1);
937 QString urlString
= idx
.data(HistoryModel::UrlStringRole
).toString();
938 if (index
.row() % 2) {
939 QUrl url
= urlString
;
940 QString s
= url
.toString(QUrl::RemoveScheme
941 | QUrl::RemoveUserInfo
942 | QUrl::StripTrailingSlash
);
943 return s
.mid(2); // strip // from the front
947 return QAbstractProxyModel::data(index
, role
);
950 int HistoryCompletionModel::rowCount(const QModelIndex
&parent
) const
952 return (parent
.isValid() || !sourceModel()) ? 0 : sourceModel()->rowCount(parent
) * 2;
955 int HistoryCompletionModel::columnCount(const QModelIndex
&parent
) const
957 return (parent
.isValid()) ? 0 : 1;
960 QModelIndex
HistoryCompletionModel::mapFromSource(const QModelIndex
&sourceIndex
) const
962 int row
= sourceIndex
.row() * 2;
963 return index(row
, sourceIndex
.column());
966 QModelIndex
HistoryCompletionModel::mapToSource(const QModelIndex
&proxyIndex
) const
969 return QModelIndex();
970 int row
= proxyIndex
.row() / 2;
971 return sourceModel()->index(row
, proxyIndex
.column());
974 QModelIndex
HistoryCompletionModel::index(int row
, int column
, const QModelIndex
&parent
) const
976 if (row
< 0 || row
>= rowCount(parent
)
977 || column
< 0 || column
>= columnCount(parent
))
978 return QModelIndex();
979 return createIndex(row
, column
, 0);
982 QModelIndex
HistoryCompletionModel::parent(const QModelIndex
&) const
984 return QModelIndex();
987 void HistoryCompletionModel::setSourceModel(QAbstractItemModel
*newSourceModel
)
990 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
991 disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
992 this, SLOT(sourceReset()));
993 disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
994 this, SLOT(sourceReset()));
997 QAbstractProxyModel::setSourceModel(newSourceModel
);
999 if (newSourceModel
) {
1000 connect(newSourceModel
, SIGNAL(modelReset()), this, SLOT(sourceReset()));
1001 connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
1002 this, SLOT(sourceReset()));
1003 connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
1004 this, SLOT(sourceReset()));
1010 void HistoryCompletionModel::sourceReset()
1015 HistoryTreeModel::HistoryTreeModel(QAbstractItemModel
*sourceModel
, QObject
*parent
)
1016 : QAbstractProxyModel(parent
)
1018 setSourceModel(sourceModel
);
1021 QVariant
HistoryTreeModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
1023 return sourceModel()->headerData(section
, orientation
, role
);
1026 QVariant
HistoryTreeModel::data(const QModelIndex
&index
, int role
) const
1028 if ((role
== Qt::EditRole
|| role
== Qt::DisplayRole
)) {
1029 int start
= index
.internalId();
1031 int offset
= sourceDateRow(index
.row());
1032 if (index
.column() == 0) {
1033 QModelIndex idx
= sourceModel()->index(offset
, 0);
1034 QDate date
= idx
.data(HistoryModel::DateRole
).toDate();
1035 if (date
== QDate::currentDate())
1036 return tr("Earlier Today");
1037 return date
.toString(QLatin1String("dddd, MMMM d, yyyy"));
1039 if (index
.column() == 1) {
1040 return tr("%1 items").arg(rowCount(index
.sibling(index
.row(), 0)));
1044 if (role
== Qt::DecorationRole
&& index
.column() == 0 && !index
.parent().isValid())
1045 return QIcon(QLatin1String(":history.png"));
1046 if (role
== HistoryModel::DateRole
&& index
.column() == 0 && index
.internalId() == 0) {
1047 int offset
= sourceDateRow(index
.row());
1048 QModelIndex idx
= sourceModel()->index(offset
, 0);
1049 return idx
.data(HistoryModel::DateRole
);
1052 return QAbstractProxyModel::data(index
, role
);
1055 int HistoryTreeModel::columnCount(const QModelIndex
&parent
) const
1057 return sourceModel()->columnCount(mapToSource(parent
));
1060 int HistoryTreeModel::rowCount(const QModelIndex
&parent
) const
1062 if ( parent
.internalId() != 0
1063 || parent
.column() > 0
1067 // row count OF dates
1068 if (!parent
.isValid()) {
1069 if (!m_sourceRowCache
.isEmpty())
1070 return m_sourceRowCache
.count();
1073 int totalRows
= sourceModel()->rowCount();
1075 for (int i
= 0; i
< totalRows
; ++i
) {
1076 QDate rowDate
= sourceModel()->index(i
, 0).data(HistoryModel::DateRole
).toDate();
1077 if (rowDate
!= currentDate
) {
1078 m_sourceRowCache
.append(i
);
1079 currentDate
= rowDate
;
1083 Q_ASSERT(m_sourceRowCache
.count() == rows
);
1087 // row count FOR a date
1088 int start
= sourceDateRow(parent
.row());
1089 int end
= sourceDateRow(parent
.row() + 1);
1090 return (end
- start
);
1093 // Translate the top level date row into the offset where that date starts
1094 int HistoryTreeModel::sourceDateRow(int row
) const
1099 if (m_sourceRowCache
.isEmpty())
1100 rowCount(QModelIndex());
1102 if (row
>= m_sourceRowCache
.count()) {
1105 return sourceModel()->rowCount();
1107 return m_sourceRowCache
.at(row
);
1110 QModelIndex
HistoryTreeModel::mapToSource(const QModelIndex
&proxyIndex
) const
1112 int offset
= proxyIndex
.internalId();
1114 return QModelIndex();
1115 int startDateRow
= sourceDateRow(offset
- 1);
1116 return sourceModel()->index(startDateRow
+ proxyIndex
.row(), proxyIndex
.column());
1119 QModelIndex
HistoryTreeModel::index(int row
, int column
, const QModelIndex
&parent
) const
1122 || column
< 0 || column
>= columnCount(parent
)
1123 || parent
.column() > 0)
1124 return QModelIndex();
1126 if (!parent
.isValid())
1127 return createIndex(row
, column
, 0);
1128 return createIndex(row
, column
, parent
.row() + 1);
1131 QModelIndex
HistoryTreeModel::parent(const QModelIndex
&index
) const
1133 int offset
= index
.internalId();
1134 if (offset
== 0 || !index
.isValid())
1135 return QModelIndex();
1136 return createIndex(offset
- 1, 0, 0);
1139 bool HistoryTreeModel::hasChildren(const QModelIndex
&parent
) const
1141 QModelIndex grandparent
= parent
.parent();
1142 if (!grandparent
.isValid())
1147 Qt::ItemFlags
HistoryTreeModel::flags(const QModelIndex
&index
) const
1149 if (!index
.isValid())
1150 return Qt::NoItemFlags
;
1151 return Qt::ItemIsSelectable
| Qt::ItemIsEnabled
| Qt::ItemIsDragEnabled
;
1154 bool HistoryTreeModel::removeRows(int row
, int count
, const QModelIndex
&parent
)
1156 if (row
< 0 || count
<= 0 || row
+ count
> rowCount(parent
))
1159 if (parent
.isValid()) {
1161 int offset
= sourceDateRow(parent
.row());
1162 return sourceModel()->removeRows(offset
+ row
, count
);
1164 // removing whole dates
1165 for (int i
= row
+ count
- 1; i
>= row
; --i
) {
1166 QModelIndex dateParent
= index(i
, 0);
1167 int offset
= sourceDateRow(dateParent
.row());
1168 if (!sourceModel()->removeRows(offset
, rowCount(dateParent
)))
1175 void HistoryTreeModel::setSourceModel(QAbstractItemModel
*newSourceModel
)
1177 if (sourceModel()) {
1178 disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
1179 disconnect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
1180 disconnect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
1181 this, SLOT(sourceRowsInserted(const QModelIndex
&, int, int)));
1182 disconnect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
1183 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
1186 QAbstractProxyModel::setSourceModel(newSourceModel
);
1188 if (newSourceModel
) {
1189 connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(sourceReset()));
1190 connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(sourceReset()));
1191 connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex
&, int, int)),
1192 this, SLOT(sourceRowsInserted(const QModelIndex
&, int, int)));
1193 connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex
&, int, int)),
1194 this, SLOT(sourceRowsRemoved(const QModelIndex
&, int, int)));
1200 void HistoryTreeModel::sourceReset()
1202 m_sourceRowCache
.clear();
1206 void HistoryTreeModel::sourceRowsInserted(const QModelIndex
&parent
, int start
, int end
)
1208 Q_UNUSED(parent
); // Avoid warnings when compiling release
1209 Q_ASSERT(!parent
.isValid());
1210 if (start
!= 0 || start
!= end
) {
1211 m_sourceRowCache
.clear();
1216 m_sourceRowCache
.clear();
1217 QModelIndex treeIndex
= mapFromSource(sourceModel()->index(start
, 0));
1218 QModelIndex treeParent
= treeIndex
.parent();
1219 if (rowCount(treeParent
) == 1) {
1220 beginInsertRows(QModelIndex(), 0, 0);
1223 beginInsertRows(treeParent
, treeIndex
.row(), treeIndex
.row());
1228 QModelIndex
HistoryTreeModel::mapFromSource(const QModelIndex
&sourceIndex
) const
1230 if (!sourceIndex
.isValid())
1231 return QModelIndex();
1233 if (m_sourceRowCache
.isEmpty())
1234 rowCount(QModelIndex());
1236 QList
<int>::iterator it
;
1237 it
= qLowerBound(m_sourceRowCache
.begin(), m_sourceRowCache
.end(), sourceIndex
.row());
1238 if (*it
!= sourceIndex
.row())
1240 int dateRow
= qMax(0, it
- m_sourceRowCache
.begin());
1241 int row
= sourceIndex
.row() - m_sourceRowCache
.at(dateRow
);
1242 return createIndex(row
, sourceIndex
.column(), dateRow
+ 1);
1245 void HistoryTreeModel::sourceRowsRemoved(const QModelIndex
&parent
, int start
, int end
)
1247 Q_UNUSED(parent
); // Avoid warnings when compiling release
1248 Q_ASSERT(!parent
.isValid());
1249 if (m_sourceRowCache
.isEmpty())
1251 for (int i
= end
; i
>= start
;) {
1252 QList
<int>::iterator it
;
1253 it
= qLowerBound(m_sourceRowCache
.begin(), m_sourceRowCache
.end(), i
);
1255 if (it
== m_sourceRowCache
.end()) {
1256 m_sourceRowCache
.clear();
1263 int row
= qMax(0, it
- m_sourceRowCache
.begin());
1264 int offset
= m_sourceRowCache
[row
];
1265 QModelIndex dateParent
= index(row
, 0);
1266 // If we can remove all the rows in the date do that and skip over them
1267 int rc
= rowCount(dateParent
);
1268 if (i
- rc
+ 1 == offset
&& start
<= i
- rc
+ 1) {
1269 beginRemoveRows(QModelIndex(), row
, row
);
1270 m_sourceRowCache
.removeAt(row
);
1273 beginRemoveRows(dateParent
, i
- offset
, i
- offset
);
1277 for (int j
= row
; j
< m_sourceRowCache
.count(); ++j
)
1278 --m_sourceRowCache
[j
];