diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/tap_parameter_dialog.cpp | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/tap_parameter_dialog.cpp')
-rw-r--r-- | ui/qt/tap_parameter_dialog.cpp | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/ui/qt/tap_parameter_dialog.cpp b/ui/qt/tap_parameter_dialog.cpp new file mode 100644 index 0000000..4652b67 --- /dev/null +++ b/ui/qt/tap_parameter_dialog.cpp @@ -0,0 +1,613 @@ +/* tap_parameter_dialog.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * @file Tap parameter dialog class + * + * Base class for statistics dialogs. Subclasses must implement: + * - fillTree. Called when the dialog is first displayed and when a display + * filter is applied. In most cases the subclass should clear the tree and + * retap packets here. + * - filterExpression. If the subclass supports filtering context menu items + * ("Apply As Filter", etc.) it should fill in ctx_menu_ and implement + * filterExpression. + * - getTreeAsString or treeItemData. Used for "Copy" and "Save As...". + * - + */ + +#include "tap_parameter_dialog.h" +#include <ui_tap_parameter_dialog.h> + +#include <errno.h> + +#include "epan/stat_tap_ui.h" + +#ifdef Q_OS_WIN +#include <windows.h> +#include "ui/packet_range.h" +#include "ui/win32/file_dlg_win32.h" +#endif // Q_OS_WIN + +#include "ui/util.h" +#include <wsutil/utf8_entities.h> + +#include "wsutil/file_util.h" + +#include "progress_frame.h" +#include <ui/qt/utils/qt_ui_utils.h> +#include "main_application.h" + +#include <QClipboard> +#include <QContextMenuEvent> +#include <QMessageBox> +#include <QFileDialog> + +// The GTK+ counterpart uses tap_param_dlg, which we don't use. If we +// need tap parameters we should probably create a TapParameterDialog +// class based on WiresharkDialog and subclass it here. + +// To do: +// - Add tap parameters? SCSI SRT uses PARAM_ENUM. Everything appears to use +// PARAM_FILTER. Nothing uses _UINT, _STRING, or _UUID. +// - Update to match bug 9452 / r53657. +// - Create a TapParameterTreeWidgetItem class? +// - Better / more usable XML output. + +const int expand_all_threshold_ = 100; // Arbitrary + +static QHash<const QString, tpdCreator> cfg_str_to_creator_; +const QString TapParameterDialog::action_name_ = "TapParameterAction"; + +TapParameterDialog::TapParameterDialog(QWidget &parent, CaptureFile &cf, int help_topic) : + WiresharkDialog(parent, cf), + ui(new Ui::TapParameterDialog), + help_topic_(help_topic) +{ + ui->setupUi(this); + + // Only show a hint label if a subclass provides a hint. + ui->hintLabel->hide(); + + ctx_menu_.addAction(ui->actionCopyToClipboard); + ctx_menu_.addAction(ui->actionSaveAs); + + QPushButton *button; + button = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole); + connect(button, &QPushButton::clicked, this, &TapParameterDialog::on_actionCopyToClipboard_triggered); + + button = ui->buttonBox->addButton(tr("Save as…"), QDialogButtonBox::ActionRole); + connect(button, &QPushButton::clicked, this, &TapParameterDialog::on_actionSaveAs_triggered); + + connect(ui->displayFilterLineEdit, SIGNAL(textChanged(QString)), + this, SLOT(updateWidgets())); + + ProgressFrame::addToButtonBox(ui->buttonBox, &parent); + + if (help_topic_ < 1) { + ui->buttonBox->button(QDialogButtonBox::Help)->hide(); + } + + if (!ui->displayFilterLineEdit->text().isEmpty()) { + QString filter = ui->displayFilterLineEdit->text(); + emit updateFilter(filter); + } + show_timer_ = new QTimer(this); + setRetapOnShow(true); +} + +TapParameterDialog::~TapParameterDialog() +{ + delete ui; + show_timer_->stop(); + delete show_timer_; +} + +void TapParameterDialog::registerDialog(const QString title, const char *cfg_abbr, register_stat_group_t group, stat_tap_init_cb tap_init_cb, tpdCreator creator) +{ + stat_tap_ui ui_info; + + ui_info.group = group; + ui_info.title = title.toUtf8().constData(); + ui_info.cli_string = cfg_abbr; + ui_info.tap_init_cb = tap_init_cb; + ui_info.nparams = 0; // We'll need this for SCSI SRT + ui_info.params = NULL; + register_stat_tap_ui(&ui_info, NULL); + + QString cfg_str = cfg_abbr; + cfg_str_to_creator_[cfg_str] = creator; + + QAction *tpd_action = new QAction(title, mainApp); + tpd_action->setObjectName(action_name_); + tpd_action->setData(cfg_str); + mainApp->addDynamicMenuGroupItem(group, tpd_action); +} + +TapParameterDialog *TapParameterDialog::showTapParameterStatistics(QWidget &parent, CaptureFile &cf, const QString cfg_str, const QString arg, void *) +{ + if (cfg_str_to_creator_.contains(cfg_str)) { + TapParameterDialog *tpd = cfg_str_to_creator_[cfg_str](parent, cfg_str, arg, cf); + return tpd; + } + return NULL; +} + +QTreeWidget *TapParameterDialog::statsTreeWidget() +{ + return ui->statsTreeWidget; +} + +QLineEdit *TapParameterDialog::displayFilterLineEdit() +{ + return ui->displayFilterLineEdit; +} + +QPushButton *TapParameterDialog::applyFilterButton() +{ + return ui->applyFilterButton; +} + +QVBoxLayout *TapParameterDialog::verticalLayout() +{ + return ui->verticalLayout; +} + +QHBoxLayout *TapParameterDialog::filterLayout() +{ + return ui->filterLayout; +} + +QString TapParameterDialog::displayFilter() +{ + return ui->displayFilterLineEdit->text(); +} + +// This assumes that we're called before signals are connected or show() +// is called. +void TapParameterDialog::setDisplayFilter(const QString &filter) +{ + ui->displayFilterLineEdit->setText(filter); +} + +void TapParameterDialog::setHint(const QString &hint) +{ + ui->hintLabel->setText(hint); + ui->hintLabel->show(); +} + +void TapParameterDialog::setRetapOnShow(bool retap) +{ + show_timer_->stop(); + if (retap) { + show_timer_->singleShot(0, this, SLOT(on_applyFilterButton_clicked())); + } +} + +void TapParameterDialog::filterActionTriggered() +{ + FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender()); + QString filter_expr = filterExpression(); + + if (!fa || filter_expr.isEmpty()) { + return; + } + + emit filterAction(filter_expr, fa->action(), fa->actionType()); +} + +void TapParameterDialog::collapseAllActionTriggered() { + ui->statsTreeWidget->collapseAll(); +} +void TapParameterDialog::expandAllActionTriggered() { + ui->statsTreeWidget->expandAll(); +} + +QString TapParameterDialog::itemDataToPlain(QVariant var, int width) +{ + QString plain_str; + int align_mul = 1; + + switch (var.userType()) { + case QMetaType::QString: + align_mul = -1; + // Fall through + case QMetaType::Int: + case QMetaType::UInt: + plain_str = var.toString(); + break; + case QMetaType::Double: + plain_str = QString::number(var.toDouble(), 'f', 6); + break; + default: + break; + } + + if (plain_str.length() < width) { + plain_str = QString("%1").arg(plain_str, width * align_mul); + } + return plain_str; +} + +QList<QVariant> TapParameterDialog::treeItemData(QTreeWidgetItem *) const +{ + return QList<QVariant>(); +} + +const QString plain_sep_ = " "; +QByteArray TapParameterDialog::getTreeAsString(st_format_type format) +{ + QByteArray ba; + QTreeWidgetItemIterator it(ui->statsTreeWidget, QTreeWidgetItemIterator::NotHidden); + + QList<int> col_widths; + QByteArray footer; + + // Title + header + switch (format) { + case ST_FORMAT_PLAIN: + { + // Iterating over trees. + QTreeWidgetItemIterator width_it(it); + QString plain_header; + while (*width_it) { + // Iterating over items within this tree. + for (int col=0; col < ui->statsTreeWidget->columnCount(); col++) { + if (col_widths.size() <= col) { + col_widths.append(static_cast<int>(ui->statsTreeWidget->headerItem()->text(col).length())); + } + QVariant var = ui->statsTreeWidget->headerItem()->data(col, Qt::DisplayRole); + if (var.userType() == QMetaType::QString) { + col_widths[col] = qMax(col_widths[col], static_cast<int>(itemDataToPlain(var).length())); + } + } + ++width_it; + } + QStringList ph_parts; + for (int col = 0; col < ui->statsTreeWidget->columnCount() && col < col_widths.length(); col++) { + ph_parts << ui->statsTreeWidget->headerItem()->text(col); + } + plain_header = ph_parts.join(plain_sep_); + + QByteArray top_separator; + top_separator.fill('=', plain_header.length()); + top_separator.append('\n'); + QString file_header = QString("%1 - %2:\n").arg(windowSubtitle(), cap_file_.fileDisplayName()); + footer.fill('-', plain_header.length()); + footer.append('\n'); + plain_header.append('\n'); + + ba.append(top_separator); + ba.append(file_header.toUtf8()); + ba.append(plain_header.toUtf8()); + ba.append(footer); + break; + } + case ST_FORMAT_CSV: + { + QString csv_header; + QStringList ch_parts; + for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) { + ch_parts << QString("\"%1\"").arg(ui->statsTreeWidget->headerItem()->text(col)); + } + csv_header = ch_parts.join(","); + csv_header.append('\n'); + ba.append(csv_header.toUtf8().constData()); + break; + } + case ST_FORMAT_XML: + { + // XXX What's a useful format? This mostly conforms to DocBook. + ba.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + QString title = html_escape(windowSubtitle()); + QString xml_header = QString("<table>\n<title>%1</title>\n").arg(title); + ba.append(xml_header.toUtf8()); + ba.append("<thead>\n<row>\n"); + for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) { + title = html_escape(ui->statsTreeWidget->headerItem()->text(col)); + title = QString(" <entry>%1</entry>\n").arg(title); + ba.append(title.toUtf8()); + } + ba.append("</row>\n</thead>\n"); + ba.append("<tbody>\n"); + footer = "</tbody>\n</table>\n"; + break; + } + case ST_FORMAT_YAML: + { + QString yaml_header; + ba.append("---\n"); + yaml_header = QString("Description: \"%1\"\nFile: \"%2\"\nItems:\n").arg(windowSubtitle()).arg(cap_file_.fileDisplayName()); + ba.append(yaml_header.toUtf8()); + break; + } + default: + break; + } + + // Data + while (*it) { + QList<QVariant> tid = treeItemData((*it)); + if (tid.length() < 1) { + ++it; + continue; + } + + if (tid.length() < ui->statsTreeWidget->columnCount()) { + // Assume we have a header + } + + // Assume var length == columnCount + QString line; + QStringList parts; + + switch (format) { + case ST_FORMAT_PLAIN: + { + int i = 0; + foreach (QVariant var, tid) { + parts << itemDataToPlain(var, col_widths[i]); + i++; + } + line = parts.join(plain_sep_); + line.append('\n'); + break; + } + case ST_FORMAT_CSV: + foreach (QVariant var, tid) { + if (var.userType() == QMetaType::QString) { + parts << QString("\"%1\"").arg(var.toString()); + } else { + parts << var.toString(); + } + } + line = parts.join(","); + line.append('\n'); + break; + case ST_FORMAT_XML: + { + line = "<row>\n"; + foreach (QVariant var, tid) { + QString entry = html_escape(var.toString()); + line.append(QString(" <entry>%1</entry>\n").arg(entry)); + } + line.append("</row>\n"); + break; + } + case ST_FORMAT_YAML: + { + int col = 0; + QString indent = "-"; + foreach (QVariant var, tid) { + QString entry; + if (var.userType() == QMetaType::QString) { + entry = QString("\"%1\"").arg(var.toString()); + } else { + entry = var.toString(); + } + line.append(QString(" %1 %2: %3\n").arg(indent).arg(ui->statsTreeWidget->headerItem()->text(col), entry)); + indent = " "; + col++; + } + break; + } + default: + break; + } + + ba.append(line.toUtf8()); + ++it; + } + + // Footer + ba.append(footer); // plain only? + return ba; +} + +void TapParameterDialog::drawTreeItems() +{ + if (ui->statsTreeWidget->model()->rowCount() < expand_all_threshold_) { + ui->statsTreeWidget->expandAll(); + } + + for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) { + ui->statsTreeWidget->resizeColumnToContents(col); + } +} + +void TapParameterDialog::contextMenuEvent(QContextMenuEvent *event) +{ + bool enable = filterExpression().length() > 0 ? true : false; + + foreach (QAction *fa, filter_actions_) { + fa->setEnabled(enable); + } + + ctx_menu_.popup(event->globalPos()); +} + +void TapParameterDialog::addFilterActions() +{ + QMenu *submenu; + QAction *insert_action = ctx_menu_.actions().first(); + + FilterAction::Action cur_action = FilterAction::ActionApply; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + filter_actions_ << fa; + } + ctx_menu_.insertMenu(insert_action, submenu); + + cur_action = FilterAction::ActionPrepare; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + filter_actions_ << fa; + } + ctx_menu_.insertMenu(insert_action, submenu); + + cur_action = FilterAction::ActionFind; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes(cur_action)) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + filter_actions_ << fa; + } + ctx_menu_.insertMenu(insert_action, submenu); + + cur_action = FilterAction::ActionColorize; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes(cur_action)) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + filter_actions_ << fa; + } + ctx_menu_.insertMenu(insert_action, submenu); + ctx_menu_.insertSeparator(insert_action); +} + +void TapParameterDialog::addTreeCollapseAllActions() +{ + ctx_menu_.addSeparator(); + + QAction *collapse = new QAction(tr("Collapse All"), this); + ctx_menu_.addAction(collapse); + connect(collapse, SIGNAL(triggered()), this, SLOT(collapseAllActionTriggered())); + + QAction *expand = new QAction(tr("Expand All"), this); + ctx_menu_.addAction(expand); + connect(expand, SIGNAL(triggered()), this, SLOT(expandAllActionTriggered())); +} + +void TapParameterDialog::updateWidgets() +{ + bool edit_enable = true; + bool apply_enable = true; + + if (file_closed_) { + edit_enable = false; + apply_enable = false; + } else if (!ui->displayFilterLineEdit->checkFilter()) { + // XXX Tell the user why the filter is invalid. + apply_enable = false; + } + ui->displayFilterLineEdit->setEnabled(edit_enable); + ui->applyFilterButton->setEnabled(apply_enable); + + WiresharkDialog::updateWidgets(); +} + +void TapParameterDialog::on_applyFilterButton_clicked() +{ + beginRetapPackets(); + if (!ui->displayFilterLineEdit->checkFilter()) + return; + + QString filter = ui->displayFilterLineEdit->text(); + emit updateFilter(filter); + // If we wanted to be fancy we could add an isRetapping function to + // either WiresharkDialog or CaptureFile and use it in updateWidgets + // to enable and disable the apply button as needed. + // For now we use more simple but less useful logic. + bool df_enabled = ui->displayFilterLineEdit->isEnabled(); + bool af_enabled = ui->applyFilterButton->isEnabled(); + ui->displayFilterLineEdit->setEnabled(false); + ui->applyFilterButton->setEnabled(false); + fillTree(); + ui->applyFilterButton->setEnabled(af_enabled); + ui->displayFilterLineEdit->setEnabled(df_enabled); + endRetapPackets(); +} + +void TapParameterDialog::on_actionCopyToClipboard_triggered() +{ + mainApp->clipboard()->setText(getTreeAsString(ST_FORMAT_PLAIN)); +} + +void TapParameterDialog::on_actionSaveAs_triggered() +{ + QString selectedFilter; + st_format_type format; + const char *file_ext; + FILE *f; + bool success = false; + int last_errno; + +#ifdef Q_OS_WIN + HANDLE da_ctx = set_thread_per_monitor_v2_awareness(); +#endif + QFileDialog SaveAsDialog(this, mainApp->windowTitleString(tr("Save Statistics As…")), + get_open_dialog_initial_dir()); + SaveAsDialog.setNameFilter(tr("Plain text file (*.txt);;" + "Comma separated values (*.csv);;" + "XML document (*.xml);;" + "YAML document (*.yaml)")); + SaveAsDialog.selectNameFilter(tr("Plain text file (*.txt)")); + SaveAsDialog.setAcceptMode(QFileDialog::AcceptSave); + int result = SaveAsDialog.exec(); +#ifdef Q_OS_WIN + revert_thread_per_monitor_v2_awareness(da_ctx); +#endif + if (!result) { + return; + } + selectedFilter= SaveAsDialog.selectedNameFilter(); + if (selectedFilter.contains("*.yaml", Qt::CaseInsensitive)) { + format = ST_FORMAT_YAML; + file_ext = ".yaml"; + } + else if (selectedFilter.contains("*.xml", Qt::CaseInsensitive)) { + format = ST_FORMAT_XML; + file_ext = ".xml"; + } + else if (selectedFilter.contains("*.csv", Qt::CaseInsensitive)) { + format = ST_FORMAT_CSV; + file_ext = ".csv"; + } + else { + format = ST_FORMAT_PLAIN; + file_ext = ".txt"; + } + + // Get selected filename and add extension of necessary + QString file_name = SaveAsDialog.selectedFiles()[0]; + if (!file_name.endsWith(file_ext, Qt::CaseInsensitive)) { + file_name.append(file_ext); + } + + QByteArray tree_as_ba = getTreeAsString(format); + + // actually save the file + f = ws_fopen (file_name.toUtf8().constData(), "w"); + last_errno = errno; + if (f) { + if (fputs(tree_as_ba.data(), f) != EOF) { + success = true; + } + last_errno = errno; + fclose(f); + } + if (!success) { + QMessageBox::warning(this, tr("Error saving file %1").arg(file_name), + g_strerror (last_errno)); + } +} + +void TapParameterDialog::on_buttonBox_helpRequested() +{ + if (help_topic_ > 0) { + mainApp->helpTopicAction((topic_action_e) help_topic_); + } +} |