From 9edf06383acdea1fa8e22ebabda134a0c0fd5df8 Mon Sep 17 00:00:00 2001 From: Roland Knall Date: Sat, 4 Jun 2022 14:48:37 +0200 Subject: [PATCH] Qt: Move most Contextmenu stuff to TrafficTree Create a new class, which handles the context menu stuff for the traffic data, as well as remove unnecessary signals used by the sub-dialogs. --- docbook/release-notes.adoc | 4 + ui/logwolf/CMakeLists.txt | 2 + ui/qt/CMakeLists.txt | 2 + ui/qt/conversation_dialog.cpp | 7 +- ui/qt/conversation_dialog.h | 1 - ui/qt/endpoint_dialog.cpp | 9 +- ui/qt/endpoint_dialog.h | 1 - ui/qt/models/atap_data_model.cpp | 4 + ui/qt/models/atap_data_model.h | 3 + ui/qt/widgets/traffic_tab.cpp | 240 ++------------------------- ui/qt/widgets/traffic_tab.h | 36 +--- ui/qt/widgets/traffic_tree.cpp | 271 +++++++++++++++++++++++++++++++ ui/qt/widgets/traffic_tree.h | 77 +++++++++ 13 files changed, 381 insertions(+), 276 deletions(-) create mode 100644 ui/qt/widgets/traffic_tree.cpp create mode 100644 ui/qt/widgets/traffic_tree.h diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index a9473e9f11..8783fc8f2e 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -30,6 +30,10 @@ wsbuglink:17779[] Code using the Lua GRegex module will have to be updated to use lrexlib-pcre2 instead. In most cases the API should be compatible and the conversion just requires a module name change. +* 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 + 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 006ca69282..1b72e95431 100644 --- a/ui/logwolf/CMakeLists.txt +++ b/ui/logwolf/CMakeLists.txt @@ -50,6 +50,7 @@ set(WIRESHARK_WIDGET_HEADERS ../qt/widgets/wireless_timeline.h # Required by PacketListModel ../qt/widgets/tabnav_tree_view.h ../qt/widgets/traffic_tab.h + ../qt/widgets/traffic_tree.h ../qt/widgets/wireshark_file_dialog.h ) @@ -275,6 +276,7 @@ set(WIRESHARK_WIDGET_SRCS ../qt/widgets/wireless_timeline.cpp # Required by PacketListModel ../qt/widgets/tabnav_tree_view.cpp ../qt/widgets/traffic_tab.cpp + ../qt/widgets/traffic_tree.cpp ../qt/widgets/wireshark_file_dialog.cpp ) diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index 547841ad3c..168a9a3baf 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -50,6 +50,7 @@ set(WIRESHARK_WIDGET_HEADERS widgets/syntax_line_edit.h widgets/tabnav_tree_view.h widgets/traffic_tab.h + widgets/traffic_tree.h widgets/wireless_timeline.h widgets/wireshark_file_dialog.h ) @@ -300,6 +301,7 @@ set(WIRESHARK_WIDGET_SRCS widgets/syntax_line_edit.cpp widgets/tabnav_tree_view.cpp widgets/traffic_tab.cpp + widgets/traffic_tree.cpp widgets/wireless_timeline.cpp widgets/wireshark_file_dialog.cpp ) diff --git a/ui/qt/conversation_dialog.cpp b/ui/qt/conversation_dialog.cpp index c264b530d5..ec6d5794e2 100644 --- a/ui/qt/conversation_dialog.cpp +++ b/ui/qt/conversation_dialog.cpp @@ -125,15 +125,10 @@ ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf, int cli void ConversationDialog::captureFileClosing() { trafficTab()->disableTap(); - TrafficTableDialog::captureFileClosing(); -} - -void ConversationDialog::captureFileClosed() -{ displayFilterCheckBox()->setEnabled(false); follow_bt_->setEnabled(false); graph_bt_->setEnabled(false); - TrafficTableDialog::captureFileClosed(); + TrafficTableDialog::captureFileClosing(); } void ConversationDialog::followStream() diff --git a/ui/qt/conversation_dialog.h b/ui/qt/conversation_dialog.h index d1487a4c3b..a1c3336ca8 100644 --- a/ui/qt/conversation_dialog.h +++ b/ui/qt/conversation_dialog.h @@ -28,7 +28,6 @@ public: protected: void captureFileClosing(); - void captureFileClosed(); signals: void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); diff --git a/ui/qt/endpoint_dialog.cpp b/ui/qt/endpoint_dialog.cpp index 57c9eedf8c..5f07b6a83a 100644 --- a/ui/qt/endpoint_dialog.cpp +++ b/ui/qt/endpoint_dialog.cpp @@ -83,6 +83,7 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_i connect(action, &QAction::triggered, this, &EndpointDialog::saveMap); map_bt_->setMenu(map_menu_); #endif + addProgressFrame(&parent); QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close); @@ -96,14 +97,8 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_i void EndpointDialog::captureFileClosing() { trafficTab()->disableTap(); - - TrafficTableDialog::captureFileClosing(); -} - -void EndpointDialog::captureFileClosed() -{ displayFilterCheckBox()->setEnabled(false); - TrafficTableDialog::captureFileClosed(); + TrafficTableDialog::captureFileClosing(); } void EndpointDialog::tabChanged(int idx) diff --git a/ui/qt/endpoint_dialog.h b/ui/qt/endpoint_dialog.h index cc8cab33e3..c4372db04a 100644 --- a/ui/qt/endpoint_dialog.h +++ b/ui/qt/endpoint_dialog.h @@ -33,7 +33,6 @@ signals: protected: void captureFileClosing(); - void captureFileClosed(); private: #ifdef HAVE_MAXMINDDB diff --git a/ui/qt/models/atap_data_model.cpp b/ui/qt/models/atap_data_model.cpp index 6b7538bb3a..73a23e712c 100644 --- a/ui/qt/models/atap_data_model.cpp +++ b/ui/qt/models/atap_data_model.cpp @@ -106,12 +106,15 @@ bool ATapDataModel::enableTap() &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr); if (errorString && errorString->len > 0) { _disableTap = true; + emit tapListenerChanged(false); return false; } if (errorString) g_string_free(errorString, TRUE); + emit tapListenerChanged(true); + return true; } @@ -121,6 +124,7 @@ void ATapDataModel::disableTap() if (!_disableTap) remove_tap_listener(hash()); _disableTap = true; + emit tapListenerChanged(false); } int ATapDataModel::rowCount(const QModelIndex &) const diff --git a/ui/qt/models/atap_data_model.h b/ui/qt/models/atap_data_model.h index 4a47f06784..f951e23f56 100644 --- a/ui/qt/models/atap_data_model.h +++ b/ui/qt/models/atap_data_model.h @@ -187,6 +187,9 @@ public: bool hasGeoIPData(); #endif +signals: + void tapListenerChanged(bool enable); + protected: static void tapReset(void *tapdata); diff --git a/ui/qt/widgets/traffic_tab.cpp b/ui/qt/widgets/traffic_tab.cpp index 22562906e4..9230c4c54e 100644 --- a/ui/qt/widgets/traffic_tab.cpp +++ b/ui/qt/widgets/traffic_tab.cpp @@ -18,11 +18,12 @@ #include "ui/recent.h" -#include -#include #include -#include #include +#include +#include +#include +#include #include #include @@ -83,8 +84,6 @@ TrafficTab::TrafficTab(QWidget * parent) : _nameResolution = false; _tableName = QString(); _cliId = 0; - _saveRaw = true; - _exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA; _recentList = nullptr; } @@ -93,7 +92,7 @@ TrafficTab::~TrafficTab() prefs_clear_string_list(*_recentList); *_recentList = NULL; - QVector protocols = selectedProtocols(); + QList protocols = _tabs.keys(); foreach (int protoId, protocols) { char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(protoId))); @@ -200,13 +199,13 @@ void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate) QTreeView * TrafficTab::createTree(int protoId) { - QTreeView * tree = new QTreeView(this); + TrafficTree * tree = new TrafficTree(_tableName, this); + if (_createModel) { ATapDataModel * model = _createModel(protoId, ""); + connect(model, &ATapDataModel::tapListenerChanged, tree, &TrafficTree::tapListenerEnabled); + model->enableTap(); - tree->setAlternatingRowColors(true); - tree->setRootIsDecorated(false); - tree->setSortingEnabled(true); foreach(int col, _createDelegates.keys()) { @@ -217,8 +216,6 @@ QTreeView * TrafficTab::createTree(int protoId) } } - tree->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree, &QTreeView::customContextMenuRequested, this, &TrafficTab::customContextMenuRequested); QSortFilterProxyModel * proxyModel = new QSortFilterProxyModel(); proxyModel->setSourceModel(model); tree->setModel(proxyModel); @@ -241,12 +238,6 @@ QTreeView * TrafficTab::createTree(int protoId) return tree; } -QVector TrafficTab::selectedProtocols() const -{ - QVector result = QVector::fromList(_tabs.keys()); - return result; -} - void TrafficTab::useAbsoluteTime(bool absolute) { for(int idx = 0; idx < count(); idx++) @@ -447,216 +438,13 @@ bool TrafficTab::hasNameResolution(int tabIdx) return dataModel->allowsNameResolution(); } -static QMap fad_to_cd_; - -static void initDirection() -{ - if (fad_to_cd_.count() == 0) { - fad_to_cd_[FilterAction::ActionDirectionAToFromB] = CONV_DIR_A_TO_FROM_B; - fad_to_cd_[FilterAction::ActionDirectionAToB] = CONV_DIR_A_TO_B; - fad_to_cd_[FilterAction::ActionDirectionAFromB] = CONV_DIR_A_FROM_B; - fad_to_cd_[FilterAction::ActionDirectionAToFromAny] = CONV_DIR_A_TO_FROM_ANY; - fad_to_cd_[FilterAction::ActionDirectionAToAny] = CONV_DIR_A_TO_ANY; - fad_to_cd_[FilterAction::ActionDirectionAFromAny] = CONV_DIR_A_FROM_ANY; - fad_to_cd_[FilterAction::ActionDirectionAnyToFromB] = CONV_DIR_ANY_TO_FROM_B; - fad_to_cd_[FilterAction::ActionDirectionAnyToB] = CONV_DIR_ANY_TO_B; - fad_to_cd_[FilterAction::ActionDirectionAnyFromB] = CONV_DIR_ANY_FROM_B; - } -} - -QMenu * TrafficTab::createActionSubMenu(FilterAction::Action cur_action, QModelIndex idx, bool isConversation) -{ - initDirection(); - - conv_item_t * conv_item = nullptr; - if (isConversation) - { - ConversationDataModel * model = qobject_cast(modelForTabIndex()); - if (model) - conv_item = model->itemForRow(idx.row()); - } - - QMenu * subMenu = new QMenu(FilterAction::actionName(cur_action)); - subMenu->setEnabled(!_disableTaps); - foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { - if (isConversation && conv_item) { - QMenu *subsubmenu = subMenu->addMenu(FilterAction::actionTypeName(at)); - subsubmenu->setEnabled(!_disableTaps); - foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) { - FilterAction *fa = new FilterAction(subsubmenu, cur_action, at, ad); - fa->setEnabled(!_disableTaps); - QString filter = get_conversation_filter(conv_item, (conv_direction_e) fad_to_cd_[fa->actionDirection()]); - fa->setProperty("filter", filter); - subsubmenu->addAction(fa); - connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); - } - } else { - FilterAction *fa = new FilterAction(subMenu, cur_action, at); - fa->setEnabled(!_disableTaps); - fa->setProperty("filter", idx.data(ATapDataModel::DISPLAY_FILTER)); - subMenu->addAction(fa); - - connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); - } - } - - return subMenu; -} - -void TrafficTab::customContextMenuRequested(const QPoint &pos) -{ - if (! qobject_cast(sender())) - return; - - QTreeView * tree = qobject_cast(sender()); - QModelIndex idx = tree->indexAt(pos); - - QMenu ctxMenu; - - bool isConv = false; - QSortFilterProxyModel * proxy = qobject_cast(tree->model()); - if (proxy) { - ConversationDataModel * model = qobject_cast(proxy->sourceModel()); - if (model) - isConv = true; - } - - ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionApply, idx, isConv)); - ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionPrepare, idx, isConv)); - ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionFind, idx, isConv)); - ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionColorize, idx, isConv)); - - ctxMenu.addSeparator(); - ctxMenu.addMenu(createCopyMenu()); - - ctxMenu.addSeparator(); - QAction * act = ctxMenu.addAction(tr("Resize all columns to content")); - connect(act, &QAction::triggered, [tree]() { - for (int col = 0; col < tree->model()->columnCount(); col++) - tree->resizeColumnToContents(col); - }); - - ctxMenu.exec(tree->mapToGlobal(pos)); -} - -void TrafficTab::filterActionTriggered() -{ - FilterAction *fa = qobject_cast(sender()); - if (!fa) - return; - - QString filter = fa->property("filter").toString(); - if (filter.length() > 0) - emit filterAction(filter, fa->action(), fa->actionType()); -} - QMenu * TrafficTab::createCopyMenu(QWidget *parent) { - QMenu *copy_menu = new QMenu(tr("Copy %1 table").arg(_tableName), parent); - QAction *ca; - ca = copy_menu->addAction(tr("as CSV")); - ca->setToolTip(tr("Copy all values of this page to the clipboard in CSV (Comma Separated Values) format.")); - ca->setProperty("copy_as", TrafficTab::CLIPBOARD_CSV); - connect(ca, &QAction::triggered, this, &TrafficTab::clipboardAction); - ca = copy_menu->addAction(tr("as YAML")); - ca->setToolTip(tr("Copy all values of this page to the clipboard in the YAML data serialization format.")); - ca->setProperty("copy_as", TrafficTab::CLIPBOARD_YAML); - connect(ca, &QAction::triggered, this, &TrafficTab::clipboardAction); - ca = copy_menu->addAction(tr("as Json")); - ca->setToolTip(tr("Copy all values of this page to the clipboard in the Json data serialization format.")); - ca->setProperty("copy_as", TrafficTab::CLIPBOARD_JSON); - connect(ca, &QAction::triggered, this, &TrafficTab::clipboardAction); + TrafficTree * tree = qobject_cast(currentWidget()); + if ( ! tree) + return nullptr; - copy_menu->addSeparator(); - ca = copy_menu->addAction(tr("Save data as raw")); - ca->setToolTip(tr("Disable data formatting for export/clipboard and save as raw data")); - ca->setCheckable(true); - ca->setChecked(_exportRole == ATapDataModel::UNFORMATTED_DISPLAYDATA); - connect(ca, &QAction::triggered, this, &TrafficTab::toggleSaveRaw); - - return copy_menu; -} - -void TrafficTab::toggleSaveRaw() -{ - if (_exportRole == ATapDataModel::UNFORMATTED_DISPLAYDATA) - _exportRole = Qt::DisplayRole; - else - _exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA; -} - -void TrafficTab::clipboardAction() -{ - QAction * ca = qobject_cast(sender()); - if (ca && ca->property("copy_as").isValid()) - copyToClipboard((eTrafficTabClipboard)ca->property("copy_as").toInt()); -} - -void TrafficTab::copyToClipboard(eTrafficTabClipboard type, int tabIdx) -{ - int idx = tabIdx < 0 || tabIdx >= count() ? currentIndex() : tabIdx; - - QSortFilterProxyModel * model = nullptr; - if (qobject_cast(widget(idx))) { - QTreeView * tree = qobject_cast(widget(idx)); - if (qobject_cast(tree->model())) { - model = qobject_cast(tree->model()); - } - } - if (!model) - return; - - QString clipText; - QTextStream stream(&clipText, QIODevice::Text); - - if (type == CLIPBOARD_CSV) { - for (int row = 0; row < model->rowCount(); row++) { - QStringList rdsl; - for (int col = 0; col < model->columnCount(); col++) { - QModelIndex idx = model->index(row, col); - QVariant v = model->data(idx, _exportRole); - if (!v.isValid()) { - rdsl << "\"\""; - } else if (v.userType() == QMetaType::QString) { - rdsl << QString("\"%1\"").arg(v.toString()); - } else { - rdsl << v.toString(); - } - } - stream << rdsl.join(",") << '\n'; - } - } else if (type == CLIPBOARD_YAML) { - stream << "---" << '\n'; - for (int row = 0; row < model->rowCount(); row++) { - stream << "-" << '\n'; - for (int col = 0; col < model->columnCount(); col++) { - QModelIndex idx = model->index(row, col); - QVariant v = model->data(idx, _exportRole); - stream << " - " << v.toString() << '\n'; - } - } - } else if (type == CLIPBOARD_JSON) { - QMap headers; - for (int cnt = 0; cnt < model->columnCount(); cnt++) - headers.insert(cnt, model->headerData(cnt, Qt::Horizontal, Qt::DisplayRole).toString()); - - QJsonArray records; - - for (int row = 0; row < model->rowCount(); row++) { - QJsonObject rowData; - foreach(int col, headers.keys()) { - QModelIndex idx = model->index(row, col); - rowData.insert(headers[col], model->data(idx, _exportRole).toString()); - } - records.push_back(rowData); - } - - QJsonDocument json; - json.setArray(records); - stream << json.toJson(); - } - - mainApp->clipboard()->setText(stream.readAll()); + return tree->createCopyMenu(parent); } #ifdef HAVE_MAXMINDDB @@ -790,7 +578,7 @@ QUrl TrafficTab::createGeoIPMap(bool json_only, int tabIdx) { int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx; ATapDataModel * dataModel = modelForTabIndex(tab); - if (! dataModel || ! hasGeoIPData(tabIdx)) { + if (! (dataModel && dataModel->hasGeoIPData())) { QMessageBox::warning(this, tr("Map file error"), tr("No endpoints available to map")); return QUrl(); } diff --git a/ui/qt/widgets/traffic_tab.h b/ui/qt/widgets/traffic_tab.h index 08856a8f45..778ae98c81 100644 --- a/ui/qt/widgets/traffic_tab.h +++ b/ui/qt/widgets/traffic_tab.h @@ -75,16 +75,6 @@ class TrafficTab : public QTabWidget public: - /** - * @brief Type for the selection of export - * @see copyToClipboard - */ - typedef enum { - CLIPBOARD_CSV, /* export as CSV */ - CLIPBOARD_YAML, /* export as YAML */ - CLIPBOARD_JSON /* export as JSON */ - } eTrafficTabClipboard; - TrafficTab(QWidget *parent = nullptr); virtual ~TrafficTab(); @@ -115,13 +105,6 @@ public: */ void setDelegate(int column, ATapCreateDelegate createDelegate); - /** - * @brief Returns a list of all selected protocols in the traffic tab - * - * @return QVector a list containing all protocols currently being displayed - */ - QVector selectedProtocols() const; - /** * @brief Set the filter or remove it by providing an empty filter * @@ -219,7 +202,7 @@ public slots: signals: void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); - void tabDataChanged(int index); + void tabDataChanged(int idx); void retapRequired(); private: @@ -235,23 +218,10 @@ private: bool _disableTaps; bool _nameResolution; - int _exportRole; - bool _saveRaw; - void updateTabs(); QTreeView * createTree(int protoId); ATapDataModel * modelForTabIndex(int tabIdx = -1); - /** - * @brief Copy the content of the tab to the clipboard as CSV or YAML - * - * @param type Either CSV or YAML has to be selected, defaults to CSV - * @param idx the index of the page. If it is out of bounds or < 0, the current index is being used - * @see eTrafficTabClipboard - */ - void copyToClipboard(eTrafficTabClipboard type, int idx = -1); - - QMenu * createActionSubMenu(FilterAction::Action cur_action, QModelIndex idx, bool isConversation); #ifdef HAVE_MAXMINDDB bool writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel); @@ -259,10 +229,6 @@ private: private slots: void toggleTab(bool checked = false); - void customContextMenuRequested(const QPoint &pos); - void filterActionTriggered(); - void clipboardAction(); - void toggleSaveRaw(); void modelReset(); diff --git a/ui/qt/widgets/traffic_tree.cpp b/ui/qt/widgets/traffic_tree.cpp new file mode 100644 index 0000000000..a04e0b7ff2 --- /dev/null +++ b/ui/qt/widgets/traffic_tree.cpp @@ -0,0 +1,271 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "ui/recent.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TrafficTree::TrafficTree(QString baseName, QWidget *parent) : + QTreeView(parent) +{ + _tapEnabled = true; + _saveRaw = true; + _baseName = baseName; + _exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA; + + setAlternatingRowColors(true); + setRootIsDecorated(false); + setSortingEnabled(true); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &QTreeView::customContextMenuRequested, this, &TrafficTree::customContextMenu); +} + +void TrafficTree::tapListenerEnabled(bool enable) +{ + _tapEnabled = enable; +} + +ATapDataModel * TrafficTree::dataModel() +{ + QSortFilterProxyModel * proxy = qobject_cast(model()); + if (proxy) + return qobject_cast(proxy->sourceModel()); + return nullptr; +} + +void TrafficTree::customContextMenu(const QPoint &pos) +{ + if (sender() != this) + return; + + QMenu ctxMenu; + bool isConv = false; + + QModelIndex idx = indexAt(pos); + ConversationDataModel * model = qobject_cast(dataModel()); + if (model) + isConv = true; + + ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionApply, idx, isConv)); + ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionPrepare, idx, isConv)); + ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionFind, idx, isConv)); + ctxMenu.addMenu(createActionSubMenu(FilterAction::ActionColorize, idx, isConv)); + + ctxMenu.addSeparator(); + ctxMenu.addMenu(createCopyMenu()); + + ctxMenu.addSeparator(); + QAction * act = ctxMenu.addAction(tr("Resize all columns to content")); + connect(act, &QAction::triggered, this, &TrafficTree::resizeAction); + + ctxMenu.exec(mapToGlobal(pos)); +} + +static QMap fad_to_cd_; +static void initDirection() +{ + if (fad_to_cd_.count() == 0) { + fad_to_cd_[FilterAction::ActionDirectionAToFromB] = CONV_DIR_A_TO_FROM_B; + fad_to_cd_[FilterAction::ActionDirectionAToB] = CONV_DIR_A_TO_B; + fad_to_cd_[FilterAction::ActionDirectionAFromB] = CONV_DIR_A_FROM_B; + fad_to_cd_[FilterAction::ActionDirectionAToFromAny] = CONV_DIR_A_TO_FROM_ANY; + fad_to_cd_[FilterAction::ActionDirectionAToAny] = CONV_DIR_A_TO_ANY; + fad_to_cd_[FilterAction::ActionDirectionAFromAny] = CONV_DIR_A_FROM_ANY; + fad_to_cd_[FilterAction::ActionDirectionAnyToFromB] = CONV_DIR_ANY_TO_FROM_B; + fad_to_cd_[FilterAction::ActionDirectionAnyToB] = CONV_DIR_ANY_TO_B; + fad_to_cd_[FilterAction::ActionDirectionAnyFromB] = CONV_DIR_ANY_FROM_B; + } +} + +QMenu * TrafficTree::createActionSubMenu(FilterAction::Action cur_action, QModelIndex idx, bool isConversation) +{ + initDirection(); + + conv_item_t * conv_item = nullptr; + if (isConversation) + { + ConversationDataModel * model = qobject_cast(dataModel()); + if (model) + conv_item = model->itemForRow(idx.row()); + } + + QMenu * subMenu = new QMenu(FilterAction::actionName(cur_action)); + subMenu->setEnabled(_tapEnabled); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + if (isConversation && conv_item) { + QMenu *subsubmenu = subMenu->addMenu(FilterAction::actionTypeName(at)); + foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) { + FilterAction *fa = new FilterAction(subsubmenu, cur_action, at, ad); + QString filter = get_conversation_filter(conv_item, (conv_direction_e) fad_to_cd_[fa->actionDirection()]); + fa->setProperty("filter", filter); + subsubmenu->addAction(fa); + connect(fa, &QAction::triggered, this, &TrafficTree::useFilterAction); + } + } else { + FilterAction *fa = new FilterAction(subMenu, cur_action, at); + fa->setProperty("filter", idx.data(ATapDataModel::DISPLAY_FILTER)); + subMenu->addAction(fa); + + connect(fa, &QAction::triggered, this, &TrafficTree::useFilterAction); + } + } + + return subMenu; +} + +QMenu * TrafficTree::createCopyMenu(QWidget *parent) +{ + QMenu *copy_menu = new QMenu(tr("Copy %1 table").arg(_baseName), parent); + QAction *ca; + ca = copy_menu->addAction(tr("as CSV")); + ca->setToolTip(tr("Copy all values of this page to the clipboard in CSV (Comma Separated Values) format.")); + ca->setProperty("copy_as", TrafficTree::CLIPBOARD_CSV); + connect(ca, &QAction::triggered, this, &TrafficTree::clipboardAction); + ca = copy_menu->addAction(tr("as YAML")); + ca->setToolTip(tr("Copy all values of this page to the clipboard in the YAML data serialization format.")); + ca->setProperty("copy_as", TrafficTree::CLIPBOARD_YAML); + connect(ca, &QAction::triggered, this, &TrafficTree::clipboardAction); + ca = copy_menu->addAction(tr("as Json")); + ca->setToolTip(tr("Copy all values of this page to the clipboard in the Json data serialization format.")); + ca->setProperty("copy_as", TrafficTree::CLIPBOARD_JSON); + connect(ca, &QAction::triggered, this, &TrafficTree::clipboardAction); + + copy_menu->addSeparator(); + ca = copy_menu->addAction(tr("Save data as raw")); + ca->setToolTip(tr("Disable data formatting for export/clipboard and save as raw data")); + ca->setCheckable(true); + ca->setChecked(_exportRole == ATapDataModel::UNFORMATTED_DISPLAYDATA); + connect(ca, &QAction::triggered, this, &TrafficTree::toggleSaveRawAction); + + return copy_menu; +} + +void TrafficTree::useFilterAction() +{ + FilterAction *fa = qobject_cast(sender()); + if (!fa || !_tapEnabled) + return; + + QString filter = fa->property("filter").toString(); + if (filter.length() > 0) + { + MainWindow * mainWin = (MainWindow *)(mainApp->mainWindow()); + mainWin->setDisplayFilter(filter, fa->action(), fa->actionType()); + } +} + +void TrafficTree::clipboardAction() +{ + QAction * ca = qobject_cast(sender()); + if (ca && ca->property("copy_as").isValid()) + copyToClipboard((eTrafficTreeClipboard)ca->property("copy_as").toInt()); +} + +void TrafficTree::resizeAction() +{ + for (int col = 0; col < model()->columnCount(); col++) + resizeColumnToContents(col); +} + +void TrafficTree::toggleSaveRawAction() +{ + if (_exportRole == ATapDataModel::UNFORMATTED_DISPLAYDATA) + _exportRole = Qt::DisplayRole; + else + _exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA; +} + +void TrafficTree::copyToClipboard(eTrafficTreeClipboard type) +{ + ATapDataModel * model = dataModel(); + if (!model) + return; + + QString clipText; + QTextStream stream(&clipText, QIODevice::Text); + + if (type == CLIPBOARD_CSV) { + for (int row = 0; row < model->rowCount(); row++) { + QStringList rdsl; + for (int col = 0; col < model->columnCount(); col++) { + QModelIndex idx = model->index(row, col); + QVariant v = model->data(idx, _exportRole); + if (!v.isValid()) { + rdsl << "\"\""; + } else if (v.userType() == QMetaType::QString) { + rdsl << QString("\"%1\"").arg(v.toString()); + } else { + rdsl << v.toString(); + } + } + stream << rdsl.join(",") << '\n'; + } + } else if (type == CLIPBOARD_YAML) { + stream << "---" << '\n'; + for (int row = 0; row < model->rowCount(); row++) { + stream << "-" << '\n'; + for (int col = 0; col < model->columnCount(); col++) { + QModelIndex idx = model->index(row, col); + QVariant v = model->data(idx, _exportRole); + stream << " - " << v.toString() << '\n'; + } + } + } else if (type == CLIPBOARD_JSON) { + QMap headers; + for (int cnt = 0; cnt < model->columnCount(); cnt++) + headers.insert(cnt, model->headerData(cnt, Qt::Horizontal, Qt::DisplayRole).toString()); + + QJsonArray records; + + for (int row = 0; row < model->rowCount(); row++) { + QJsonObject rowData; + foreach(int col, headers.keys()) { + QModelIndex idx = model->index(row, col); + rowData.insert(headers[col], model->data(idx, _exportRole).toString()); + } + records.push_back(rowData); + } + + QJsonDocument json; + json.setArray(records); + stream << json.toJson(); + } + + mainApp->clipboard()->setText(stream.readAll()); +} + diff --git a/ui/qt/widgets/traffic_tree.h b/ui/qt/widgets/traffic_tree.h new file mode 100644 index 0000000000..48946db1c8 --- /dev/null +++ b/ui/qt/widgets/traffic_tree.h @@ -0,0 +1,77 @@ +/** @file + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TRAFFIC_TREE_H +#define TRAFFIC_TREE_H + +#include "config.h" + +#include + +#include +#include + +#include +#include + +class TrafficTree : public QTreeView +{ + Q_OBJECT + +public: + /** + * @brief Type for the selection of export + * @see copyToClipboard + */ + typedef enum { + CLIPBOARD_CSV, /* export as CSV */ + CLIPBOARD_YAML, /* export as YAML */ + CLIPBOARD_JSON /* export as JSON */ + } eTrafficTreeClipboard; + + TrafficTree(QString baseName, QWidget *parent = nullptr); + + /** + * @brief Create a menu containing clipboard copy entries for this tab + * + * It will create all entries, including copying the content of the currently selected tab + * to CSV, YAML and JSON + * + * @param parent the parent object or null + * @return QMenu* the resulting menu or null + */ + QMenu * createCopyMenu(QWidget * parent = nullptr); + +signals: + void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); + +public slots: + void tapListenerEnabled(bool enable); + +private: + bool _tapEnabled; + int _exportRole; + bool _saveRaw; + QString _baseName; + + ATapDataModel * dataModel(); + + QMenu * createActionSubMenu(FilterAction::Action cur_action, QModelIndex idx, bool isConversation); + void copyToClipboard(eTrafficTreeClipboard type); + +private slots: + void customContextMenu(const QPoint &pos); + void useFilterAction(); + void clipboardAction(); + void resizeAction(); + void toggleSaveRawAction(); + +}; + +#endif // TRAFFIC_TREE_H