mirror of
https://invent.kde.org/network/falkon.git
synced 2024-11-13 18:42:11 +01:00
565 lines
15 KiB
C++
565 lines
15 KiB
C++
/* ============================================================
|
|
* QupZilla - WebKit based browser
|
|
* Copyright (C) 2010-2014 David Rosca <nowrep@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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/>.
|
|
* ============================================================ */
|
|
#include "historymodel.h"
|
|
#include "historyitem.h"
|
|
#include "iconprovider.h"
|
|
|
|
#include <QApplication>
|
|
#include <QSqlQuery>
|
|
#include <QDateTime>
|
|
#include <QTimer>
|
|
|
|
static QString dateTimeToString(const QDateTime &dateTime)
|
|
{
|
|
const QDateTime current = QDateTime::currentDateTime();
|
|
if (current.date() == dateTime.date()) {
|
|
return dateTime.time().toString("h:mm");
|
|
}
|
|
|
|
return dateTime.toString("d.M.yyyy h:mm");
|
|
}
|
|
|
|
HistoryModel::HistoryModel(History* history)
|
|
: QAbstractItemModel(history)
|
|
, m_rootItem(new HistoryItem(0))
|
|
, m_todayItem(0)
|
|
, m_history(history)
|
|
{
|
|
init();
|
|
|
|
connect(m_history, SIGNAL(resetHistory()), this, SLOT(resetHistory()));
|
|
connect(m_history, SIGNAL(historyEntryAdded(HistoryEntry)), this, SLOT(historyEntryAdded(HistoryEntry)));
|
|
connect(m_history, SIGNAL(historyEntryDeleted(HistoryEntry)), this, SLOT(historyEntryDeleted(HistoryEntry)));
|
|
connect(m_history, SIGNAL(historyEntryEdited(HistoryEntry,HistoryEntry)), this, SLOT(historyEntryEdited(HistoryEntry,HistoryEntry)));
|
|
}
|
|
|
|
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");
|
|
case 2:
|
|
return tr("Visit Date");
|
|
case 3:
|
|
return tr("Visit Count");
|
|
}
|
|
}
|
|
|
|
return QAbstractItemModel::headerData(section, orientation, role);
|
|
}
|
|
|
|
QVariant HistoryModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
HistoryItem* item = itemFromIndex(index);
|
|
|
|
if (index.row() < 0 || !item) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (item->isTopLevel()) {
|
|
switch (role) {
|
|
case IsTopLevelRole:
|
|
return true;
|
|
case TimestampStartRole:
|
|
return item->startTimestamp();
|
|
case TimestampEndRole:
|
|
return item->endTimestamp();
|
|
case Qt::DisplayRole:
|
|
case Qt::EditRole:
|
|
return index.column() == 0 ? item->title : QVariant();
|
|
case Qt::DecorationRole:
|
|
return index.column() == 0 ? QIcon(":/icons/menu/history_entry.png") : QVariant();
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
const HistoryEntry entry = item->historyEntry;
|
|
|
|
switch (role) {
|
|
case IdRole:
|
|
return entry.id;
|
|
case TitleRole:
|
|
return entry.title;
|
|
case UrlRole:
|
|
return entry.url;
|
|
case UrlStringRole:
|
|
return entry.urlString;
|
|
case IconRole:
|
|
return item->icon();
|
|
case IconLoadedRole:
|
|
return item->iconLoaded();
|
|
case IsTopLevelRole:
|
|
return false;
|
|
case TimestampStartRole:
|
|
return -1;
|
|
case TimestampEndRole:
|
|
return -1;
|
|
case Qt::ToolTipRole:
|
|
if (index.column() == 0) {
|
|
return QString("%1\n%2").arg(entry.title, entry.urlString);
|
|
}
|
|
case Qt::DisplayRole:
|
|
case Qt::EditRole:
|
|
switch (index.column()) {
|
|
case 0:
|
|
return entry.title;
|
|
case 1:
|
|
return entry.urlString;
|
|
case 2:
|
|
return dateTimeToString(entry.date);
|
|
case 3:
|
|
return entry.count;
|
|
}
|
|
break;
|
|
case Qt::DecorationRole:
|
|
if (index.column() == 0) {
|
|
return item->icon().isNull() ? qIconProvider->emptyWebIcon() : item->icon();
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool HistoryModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
HistoryItem* item = itemFromIndex(index);
|
|
|
|
if (index.row() < 0 || !item || item->isTopLevel()) {
|
|
return false;
|
|
}
|
|
|
|
if (role == IconRole) {
|
|
item->setIcon(value.value<QIcon>());
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QModelIndex HistoryModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent)) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
HistoryItem* parentItem = itemFromIndex(parent);
|
|
HistoryItem* childItem = parentItem->child(row);
|
|
|
|
return childItem ? createIndex(row, column, childItem) : QModelIndex();
|
|
}
|
|
|
|
QModelIndex HistoryModel::parent(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
HistoryItem* childItem = itemFromIndex(index);
|
|
HistoryItem* parentItem = childItem->parent();
|
|
|
|
if (!parentItem || parentItem == m_rootItem) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
return createIndex(parentItem->row(), 0, parentItem);
|
|
}
|
|
|
|
Qt::ItemFlags HistoryModel::flags(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return 0;
|
|
}
|
|
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
}
|
|
|
|
int HistoryModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.column() > 0) {
|
|
return 0;
|
|
}
|
|
|
|
HistoryItem* parentItem = itemFromIndex(parent);
|
|
|
|
return parentItem->childCount();
|
|
}
|
|
|
|
int HistoryModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent)
|
|
|
|
return 4;
|
|
}
|
|
|
|
bool HistoryModel::hasChildren(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return true;
|
|
}
|
|
|
|
HistoryItem* item = itemFromIndex(parent);
|
|
|
|
return item ? item->isTopLevel() : false;
|
|
}
|
|
|
|
HistoryItem* HistoryModel::itemFromIndex(const QModelIndex &index) const
|
|
{
|
|
if (index.isValid()) {
|
|
HistoryItem* item = static_cast<HistoryItem*>(index.internalPointer());
|
|
|
|
if (item) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return m_rootItem;
|
|
}
|
|
|
|
void HistoryModel::removeTopLevelIndexes(const QList<QPersistentModelIndex> &indexes)
|
|
{
|
|
foreach (const QPersistentModelIndex &index, indexes) {
|
|
if (index.parent().isValid()) {
|
|
continue;
|
|
}
|
|
|
|
int row = index.row();
|
|
HistoryItem* item = m_rootItem->child(row);
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
beginRemoveRows(QModelIndex(), row, row);
|
|
delete item;
|
|
endRemoveRows();
|
|
|
|
if (item == m_todayItem) {
|
|
m_todayItem = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HistoryModel::resetHistory()
|
|
{
|
|
beginResetModel();
|
|
|
|
delete m_rootItem;
|
|
m_todayItem = 0;
|
|
m_rootItem = new HistoryItem(0);
|
|
|
|
init();
|
|
|
|
endResetModel();
|
|
}
|
|
|
|
bool HistoryModel::canFetchMore(const QModelIndex &parent) const
|
|
{
|
|
HistoryItem* parentItem = itemFromIndex(parent);
|
|
|
|
return parentItem ? parentItem->canFetchMore : false;
|
|
}
|
|
|
|
void HistoryModel::fetchMore(const QModelIndex &parent)
|
|
{
|
|
HistoryItem* parentItem = itemFromIndex(parent);
|
|
|
|
if (!parent.isValid() || !parentItem) {
|
|
return;
|
|
}
|
|
|
|
parentItem->canFetchMore = false;
|
|
|
|
QList<int> idList;
|
|
for (int i = 0; i < parentItem->childCount(); ++i) {
|
|
idList.append(parentItem->child(i)->historyEntry.id);
|
|
}
|
|
|
|
QSqlQuery query;
|
|
query.prepare("SELECT id, count, title, url, date FROM history WHERE date BETWEEN ? AND ? ORDER BY date DESC");
|
|
query.addBindValue(parentItem->endTimestamp());
|
|
query.addBindValue(parentItem->startTimestamp());
|
|
query.exec();
|
|
|
|
QVector<HistoryEntry> list;
|
|
|
|
while (query.next()) {
|
|
HistoryEntry entry;
|
|
entry.id = query.value(0).toInt();
|
|
entry.count = query.value(1).toInt();
|
|
entry.title = query.value(2).toString();
|
|
entry.url = query.value(3).toUrl();
|
|
entry.date = QDateTime::fromMSecsSinceEpoch(query.value(4).toLongLong());
|
|
entry.urlString = entry.url.toEncoded();
|
|
|
|
if (!idList.contains(entry.id)) {
|
|
list.append(entry);
|
|
}
|
|
}
|
|
|
|
if (list.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
beginInsertRows(parent, 0, list.size() - 1);
|
|
|
|
foreach (const HistoryEntry &entry, list) {
|
|
HistoryItem* newItem = new HistoryItem(parentItem);
|
|
newItem->historyEntry = entry;
|
|
}
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
void HistoryModel::historyEntryAdded(const HistoryEntry &entry)
|
|
{
|
|
if (!m_todayItem) {
|
|
beginInsertRows(QModelIndex(), 0, 0);
|
|
|
|
m_todayItem = new HistoryItem(0);
|
|
m_todayItem->setStartTimestamp(-1);
|
|
m_todayItem->setEndTimestamp(QDateTime(QDate::currentDate()).toMSecsSinceEpoch());
|
|
m_todayItem->title = tr("Today");
|
|
|
|
m_rootItem->prependChild(m_todayItem);
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
beginInsertRows(createIndex(0, 0, m_todayItem), 0, 0);
|
|
|
|
HistoryItem* item = new HistoryItem();
|
|
item->historyEntry = entry;
|
|
|
|
m_todayItem->prependChild(item);
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
void HistoryModel::historyEntryDeleted(const HistoryEntry &entry)
|
|
{
|
|
HistoryItem* item = findHistoryItem(entry);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
HistoryItem* parentItem = item->parent();
|
|
int row = item->row();
|
|
|
|
beginRemoveRows(createIndex(parentItem->row(), 0, parentItem), row, row);
|
|
delete item;
|
|
endRemoveRows();
|
|
|
|
checkEmptyParentItem(parentItem);
|
|
}
|
|
|
|
void HistoryModel::historyEntryEdited(const HistoryEntry &before, const HistoryEntry &after)
|
|
{
|
|
#if 0
|
|
HistoryItem* item = findHistoryItem(before);
|
|
|
|
if (item) {
|
|
HistoryItem* parentItem = item->parent();
|
|
const QModelIndex sourceParent = createIndex(parentItem->row(), 0, parentItem);
|
|
const QModelIndex destinationParent = createIndex(m_todayItem->row(), 0, m_todayItem);
|
|
int row = item->row();
|
|
|
|
beginMoveRows(sourceParent, row, row, destinationParent, 0);
|
|
item->historyEntry = after;
|
|
item->refreshIcon();
|
|
item->changeParent(m_todayItem);
|
|
endMoveRows(); // This line sometimes throw "std::bad_alloc" ... I don't know why ?!
|
|
|
|
checkEmptyParentItem(parentItem);
|
|
}
|
|
else {
|
|
historyEntryAdded(after);
|
|
}
|
|
#endif
|
|
historyEntryDeleted(before);
|
|
historyEntryAdded(after);
|
|
}
|
|
|
|
HistoryItem* HistoryModel::findHistoryItem(const HistoryEntry &entry)
|
|
{
|
|
HistoryItem* parentItem = 0;
|
|
qint64 timestamp = entry.date.toMSecsSinceEpoch();
|
|
|
|
for (int i = 0; i < m_rootItem->childCount(); ++i) {
|
|
HistoryItem* item = m_rootItem->child(i);
|
|
|
|
if (item->endTimestamp() < timestamp) {
|
|
parentItem = item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!parentItem) {
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < parentItem->childCount(); ++i) {
|
|
HistoryItem* item = parentItem->child(i);
|
|
if (item->historyEntry.id == entry.id) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void HistoryModel::checkEmptyParentItem(HistoryItem* item)
|
|
{
|
|
if (item->childCount() == 0 && item->isTopLevel()) {
|
|
int row = item->row();
|
|
|
|
beginRemoveRows(QModelIndex(), row, row);
|
|
delete item;
|
|
endRemoveRows();
|
|
|
|
if (item == m_todayItem) {
|
|
m_todayItem = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HistoryModel::init()
|
|
{
|
|
QSqlQuery query;
|
|
query.exec("SELECT MIN(date) FROM history");
|
|
if (!query.next()) {
|
|
return;
|
|
}
|
|
|
|
const qint64 minTimestamp = query.value(0).toLongLong();
|
|
if (minTimestamp <= 0) {
|
|
return;
|
|
}
|
|
|
|
const QDate today = QDate::currentDate();
|
|
const QDate week = today.addDays(1 - today.dayOfWeek());
|
|
const QDate month = QDate(today.year(), today.month(), 1);
|
|
const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
qint64 timestamp = currentTimestamp;
|
|
while (timestamp > minTimestamp) {
|
|
QDate timestampDate = QDateTime::fromMSecsSinceEpoch(timestamp).date();
|
|
qint64 endTimestamp;
|
|
QString itemName;
|
|
|
|
if (timestampDate == today) {
|
|
endTimestamp = QDateTime(today).toMSecsSinceEpoch();
|
|
|
|
itemName = tr("Today");
|
|
}
|
|
else if (timestampDate >= week) {
|
|
endTimestamp = QDateTime(week).toMSecsSinceEpoch();
|
|
|
|
itemName = tr("This Week");
|
|
}
|
|
else if (timestampDate.month() == month.month() && timestampDate.year() == month.year()) {
|
|
endTimestamp = QDateTime(month).toMSecsSinceEpoch();
|
|
|
|
itemName = tr("This Month");
|
|
}
|
|
else {
|
|
QDate startDate(timestampDate.year(), timestampDate.month(), timestampDate.daysInMonth());
|
|
QDate endDate(startDate.year(), startDate.month(), 1);
|
|
|
|
timestamp = QDateTime(startDate, QTime(23, 59, 59)).toMSecsSinceEpoch();
|
|
endTimestamp = QDateTime(endDate).toMSecsSinceEpoch();
|
|
itemName = QString("%1 %2").arg(History::titleCaseLocalizedMonth(timestampDate.month()), QString::number(timestampDate.year()));
|
|
}
|
|
|
|
QSqlQuery query;
|
|
query.prepare("SELECT id FROM history WHERE date BETWEEN ? AND ? LIMIT 1");
|
|
query.addBindValue(endTimestamp);
|
|
query.addBindValue(timestamp);
|
|
query.exec();
|
|
|
|
if (query.next()) {
|
|
HistoryItem* item = new HistoryItem(m_rootItem);
|
|
item->setStartTimestamp(timestamp == currentTimestamp ? -1 : timestamp);
|
|
item->setEndTimestamp(endTimestamp);
|
|
item->title = itemName;
|
|
item->canFetchMore = true;
|
|
|
|
if (timestamp == currentTimestamp) {
|
|
m_todayItem = item;
|
|
}
|
|
}
|
|
|
|
timestamp = endTimestamp - 1;
|
|
}
|
|
}
|
|
|
|
HistoryFilterModel::HistoryFilterModel(QAbstractItemModel* parent)
|
|
: QSortFilterProxyModel(parent)
|
|
{
|
|
setSourceModel(parent);
|
|
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
m_filterTimer = new QTimer(this);
|
|
m_filterTimer->setSingleShot(true);
|
|
m_filterTimer->setInterval(500);
|
|
|
|
connect(m_filterTimer, SIGNAL(timeout()), this, SLOT(startFiltering()));
|
|
}
|
|
|
|
void HistoryFilterModel::setFilterFixedString(const QString &pattern)
|
|
{
|
|
m_pattern = pattern;
|
|
|
|
m_filterTimer->stop();
|
|
m_filterTimer->start();
|
|
}
|
|
|
|
void HistoryFilterModel::startFiltering()
|
|
{
|
|
if (m_pattern.isEmpty()) {
|
|
emit collapseAllItems();
|
|
QSortFilterProxyModel::setFilterFixedString(m_pattern);
|
|
return;
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
// Expand all items also calls fetchmore
|
|
emit expandAllItems();
|
|
|
|
QSortFilterProxyModel::setFilterFixedString(m_pattern);
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
bool HistoryFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
{
|
|
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
|
|
|
if (index.data(HistoryModel::IsTopLevelRole).toBool()) {
|
|
return true;
|
|
}
|
|
|
|
return (index.data(HistoryModel::UrlStringRole).toString().contains(m_pattern, Qt::CaseInsensitive) ||
|
|
index.data(HistoryModel::TitleRole).toString().contains(m_pattern, Qt::CaseInsensitive));
|
|
}
|