mirror of
https://invent.kde.org/network/falkon.git
synced 2024-12-24 12:46:35 +01:00
TabModel: Rework drag&drop logic
Use QMimeData subclass to safely pass pointers. Only allow to drop one tab at a time and disable drop action when not possible. Allow to move tabs to other windows, with the exception of tabs with children.
This commit is contained in:
parent
ca15006145
commit
9eccf4fae3
@ -21,8 +21,39 @@
|
||||
#include "tabbedwebview.h"
|
||||
#include "browserwindow.h"
|
||||
|
||||
#include <QMimeData>
|
||||
// TabModelMimeData
|
||||
TabModelMimeData::TabModelMimeData()
|
||||
: QMimeData()
|
||||
{
|
||||
}
|
||||
|
||||
WebTab *TabModelMimeData::tab() const
|
||||
{
|
||||
return m_tab;
|
||||
}
|
||||
|
||||
void TabModelMimeData::setTab(WebTab *tab)
|
||||
{
|
||||
m_tab = tab;
|
||||
}
|
||||
|
||||
bool TabModelMimeData::hasFormat(const QString &format) const
|
||||
{
|
||||
return mimeType() == format;
|
||||
}
|
||||
|
||||
QStringList TabModelMimeData::formats() const
|
||||
{
|
||||
return {mimeType()};
|
||||
}
|
||||
|
||||
// static
|
||||
QString TabModelMimeData::mimeType()
|
||||
{
|
||||
return QSL("application/falkon.tabmodel.tab");
|
||||
}
|
||||
|
||||
// TabModel
|
||||
TabModel::TabModel(BrowserWindow *window, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_window(window)
|
||||
@ -111,61 +142,74 @@ Qt::DropActions TabModel::supportedDropActions() const
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
#define MIMETYPE QStringLiteral("application/falkon.tabmodel.tab")
|
||||
|
||||
QStringList TabModel::mimeTypes() const
|
||||
{
|
||||
return {MIMETYPE};
|
||||
return {TabModelMimeData::mimeType()};
|
||||
}
|
||||
|
||||
QMimeData *TabModel::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
QByteArray data;
|
||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
if (index.isValid() && index.column() == 0) {
|
||||
stream << index.row();
|
||||
if (indexes.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
WebTab *tab = indexes.at(0).data(WebTabRole).value<WebTab*>();
|
||||
if (!tab) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setData(MIMETYPE, data);
|
||||
TabModelMimeData *mimeData = new TabModelMimeData;
|
||||
mimeData->setTab(tab);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
bool TabModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(row)
|
||||
if (action != Qt::MoveAction || parent.isValid() || column > 0 || !m_window) {
|
||||
return false;
|
||||
}
|
||||
const TabModelMimeData *mimeData = qobject_cast<const TabModelMimeData*>(data);
|
||||
if (!mimeData) {
|
||||
return false;
|
||||
}
|
||||
return mimeData->tab();
|
||||
}
|
||||
|
||||
bool TabModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_window || !data->hasFormat(MIMETYPE) || parent.isValid() || column != 0) {
|
||||
if (!canDropMimeData(data, action, row, column, parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray encodedData = data->data(MIMETYPE);
|
||||
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
||||
const TabModelMimeData *mimeData = static_cast<const TabModelMimeData*>(data);
|
||||
WebTab *tab = mimeData->tab();
|
||||
|
||||
QVector<WebTab*> tabs;
|
||||
while (!stream.atEnd()) {
|
||||
int idx;
|
||||
stream >> idx;
|
||||
WebTab *t = tab(index(idx));
|
||||
if (t) {
|
||||
tabs.append(t);
|
||||
if (tab->browserWindow() == m_window) {
|
||||
if (tab->isPinned()) {
|
||||
if (row < 0) {
|
||||
row = m_window->tabWidget()->pinnedTabsCount();
|
||||
}
|
||||
if (row > m_window->tabWidget()->pinnedTabsCount()) {
|
||||
tab->togglePinned();
|
||||
}
|
||||
} else {
|
||||
if (row < 0) {
|
||||
row = m_window->tabWidget()->count();
|
||||
}
|
||||
if (row < m_window->tabWidget()->pinnedTabsCount()) {
|
||||
tab->togglePinned();
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tabs.isEmpty()) {
|
||||
return false;
|
||||
tab->moveTab(row > mimeData->tab()->tabIndex() ? row - 1 : row);
|
||||
} else {
|
||||
if (row < 0) {
|
||||
row = m_window->tabCount();
|
||||
}
|
||||
|
||||
for (int i = 0; i < tabs.count(); ++i) {
|
||||
const int from = tabs.at(i)->tabIndex();
|
||||
const int to = row >= from ? row - 1 : row++;
|
||||
// FIXME: This switches order when moving > 2 non-contiguous indices
|
||||
m_window->tabWidget()->moveTab(from, to);
|
||||
if (tab->browserWindow()) {
|
||||
tab->browserWindow()->tabWidget()->detachTab(tab);
|
||||
}
|
||||
tab->setPinned(row < m_window->tabWidget()->pinnedTabsCount());
|
||||
m_window->tabWidget()->insertView(row, tab, Qz::NT_SelectedTab);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -17,6 +17,8 @@
|
||||
* ============================================================ */
|
||||
#pragma once
|
||||
|
||||
#include <QPointer>
|
||||
#include <QMimeData>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "qzcommon.h"
|
||||
@ -24,6 +26,25 @@
|
||||
class WebTab;
|
||||
class BrowserWindow;
|
||||
|
||||
class FALKON_EXPORT TabModelMimeData : public QMimeData
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TabModelMimeData();
|
||||
|
||||
WebTab *tab() const;
|
||||
void setTab(WebTab *tab);
|
||||
|
||||
bool hasFormat(const QString &format) const override;
|
||||
QStringList formats() const override;
|
||||
|
||||
static QString mimeType();
|
||||
|
||||
private:
|
||||
QPointer<WebTab> m_tab;
|
||||
};
|
||||
|
||||
class FALKON_EXPORT TabModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -53,6 +74,7 @@ public:
|
||||
Qt::DropActions supportedDropActions() const override;
|
||||
QStringList mimeTypes() const override;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
|
||||
private:
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "tabtreemodel.h"
|
||||
#include "tabmodel.h"
|
||||
#include "webtab.h"
|
||||
#include "tabwidget.h"
|
||||
#include "browserwindow.h"
|
||||
|
||||
#include <QTimer>
|
||||
@ -29,7 +30,6 @@ 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);
|
||||
|
||||
@ -52,11 +52,6 @@ TabTreeModelItem::~TabTreeModelItem()
|
||||
}
|
||||
}
|
||||
|
||||
bool TabTreeModelItem::isRoot() const
|
||||
{
|
||||
return !tab;
|
||||
}
|
||||
|
||||
void TabTreeModelItem::setParent(TabTreeModelItem *item)
|
||||
{
|
||||
if (parent == item) {
|
||||
@ -84,8 +79,9 @@ void TabTreeModelItem::addChild(TabTreeModelItem *item, int index)
|
||||
|
||||
TabTreeModel::TabTreeModel(BrowserWindow *window, QObject *parent)
|
||||
: QAbstractProxyModel(parent)
|
||||
, m_window(window)
|
||||
{
|
||||
connect(window, &BrowserWindow::aboutToClose, this, &TabTreeModel::syncTopLevelTabs);
|
||||
connect(m_window, &BrowserWindow::aboutToClose, this, &TabTreeModel::syncTopLevelTabs);
|
||||
|
||||
connect(this, &QAbstractProxyModel::sourceModelChanged, this, &TabTreeModel::init);
|
||||
}
|
||||
@ -186,44 +182,53 @@ QModelIndex TabTreeModel::mapToSource(const QModelIndex &proxyIndex) const
|
||||
return it->sourceIndex;
|
||||
}
|
||||
|
||||
bool TabTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
bool TabTreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (action == Qt::IgnoreAction) {
|
||||
Q_UNUSED(row)
|
||||
Q_UNUSED(parent)
|
||||
if (action != Qt::MoveAction || column > 0 || !m_window) {
|
||||
return false;
|
||||
}
|
||||
const TabModelMimeData *mimeData = qobject_cast<const TabModelMimeData*>(data);
|
||||
if (!mimeData) {
|
||||
return false;
|
||||
}
|
||||
WebTab *tab = mimeData->tab();
|
||||
if (!tab) {
|
||||
return false;
|
||||
}
|
||||
// This would require moving the entire tab tree
|
||||
if (tab->browserWindow() != m_window && !tab->childTabs().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString mimeType = mimeTypes().at(0);
|
||||
|
||||
if (!data->hasFormat(mimeType) || column > 0) {
|
||||
bool TabTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
if (!canDropMimeData(data, action, row, column, parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray encodedData = data->data(mimeType);
|
||||
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
||||
const TabModelMimeData *mimeData = static_cast<const TabModelMimeData*>(data);
|
||||
WebTab *tab = mimeData->tab();
|
||||
|
||||
QVector<WebTab*> tabs;
|
||||
while (!stream.atEnd()) {
|
||||
int index;
|
||||
stream >> index;
|
||||
WebTab *tab = sourceModel()->index(index, 0).data(TabModel::WebTabRole).value<WebTab*>();
|
||||
if (tab) {
|
||||
tabs.append(tab);
|
||||
}
|
||||
if (tab->isPinned()) {
|
||||
tab->togglePinned();
|
||||
}
|
||||
|
||||
if (tabs.isEmpty()) {
|
||||
return false;
|
||||
if (tab->browserWindow() != m_window) {
|
||||
if (tab->browserWindow()) {
|
||||
tab->browserWindow()->tabWidget()->detachTab(tab);
|
||||
m_window->tabWidget()->addView(tab, Qz::NT_SelectedTab);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -49,6 +49,7 @@ public:
|
||||
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
|
||||
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
|
||||
|
||||
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
|
||||
private:
|
||||
@ -67,6 +68,7 @@ private:
|
||||
void connectTab(WebTab *tab);
|
||||
void syncTopLevelTabs();
|
||||
|
||||
BrowserWindow *m_window;
|
||||
TabTreeModelItem *m_root = nullptr;
|
||||
QHash<WebTab*, TabTreeModelItem*> m_items;
|
||||
};
|
||||
|
@ -199,6 +199,11 @@ WebTab::WebTab(QWidget *parent)
|
||||
});
|
||||
}
|
||||
|
||||
BrowserWindow *WebTab::browserWindow() const
|
||||
{
|
||||
return m_window;
|
||||
}
|
||||
|
||||
TabbedWebView* WebTab::webView() const
|
||||
{
|
||||
return m_webView;
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
|
||||
explicit WebTab(QWidget *parent = nullptr);
|
||||
|
||||
BrowserWindow *browserWindow() const;
|
||||
TabbedWebView* webView() const;
|
||||
LocationBar* locationBar() const;
|
||||
TabIcon* tabIcon() const;
|
||||
|
Loading…
Reference in New Issue
Block a user