From b933e87c995b896d4076b4f1c5ace0a0b069a062 Mon Sep 17 00:00:00 2001 From: David Rosca Date: Tue, 6 Feb 2018 11:47:51 +0100 Subject: [PATCH] Add TabMruModel Sorts tabs in most recently used order. --- autotests/tabmodeltest.cpp | 60 +++++++++ autotests/tabmodeltest.h | 1 + src/lib/CMakeLists.txt | 1 + src/lib/tabwidget/tabmrumodel.cpp | 207 ++++++++++++++++++++++++++++++ src/lib/tabwidget/tabmrumodel.h | 63 +++++++++ 5 files changed, 332 insertions(+) create mode 100644 src/lib/tabwidget/tabmrumodel.cpp create mode 100644 src/lib/tabwidget/tabmrumodel.h diff --git a/autotests/tabmodeltest.cpp b/autotests/tabmodeltest.cpp index 87112f1f6..c29ee733d 100644 --- a/autotests/tabmodeltest.cpp +++ b/autotests/tabmodeltest.cpp @@ -19,6 +19,7 @@ #include "autotests.h" #include "tabmodel.h" #include "tabtreemodel.h" +#include "tabmrumodel.h" #include "webtab.h" #include "tabwidget.h" #include "mainapplication.h" @@ -259,5 +260,64 @@ void TabModelTest::resetTreeModelTest() QCOMPARE(model.rowCount(QModelIndex()), 0); } +void TabModelTest::mruModelTest() +{ + BrowserWindow *w = mApp->createWindow(Qz::BW_NewWindow); + + TabModel sourceModel(w); + TabMruModel model(w); + model.setSourceModel(&sourceModel); + ModelTest modelTest(&model); + + w->tabWidget()->addView(QUrl()); + w->tabWidget()->addView(QUrl()); + w->tabWidget()->addView(QUrl()); + w->tabWidget()->addView(QUrl()); + w->tabWidget()->addView(QUrl()); + + QTRY_COMPARE(model.rowCount(QModelIndex()), 6); + + WebTab *tab0 = w->tabWidget()->webTab(0); + WebTab *tab1 = w->tabWidget()->webTab(1); + WebTab *tab2 = w->tabWidget()->webTab(2); + WebTab *tab3 = w->tabWidget()->webTab(3); + WebTab *tab4 = w->tabWidget()->webTab(4); + WebTab *tab5 = w->tabWidget()->webTab(5); + + QCOMPARE(model.index(0, 0).data(TabModel::WebTabRole).value(), tab0); + QCOMPARE(model.index(1, 0).data(TabModel::WebTabRole).value(), tab1); + QCOMPARE(model.index(2, 0).data(TabModel::WebTabRole).value(), tab2); + QCOMPARE(model.index(3, 0).data(TabModel::WebTabRole).value(), tab3); + QCOMPARE(model.index(4, 0).data(TabModel::WebTabRole).value(), tab4); + QCOMPARE(model.index(5, 0).data(TabModel::WebTabRole).value(), tab5); + + w->tabWidget()->setCurrentIndex(2); + QCOMPARE(model.index(0, 0).data(TabModel::WebTabRole).value(), tab2); + QCOMPARE(model.index(1, 0).data(TabModel::WebTabRole).value(), tab0); + QCOMPARE(model.index(2, 0).data(TabModel::WebTabRole).value(), tab1); + QCOMPARE(model.index(3, 0).data(TabModel::WebTabRole).value(), tab3); + QCOMPARE(model.index(4, 0).data(TabModel::WebTabRole).value(), tab4); + QCOMPARE(model.index(5, 0).data(TabModel::WebTabRole).value(), tab5); + w->tabWidget()->setCurrentIndex(2); + + w->tabWidget()->setCurrentIndex(4); + QCOMPARE(model.index(0, 0).data(TabModel::WebTabRole).value(), tab4); + QCOMPARE(model.index(1, 0).data(TabModel::WebTabRole).value(), tab2); + QCOMPARE(model.index(2, 0).data(TabModel::WebTabRole).value(), tab0); + QCOMPARE(model.index(3, 0).data(TabModel::WebTabRole).value(), tab1); + QCOMPARE(model.index(4, 0).data(TabModel::WebTabRole).value(), tab3); + QCOMPARE(model.index(5, 0).data(TabModel::WebTabRole).value(), tab5); + + w->tabWidget()->setCurrentIndex(5); + QCOMPARE(model.index(0, 0).data(TabModel::WebTabRole).value(), tab5); + QCOMPARE(model.index(1, 0).data(TabModel::WebTabRole).value(), tab4); + QCOMPARE(model.index(2, 0).data(TabModel::WebTabRole).value(), tab2); + QCOMPARE(model.index(3, 0).data(TabModel::WebTabRole).value(), tab0); + QCOMPARE(model.index(4, 0).data(TabModel::WebTabRole).value(), tab1); + QCOMPARE(model.index(5, 0).data(TabModel::WebTabRole).value(), tab3); + + QTest::qWait(10); + delete w; +} FALKONTEST_MAIN(TabModelTest) diff --git a/autotests/tabmodeltest.h b/autotests/tabmodeltest.h index 59187f2ff..5bdc0ac81 100644 --- a/autotests/tabmodeltest.h +++ b/autotests/tabmodeltest.h @@ -32,4 +32,5 @@ private slots: void pinTabTest(); void treeModelTest(); void resetTreeModelTest(); + void mruModelTest(); }; diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 1a06e8137..343c113bc 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -181,6 +181,7 @@ set(SRCS ${SRCS} tabwidget/tabbar.cpp tabwidget/tabicon.cpp tabwidget/tabmodel.cpp + tabwidget/tabmrumodel.cpp tabwidget/tabtreemodel.cpp tabwidget/tabstackedwidget.cpp tabwidget/tabwidget.cpp diff --git a/src/lib/tabwidget/tabmrumodel.cpp b/src/lib/tabwidget/tabmrumodel.cpp new file mode 100644 index 000000000..c71f613e0 --- /dev/null +++ b/src/lib/tabwidget/tabmrumodel.cpp @@ -0,0 +1,207 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2018 David Rosca +* +* 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 . +* ============================================================ */ +#include "tabmrumodel.h" +#include "tabmodel.h" +#include "webtab.h" +#include "tabwidget.h" +#include "browserwindow.h" + +class TabMruModelItem +{ +public: + explicit TabMruModelItem(WebTab *tab = nullptr, const QModelIndex &index = QModelIndex()); + ~TabMruModelItem(); + + WebTab *tab = nullptr; + QVector children; + QPersistentModelIndex sourceIndex; +}; + +TabMruModelItem::TabMruModelItem(WebTab *tab, const QModelIndex &index) + : tab(tab) + , sourceIndex(index) +{ +} + +TabMruModelItem::~TabMruModelItem() +{ + qDeleteAll(children); +} + +TabMruModel::TabMruModel(BrowserWindow *window, QObject *parent) + : QAbstractProxyModel(parent) + , m_window(window) +{ + connect(this, &QAbstractProxyModel::sourceModelChanged, this, &TabMruModel::init); +} + +TabMruModel::~TabMruModel() +{ + delete m_root; +} + +QModelIndex TabMruModel::tabIndex(WebTab *tab) const +{ + TabMruModelItem *item = m_items.value(tab); + return item ? createIndex(m_root->children.indexOf(item), 0, item) : QModelIndex(); +} + +WebTab *TabMruModel::tab(const QModelIndex &index) const +{ + TabMruModelItem *it = item(index); + return it ? it->tab : nullptr; +} + +Qt::ItemFlags TabMruModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +int TabMruModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_items.count(); +} + +int TabMruModel::columnCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) { + return 0; + } + return 1; +} + +QModelIndex TabMruModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + return QModelIndex(); +} + +QModelIndex TabMruModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + return createIndex(row, column, m_root->children.at(row)); +} + +QModelIndex TabMruModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + return tabIndex(sourceIndex.data(TabModel::WebTabRole).value()); +} + +QModelIndex TabMruModel::mapToSource(const QModelIndex &proxyIndex) const +{ + TabMruModelItem *it = item(proxyIndex); + if (!it) { + return QModelIndex(); + } + return it->sourceIndex; +} + +void TabMruModel::init() +{ + delete m_root; + m_items.clear(); + + m_root = new TabMruModelItem; + sourceRowsInserted(QModelIndex(), 0, sourceModel()->rowCount()); + currentTabChanged(m_window->tabWidget()->currentIndex()); + + connect(m_window->tabWidget(), &TabWidget::currentChanged, this, &TabMruModel::currentTabChanged, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &TabMruModel::sourceDataChanged, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &TabMruModel::sourceRowsInserted, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &TabMruModel::sourceRowsAboutToBeRemoved, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::modelReset, this, &TabMruModel::sourceReset, Qt::UniqueConnection); +} + +QModelIndex TabMruModel::index(TabMruModelItem *item) const +{ + if (!item || item == m_root) { + return QModelIndex(); + } + return createIndex(m_root->children.indexOf(item), 0, item); +} + +TabMruModelItem *TabMruModel::item(const QModelIndex &index) const +{ + return static_cast(index.internalPointer()); +} + +void TabMruModel::currentTabChanged(int index) +{ + TabMruModelItem *it = item(mapFromSource(sourceModel()->index(index, 0))); + if (it) { + const int from = m_root->children.indexOf(it); + if (!beginMoveRows(QModelIndex(), from, from, QModelIndex(), 0)) { + qWarning() << "Invalid beginMoveRows" << from; + return; + } + m_root->children.removeAt(from); + m_root->children.insert(0, it); + endMoveRows(); + } +} + +void TabMruModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) +{ + emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles); +} + +void TabMruModel::sourceRowsInserted(const QModelIndex &parent, int start, int end) +{ + for (int i = start; i <= end; ++i) { + const QModelIndex index = sourceModel()->index(i, 0, parent); + WebTab *tab = index.data(TabModel::WebTabRole).value(); + if (tab) { + beginInsertRows(QModelIndex(), m_items.count(), m_items.count()); + TabMruModelItem *item = new TabMruModelItem(tab, index); + m_items[tab] = item; + m_root->children.append(item); + endInsertRows(); + } + } +} + +void TabMruModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + for (int i = start; i <= end; ++i) { + const QModelIndex index = sourceModel()->index(i, 0, parent); + TabMruModelItem *it = item(mapFromSource(index)); + if (it) { + const int idx = m_root->children.indexOf(it); + beginRemoveRows(QModelIndex(), idx, idx); + m_items.remove(it->tab); + m_root->children.removeAt(idx); + delete it; + endRemoveRows(); + } + } +} + +void TabMruModel::sourceReset() +{ + beginResetModel(); + init(); + endResetModel(); +} diff --git a/src/lib/tabwidget/tabmrumodel.h b/src/lib/tabwidget/tabmrumodel.h new file mode 100644 index 000000000..1bf7bb4d9 --- /dev/null +++ b/src/lib/tabwidget/tabmrumodel.h @@ -0,0 +1,63 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2018 David Rosca +* +* 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 . +* ============================================================ */ +#pragma once + +#include + +#include "qzcommon.h" + +class WebTab; +class BrowserWindow; + +class TabMruModelItem; + +class FALKON_EXPORT TabMruModel : public QAbstractProxyModel +{ + Q_OBJECT + +public: + explicit TabMruModel(BrowserWindow *window, QObject *parent = nullptr); + ~TabMruModel(); + + QModelIndex tabIndex(WebTab *tab) const; + WebTab *tab(const QModelIndex &index) const; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &index) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + +private: + void init(); + QModelIndex index(TabMruModelItem *item) const; + TabMruModelItem *item(const QModelIndex &index) const; + + void currentTabChanged(int index); + void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + void sourceRowsInserted(const QModelIndex &parent, int start, int end); + void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void sourceReset(); + + BrowserWindow *m_window; + TabMruModelItem *m_root = nullptr; + QHash m_items; +};