Qt: Redesign TrafficTree Dialogs UI

The new UI should better group functionality and as well as better
showing which taps are available and can be used.
This commit is contained in:
Roland Knall 2022-06-08 14:33:51 +02:00
parent f1cbc6b662
commit a4f25e5115
15 changed files with 598 additions and 275 deletions

View File

@ -37,6 +37,9 @@ wsbuglink:17779[]
- Conversations will be sorted via second address and first port number
- Endpoints will be sorted via port numbers
- IPv6 addresses are sorted correctly after IPv4 addresses
- 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
* The PCRE2 library (https://www.pcre.org/) is now a required dependency to build Wireshark.

View File

@ -52,6 +52,7 @@ set(WIRESHARK_WIDGET_HEADERS
../qt/widgets/tabnav_tree_view.h
../qt/widgets/traffic_tab.h
../qt/widgets/traffic_tree.h
../qt/widgets/traffic_types_list.h
../qt/widgets/wireshark_file_dialog.h
)
@ -279,6 +280,7 @@ set(WIRESHARK_WIDGET_SRCS
../qt/widgets/tabnav_tree_view.cpp
../qt/widgets/traffic_tab.cpp
../qt/widgets/traffic_tree.cpp
../qt/widgets/traffic_types_list.cpp
../qt/widgets/wireshark_file_dialog.cpp
)

View File

@ -52,6 +52,7 @@ set(WIRESHARK_WIDGET_HEADERS
widgets/tabnav_tree_view.h
widgets/traffic_tab.h
widgets/traffic_tree.h
widgets/traffic_types_list.h
widgets/wireless_timeline.h
widgets/wireshark_file_dialog.h
)
@ -304,6 +305,7 @@ set(WIRESHARK_WIDGET_SRCS
widgets/tabnav_tree_view.cpp
widgets/traffic_tab.cpp
widgets/traffic_tree.cpp
widgets/traffic_types_list.cpp
widgets/wireless_timeline.cpp
widgets/wireshark_file_dialog.cpp
)

View File

@ -22,6 +22,7 @@
#include <ui/qt/models/timeline_delegate.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/widgets/traffic_tab.h>
#include <ui/qt/widgets/traffic_types_list.h>
#include "main_application.h"
#include <QCheckBox>
@ -92,7 +93,9 @@ ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf) :
TrafficTableDialog(parent, cf, table_name_),
tcp_graph_requested_(false)
{
trafficTab()->setProtocolInfo(tr("Conversation"), &(recent.conversation_tabs), &createModel);
trafficList()->setProtocolInfo(table_name_, &(recent.conversation_tabs));
trafficTab()->setProtocolInfo(table_name_, trafficList()->protocols(), trafficList()->selectedProtocols(), &createModel);
trafficTab()->setDelegate(CONV_COLUMN_START, &createDelegate);
trafficTab()->setDelegate(CONV_COLUMN_DURATION, &createDelegate);
trafficTab()->setFilter(cf.displayFilter());
@ -114,13 +117,6 @@ ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf) :
absoluteTimeCheckBox()->show();
addProgressFrame(&parent);
QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
if (close_bt) {
close_bt->setDefault(true);
}
updateWidgets();
}

View File

@ -26,6 +26,7 @@
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/widgets/wireshark_file_dialog.h>
#include <ui/qt/widgets/traffic_tab.h>
#include <ui/qt/widgets/traffic_types_list.h>
#include "main_application.h"
#include <QCheckBox>
@ -66,7 +67,9 @@ static ATapDataModel * createModel(int protoId, QString filter)
EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf) :
TrafficTableDialog(parent, cf, table_name_)
{
trafficTab()->setProtocolInfo(tr("Endpoints"), &(recent.endpoint_tabs), &createModel);
trafficList()->setProtocolInfo(table_name_, &(recent.endpoint_tabs));
trafficTab()->setProtocolInfo(table_name_, trafficList()->protocols(), trafficList()->selectedProtocols(), &createModel);
trafficTab()->setFilter(cf.displayFilter());
displayFilterCheckBox()->setChecked(cf.displayFilter().length() > 0);
connect(trafficTab(), &TrafficTab::filterAction, this, &EndpointDialog::filterAction);
@ -86,13 +89,6 @@ EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf) :
map_bt_->setMenu(map_menu_);
#endif
addProgressFrame(&parent);
QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
if (close_bt) {
close_bt->setDefault(true);
}
updateWidgets();
}

View File

