Warning, file /network/falkon/src/lib/tabwidget/tabtreemodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "tabtreemodel.h"
0019 #include "tabmodel.h"
0020 #include "webtab.h"
0021 #include "tabwidget.h"
0022 #include "browserwindow.h"
0023 
0024 #include <QTimer>
0025 #include <QMimeData>
0026 
0027 class TabTreeModelItem
0028 {
0029 public:
0030     explicit TabTreeModelItem(WebTab *tab = nullptr, const QModelIndex &index = QModelIndex());
0031     ~TabTreeModelItem();
0032 
0033     void setParent(TabTreeModelItem *item);
0034     void addChild(TabTreeModelItem *item, int index = -1);
0035 
0036     WebTab *tab = nullptr;
0037     TabTreeModelItem *parent = nullptr;
0038     QVector<TabTreeModelItem*> children;
0039     QPersistentModelIndex sourceIndex;
0040 };
0041 
0042 TabTreeModelItem::TabTreeModelItem(WebTab *tab, const QModelIndex &index)
0043     : tab(tab)
0044     , sourceIndex(index)
0045 {
0046 }
0047 
0048 TabTreeModelItem::~TabTreeModelItem()
0049 {
0050     for (TabTreeModelItem *child : std::as_const(children)) {
0051         delete child;
0052     }
0053 }
0054 
0055 void TabTreeModelItem::setParent(TabTreeModelItem *item)
0056 {
0057     if (parent == item) {
0058         return;
0059     }
0060     if (parent) {
0061         parent->children.removeOne(this);
0062     }
0063     parent = item;
0064     if (parent) {
0065         parent->children.append(this);
0066     }
0067 }
0068 
0069 void TabTreeModelItem::addChild(TabTreeModelItem *item, int index)
0070 {
0071     item->setParent(nullptr);
0072     item->parent = this;
0073     if (index < 0 || index > children.size()) {
0074         children.append(item);
0075     } else {
0076         children.insert(index, item);
0077     }
0078 }
0079 
0080 TabTreeModel::TabTreeModel(BrowserWindow *window, QObject *parent)
0081     : QAbstractProxyModel(parent)
0082     , m_window(window)
0083 {
0084     connect(m_window, &BrowserWindow::aboutToClose, this, &TabTreeModel::syncTopLevelTabs);
0085 
0086     connect(this, &QAbstractProxyModel::sourceModelChanged, this, &TabTreeModel::init);
0087 }
0088 
0089 TabTreeModel::~TabTreeModel()
0090 {
0091     delete m_root;
0092 }
0093 
0094 QModelIndex TabTreeModel::tabIndex(WebTab *tab) const
0095 {
0096     TabTreeModelItem *item = m_items.value(tab);
0097     if (!item) {
0098         return {};
0099     }
0100     return createIndex(item->parent->children.indexOf(item), 0, item);
0101 }
0102 
0103 WebTab *TabTreeModel::tab(const QModelIndex &index) const
0104 {
0105     TabTreeModelItem *it = item(index);
0106     return it ? it->tab : nullptr;
0107 }
0108 
0109 Qt::ItemFlags TabTreeModel::flags(const QModelIndex &index) const
0110 {
0111     TabTreeModelItem *it = item(index);
0112     if (!it || !it->tab) {
0113         return Qt::ItemIsDropEnabled;
0114     }
0115     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0116     if (!it->tab->isPinned()) {
0117         flags |= Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled;
0118     }
0119     return flags;
0120 }
0121 
0122 QVariant TabTreeModel::data(const QModelIndex &index, int role) const
0123 {
0124     return sourceModel()->data(mapToSource(index), role);
0125 }
0126 
0127 int TabTreeModel::rowCount(const QModelIndex &parent) const
0128 {
0129     TabTreeModelItem *it = item(parent);
0130     if (!it) {
0131         return 0;
0132     }
0133     return it->children.count();
0134 }
0135 
0136 int TabTreeModel::columnCount(const QModelIndex &parent) const
0137 {
0138     if (parent.column() > 0) {
0139         return 0;
0140     }
0141     return 1;
0142 }
0143 
0144 bool TabTreeModel::hasChildren(const QModelIndex &parent) const
0145 {
0146     TabTreeModelItem *it = item(parent);
0147     if (!it) {
0148         return false;
0149     }
0150     return !it->children.isEmpty();
0151 }
0152 
0153 QModelIndex TabTreeModel::parent(const QModelIndex &child) const
0154 {
0155     TabTreeModelItem *it = item(child);
0156     if (!it) {
0157         return {};
0158     }
0159     return index(it->parent);
0160 }
0161 
0162 QModelIndex TabTreeModel::index(int row, int column, const QModelIndex &parent) const
0163 {
0164     if (!hasIndex(row, column, parent)) {
0165         return {};
0166     }
0167     TabTreeModelItem *parentItem = item(parent);
0168     return createIndex(row, column, parentItem->children.at(row));
0169 }
0170 
0171 QModelIndex TabTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
0172 {
0173     return tabIndex(sourceIndex.data(TabModel::WebTabRole).value<WebTab*>());
0174 }
0175 
0176 QModelIndex TabTreeModel::mapToSource(const QModelIndex &proxyIndex) const
0177 {
0178     TabTreeModelItem *it = item(proxyIndex);
0179     if (!it) {
0180         return {};
0181     }
0182     return it->sourceIndex;
0183 }
0184 
0185 bool TabTreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
0186 {
0187     Q_UNUSED(row)
0188     Q_UNUSED(parent)
0189     if (action != Qt::MoveAction || column > 0 || !m_window) {
0190         return false;
0191     }
0192     const auto *mimeData = qobject_cast<const TabModelMimeData*>(data);
0193     if (!mimeData) {
0194         return false;
0195     }
0196     WebTab *tab = mimeData->tab();
0197     if (!tab) {
0198         return false;
0199     }
0200     // This would require moving the entire tab tree
0201     if (tab->browserWindow() != m_window && !tab->childTabs().isEmpty()) {
0202         return false;
0203     }
0204     return true;
0205 }
0206 
0207 bool TabTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0208 {
0209     if (!canDropMimeData(data, action, row, column, parent)) {
0210         return false;
0211     }
0212 
0213     const auto *mimeData = static_cast<const TabModelMimeData*>(data);
0214     WebTab *tab = mimeData->tab();
0215 
0216     if (tab->isPinned()) {
0217         tab->togglePinned();
0218     }
0219 
0220     if (tab->browserWindow() != m_window) {
0221         if (tab->browserWindow()) {
0222             tab->browserWindow()->tabWidget()->detachTab(tab);
0223             m_window->tabWidget()->addView(tab, Qz::NT_SelectedTab);
0224         }
0225     }
0226 
0227     TabTreeModelItem *it = m_items.value(tab);
0228     TabTreeModelItem *parentItem = item(parent);
0229     if (!it || !parentItem) {
0230         return false;
0231     }
0232     if (it->parent == parentItem && row < 0) {
0233         return false;
0234     }
0235     if (!parentItem->tab) {
0236         tab->setParentTab(nullptr);
0237         if (row < 0) {
0238             row = m_root->children.count();
0239         }
0240         const QModelIndex fromIdx = index(it);
0241         const int childPos = row > fromIdx.row() ? row - 1 : row;
0242         if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), row)) {
0243             qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << row;
0244             return true;
0245         }
0246         m_root->addChild(it, childPos);
0247         endMoveRows();
0248     } else {
0249         parentItem->tab->addChildTab(tab, row);
0250     }
0251 
0252     return true;
0253 }
0254 
0255 void TabTreeModel::init()
0256 {
0257     delete m_root;
0258     m_items.clear();
0259 
0260     m_root = new TabTreeModelItem;
0261 
0262     for (int i = 0; i < sourceModel()->rowCount(); ++i) {
0263         const QModelIndex index = sourceModel()->index(i, 0);
0264         auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
0265         if (tab && !tab->parentTab()) {
0266             auto *item = new TabTreeModelItem(tab, index);
0267             m_items[tab] = item;
0268             m_root->addChild(createItems(item));
0269         }
0270     }
0271 
0272     for (TabTreeModelItem *item : std::as_const(m_items)) {
0273         connectTab(item->tab);
0274     }
0275 
0276     connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &TabTreeModel::sourceDataChanged, Qt::UniqueConnection);
0277     connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &TabTreeModel::sourceRowsInserted, Qt::UniqueConnection);
0278     connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &TabTreeModel::sourceRowsAboutToBeRemoved, Qt::UniqueConnection);
0279     connect(sourceModel(), &QAbstractItemModel::modelReset, this, &TabTreeModel::sourceReset, Qt::UniqueConnection);
0280 }
0281 
0282 QModelIndex TabTreeModel::index(TabTreeModelItem *item) const
0283 {
0284     if (!item || item == m_root) {
0285         return {};
0286     }
0287     return createIndex(item->parent->children.indexOf(item), 0, item);
0288 }
0289 
0290 TabTreeModelItem *TabTreeModel::item(const QModelIndex &index) const
0291 {
0292     auto *it = static_cast<TabTreeModelItem*>(index.internalPointer());
0293     return it ? it : m_root;
0294 }
0295 
0296 TabTreeModelItem *TabTreeModel::createItems(TabTreeModelItem *root)
0297 {
0298     const auto children = root->tab->childTabs();
0299     for (WebTab *child : children) {
0300         const QModelIndex index = sourceModel()->index(child->tabIndex(), 0);
0301         auto *item = new TabTreeModelItem(child, index);
0302         m_items[child] = item;
0303         root->addChild(createItems(item));
0304     }
0305     return root;
0306 }
0307 
0308 void TabTreeModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0309 {
0310     Q_EMIT dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles);
0311 }
0312 
0313 void TabTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
0314 {
0315     for (int i = start; i <= end; ++i) {
0316         insertIndex(sourceModel()->index(i, 0, parent));
0317     }
0318 }
0319 
0320 void TabTreeModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0321 {
0322     for (int i = start; i <= end; ++i) {
0323         removeIndex(sourceModel()->index(i, 0, parent));
0324     }
0325 }
0326 
0327 void TabTreeModel::sourceReset()
0328 {
0329     beginResetModel();
0330     init();
0331     endResetModel();
0332 }
0333 
0334 void TabTreeModel::insertIndex(const QModelIndex &sourceIndex)
0335 {
0336     auto *tab = sourceIndex.data(TabModel::WebTabRole).value<WebTab*>();
0337     if (!tab) {
0338         return;
0339     }
0340     TabTreeModelItem *parent = m_items.value(tab->parentTab());
0341     if (!parent) {
0342         parent = m_root;
0343     }
0344     auto *item = new TabTreeModelItem(tab, sourceIndex);
0345 
0346     const int idx = parent->children.count();
0347     beginInsertRows(tabIndex(tab->parentTab()), idx, idx);
0348     m_items[tab] = item;
0349     parent->addChild(item);
0350     endInsertRows();
0351 
0352     connectTab(tab);
0353 }
0354 
0355 void TabTreeModel::removeIndex(const QModelIndex &sourceIndex)
0356 {
0357     auto *tab = sourceIndex.data(TabModel::WebTabRole).value<WebTab*>();
0358     if (!tab) {
0359         return;
0360     }
0361     TabTreeModelItem *item = m_items.value(tab);
0362     if (!item) {
0363         return;
0364     }
0365 
0366     const QModelIndex index = mapFromSource(sourceIndex);
0367     beginRemoveRows(index.parent(), index.row(), index.row());
0368     item->setParent(nullptr);
0369     Q_ASSERT(item->children.isEmpty());
0370     delete item;
0371     endRemoveRows();
0372 
0373     tab->disconnect(this);
0374 }
0375 
0376 void TabTreeModel::connectTab(WebTab *tab)
0377 {
0378     TabTreeModelItem *item = m_items.value(tab);
0379     Q_ASSERT(item);
0380 
0381     connect(tab, &WebTab::parentTabChanged, this, [=](WebTab *parent) {
0382         // Handle only move to root, everything else is done in childTabAdded
0383         if (item->parent == m_root || parent) {
0384             return;
0385         }
0386         int pos = m_root->children.count();
0387         // Move it to the same spot as old parent
0388         if (item->parent->parent == m_root) {
0389             pos = m_root->children.indexOf(item->parent);
0390         }
0391         const QModelIndex fromIdx = index(item);
0392         if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), pos)) {
0393             qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << pos;
0394             return;
0395         }
0396         m_root->addChild(item, pos);
0397         endMoveRows();
0398     });
0399 
0400     connect(tab, &WebTab::childTabAdded, this, [=](WebTab *child, int pos) {
0401         TabTreeModelItem *from = m_items.value(child);
0402         if (!from) {
0403             return;
0404         }
0405         const QModelIndex fromIdx = index(from);
0406         const QModelIndex toIdx = index(item);
0407         const int childPos = fromIdx.parent() == toIdx && pos > fromIdx.row() ? pos - 1 : pos;
0408         if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), toIdx, pos)) {
0409             qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << toIdx << pos;
0410             return;
0411         }
0412         item->addChild(from, childPos);
0413         endMoveRows();
0414     });
0415 }
0416 
0417 void TabTreeModel::syncTopLevelTabs()
0418 {
0419     // Move all normal top-level tabs to the beginning to preserve order in session
0420 
0421     int index = -1;
0422 
0423     const auto items = m_root->children;
0424     for (TabTreeModelItem *item : items) {
0425         if (!item->tab->isPinned()) {
0426             const int tabIndex = item->tab->tabIndex();
0427             if (index < 0 || tabIndex < index) {
0428                 index = tabIndex;
0429             }
0430         }
0431     }
0432 
0433     if (index < 0) {
0434         return;
0435     }
0436 
0437     for (TabTreeModelItem *item : items) {
0438         if (!item->tab->isPinned()) {
0439             item->tab->moveTab(index++);
0440         }
0441     }
0442 }