Qt: Hide columns for traffic tree

Allow columns to be hidden for the traffic tree dialogs
Conversations and Endpoints and store the information profile
specific
This commit is contained in:
Roland Knall 2022-06-19 11:18:16 +02:00
parent f75f577579
commit 2b4878d452
9 changed files with 232 additions and 12 deletions

View File

@ -40,6 +40,7 @@ wsbuglink:17779[]
- The dialog elements have been moved to make it easier to handle for new users.
- Selection of tap elements is done via list
- All configurations and options are done via a left side button row
- Columns for the Conversations and Endpoint dialogs can be hidden by context menu
* The PCRE2 library (https://www.pcre.org/) is now a required dependency to build Wireshark.
@ -77,6 +78,8 @@ The following features are new (or have been significantly updated) since versio
* The Windows installers now ship with Qt 6.2.3.
They previously shipped with Qt 6.2.4.
* The Conversation and Endpoint dialogs have been reworked extensively
The following features are new (or have been significantly updated) since version 3.6.0:
* The Windows installers now ship with Npcap 1.60.

View File

@ -95,7 +95,7 @@ ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf) :
{
trafficList()->setProtocolInfo(table_name_, &(recent.conversation_tabs));
trafficTab()->setProtocolInfo(table_name_, trafficList()->protocols(), trafficList()->selectedProtocols(), &createModel);
trafficTab()->setProtocolInfo(table_name_, trafficList(), &(recent.conversation_tabs_columns), &createModel);
trafficTab()->setDelegate(CONV_COLUMN_START, &createDelegate);
trafficTab()->setDelegate(CONV_COLUMN_DURATION, &createDelegate);
trafficTab()->setFilter(cf.displayFilter());

View File

@ -69,7 +69,7 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf) :
{
trafficList()->setProtocolInfo(table_name_, &(recent.endpoint_tabs));
trafficTab()->setProtocolInfo(table_name_, trafficList()->protocols(), trafficList()->selectedProtocols(), &createModel);
trafficTab()->setProtocolInfo(table_name_, trafficList(), &(recent.endpoint_tabs_columns), &createModel);
trafficTab()->setFilter(cf.displayFilter());
displayFilterCheckBox()->setChecked(cf.displayFilter().length() > 0);
connect(trafficTab(), &TrafficTab::filterAction, this, &EndpointDialog::filterAction);

View File

@ -22,6 +22,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/traffic_types_list.h>
#include <ui/qt/widgets/detachable_tabwidget.h>
#include <QStringList>
@ -176,6 +177,24 @@ bool TrafficDataFilterProxy::lessThan(const QModelIndex &source_left, const QMod
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
bool TrafficDataFilterProxy::filterAcceptsColumn(int source_column, const QModelIndex &) const
{
return (!hideColumns_.contains(source_column));
}
void TrafficDataFilterProxy::setColumnVisibility(int column, bool visible)
{
hideColumns_.removeAll(column);
if (!visible)
hideColumns_.append(column);
invalidateFilter();
}
bool TrafficDataFilterProxy::columnVisible(int column) const
{
return ! hideColumns_.contains(column);
}
TrafficTab::TrafficTab(QWidget * parent) :
DetachableTabWidget(parent)
@ -189,14 +208,17 @@ TrafficTab::TrafficTab(QWidget * parent) :
TrafficTab::~TrafficTab()
{}
void TrafficTab::setProtocolInfo(QString tableName, QList<int> allProtocols, QList<int> openTabs, ATapModelCallback createModel)
void TrafficTab::setProtocolInfo(QString tableName, TrafficTypesList * trafficList, GList ** recentColumnList, ATapModelCallback createModel)
{
setTabBasename(tableName);
_allProtocols = allProtocols;
_allProtocols = trafficList->protocols();
if (createModel)
_createModel = createModel;
setOpenTabs(openTabs);
_recentColumnList = recentColumnList;
setOpenTabs(trafficList->selectedProtocols());
}
void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate)
@ -228,7 +250,7 @@ void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate)
QTreeView * TrafficTab::createTree(int protoId)
{
TrafficTree * tree = new TrafficTree(tabBasename(), this);
TrafficTree * tree = new TrafficTree(tabBasename(), _recentColumnList, this);
if (_createModel) {
ATapDataModel * model = _createModel(protoId, "");
@ -253,6 +275,8 @@ QTreeView * TrafficTab::createTree(int protoId)
tree->setSelectionModel(ism);
connect(ism, &QItemSelectionModel::currentChanged, this, &TrafficTab::doCurrentIndexChange);
tree->applyRecentColumns();
tree->sortByColumn(0, Qt::AscendingOrder);
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, [tree]() {
@ -262,6 +286,13 @@ QTreeView * TrafficTab::createTree(int protoId)
}
});
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, &TrafficTab::modelReset);
/* If the columns for the tree have changed, contact the tab. By also having the tab
* columns changed signal connecting back to the tree, it will propagate to all trees
* registered with this tab. Attention, this heavily relies on the fact, that all
* tree data models are identical */
connect(tree, &TrafficTree::columnsHaveChanged, this, &TrafficTab::columnsHaveChanged);
connect(this, &TrafficTab::columnsHaveChanged, tree, &TrafficTree::columnsChanged);
}
return tree;