@ -76,24 +76,20 @@ QString ATapDataModel::tap() const
#ifdef HAVE_MAXMINDDB
bool ATapDataModel::hasGeoIPData()
{
QString key = QString("geoip_found_%1").arg(_protoId);
if (! _lookUp.keys().contains(key)) {
bool coordsFound = false;
int row = 0;
int count = rowCount(QModelIndex());
while (!coordsFound && row < count)
{
QModelIndex idx = index(row, 0);
if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
coordsFound = qobject_cast<EndpointDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
coordsFound = qobject_cast<ConversationDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
row++;
}
_lookUp.insert(key, coordsFound);
bool coordsFound = false;
int row = 0;
int count = rowCount();
while (!coordsFound && row < count)
{
QModelIndex idx = index(row, 0);
if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
coordsFound = qobject_cast<EndpointDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
coordsFound = qobject_cast<ConversationDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
row++;
}
return _lookUp.value(key, false).toBool();
return coordsFound;
}
#endif
@ -190,7 +186,6 @@ void ATapDataModel::resetData()
return;
beginResetModel();
_lookUp.clear();
storage_ = nullptr;
if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
reset_hostlist_table_data(&hash_);
@ -209,7 +204,6 @@ void ATapDataModel::updateData(GArray * newData)
return;
beginResetModel();
_lookUp.clear();
storage_ = newData;
endResetModel();
@ -510,15 +504,15 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
if (column == EndpointDataModel::ENDP_COLUMN_ADDR)
return (int)item->myaddress.type;
return (int) AT_NONE;
} else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_VECTOR) {
} else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) {
if (column == EndpointDataModel::ENDP_COLUMN_ADDR) {
if (role == ATapDataModel::DATA_IPV4_INTEGER && item->myaddress.type == AT_IPv4) {
const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data;
return (quint32) GUINT32_TO_BE(*ip4);
}
else if (role == ATapDataModel::DATA_IPV6_VECTOR && item->myaddress.type == AT_IPv6) {
else if (role == ATapDataModel::DATA_IPV6_LIST && item->myaddress.type == AT_IPv6) {
const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data;
QVector<quint8> result;
QList<quint8> result;
result.reserve(16);
std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
return QVariant::fromValue(result);
@ -538,7 +532,7 @@ void ConversationDataModel::doDataUpdate()
_minRelStartTime = 0;
_maxRelStopTime = 0;
for (int row = 0; row < rowCount(QModelIndex()); row ++) {
for (int row = 0; row < rowCount(); row ++) {
conv_item_t *conv_item = &g_array_index(storage_, conv_item_t, row);
if (row == 0) {
@ -801,16 +795,16 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
return (int)tst_address.type;
}
return (int) AT_NONE;
} else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_VECTOR) {
} else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) {
if (column == ConversationDataModel::CONV_COLUMN_SRC_ADDR || column == ConversationDataModel::CONV_COLUMN_DST_ADDR) {
address tst_address = column == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address;
if (role == ATapDataModel::DATA_IPV4_INTEGER && tst_address.type == AT_IPv4) {
const ws_in4_addr * ip4 = (const ws_in4_addr *) tst_address.data;
return (quint32) GUINT32_TO_BE(*ip4);
}
else if (role == ATapDataModel::DATA_IPV6_VECTOR && tst_address.type == AT_IPv6) {
else if (role == ATapDataModel::DATA_IPV6_LIST && tst_address.type == AT_IPv6) {
const ws_in6_addr * ip6 = (const ws_in6_addr *) tst_address.data;
QVector<quint8> result;
QList<quint8> result;
result.reserve(16);
std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
return QVariant::fromValue(result);
@ -823,7 +817,7 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
conv_item_t * ConversationDataModel::itemForRow(int row)
{
if (row < 0 || row >= rowCount(QModelIndex()))
if (row < 0 || row >= rowCount())
return nullptr;
return (conv_item_t *)&g_array_index(storage_, conv_item_t, row);
}

View File

@ -50,7 +50,7 @@ public:
ROW_IS_FILTERED,
DATA_ADDRESS_TYPE,
DATA_IPV4_INTEGER,
DATA_IPV6_VECTOR,
DATA_IPV6_LIST,
};
typedef enum {
@ -232,8 +232,6 @@ protected:
private:
int _protoId;
QMap<QString, QVariant> _lookUp;
conv_hash_t hash_;
};

View File

@ -19,6 +19,7 @@
#include "main_application.h"
#include <ui/qt/widgets/traffic_tab.h>
#include <ui/qt/widgets/traffic_types_list.h>
#include <QCheckBox>
#include <QClipboard>
@ -51,20 +52,28 @@ TrafficTableDialog::TrafficTableDialog(QWidget &parent, CaptureFile &cf, const Q
ui->absoluteTimeCheckBox->hide();
setWindowSubtitle(QString("%1s").arg(table_name));
ui->grpSettings->setTitle(QString("%1 Settings").arg(table_name));
copy_bt_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
copy_bt_ = buttonBox()->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
copy_bt_->setMenu(ui->trafficTab->createCopyMenu(copy_bt_));
ui->trafficTab->setFocus();
ui->trafficTab->useNanosecondTimestamps(cf.timestampPrecision() == WTAP_TSPREC_NSEC);
connect(ui->trafficList, &TrafficTypesList::protocolsChanged, ui->trafficTab, &TrafficTab::setOpenTabs);
connect(ui->trafficTab, &TrafficTab::tabsChanged, ui->trafficList, &TrafficTypesList::selectProtocols);
connect(mainApp, SIGNAL(addressResolutionChanged()), this, SLOT(currentTabChanged()));
connect(ui->trafficTab, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged()));
connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)), this, SLOT(captureEvent(CaptureEvent)));
connect(ui->absoluteTimeCheckBox, &QCheckBox::toggled, trafficTab(), &TrafficTab::useAbsoluteTime);
connect(trafficTab(), &TrafficTab::retapRequired, &cap_file_, &CaptureFile::delayedRetapPackets);
connect(ui->absoluteTimeCheckBox, &QCheckBox::toggled, ui->trafficTab, &TrafficTab::useAbsoluteTime);
connect(ui->trafficTab, &TrafficTab::retapRequired, &cap_file_, &CaptureFile::delayedRetapPackets);
QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
if (close_bt)
close_bt->setDefault(true);
addProgressFrame(&parent);
}
TrafficTableDialog::~TrafficTableDialog()
@ -79,7 +88,7 @@ void TrafficTableDialog::addProgressFrame(QObject *parent)
QDialogButtonBox *TrafficTableDialog::buttonBox() const
{
return ui->buttonBox;
return ui->btnBoxSettings;
}
QCheckBox *TrafficTableDialog::displayFilterCheckBox() const
@ -97,9 +106,15 @@ TrafficTab *TrafficTableDialog::trafficTab() const
return ui->trafficTab;
}
TrafficTypesList *TrafficTableDialog::trafficList() const
{
return ui->trafficList;
}
void TrafficTableDialog::currentTabChanged()
{
bool has_resolution = ui->trafficTab->hasNameResolution();
copy_bt_->setMenu(ui->trafficTab->createCopyMenu(copy_bt_));
ui->nameResolutionCheckBox->setEnabled(has_resolution);
if (! has_resolution) {

View File

@ -31,6 +31,7 @@ class QPushButton;
class QTabWidget;
class QTreeWidget;
class TrafficTab;
class TrafficTypesList;
namespace Ui {
class TrafficTableDialog;
@ -69,6 +70,7 @@ protected:
QCheckBox *displayFilterCheckBox() const;
QCheckBox *absoluteTimeCheckBox() const;
TrafficTab *trafficTab() const;
TrafficTypesList *trafficList() const;
protected slots:
virtual void currentTabChanged();

View File

@ -10,80 +10,94 @@
<height>475</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="TrafficTab" name="trafficTab"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="nameResolutionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show resolved addresses and port names rather than plain values. The corresponding name resolution preference must be enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name resolution</string>
<widget class="QWidget" name="widget" native="true">
<property name="maximumSize">
<size>
<width>210</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="grpSettings">
<property name="title">
<string>GroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="nameResolutionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show resolved addresses and port names rather than plain values. The corresponding name resolution preference must be enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name resolution</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="absoluteTimeCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show absolute times in the start time column.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Absolute start time</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayFilterCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only show conversations matching the current display filter&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Limit to display filter</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="btnBoxSettings">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::NoButton</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="TrafficTypesList" name="trafficList"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="displayFilterCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only show conversations matching the current display filter&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Limit to display filter</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="absoluteTimeCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show absolute times in the start time column.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Absolute start time</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
<widget class="TrafficTab" name="trafficTab"/>
</item>
</layout>
</item>
@ -106,6 +120,11 @@
<header>ui/qt/widgets/traffic_tab.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TrafficTypesList</class>
<extends>QTreeView</extends>
<header>ui/qt/widgets/traffic_types_list.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>

View File

@ -16,8 +16,6 @@
#include <wsutil/utf8_entities.h>
#include <wsutil/filesystem.h>
#include "ui/recent.h"
#include <ui/qt/main_application.h>
#include <ui/qt/filter_action.h>
#include <ui/qt/models/atap_data_model.h>
@ -26,7 +24,6 @@
#include <ui/qt/widgets/traffic_tree.h>
#include <ui/qt/widgets/detachable_tabwidget.h>
#include <QVector>
#include <QStringList>
#include <QTreeView>
#include <QList>
@ -175,103 +172,26 @@ bool TrafficDataFilterProxy::lessThan(const QModelIndex &source_left, const QMod
}
static gboolean iterateProtocols(const void *key, void *value, void *userdata)
{
QMap<int, QString> *protocols = (QMap<int, QString> *)userdata;
register_ct_t* ct = (register_ct_t*)value;
const QString title = (const gchar*)key;
int proto_id = get_conversation_proto_id(ct);
protocols->insert(proto_id, title);
return FALSE;
}
TrafficTab::TrafficTab(QWidget * parent) :
DetachableTabWidget(parent)
{
_createModel = nullptr;
_disableTaps = false;
_nameResolution = false;
_recentList = nullptr;
setTabBasename(QString());
}
TrafficTab::~TrafficTab()
{
prefs_clear_string_list(*_recentList);
*_recentList = NULL;
_protocolButtons.clear();
{}
foreach (int protoId, _tabs.keys())
{
char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(protoId)));
*_recentList = g_list_append(*_recentList, title);
}
}
void TrafficTab::setProtocolInfo(QString tableName, GList ** recentList, ATapModelCallback createModel)
void TrafficTab::setProtocolInfo(QString tableName, QList<int> allProtocols, QList<int> openTabs, ATapModelCallback createModel)
{
setTabBasename(tableName);
_recentList = recentList;
_allProtocols = allProtocols;
if (createModel)
_createModel = createModel;
for (GList * endTab = *_recentList; endTab; endTab = endTab->next) {
int protoId = proto_get_id_by_short_name((const char *)endTab->data);
if (protoId > -1 && ! _protocols.contains(protoId))
_protocols.append(protoId);
}
if (_protocols.isEmpty()) {
QStringList protoNames = QStringList() << "eth" << "ip" << "ipv6" << "tcp" << "udp";
foreach(QString name, protoNames)
_protocols << proto_get_id_by_filter_name(name.toStdString().c_str());
}
QWidget * container = new QWidget(this);
container->setFixedHeight(tabBar()->height());
container->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
QHBoxLayout * layout = new QHBoxLayout(container);
layout->setContentsMargins(1, 0, 1, 0);
QPushButton * cornerButton = new QPushButton(tr("%1 Types").arg(tableName));
cornerButton->setFixedHeight(tabBar()->height());
cornerButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
QMenu * cornerMenu = new QMenu();
conversation_table_iterate_tables(iterateProtocols, &_allTaps);
foreach (int protoId, _allTaps.keys())
{
QAction * endPoint = new QAction(_allTaps[protoId], this);
endPoint->setProperty("protocol", QVariant::fromValue(protoId));
endPoint->setCheckable(true);
endPoint->setChecked(_protocols.contains(protoId));
connect(endPoint, &QAction::triggered, this, &TrafficTab::toggleTab);
_protocolButtons.insert(protoId, endPoint);
cornerMenu->addAction(endPoint);
}
cornerButton->setMenu(cornerMenu);
layout->addWidget(cornerButton);
setCornerWidget(container, Qt::TopRightCorner);
updateTabs();
}
void TrafficTab::toggleTab(bool checked)
{
QAction * orig = qobject_cast<QAction *>(sender());
if (!orig || ! orig->property("protocol").isValid())
return;
int protocol = orig->property("protocol").toInt();
if (!checked && _protocols.contains(protocol))
_protocols.removeAll(protocol);
else if (checked && ! _protocols.contains(protocol))
_protocols.append(protocol);
updateTabs();
setOpenTabs(openTabs);
}
void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate)
@ -376,61 +296,79 @@ void TrafficTab::disableTap()
emit disablingTaps();
}
void TrafficTab::updateTabs()
void TrafficTab::setOpenTabs(QList<int> protocols)
{
QList<int> keys = _tabs.keys();
QList<int> allProtocols = _allTaps.keys();
QList<int> tabs = _tabs.keys();
QList<int> remove;
blockSignals(true);
/* Adding new Tabs, and keeping the same order they are in the drop-down menu */
foreach (int proto, _protocols) {
if (!keys.contains(proto)) {
int insertIndex = -1;
auto bIdx = allProtocols.indexOf(proto);
int idx = 0;
while (insertIndex < 0 && idx < keys.count())
{
auto aIdx = allProtocols.indexOf(keys[idx]);
if (aIdx < 0) /* Key not in all protocols. This would be a fluke */
break;
if (aIdx > bIdx) /* Should never be equal, as proto is not yet in keys */
insertIndex = _tabs[keys[idx]];
idx++;
}
QTreeView * tree = createTree(proto);
QString tableName = proto_get_protocol_short_name(find_protocol_by_id(proto));
TabData tabData(tableName, proto);
QVariant storage;
storage.setValue(tabData);
if (tree->model()->rowCount() > 0)
tableName += QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(tree->model()->rowCount());
int tabId = insertTab(insertIndex, tree, tableName);
_protocolButtons[proto]->setChecked(true);
tabBar()->setTabData(tabId, storage);
}
}
/* Removing tabs no longer required. First filter the key array, for all tabs which
* are still being displayed */
foreach(int key, keys)
foreach(int protocol, protocols)
{
if ( _protocols.contains(key)) {
_protocolButtons[key]->setChecked(true);
keys.removeAll(key);
}
}
/* Removal step 2, now actually remove all elements. Counting down, otherwise removing
* a tab will shift the indeces */
for(int idx = count(); idx > 0; idx--) {
TabData tabData = qvariant_cast<TabData>(tabBar()->tabData(idx - 1));
if (keys.contains(tabData.protoId())) {
removeTab(idx - 1);
_protocolButtons[tabData.protoId()]->setChecked(false);
if (! tabs.contains(protocol)) {
insertProtoTab(protocol, false);
}
tabs.removeAll(protocol);
}
foreach(int protocol, tabs)
removeProtoTab(protocol, false);
blockSignals(false);
emit tabsChanged(_tabs.keys());
emit retapRequired();
}
void TrafficTab::insertProtoTab(int protoId, bool emitSignals)
{
QList<int> lUsed = _tabs.keys();
if (lUsed.contains(protoId) && lUsed.count() != count())
{
_tabs.clear();
for (int idx = 0; idx < count(); idx++) {
TabData tabData = qvariant_cast<TabData>(tabBar()->tabData(idx));
_tabs.insert(tabData.protoId(), idx);
}
lUsed = _tabs.keys();
}
if (protoId <= 0 || lUsed.contains(protoId))
return;
QList<int> lFull = _allProtocols;
int idx = lFull.indexOf(protoId);
if (idx < 0)
return;
QList<int> part = lFull.mid(0, idx);
int insertAt = 0;
if (part.count() > 0) {
for (int cnt = idx - 1; cnt >= 0; cnt--) {
if (lUsed.contains(part[cnt]) && part[cnt] != protoId) {
insertAt = lUsed.indexOf(part[cnt]) + 1;
break;
}
}
}
QTreeView * tree = createTree(protoId);
QString tableName = proto_get_protocol_short_name(find_protocol_by_id(protoId));
TabData tabData(tableName, protoId);
QVariant storage;
storage.setValue(tabData);
if (tree->model()->rowCount() > 0)
tableName += QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(tree->model()->rowCount());
int tabId = -1;
if (insertAt > -1)
tabId = insertTab(insertAt, tree, tableName);
else
tabId = addTab(tree, tableName);
if (tabId >= 0)
tabBar()->setTabData(tabId, storage);
/* We reset the correct tab idxs. That operations is costly, but it is only
* called during this operation and ensures, that other operations do not
* need to iterate, but rather can lookup the indeces. */
@ -440,7 +378,37 @@ void TrafficTab::updateTabs()
_tabs.insert(tabData.protoId(), idx);
}
emit retapRequired();
if (emitSignals) {
emit tabsChanged(_tabs.keys());
emit retapRequired();
}
}
void TrafficTab::removeProtoTab(int protoId, bool emitSignals)
{
if (_tabs.keys().contains(protoId)) {
for(int idx = 0; idx < count(); idx++) {
TabData tabData = qvariant_cast<TabData>(tabBar()->tabData(idx));
if (protoId == tabData.protoId()) {
removeTab(idx);
break;
}
}
}
/* We reset the correct tab idxs. That operations is costly, but it is only
* called during this operation and ensures, that other operations do not
* need to iterate, but rather can lookup the indeces. */
_tabs.clear();
for (int idx = 0; idx < count(); idx++) {
TabData tabData = qvariant_cast<TabData>(tabBar()->tabData(idx));
_tabs.insert(tabData.protoId(), idx);
}
if (emitSignals) {
emit tabsChanged(_tabs.keys());
emit retapRequired();
}
}
void TrafficTab::doCurrentIndexChange(const QModelIndex & cur, const QModelIndex &)
@ -484,7 +452,7 @@ void TrafficTab::modelReset()
return;
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(sender());
if (! qobject_cast<ATapDataModel *>(qsfpm->sourceModel()))
if (!qsfpm || ! qobject_cast<ATapDataModel *>(qsfpm->sourceModel()))
return;
ATapDataModel * atdm = qobject_cast<ATapDataModel *>(qsfpm->sourceModel());
@ -519,7 +487,7 @@ ATapDataModel * TrafficTab::modelForWidget(QWidget * searchWidget)
QTreeView * tree = qobject_cast<QTreeView *>(searchWidget);
if (qobject_cast<TrafficDataFilterProxy *>(tree->model())) {
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(tree->model());
if (qobject_cast<ATapDataModel *>(qsfpm->sourceModel())) {
if (qsfpm && qobject_cast<ATapDataModel *>(qsfpm->sourceModel())) {
return qobject_cast<ATapDataModel *>(qsfpm->sourceModel());
}
}
@ -736,9 +704,6 @@ void TrafficTab::detachTab(int tabIdx, QPoint pos) {
if (!model)
return;
int protocol = model->protoId();
_protocols.removeAll(protocol);
TrafficTree * tree = qobject_cast<TrafficTree *>(widget(tabIdx));
if (!tree)
return;
@ -746,7 +711,7 @@ void TrafficTab::detachTab(int tabIdx, QPoint pos) {
connect(this, &TrafficTab::disablingTaps ,tree , &TrafficTree::disableTap);
DetachableTabWidget::detachTab(tabIdx, pos);
updateTabs();
removeProtoTab(model->protoId());
}
void TrafficTab::attachTab(QWidget * content, QString name)
@ -757,8 +722,5 @@ void TrafficTab::attachTab(QWidget * content, QString name)
return;
}
int protocol = model->protoId();
_protocols.append(protocol);
updateTabs();
insertProtoTab(model->protoId());
}

