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