forked from osmocom/wireshark
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:
parent
f72a33fc1c
commit
9edf06383a
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -28,7 +28,6 @@ public:
|
|||
|
||||
protected:
|
||||
void captureFileClosing();
|
||||
void captureFileClosed();
|
||||
|
||||
signals:
|
||||
void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -33,7 +33,6 @@ signals:
|
|||
|
||||
protected:
|
||||
void captureFileClosing();
|
||||
void captureFileClosed();
|
||||
|
||||
private:
|
||||
#ifdef HAVE_MAXMINDDB
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -187,6 +187,9 @@ public:
|
|||
bool hasGeoIPData();
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void tapListenerChanged(bool enable);
|
||||
|
||||
protected:
|
||||
|
||||
static void tapReset(void *tapdata);
|
||||
|
|
|
@ -18,11 +18,12 @@
|
|||
|
||||
#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/utils/variant_pointer.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 <QStringList>
|
||||
|
@ -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<int> protocols = selectedProtocols();
|
||||
QList<int> 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<int> TrafficTab::selectedProtocols() const
|
||||
{
|
||||
QVector<int> result = QVector<int>::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<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 *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<TrafficTree *>(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<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());
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
*
|
||||
|
@ -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();
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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
|
Loading…
Reference in New Issue