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.
This commit is contained in:
Roland Knall 2022-06-04 14:48:37 +02:00
parent f72a33fc1c
commit 9edf06383a
13 changed files with 381 additions and 276 deletions

View File

@ -30,6 +30,10 @@ wsbuglink:17779[]
Code using the Lua GRegex module will have to be updated to use lrexlib-pcre2 instead. 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. 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. Many improvements have been made.
See the “New and Updated Features” section below for more details. See the “New and Updated Features” section below for more details.

View File

@ -50,6 +50,7 @@ set(WIRESHARK_WIDGET_HEADERS
../qt/widgets/wireless_timeline.h # Required by PacketListModel ../qt/widgets/wireless_timeline.h # Required by PacketListModel
../qt/widgets/tabnav_tree_view.h ../qt/widgets/tabnav_tree_view.h
../qt/widgets/traffic_tab.h ../qt/widgets/traffic_tab.h
../qt/widgets/traffic_tree.h
../qt/widgets/wireshark_file_dialog.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/wireless_timeline.cpp # Required by PacketListModel
../qt/widgets/tabnav_tree_view.cpp ../qt/widgets/tabnav_tree_view.cpp
../qt/widgets/traffic_tab.cpp ../qt/widgets/traffic_tab.cpp
../qt/widgets/traffic_tree.cpp
../qt/widgets/wireshark_file_dialog.cpp ../qt/widgets/wireshark_file_dialog.cpp
) )

View File

@ -50,6 +50,7 @@ set(WIRESHARK_WIDGET_HEADERS
widgets/syntax_line_edit.h widgets/syntax_line_edit.h
widgets/tabnav_tree_view.h widgets/tabnav_tree_view.h
widgets/traffic_tab.h widgets/traffic_tab.h
widgets/traffic_tree.h
widgets/wireless_timeline.h widgets/wireless_timeline.h
widgets/wireshark_file_dialog.h widgets/wireshark_file_dialog.h
) )
@ -300,6 +301,7 @@ set(WIRESHARK_WIDGET_SRCS
widgets/syntax_line_edit.cpp widgets/syntax_line_edit.cpp
widgets/tabnav_tree_view.cpp widgets/tabnav_tree_view.cpp
widgets/traffic_tab.cpp widgets/traffic_tab.cpp
widgets/traffic_tree.cpp
widgets/wireless_timeline.cpp widgets/wireless_timeline.cpp
widgets/wireshark_file_dialog.cpp widgets/wireshark_file_dialog.cpp
) )

View File

@ -125,15 +125,10 @@ ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf, int cli
void ConversationDialog::captureFileClosing() void ConversationDialog::captureFileClosing()
{ {
trafficTab()->disableTap(); trafficTab()->disableTap();
TrafficTableDialog::captureFileClosing();
}
void ConversationDialog::captureFileClosed()
{
displayFilterCheckBox()->setEnabled(false); displayFilterCheckBox()->setEnabled(false);
follow_bt_->setEnabled(false); follow_bt_->setEnabled(false);
graph_bt_->setEnabled(false); graph_bt_->setEnabled(false);
TrafficTableDialog::captureFileClosed(); TrafficTableDialog::captureFileClosing();
} }
void ConversationDialog::followStream() void ConversationDialog::followStream()

View File

@ -28,7 +28,6 @@ public:
protected: protected:
void captureFileClosing(); void captureFileClosing();
void captureFileClosed();
signals: signals:
void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type);

View File

@ -83,6 +83,7 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_i
connect(action, &QAction::triggered, this, &EndpointDialog::saveMap); connect(action, &QAction::triggered, this, &EndpointDialog::saveMap);
map_bt_->setMenu(map_menu_); map_bt_->setMenu(map_menu_);
#endif #endif
addProgressFrame(&parent); addProgressFrame(&parent);
QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close); QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
@ -96,14 +97,8 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_i
void EndpointDialog::captureFileClosing() void EndpointDialog::captureFileClosing()
{ {
trafficTab()->disableTap(); trafficTab()->disableTap();
TrafficTableDialog::captureFileClosing();
}
void EndpointDialog::captureFileClosed()
{
displayFilterCheckBox()->setEnabled(false); displayFilterCheckBox()->setEnabled(false);
TrafficTableDialog::captureFileClosed(); TrafficTableDialog::captureFileClosing();
} }
void EndpointDialog::tabChanged(int idx) void EndpointDialog::tabChanged(int idx)