View File

@ -12,9 +12,12 @@
#include "config.h"
#include <glib.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/filter_action.h>
#include <ui/qt/widgets/detachable_tabwidget.h>
#include <ui/qt/widgets/traffic_types_list.h>
#include <QTabWidget>
#include <QTreeView>
@ -67,10 +70,17 @@ class TrafficDataFilterProxy : public QSortFilterProxyModel
public:
TrafficDataFilterProxy(QObject *parent = nullptr);
void setColumnVisibility(int column, bool visible);
bool columnVisible(int column) const;
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const;
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
private:
QList<int> hideColumns_;
};
/**
@ -97,13 +107,13 @@ public:
* without having to removing the predefined object during setup of the UI.
*
* @param tableName The name for the table. Used for the protocol selection button
* @param allProtocols a list of all possible protocols. It's order will set the tab oder
* @param openTabs a list of protocol ids to open at start of dialog
* @param trafficList an element of traffictypeslist, which handles all profile selections
* @param recentColumnList a list of columns to be displayed for this traffic type
* @param createModel A callback, which will create the correct model for the trees
*
* @see ATapModelCallback
*/
void setProtocolInfo(QString tableName, QList<int> allProtocols, QList<int> openTabs, ATapModelCallback createModel);
void setProtocolInfo(QString tableName, TrafficTypesList * trafficList, GList ** recentColumnList, ATapModelCallback createModel);
/**
* @brief Set the Delegate object for a specific column
@ -219,6 +229,7 @@ signals:
void retapRequired();
void disablingTaps();
void tabsChanged(QList<int> protocols);
void columnsHaveChanged(QList<int> columns);
protected slots:
@ -230,6 +241,7 @@ private:
QMap<int, int> _tabs;
ATapModelCallback _createModel;
QMap<int, ATapCreateDelegate> _createDelegates;
GList ** _recentColumnList;
bool _disableTaps;
bool _nameResolution;

View File

@ -25,6 +25,7 @@
#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 <QStringList>
@ -39,20 +40,120 @@
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QHeaderView>
TrafficTree::TrafficTree(QString baseName, QWidget *parent) :
TrafficTreeHeaderView::TrafficTreeHeaderView(GList ** recentColumnList, QWidget * parent):
QHeaderView(Qt::Horizontal, parent)
{
_recentColumnList = recentColumnList;
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QHeaderView::customContextMenuRequested, this, &TrafficTreeHeaderView::headerContextMenu);
}
TrafficTreeHeaderView::~TrafficTreeHeaderView()
{}
void TrafficTreeHeaderView::headerContextMenu(const QPoint &pos)
{
TrafficTree * tree = qobject_cast<TrafficTree *>(parent());
if (!tree)
return;
TrafficDataFilterProxy * proxy = qobject_cast<TrafficDataFilterProxy *>(tree->model());
if (sender() != this || ! proxy)
return;
QMenu ctxMenu;
for (int col = 0; col < tree->dataModel()->columnCount(); col++)
{
QString name = tree->dataModel()->headerData(col).toString();
QAction * action = new QAction(name);
action->setCheckable(true);
action->setChecked(proxy->columnVisible(col));
action->setProperty("col_nr", col);
ctxMenu.addAction(action);
connect(action, &QAction::triggered, this, &TrafficTreeHeaderView::columnTriggered);
}
ctxMenu.exec(mapToGlobal(pos));
}
void TrafficTreeHeaderView::applyRecent()
{
TrafficTree * tree = qobject_cast<TrafficTree *>(parent());
if (!tree)
return;
QList<int> columns;
for (GList * endTab = *_recentColumnList; endTab; endTab = endTab->next) {
QString colStr = QString((const char *)endTab->data);
bool ok = false;
int col = colStr.toInt(&ok);
if (ok)
columns << col;
}
if (columns.count() > 0) {
TrafficDataFilterProxy * proxy = qobject_cast<TrafficDataFilterProxy *>(tree->model());
for (int col = 0; col < tree->dataModel()->columnCount(); col++) {
proxy->setColumnVisibility(col, columns.contains(col));
}
}
}
void TrafficTreeHeaderView::columnTriggered(bool checked)
{
TrafficTree * tree = qobject_cast<TrafficTree *>(parent());
if (!tree)
return;
TrafficDataFilterProxy * proxy = qobject_cast<TrafficDataFilterProxy *>(tree->model());
QAction * entry = qobject_cast<QAction *>(sender());
if (! proxy || ! entry || ! entry->property("col_nr").isValid())
return;
int col = entry->property("col_nr").toInt();
proxy->setColumnVisibility(col, checked);
prefs_clear_string_list(*_recentColumnList);
*_recentColumnList = NULL;
QList<int> visible;
for (int col = 0; col < tree->dataModel()->columnCount(); col++) {
if (proxy->columnVisible(col)) {
visible << col;
gchar *nr = qstring_strdup(QString::number(col));
*_recentColumnList = g_list_append(*_recentColumnList, nr);
}
}
emit columnsHaveChanged(visible);
}
TrafficTree::TrafficTree(QString baseName, GList ** recentColumnList, QWidget *parent) :
QTreeView(parent)
{
_tapEnabled = true;
_saveRaw = true;
_baseName = baseName;
_exportRole = ATapDataModel::UNFORMATTED_DISPLAYDATA;
_header = nullptr;
setAlternatingRowColors(true);
setRootIsDecorated(false);
setSortingEnabled(true);
setContextMenuPolicy(Qt::CustomContextMenu);
_header = new TrafficTreeHeaderView(recentColumnList);
setHeader(_header);
connect(_header, &TrafficTreeHeaderView::columnsHaveChanged, this, &TrafficTree::columnsHaveChanged);
connect(this, &QTreeView::customContextMenuRequested, this, &TrafficTree::customContextMenu);
}
@ -277,3 +378,22 @@ void TrafficTree::disableTap()
return;
model->disableTap();
}
void TrafficTree::applyRecentColumns()
{
if (_header)
_header->applyRecent();
}
void TrafficTree::columnsChanged(QList<int> columns)
{
TrafficDataFilterProxy * proxy = qobject_cast<TrafficDataFilterProxy *>(model());
if (!proxy)
return;
for (int col = 0; col < dataModel()->columnCount(); col++) {
proxy->setColumnVisibility(col, columns.contains(col));
}
resizeAction();
}

View File

@ -12,6 +12,8 @@
#include "config.h"
#include <glib.h>
#include <ui/recent.h>
#include <ui/qt/models/atap_data_model.h>
@ -19,6 +21,29 @@
#include <QTreeView>
#include <QMenu>
#include <QHeaderView>
class TrafficTreeHeaderView : public QHeaderView
{
Q_OBJECT
public:
TrafficTreeHeaderView(GList ** recentColumnList, QWidget * parent = nullptr);
~TrafficTreeHeaderView();
void applyRecent();
signals:
void columnsHaveChanged(QList<int> visible);
private:
GList ** _recentColumnList;
private slots:
void headerContextMenu(const QPoint &pos);
void columnTriggered(bool checked = false);
};
class TrafficTree : public QTreeView
{
@ -35,7 +60,7 @@ public:
CLIPBOARD_JSON /* export as JSON */
} eTrafficTreeClipboard;
TrafficTree(QString baseName, QWidget *parent = nullptr);
TrafficTree(QString baseName, GList ** recentColumnList, QWidget *parent = nullptr);
/**
* @brief Create a menu containing clipboard copy entries for this tab
@ -48,12 +73,16 @@ public:
*/
QMenu * createCopyMenu(QWidget * parent = nullptr);
void applyRecentColumns();
signals:
void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type);
void columnsHaveChanged(QList<int> columns);
public slots:
void tapListenerEnabled(bool enable);
void disableTap();
void columnsChanged(QList<int> columns);
private:
bool _tapEnabled;
@ -61,18 +90,21 @@ private:
bool _saveRaw;
QString _baseName;
TrafficTreeHeaderView * _header;
ATapDataModel * dataModel();
QMenu * createActionSubMenu(FilterAction::Action cur_action, QModelIndex idx, bool isConversation);
void copyToClipboard(eTrafficTreeClipboard type);
friend class TrafficTreeHeaderView;
private slots:
void customContextMenu(const QPoint &pos);
void useFilterAction();
void clipboardAction();
void resizeAction();
void toggleSaveRawAction();
};
#endif // TRAFFIC_TREE_H

