diff options
author | Roland Knall <rknall@gmail.com> | 2022-06-01 16:28:13 +0200 |
---|---|---|
committer | Roland Knall <rknall@gmail.com> | 2022-06-04 21:28:05 +0200 |
commit | b06c1c451cec88e9cc1931d1cd8f2ce8e09ce129 (patch) | |
tree | 7328b89c14ea6d7eab056fec9652a90c5af889d9 | |
parent | 9edf06383acdea1fa8e22ebabda134a0c0fd5df8 (diff) |
Qt: Make TrafficTable detachable
Allow the endpoint and conversation dialogs to have detachable
tabs. At the same time move the tree functionality to a subclass
to better be able to handle the context menu when detached.
Right now, still a lot of tree stuff is in the tabwidget, but
could be moved to the tree for the future
-rw-r--r-- | docbook/release-notes.adoc | 2 | ||||
-rw-r--r-- | ui/logwolf/CMakeLists.txt | 2 | ||||
-rw-r--r-- | ui/qt/CMakeLists.txt | 2 | ||||
-rw-r--r-- | ui/qt/widgets/detachable_tabwidget.cpp | 212 | ||||
-rw-r--r-- | ui/qt/widgets/detachable_tabwidget.h | 87 | ||||
-rw-r--r-- | ui/qt/widgets/traffic_tab.cpp | 94 | ||||
-rw-r--r-- | ui/qt/widgets/traffic_tab.h | 14 | ||||
-rw-r--r-- | ui/qt/widgets/traffic_tree.cpp | 7 | ||||
-rw-r--r-- | ui/qt/widgets/traffic_tree.h | 1 |
9 files changed, 401 insertions, 20 deletions
diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index 8783fc8f2e..997bee1ed6 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -33,6 +33,8 @@ wsbuglink:17779[] * The Conversation and Endpoint dialogs have been redesigned with the following improvements: - The context menu now includes the option to resize all columns, as well as copying elements - Data may be exported as Json + - Tabs may be detached and reattached from the dialog + - Adding/Removing tabs will keep them in the same order all the time Many improvements have been made. See the “New and Updated Features” section below for more details. diff --git a/ui/logwolf/CMakeLists.txt b/ui/logwolf/CMakeLists.txt index 1b72e95431..0a0b002a0e 100644 --- a/ui/logwolf/CMakeLists.txt +++ b/ui/logwolf/CMakeLists.txt @@ -23,6 +23,7 @@ set(WIRESHARK_WIDGET_HEADERS ../qt/widgets/capture_filter_edit.h ../qt/widgets/clickable_label.h ../qt/widgets/copy_from_profile_button.h + ../qt/widgets/detachable_tabwidget.h ../qt/widgets/display_filter_combo.h ../qt/widgets/display_filter_edit.h ../qt/widgets/dissector_tables_view.h @@ -249,6 +250,7 @@ set(WIRESHARK_WIDGET_SRCS ../qt/widgets/capture_filter_edit.cpp ../qt/widgets/clickable_label.cpp ../qt/widgets/copy_from_profile_button.cpp + ../qt/widgets/detachable_tabwidget.cpp ../qt/widgets/display_filter_combo.cpp ../qt/widgets/display_filter_edit.cpp ../qt/widgets/dissector_tables_view.cpp diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index 168a9a3baf..77d4452a62 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -23,6 +23,7 @@ set(WIRESHARK_WIDGET_HEADERS widgets/capture_filter_edit.h widgets/clickable_label.h widgets/copy_from_profile_button.h + widgets/detachable_tabwidget.h widgets/display_filter_combo.h widgets/display_filter_edit.h widgets/dissector_tables_view.h @@ -274,6 +275,7 @@ set(WIRESHARK_WIDGET_SRCS widgets/capture_filter_edit.cpp widgets/clickable_label.cpp widgets/copy_from_profile_button.cpp + widgets/detachable_tabwidget.cpp widgets/display_filter_combo.cpp widgets/display_filter_edit.cpp widgets/dissector_tables_view.cpp diff --git a/ui/qt/widgets/detachable_tabwidget.cpp b/ui/qt/widgets/detachable_tabwidget.cpp new file mode 100644 index 0000000000..0dd62a2ffb --- /dev/null +++ b/ui/qt/widgets/detachable_tabwidget.cpp @@ -0,0 +1,212 @@ +/* @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <ui/qt/widgets/detachable_tabwidget.h> + +#include <QStackedWidget> +#include <QBoxLayout> +#include <QEvent> +#include <QCloseEvent> +#include <QMouseEvent> +#include <QDragEnterEvent> +#include <QDropEvent> +#include <QMimeData> +#include <QStringList> +#include <QApplication> +#include <QDrag> +#include <QPixmap> +#include <QPainter> + +DetachableTabWidget::DetachableTabWidget(QWidget *parent) : + QTabWidget(parent) +{ + DragDropTabBar * tabBar = new DragDropTabBar(this); + connect(tabBar, &DragDropTabBar::onDetachTab, this, &DetachableTabWidget::detachTab); + connect(tabBar, &DragDropTabBar::onMoveTab, this, &DetachableTabWidget::moveTab); + + setMovable(false); + + setTabBar(tabBar); +} + +void DetachableTabWidget::setTabBasename(QString newName) { + _tabBasename = newName; +} + +QString DetachableTabWidget::tabBasename() const { + return _tabBasename; +} + +void DetachableTabWidget::moveTab(int from, int to) +{ + QWidget * contentWidget = widget(from); + QString text = tabText(from); + + removeTab(from); + insertTab(to, contentWidget, text); + setCurrentIndex(to); +} + +void DetachableTabWidget::detachTab(int tabIdx, QPoint pos) +{ + QString name = tabText(tabIdx); + + QWidget * contentWidget = widget(tabIdx); + + /* For the widget to properly show in the dialog, it has to be + * removed properly and unhidden. QTabWidget uses a QStackedWidget for + * all parents of widgets. So we remove it from it's own parent and then + * unhide it to show the widget in the dialog */ + QStackedWidget * par = qobject_cast<QStackedWidget *>(contentWidget->parent()); + if (!par) + return; + QRect contentWidgetRect = par->frameGeometry(); + par->removeWidget(contentWidget); + contentWidget->setHidden(false); + + ToolDialog * detachedTab = new ToolDialog(contentWidget, parentWidget()); + detachedTab->setWindowModality(Qt::NonModal); + detachedTab->setWindowTitle(_tabBasename + ": " + name); + detachedTab->setObjectName(name); + detachedTab->setGeometry(contentWidgetRect); + connect(detachedTab, &ToolDialog::onCloseSignal, this, &DetachableTabWidget::attachTab); + detachedTab->move(pos); + detachedTab->show(); +} + +void DetachableTabWidget::attachTab(QWidget * content, QString name) +{ + content->setParent(this); + + int index = addTab(content, name); + if (index > -1) + setCurrentIndex(index); +} + +ToolDialog::ToolDialog(QWidget *contentWidget, QWidget *parent, Qt::WindowFlags f) : + QDialog(parent, f) +{ + _contentWidget = contentWidget; + + _contentWidget->setParent(this); + QVBoxLayout * layout = new QVBoxLayout(this); + layout->addWidget(_contentWidget); + this->setLayout(layout); +} + +bool ToolDialog::event(QEvent *event) +{ + /** + * Capture a double click event on the dialog's window frame + */ + if (event->type() == QEvent::NonClientAreaMouseButtonDblClick) { + event->accept(); + close(); + } + + return QDialog::event(event); +} + +void ToolDialog::closeEvent(QCloseEvent * /*event*/) +{ + emit onCloseSignal(_contentWidget, objectName()); +} + +DragDropTabBar::DragDropTabBar(QWidget *parent) : + QTabBar(parent) +{ + setAcceptDrops(true); + setElideMode(Qt::ElideRight); + setSelectionBehaviorOnRemove(QTabBar::SelectLeftTab); + + _dragStartPos = QPoint(); + _dragDropPos = QPoint(); + _mouseCursor = QCursor(); + _dragInitiated = false; +} + +void DragDropTabBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + event->accept(); + emit onDetachTab(tabAt(event->pos()), _mouseCursor.pos()); +} + +void DragDropTabBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + _dragStartPos = event->pos(); + + _dragDropPos = QPoint(0, 0); + _dragInitiated = false; + + QTabBar::mousePressEvent(event); +} + +void DragDropTabBar::mouseMoveEvent(QMouseEvent *event) +{ + if (!_dragStartPos.isNull() && + ((event->pos() - _dragStartPos).manhattanLength() > QApplication::startDragDistance())) + _dragInitiated = true; + + if ((event->buttons() & Qt::LeftButton) && _dragInitiated) { + QMouseEvent * finishMouseMove = new QMouseEvent(QEvent::MouseMove, event->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); + QTabBar::mouseMoveEvent(finishMouseMove); + + QDrag * drag = new QDrag(this); + QMimeData * mimeData = new QMimeData(); + mimeData->setData("action", "application/tab-detach"); + drag->setMimeData(mimeData); + + QWidget * original = parentWidget(); + if (qobject_cast<DetachableTabWidget *>(original)) { + DetachableTabWidget * tabWidget = qobject_cast<DetachableTabWidget *>(original); + original = tabWidget->widget(tabWidget->currentIndex()); + } + QPixmap pixmap = original->grab(); + QPixmap targetPixmap = QPixmap(pixmap.size()); + targetPixmap.fill(Qt::transparent); + + QPainter painter(&targetPixmap); + painter.setOpacity(0.85); + painter.drawPixmap(0, 0, pixmap); + painter.end(); + drag->setPixmap(targetPixmap); + + Qt::DropAction dropAction = drag->exec(Qt::MoveAction | Qt::CopyAction); + if (dropAction == Qt::IgnoreAction) { + event->accept(); + emit onDetachTab(tabAt(_dragStartPos), _mouseCursor.pos()); + } if (dropAction == Qt::MoveAction) { + if (! _dragDropPos.isNull()) { + event->accept(); + emit onMoveTab(tabAt(_dragStartPos), tabAt(_dragDropPos)); + } + } + } else + QTabBar::mouseMoveEvent(event); +} + +void DragDropTabBar::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData * mimeData = event->mimeData(); + QStringList formats = mimeData->formats(); + + if (formats.contains("action") && mimeData->data("action") == "application/tab-detach") + event->acceptProposedAction(); +} + +void DragDropTabBar::dropEvent(QDropEvent *event) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + _dragDropPos = event->position().toPoint(); +#else + _dragDropPos = event->pos(); +#endif + QTabBar::dropEvent(event); +} diff --git a/ui/qt/widgets/detachable_tabwidget.h b/ui/qt/widgets/detachable_tabwidget.h new file mode 100644 index 0000000000..4b5220439f --- /dev/null +++ b/ui/qt/widgets/detachable_tabwidget.h @@ -0,0 +1,87 @@ +/* @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DETACHABLE_TABWIDGET_H +#define DETACHABLE_TABWIDGET_H + +#include <QTabWidget> +#include <QDialog> +#include <QEvent> +#include <QCloseEvent> +#include <QTabBar> +#include <QPoint> +#include <QCursor> + +class DetachableTabWidget : public QTabWidget +{ + Q_OBJECT +public: + DetachableTabWidget(QWidget * parent = nullptr); + + QString tabBasename() const; + +protected: + + void setTabBasename(QString newName); + +protected slots: + + virtual void moveTab(int from, int to); + virtual void detachTab(int tabIdx, QPoint pos); + virtual void attachTab(QWidget * content, QString name); + +private: + QString _tabBasename; + +}; + +class ToolDialog : public QDialog +{ + Q_OBJECT +public: + explicit ToolDialog(QWidget * _contentWidget, QWidget * parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + +protected: + + virtual bool event(QEvent *event); + virtual void closeEvent(QCloseEvent *event); + +signals: + void onCloseSignal(QWidget * contentWidget, QString name); + +private: + QWidget * _contentWidget; +}; + +class DragDropTabBar : public QTabBar +{ + Q_OBJECT +public: + explicit DragDropTabBar(QWidget * parent); + +signals: + void onDetachTab(int tabIdx, QPoint pos); + void onMoveTab(int oldIdx, int newIdx); + +protected: + virtual void mouseDoubleClickEvent(QMouseEvent *event); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dropEvent(QDropEvent *event); + +private: + QPoint _dragStartPos; + QPoint _dragDropPos; + QCursor _mouseCursor; + bool _dragInitiated; + +}; + +#endif // DETACHABLE_TABWIDGET_H diff --git a/ui/qt/widgets/traffic_tab.cpp b/ui/qt/widgets/traffic_tab.cpp index 9230c4c54e..0d13b0f063 100644 --- a/ui/qt/widgets/traffic_tab.cpp +++ b/ui/qt/widgets/traffic_tab.cpp @@ -24,6 +24,7 @@ #include <ui/qt/utils/variant_pointer.h> #include <ui/qt/widgets/traffic_tab.h> #include <ui/qt/widgets/traffic_tree.h> +#include <ui/qt/widgets/detachable_tabwidget.h> #include <QVector> #include <QStringList> @@ -77,23 +78,24 @@ static gboolean iterateProtocols(const void *key, void *value, void *userdata) } TrafficTab::TrafficTab(QWidget * parent) : - QTabWidget(parent) + DetachableTabWidget(parent) { _createModel = nullptr; _disableTaps = false; _nameResolution = false; - _tableName = QString(); _cliId = 0; _recentList = nullptr; + setTabBasename(QString()); + } TrafficTab::~TrafficTab() { prefs_clear_string_list(*_recentList); *_recentList = NULL; + _protocolButtons.clear(); - QList<int> protocols = _tabs.keys(); - foreach (int protoId, protocols) + foreach (int protoId, _tabs.keys()) { char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(protoId))); *_recentList = g_list_append(*_recentList, title); @@ -102,7 +104,7 @@ TrafficTab::~TrafficTab() void TrafficTab::setProtocolInfo(QString tableName, int cliId, GList ** recentList, ATapModelCallback createModel) { - _tableName = tableName; + setTabBasename(tableName); _cliId = cliId; _recentList = recentList; if (createModel) @@ -145,6 +147,7 @@ void TrafficTab::setProtocolInfo(QString tableName, int cliId, GList ** recentLi endPoint->setCheckable(true); endPoint->setChecked(_protocols.contains(protoId)); connect(endPoint, &QAction::triggered, this, &TrafficTab::toggleTab); + _protocolButtons.insert(protoId, endPoint); cornerMenu->addAction(endPoint); } cornerButton->setMenu(cornerMenu); @@ -199,8 +202,8 @@ void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate) QTreeView * TrafficTab::createTree(int protoId) { - TrafficTree * tree = new TrafficTree(_tableName, this); - + TrafficTree * tree = new TrafficTree(tabBasename(), this); + if (_createModel) { ATapDataModel * model = _createModel(protoId, ""); connect(model, &ATapDataModel::tapListenerChanged, tree, &TrafficTree::tapListenerEnabled); @@ -269,15 +272,31 @@ void TrafficTab::disableTap() _disableTaps = true; cornerWidget()->setEnabled(false); + emit disablingTaps(); } void TrafficTab::updateTabs() { QList<int> keys = _tabs.keys(); + QList<int> allProtocols = _allTaps.keys(); - /* Adding new Tabs */ + /* Adding new Tabs, and keeping the same order they are in the drop-down menu */ foreach (int proto, _protocols) { if (!keys.contains(proto)) { + + int insertIndex = -1; + auto bIdx = allProtocols.indexOf(proto); + int idx = 0; + while (insertIndex < 0 && idx < keys.count()) + { + auto aIdx = allProtocols.indexOf(keys[idx]); + if (aIdx < 0) /* Key not in all protocols. This would be a fluke */ + break; + if (aIdx > bIdx) /* Should never be equal, as proto is not yet in keys */ + insertIndex = _tabs[keys[idx]]; + idx++; + } + QTreeView * tree = createTree(proto); QString tableName = proto_get_protocol_short_name(find_protocol_by_id(proto)); TabData tabData(tableName, proto); @@ -286,23 +305,29 @@ void TrafficTab::updateTabs() if (tree->model()->rowCount() > 0) tableName += QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(tree->model()->rowCount()); - int tabId = addTab(tree, tableName); + int tabId = insertTab(insertIndex, tree, tableName); + _protocolButtons[proto]->setChecked(true); tabBar()->setTabData(tabId, storage); } } - /* Removing tabs no longer required */ + /* Removing tabs no longer required. First filter the key array, for all tabs which + * are still being displayed */ foreach(int key, keys) { - if ( _protocols.contains(key)) + if ( _protocols.contains(key)) { + _protocolButtons[key]->setChecked(true); keys.removeAll(key); + } } - - /* Counting down, otherwise removing a tab will shift the indeces */ + /* Removal step 2, now actually remove all elements. Counting down, otherwise removing + * a tab will shift the indeces */ for(int idx = count(); idx > 0; idx--) { TabData tabData = qvariant_cast<TabData>(tabBar()->tabData(idx - 1)); - if (keys.contains(tabData.protoId())) + if (keys.contains(tabData.protoId())) { removeTab(idx - 1); + _protocolButtons[tabData.protoId()]->setChecked(false); + } } /* We reset the correct tab idxs. That operations is costly, but it is only @@ -384,8 +409,13 @@ ATapDataModel * TrafficTab::modelForTabIndex(int tabIdx) if (tabIdx == -1) tabIdx = currentIndex(); - if (qobject_cast<QTreeView *>(widget(tabIdx))) { - QTreeView * tree = qobject_cast<QTreeView *>(widget(tabIdx)); + return modelForWidget(widget(tabIdx)); +} + +ATapDataModel * TrafficTab::modelForWidget(QWidget * searchWidget) +{ + if (qobject_cast<QTreeView *>(searchWidget)) { + QTreeView * tree = qobject_cast<QTreeView *>(searchWidget); if (qobject_cast<QSortFilterProxyModel *>(tree->model())) { QSortFilterProxyModel * qsfpm = qobject_cast<QSortFilterProxyModel *>(tree->model()); if (qobject_cast<ATapDataModel *>(qsfpm->sourceModel())) { @@ -599,3 +629,35 @@ QUrl TrafficTab::createGeoIPMap(bool json_only, int tabIdx) return QUrl::fromLocalFile(tf.fileName()); } #endif + +void TrafficTab::detachTab(int tabIdx, QPoint pos) { + ATapDataModel * model = modelForTabIndex(tabIdx); + if (!model) + return; + + int protocol = model->protoId(); + _protocols.removeAll(protocol); + + TrafficTree * tree = qobject_cast<TrafficTree *>(widget(tabIdx)); + if (!tree) + return; + + connect(this, &TrafficTab::disablingTaps ,tree , &TrafficTree::disableTap); + DetachableTabWidget::detachTab(tabIdx, pos); + + updateTabs(); +} + +void TrafficTab::attachTab(QWidget * content, QString name) +{ + ATapDataModel * model = modelForWidget(content); + if (!model) { + attachTab(content, name); + return; + } + + int protocol = model->protoId(); + _protocols.append(protocol); + + updateTabs(); +} diff --git a/ui/qt/widgets/traffic_tab.h b/ui/qt/widgets/traffic_tab.h index 778ae98c81..b864e0f2a6 100644 --- a/ui/qt/widgets/traffic_tab.h +++ b/ui/qt/widgets/traffic_tab.h @@ -16,6 +16,7 @@ #include <ui/qt/models/atap_data_model.h> #include <ui/qt/filter_action.h> +#include <ui/qt/widgets/detachable_tabwidget.h> #include <QTabWidget> #include <QTreeView> @@ -69,12 +70,11 @@ Q_DECLARE_METATYPE(TabData) * removing the need of the dialog to know how data is being stored or * generated. */ -class TrafficTab : public QTabWidget +class TrafficTab : public DetachableTabWidget { Q_OBJECT public: - TrafficTab(QWidget *parent = nullptr); virtual ~TrafficTab(); @@ -204,12 +204,18 @@ signals: void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); void tabDataChanged(int idx); void retapRequired(); + void disablingTaps(); + +protected slots: + + virtual void detachTab(int idx, QPoint pos) override; + virtual void attachTab(QWidget * content, QString name) override; private: - QString _tableName; int _cliId; QVector<int> _protocols; QMap<int, QString> _allTaps; + QMap<int, QAction *> _protocolButtons; QMap<int, int> _tabs; GList ** _recentList; ATapModelCallback _createModel; @@ -221,7 +227,7 @@ private: void updateTabs(); QTreeView * createTree(int protoId); ATapDataModel * modelForTabIndex(int tabIdx = -1); - + ATapDataModel * modelForWidget(QWidget * widget); #ifdef HAVE_MAXMINDDB bool writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel); diff --git a/ui/qt/widgets/traffic_tree.cpp b/ui/qt/widgets/traffic_tree.cpp index a04e0b7ff2..25d08ad9d8 100644 --- a/ui/qt/widgets/traffic_tree.cpp +++ b/ui/qt/widgets/traffic_tree.cpp @@ -269,3 +269,10 @@ void TrafficTree::copyToClipboard(eTrafficTreeClipboard type) mainApp->clipboard()->setText(stream.readAll()); } +void TrafficTree::disableTap() +{ + ATapDataModel * model = dataModel(); + if (!model) + return; + model->disableTap(); +} diff --git a/ui/qt/widgets/traffic_tree.h b/ui/qt/widgets/traffic_tree.h index 48946db1c8..34f87b6f37 100644 --- a/ui/qt/widgets/traffic_tree.h +++ b/ui/qt/widgets/traffic_tree.h @@ -53,6 +53,7 @@ signals: public slots: void tapListenerEnabled(bool enable); + void disableTap(); private: bool _tapEnabled; |