View File

@ -33,7 +33,6 @@ signals:
protected: protected:
void captureFileClosing(); void captureFileClosing();
void captureFileClosed();
private: private:
#ifdef HAVE_MAXMINDDB #ifdef HAVE_MAXMINDDB

View File

@ -106,12 +106,15 @@ bool ATapDataModel::enableTap()
&ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr); &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
if (errorString && errorString->len > 0) { if (errorString && errorString->len > 0) {
_disableTap = true; _disableTap = true;
emit tapListenerChanged(false);
return false; return false;
} }
if (errorString) if (errorString)
g_string_free(errorString, TRUE); g_string_free(errorString, TRUE);
emit tapListenerChanged(true);
return true; return true;
} }
@ -121,6 +124,7 @@ void ATapDataModel::disableTap()
if (!_disableTap) if (!_disableTap)
remove_tap_listener(hash()); remove_tap_listener(hash());
_disableTap = true; _disableTap = true;
emit tapListenerChanged(false);
} }
int ATapDataModel::rowCount(const QModelIndex &) const int ATapDataModel::rowCount(const QModelIndex &) const

View File

@ -187,6 +187,9 @@ public:
bool hasGeoIPData(); bool hasGeoIPData();
#endif #endif
signals:
void tapListenerChanged(bool enable);
protected: protected:
static void tapReset(void *tapdata); static void tapReset(void *tapdata);

View File