View File

@ -12,8 +12,6 @@
#include "config.h"
#include <ui/recent.h>
#include <ui/qt/models/atap_data_model.h>
#include <ui/qt/filter_action.h>
#include <ui/qt/widgets/detachable_tabwidget.h>
@ -99,12 +97,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 recentList The list to store the selected protocols in
* @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 createModel A callback, which will create the correct model for the trees
*
* @see ATapModelCallback
*/
void setProtocolInfo(QString tableName, GList ** recentList, ATapModelCallback createModel);
void setProtocolInfo(QString tableName, QList<int> allProtocols, QList<int> openTabs, ATapModelCallback createModel);
/**
* @brief Set the Delegate object for a specific column
@ -212,11 +211,14 @@ public slots:
*/
void useAbsoluteTime(bool absolute);
void setOpenTabs(QList<int> protocols);
signals:
void filterAction(QString filter, FilterAction::Action action, FilterAction::ActionType type);
void tabDataChanged(int idx);
void retapRequired();
void disablingTaps();
void tabsChanged(QList<int> protocols);
protected slots:
@ -224,29 +226,26 @@ protected slots:
virtual void attachTab(QWidget * content, QString name) override;
private:
QVector<int> _protocols;
QMap<int, QString> _allTaps;
QMap<int, QAction *> _protocolButtons;
QList<int> _allProtocols;
QMap<int, int> _tabs;
GList ** _recentList;
ATapModelCallback _createModel;
QMap<int, ATapCreateDelegate> _createDelegates;
bool _disableTaps;
bool _nameResolution;
void updateTabs();
QTreeView * createTree(int protoId);
ATapDataModel * modelForTabIndex(int tabIdx = -1);
ATapDataModel * modelForWidget(QWidget * widget);
void insertProtoTab(int protoId, bool emitSignals = true);
void removeProtoTab(int protoId, bool emitSignals = true);
#ifdef HAVE_MAXMINDDB
bool writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel);
#endif
private slots:
void toggleTab(bool checked = false);
void modelReset();
void doCurrentIndexChange(const QModelIndex & cur, const QModelIndex & prev);