View File

@ -60,7 +60,9 @@
#define RECENT_LAST_USED_PROFILE "gui.last_used_profile"
#define RECENT_GUI_FILEOPEN_REMEMBERED_DIR "gui.fileopen_remembered_dir"
#define RECENT_GUI_CONVERSATION_TABS "gui.conversation_tabs"
#define RECENT_GUI_CONVERSATION_TABS_COLUMNS "gui.conversation_tabs_columns"
#define RECENT_GUI_ENDPOINT_TABS "gui.endpoint_tabs"
#define RECENT_GUI_ENDPOINT_TABS_COLUMNS "gui.endpoint_tabs_columns"
#define RECENT_GUI_RLC_PDUS_FROM_MAC_FRAMES "gui.rlc_pdus_from_mac_frames"
#define RECENT_GUI_CUSTOM_COLORS "gui.custom_colors"
#define RECENT_GUI_TOOLBAR_SHOW "gui.additional_toolbar_show"
@ -918,12 +920,24 @@ write_profile_recent(void)
fprintf(rf, RECENT_GUI_CONVERSATION_TABS ": %s\n", string_list);
g_free(string_list);
fprintf(rf, "\n# Conversation dialog tabs columns.\n");
fprintf(rf, "# List of conversation columns numbers.\n");
string_list = join_string_list(recent.conversation_tabs_columns);
fprintf(rf, RECENT_GUI_CONVERSATION_TABS_COLUMNS ": %s\n", string_list);
g_free(string_list);
fprintf(rf, "\n# Open endpoint dialog tabs.\n");
fprintf(rf, "# List of endpoint names, e.g. \"TCP\", \"IPv6\".\n");
string_list = join_string_list(recent.endpoint_tabs);
fprintf(rf, RECENT_GUI_ENDPOINT_TABS ": %s\n", string_list);
g_free(string_list);
fprintf(rf, "\n# Endpoint dialog tabs columns.\n");
fprintf(rf, "# List of endpoint columns numbers.\n");
string_list = join_string_list(recent.endpoint_tabs_columns);
fprintf(rf, RECENT_GUI_ENDPOINT_TABS_COLUMNS ": %s\n", string_list);
g_free(string_list);
write_recent_boolean(rf, "For RLC stats, whether to use RLC PDUs found inside MAC frames",
RECENT_GUI_RLC_PDUS_FROM_MAC_FRAMES,
recent.gui_rlc_use_pdus_from_mac);
@ -1122,8 +1136,12 @@ read_set_recent_pair_static(gchar *key, const gchar *value,
recent.has_gui_geometry_main_lower_pane = TRUE;
} else if (strcmp(key, RECENT_GUI_CONVERSATION_TABS) == 0) {
recent.conversation_tabs = prefs_get_string_list(value);
} else if (strcmp(key, RECENT_GUI_CONVERSATION_TABS_COLUMNS) == 0) {
recent.conversation_tabs_columns = prefs_get_string_list(value);
} else if (strcmp(key, RECENT_GUI_ENDPOINT_TABS) == 0) {
recent.endpoint_tabs = prefs_get_string_list(value);
} else if (strcmp(key, RECENT_GUI_ENDPOINT_TABS_COLUMNS) == 0) {
recent.endpoint_tabs_columns = prefs_get_string_list(value);
} else if (strcmp(key, RECENT_GUI_RLC_PDUS_FROM_MAC_FRAMES) == 0) {
parse_recent_boolean(value, &recent.gui_rlc_use_pdus_from_mac);
} else if (strcmp(key, RECENT_KEY_COL_WIDTH) == 0) {
@ -1611,6 +1629,8 @@ recent_cleanup(void)
g_list_free_full(recent.gui_additional_toolbars, g_free);
g_list_free_full(recent.interface_toolbars, g_free);
prefs_clear_string_list(recent.conversation_tabs);
prefs_clear_string_list(recent.conversation_tabs_columns);
prefs_clear_string_list(recent.endpoint_tabs);
prefs_clear_string_list(recent.endpoint_tabs_columns);
prefs_clear_string_list(recent.custom_colors);
}

View File

@ -128,7 +128,9 @@ typedef struct recent_settings_tag {
gboolean sys_warn_if_no_capture;
GList *col_width_list; /* column widths */
GList *conversation_tabs; /* enabled conversation dialog tabs */
GList *conversation_tabs_columns; /* save the columns for conversation dialogs */
GList *endpoint_tabs; /* enabled endpoint dialog tabs */
GList *endpoint_tabs_columns; /* save the columns for endpoint dialogs */
gchar *gui_fileopen_remembered_dir; /* folder of last capture loaded in File Open dialog */
gboolean gui_rlc_use_pdus_from_mac;
GList *custom_colors;