@ -18,11 +18,12 @@
#include "ui/recent.h" #include "ui/recent.h"
#include <ui/qt/widgets/traffic_tab.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/main_application.h> #include <ui/qt/main_application.h>
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/filter_action.h> #include <ui/qt/filter_action.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/widgets/traffic_tab.h>
#include <ui/qt/widgets/traffic_tree.h>
#include <QVector> #include <QVector>
#include <QStringList> #include <QStringList>
@ -83,8 +84,6 @@ TrafficTab::TrafficTab(QWidget * parent) :
_nameResolution = false; _nameResolution = false;
_tableName = QString(); _tableName = QString();
_cliId = 0; _cliId = 0;
_saveRaw = true;
_exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA;
_recentList = nullptr; _recentList = nullptr;
} }
@ -93,7 +92,7 @@ TrafficTab::~TrafficTab()
prefs_clear_string_list(*_recentList); prefs_clear_string_list(*_recentList);
*_recentList = NULL; *_recentList = NULL;
QVector<int> protocols = selectedProtocols(); QList<int> protocols = _tabs.keys();
foreach (int protoId, protocols) foreach (int protoId, protocols)
{ {
char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(protoId))); 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 * TrafficTab::createTree(int protoId)
{ {
QTreeView * tree = new QTreeView(this); TrafficTree * tree = new TrafficTree(_tableName, this);
if (_createModel) { if (_createModel) {
ATapDataModel * model = _createModel(protoId, ""); ATapDataModel * model = _createModel(protoId, "");
connect(model, &ATapDataModel::tapListenerChanged, tree, &TrafficTree::tapListenerEnabled);
model->enableTap(); model->enableTap();
tree->setAlternatingRowColors(true);
tree->setRootIsDecorated(false);
tree->setSortingEnabled(true);
foreach(int col, _createDelegates.keys()) 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(); QSortFilterProxyModel * proxyModel = new QSortFilterProxyModel();
proxyModel->setSourceModel(model); proxyModel->setSourceModel(model);
tree->setModel(proxyModel); tree->setModel(proxyModel);
@ -241,12 +238,6 @@ QTreeView * TrafficTab::createTree(int protoId)
return tree; return tree;
} }
QVector<int> TrafficTab::selectedProtocols() const
{
QVector<int> result = QVector<int>::fromList(_tabs.keys());
return result;
}
void TrafficTab::useAbsoluteTime(bool absolute) void TrafficTab::useAbsoluteTime(bool absolute)
{ {
for(int idx = 0; idx < count(); idx++) for(int idx = 0; idx < count(); idx++)
@ -447,216 +438,13 @@ bool TrafficTab::hasNameResolution(int tabIdx)
return dataModel->allowsNameResolution(); return dataModel->allowsNameResolution();
} }
static QMap<FilterAction::ActionDirection, int> 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<ConversationDataModel *>(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<QTreeView *>(sender()))
return;
QTreeView * tree = qobject_cast<QTreeView *>(sender());
QModelIndex idx = tree->indexAt(pos);
QMenu ctxMenu;
bool isConv = false;
QSortFilterProxyModel * proxy = qobject_cast<QSortFilterProxyModel *>(tree->model());
if (proxy) {
ConversationDataModel * model = qobject_cast<ConversationDataModel *>(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<FilterAction *>(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 * TrafficTab::createCopyMenu(QWidget *parent)
{ {
QMenu *copy_menu = new QMenu(tr("Copy %1 table").arg(_tableName), parent); TrafficTree * tree = qobject_cast<TrafficTree *>(currentWidget());
QAction *ca; if ( ! tree)
ca = copy_menu->addAction(tr("as CSV")); return nullptr;
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);
copy_menu->addSeparator(); return tree->createCopyMenu(parent);
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<QAction *>(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<QTreeView *>(widget(idx))) {
QTreeView * tree = qobject_cast<QTreeView *>(widget(idx));
if (qobject_cast<QSortFilterProxyModel *>(tree->model())) {
model = qobject_cast<QSortFilterProxyModel *>(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<int, QString> 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());
} }
#ifdef HAVE_MAXMINDDB #ifdef HAVE_MAXMINDDB
@ -790,7 +578,7 @@ QUrl TrafficTab::createGeoIPMap(bool json_only, int tabIdx)
{ {
int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx; int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx;
ATapDataModel * dataModel = modelForTabIndex(tab); 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")); QMessageBox::warning(this, tr("Map file error"), tr("No endpoints available to map"));
return QUrl(); return QUrl();
} }

View File

@ -75,16 +75,6 @@ class TrafficTab : public QTabWidget
public: 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); TrafficTab(QWidget *parent = nullptr);
virtual ~TrafficTab(); virtual ~TrafficTab();
@ -115,13 +105,6 @@ public:
*/ */
void setDelegate(int column, ATapCreateDelegate createDelegate); void setDelegate(int column, ATapCreateDelegate createDelegate);
/**
* @brief Returns a list of all selected protocols in the traffic tab
*
* @return QVector<int> a list containing all protocols currently being displayed
*/
QVector<int> selectedProtocols() const;
/** /**
* @brief Set the filter or remove it by providing an empty filter * @brief Set the filter or remove it by providing an empty filter
* *
@ -219,7 +202,7 @@ public slots:
signals: signals:
void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type); void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type);
void tabDataChanged(int index); void tabDataChanged(int idx);
void retapRequired(); void retapRequired();
private: private:
@ -235,23 +218,10 @@ private:
bool _disableTaps; bool _disableTaps;
bool _nameResolution; bool _nameResolution;
int _exportRole;
bool _saveRaw;
void updateTabs(); void updateTabs();
QTreeView * createTree(int protoId); QTreeView * createTree(int protoId);
ATapDataModel * modelForTabIndex(int tabIdx = -1); 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 #ifdef HAVE_MAXMINDDB
bool writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel); bool writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel);
@ -259,10 +229,6 @@ private:
private slots: private slots:
void toggleTab(bool checked = false); void toggleTab(bool checked = false);
void customContextMenuRequested(const QPoint &pos);
void filterActionTriggered();
void clipboardAction();
void toggleSaveRaw();
void modelReset(); void modelReset();

View File

@ -0,0 +1,271 @@
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <epan/proto.h>
#include <epan/addr_resolv.h>
#include <epan/prefs.h>
#include <epan/maxmind_db.h>
#include <epan/conversation_table.h>
#include <wsutil/utf8_entities.h>
#include <wsutil/filesystem.h>
#include "ui/recent.h"
#include <ui/qt/main_application.h>
#include <ui/qt/main_window.h>
#include <ui/qt/filter_action.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/widgets/traffic_tree.h>
#include <QVector>
#include <QStringList>
#include <QTreeView>
#include <QList>
#include <QMap>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <QTextStream>
#include <QClipboard>
#include <QMessageBox>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
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<QSortFilterProxyModel *>(model());
if (proxy)
return qobject_cast<ATapDataModel *>(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<ConversationDataModel *>(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<FilterAction::ActionDirection, int> 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<ConversationDataModel *>(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<FilterAction *>(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<QAction *>(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<int, QString> 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());
}

View File

@ -0,0 +1,77 @@
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef TRAFFIC_TREE_H
#define TRAFFIC_TREE_H
#include "config.h"
#include <ui/recent.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/filter_action.h>
#include <QTreeView>
#include <QMenu>
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