View File

@ -27,7 +27,6 @@
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/widgets/traffic_tree.h>
#include <QVector>
#include <QStringList>
#include <QTreeView>
#include <QList>
@ -53,7 +52,7 @@ TrafficTree::TrafficTree(QString baseName, QWidget *parent) :
setRootIsDecorated(false);
setSortingEnabled(true);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QTreeView::customContextMenuRequested, this, &TrafficTree::customContextMenu);
}

View File

@ -0,0 +1,236 @@
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <glib.h>
#include <epan/conversation_table.h>
#include <ui/qt/widgets/traffic_types_list.h>
#include <QStringList>
TrafficTypesRowData::TrafficTypesRowData(int protocol, QString name) :
_protocol(protocol),
_name(name),
_checked(false)
{}
int TrafficTypesRowData::protocol() const
{
return _protocol;
}
QString TrafficTypesRowData::name() const
{
return _name;
}
bool TrafficTypesRowData::checked() const
{
return _checked;
}
void TrafficTypesRowData::setChecked(bool checked)
{
_checked = checked;
}
static gboolean iterateProtocols(const void *key, void *value, void *userdata)
{
QList<TrafficTypesRowData> * protocols = (QList<TrafficTypesRowData> *)userdata;
register_ct_t* ct = (register_ct_t*)value;
const QString title = (const gchar*)key;
int proto_id = get_conversation_proto_id(ct);
TrafficTypesRowData entry(proto_id, title);
protocols->append(entry);
return FALSE;
}
TrafficTypesModel::TrafficTypesModel(GList ** recentList, QObject *parent) :
QAbstractListModel(parent),
_recentList(recentList)
{
conversation_table_iterate_tables(iterateProtocols, &_allTaps);
QList<int> _protocols;
for (GList * endTab = *_recentList; endTab; endTab = endTab->next) {
int protoId = proto_get_id_by_short_name((const char *)endTab->data);
if (protoId > -1 && ! _protocols.contains(protoId))
_protocols.append(protoId);
}
if (_protocols.isEmpty()) {
QStringList protoNames = QStringList() << "eth" << "ip" << "ipv6" << "tcp" << "udp";
foreach(QString name, protoNames)
_protocols << proto_get_id_by_filter_name(name.toStdString().c_str());
}
for(int cnt = 0; cnt < _allTaps.count(); cnt++)
{
_allTaps[cnt].setChecked(false);
if (_protocols.contains(_allTaps[cnt].protocol()))
_allTaps[cnt].setChecked(true);
}
}
int TrafficTypesModel::rowCount(const QModelIndex &) const
{
return _allTaps.count();
}
int TrafficTypesModel::columnCount(const QModelIndex &) const
{
return TrafficTypesModel::COL_NUM;
}
QVariant TrafficTypesModel::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid())
return QVariant();
TrafficTypesRowData data = _allTaps[idx.row()];
if (role == Qt::DisplayRole)
{
switch(idx.column())
{
case(TrafficTypesModel::COL_NAME):
return data.name();
case(TrafficTypesModel::COL_PROTOCOL):
return data.protocol();
}
} else if (role == Qt::CheckStateRole && idx.column() == TrafficTypesModel::COL_CHECKED) {
return data.checked() ? Qt::Checked : Qt::Unchecked;
} else if (role == TrafficTypesModel::TRAFFIC_PROTOCOL) {
return data.protocol();
} else if (role == TrafficTypesModel::TRAFFIC_IS_CHECKED) {
return (bool)data.checked();
}
return QVariant();
}
QVariant TrafficTypesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section < 0 || role != Qt::DisplayRole || orientation != Qt::Horizontal)
return QVariant();
if (section == TrafficTypesModel::COL_NAME)
return tr("Protocol");
return QVariant();
}
Qt::ItemFlags TrafficTypesModel::flags (const QModelIndex & idx) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(idx);
if (idx.isValid())
return defaultFlags | Qt::ItemIsUserCheckable;
return defaultFlags;
}
bool TrafficTypesModel::setData(const QModelIndex &idx, const QVariant &value, int role)
{
if(!idx.isValid() || role != Qt::CheckStateRole)
return false;
if (_allTaps.count() <= idx.row())
return false;
_allTaps[idx.row()].setChecked(value == Qt::Checked);
QList<int> selected;
prefs_clear_string_list(*_recentList);
*_recentList = NULL;
for (int cnt = 0; cnt < _allTaps.count(); cnt++) {
if (_allTaps[cnt].checked()) {
int protoId = _allTaps[cnt].protocol();
selected.append(protoId);
char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(protoId)));
*_recentList = g_list_append(*_recentList, title);
}
}
emit protocolsChanged(selected);
emit dataChanged(idx, idx);
return true;
}
void TrafficTypesModel::selectProtocols(QList<int> protocols)
{
beginResetModel();
for (int cnt = 0; cnt < _allTaps.count(); cnt++) {
_allTaps[cnt].setChecked(false);
if (protocols.contains(_allTaps[cnt].protocol()))
_allTaps[cnt].setChecked(true);
}
endResetModel();
}
TrafficTypesList::TrafficTypesList(QWidget *parent) :
QTreeView(parent)
{
_name = QString();
_model = nullptr;
setAlternatingRowColors(true);
setRootIsDecorated(false);
}
void TrafficTypesList::setProtocolInfo(QString name, GList ** recentList)
{
_name = name;
_model = new TrafficTypesModel(recentList);
setModel(_model);
connect(_model, &TrafficTypesModel::protocolsChanged, this, &TrafficTypesList::protocolsChanged);
resizeColumnToContents(0);
resizeColumnToContents(1);
}
void TrafficTypesList::selectProtocols(QList<int> protocols)
{
if (_model)
_model->selectProtocols(protocols);
}
QList<int> TrafficTypesList::protocols() const
{
QList<int> entries;
for (int cnt = 0; cnt < _model->rowCount(); cnt++) {
QModelIndex idx = _model->index(cnt, TrafficTypesModel::COL_CHECKED);
int protoId = _model->data(idx, TrafficTypesModel::TRAFFIC_PROTOCOL).toInt();
if (protoId > 0 && ! entries.contains(protoId))
entries.append(protoId);
}
return entries;
}
QList<int> TrafficTypesList::selectedProtocols() const
{
QList<int> entries;
for (int cnt = 0; cnt < _model->rowCount(); cnt++) {
QModelIndex idx = _model->index(cnt, TrafficTypesModel::COL_CHECKED);
int protoId = _model->data(idx, TrafficTypesModel::TRAFFIC_PROTOCOL).toInt();
if (protoId > 0 && ! entries.contains(protoId) && _model->data(idx, TrafficTypesModel::TRAFFIC_IS_CHECKED).toBool())
entries.append(protoId);
}
return entries;
}

