/* ============================================================ * 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 "tabtreemodel.h" #include "tabmodel.h" #include "webtab.h" #include class TabTreeModelItem { public: explicit TabTreeModelItem(WebTab *tab = nullptr, const QModelIndex &index = QModelIndex()); ~TabTreeModelItem(); bool isRoot() const; void setParent(TabTreeModelItem *item); void addChild(TabTreeModelItem *item, int index = -1); WebTab *tab = nullptr; TabTreeModelItem *parent = nullptr; QVector children; QPersistentModelIndex sourceIndex; }; TabTreeModelItem::TabTreeModelItem(WebTab *tab, const QModelIndex &index) : tab(tab) , sourceIndex(index) { } TabTreeModelItem::~TabTreeModelItem() { for (TabTreeModelItem *child : qAsConst(children)) { delete child; } } bool TabTreeModelItem::isRoot() const { return !tab; } void TabTreeModelItem::setParent(TabTreeModelItem *item) { if (parent == item) { return; } if (parent) { parent->children.removeOne(this); } parent = item; if (parent) { parent->children.append(this); } } void TabTreeModelItem::addChild(TabTreeModelItem *item, int index) { item->setParent(nullptr); item->parent = this; if (index < 0 || index > children.size()) { children.append(item); } else { children.insert(index, item); } } TabTreeModel::TabTreeModel(QObject *parent) : QAbstractProxyModel(parent) { connect(this, &QAbstractProxyModel::sourceModelChanged, this, &TabTreeModel::init); } TabTreeModel::~TabTreeModel() { delete m_root; } QModelIndex TabTreeModel::tabIndex(WebTab *tab) const { TabTreeModelItem *item = m_items.value(tab); if (!item) { return QModelIndex(); } return createIndex(item->parent->children.indexOf(item), 0, item); } WebTab *TabTreeModel::tab(const QModelIndex &index) const { TabTreeModelItem *it = item(index); return it ? it->tab : nullptr; } Qt::ItemFlags TabTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::ItemIsDropEnabled; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } QVariant TabTreeModel::data(const QModelIndex &index, int role) const { return sourceModel()->data(mapToSource(index), role); } int TabTreeModel::rowCount(const QModelIndex &parent) const { TabTreeModelItem *it = item(parent); if (!it) { return 0; } return it->children.count(); } int TabTreeModel::columnCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } return 1; } bool TabTreeModel::hasChildren(const QModelIndex &parent) const { TabTreeModelItem *it = item(parent); if (!it) { return false; } return !it->children.isEmpty(); } QModelIndex TabTreeModel::parent(const QModelIndex &child) const { TabTreeModelItem *it = item(child); if (!it) { return QModelIndex(); } return index(it->parent); } QModelIndex TabTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } TabTreeModelItem *parentItem = item(parent); return createIndex(row, column, parentItem->children.at(row)); } QModelIndex TabTreeModel::mapFromSource(const QModelIndex &sourceIndex) const { return tabIndex(sourceIndex.data(TabModel::WebTabRole).value()); } QModelIndex TabTreeModel::mapToSource(const QModelIndex &proxyIndex) const { TabTreeModelItem *it = item(proxyIndex); if (!it) { return QModelIndex(); } return it->sourceIndex; } bool TabTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) { return true; } const QString mimeType = mimeTypes().at(0); if (!data->hasFormat(mimeType) || column > 0) { return false; } QByteArray encodedData = data->data(mimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); QVector tabs; while (!stream.atEnd()) { int index; stream >> index; WebTab *tab = sourceModel()->index(index, 0).data(TabModel::WebTabRole).value(); if (tab) { tabs.append(tab); } } if (tabs.isEmpty()) { return false; } // Only support moving one tab WebTab *tab = tabs.at(0); TabTreeModelItem *it = m_items.value(tab); TabTreeModelItem *parentItem = item(parent); if (!it || !parentItem) { return false; } if (!parentItem->tab) { tab->setParentTab(nullptr); if (row < 0) { row = m_root->children.count(); } const QModelIndex fromIdx = index(it); const int childPos = row > fromIdx.row() ? row - 1 : row; if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), row)) { qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << row; return true; } m_root->addChild(it, childPos); endMoveRows(); } else { parentItem->tab->addChildTab(tab, row); } return true; } void TabTreeModel::init() { delete m_root; m_items.clear(); m_root = new TabTreeModelItem; for (int i = 0; i < sourceModel()->rowCount(); ++i) { const QModelIndex index = sourceModel()->index(i, 0); WebTab *tab = index.data(TabModel::WebTabRole).value(); if (tab && !tab->parentTab()) { TabTreeModelItem *item = new TabTreeModelItem(tab, index); m_items[tab] = item; m_root->addChild(createItems(item)); } } for (TabTreeModelItem *item : qAsConst(m_items)) { connectTab(item->tab); } connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &TabTreeModel::sourceDataChanged); connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &TabTreeModel::sourceRowsInserted); connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &TabTreeModel::sourceRowsAboutToBeRemoved); } QModelIndex TabTreeModel::index(TabTreeModelItem *item) const { if (!item || item == m_root) { return QModelIndex(); } return createIndex(item->parent->children.indexOf(item), 0, item); } TabTreeModelItem *TabTreeModel::item(const QModelIndex &index) const { TabTreeModelItem *it = static_cast(index.internalPointer()); return it ? it : m_root; } TabTreeModelItem *TabTreeModel::createItems(TabTreeModelItem *root) { const auto children = root->tab->childTabs(); for (WebTab *child : children) { const QModelIndex index = sourceModel()->index(child->tabIndex(), 0); TabTreeModelItem *item = new TabTreeModelItem(child, index); m_items[child] = item; root->addChild(createItems(item)); } return root; } void TabTreeModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles); } void TabTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end) { for (int i = start; i <= end; ++i) { insertIndex(sourceModel()->index(i, 0, parent)); } } void TabTreeModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { for (int i = start; i <= end; ++i) { removeIndex(sourceModel()->index(i, 0, parent)); } } void TabTreeModel::insertIndex(const QModelIndex &sourceIndex) { WebTab *tab = sourceIndex.data(TabModel::WebTabRole).value(); if (!tab) { return; } TabTreeModelItem *parent = m_items.value(tab->parentTab()); if (!parent) { parent = m_root; } TabTreeModelItem *item = new TabTreeModelItem(tab, sourceIndex); const int idx = parent->children.count(); beginInsertRows(tabIndex(tab->parentTab()), idx, idx); m_items[tab] = item; parent->addChild(item); endInsertRows(); connectTab(tab); } void TabTreeModel::removeIndex(const QModelIndex &sourceIndex) { WebTab *tab = sourceIndex.data(TabModel::WebTabRole).value(); if (!tab) { return; } TabTreeModelItem *item = m_items.value(tab); if (!item) { return; } const QModelIndex index = mapFromSource(sourceIndex); beginRemoveRows(index.parent(), index.row(), index.row()); item->setParent(nullptr); Q_ASSERT(item->children.isEmpty()); delete item; endRemoveRows(); tab->disconnect(this); } void TabTreeModel::connectTab(WebTab *tab) { TabTreeModelItem *item = m_items.value(tab); Q_ASSERT(item); connect(tab, &WebTab::parentTabChanged, this, [=](WebTab *parent) { // Handle only move to root, everything else is done in childTabAdded if (item->parent == m_root || parent) { return; } int pos = m_root->children.count(); // Move it to the same spot as old parent if (item->parent->parent == m_root) { pos = m_root->children.indexOf(item->parent); } const QModelIndex fromIdx = index(item); if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), pos)) { qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << pos; return; } m_root->addChild(item, pos); endMoveRows(); }); connect(tab, &WebTab::childTabAdded, this, [=](WebTab *child, int pos) { TabTreeModelItem *from = m_items.value(child); if (!from) { return; } const QModelIndex fromIdx = index(from); const QModelIndex toIdx = index(item); const int childPos = fromIdx.parent() == toIdx && pos > fromIdx.row() ? pos - 1 : pos; if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), toIdx, pos)) { qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << toIdx << pos; return; } item->addChild(from, childPos); endMoveRows(); }); }