View File

@ -0,0 +1,100 @@
/** @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_TYPES_LIST_H
#define TRAFFIC_TYPES_LIST_H
#include "config.h"
#include <QTreeView>
#include <QAbstractListModel>
#include <QMap>
#include <QString>
class TrafficTypesRowData
{
public:
TrafficTypesRowData(int protocol, QString name);
int protocol() const;
QString name() const;
bool checked() const;
void setChecked(bool checked);
private:
int _protocol;
QString _name;
bool _checked;
};
class TrafficTypesModel : public QAbstractListModel
{
Q_OBJECT
public:
enum {
TRAFFIC_PROTOCOL = Qt::UserRole,
TRAFFIC_IS_CHECKED,
} eTrafficUserData;
enum {
COL_CHECKED,
COL_NAME,
COL_NUM,
COL_PROTOCOL,
} eTrafficColumnNames;
TrafficTypesModel(GList ** recentList, QObject *parent = nullptr);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &idx, const QVariant &value, int role) override;
virtual Qt::ItemFlags flags (const QModelIndex & idx) const override;
QList<int> protocols() const;
public slots:
void selectProtocols(QList<int> protocols);
signals:
void protocolsChanged(QList<int> protocols);
private:
QList<TrafficTypesRowData> _allTaps;
GList ** _recentList;
};
class TrafficTypesList : public QTreeView
{
Q_OBJECT
public:
TrafficTypesList(QWidget *parent = nullptr);
void setProtocolInfo(QString name, GList ** recentList);
QList<int> protocols() const;
QList<int> selectedProtocols() const;
public slots:
void selectProtocols(QList<int> protocols);
signals:
void protocolsChanged(QList<int> protocols);
private:
QString _name;
TrafficTypesModel * _model;
};
#endif // TRAFFIC_TYPES_LIST_H