1 /* tap_parameter_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * @file Tap parameter dialog class
25 * Base class for statistics dialogs. Subclasses must implement:
26 * - fillTree. Called when the dialog is first displayed and when a display
27 * filter is applied. In most cases the subclass should clear the tree and
29 * - filterExpression. If the subclass supports filtering context menu items
30 * ("Apply As Filter", etc.) it should fill in ctx_menu_ and implement
32 * - getTreeAsString or treeItemData. Used for "Copy" and "Save As...".
36 #include "tap_parameter_dialog.h"
37 #include <ui_tap_parameter_dialog.h>
41 #include "epan/stat_tap_ui.h"
43 #include "ui/last_open_dir.h"
44 #include <wsutil/utf8_entities.h>
46 #include "wsutil/file_util.h"
48 #include "progress_frame.h"
49 #include "qt_ui_utils.h"
50 #include "wireshark_application.h"
53 #include <QContextMenuEvent>
54 #include <QMessageBox>
55 #include <QFileDialog>
57 // The GTK+ counterpart uses tap_param_dlg, which we don't use. If we
58 // need tap parameters we should probably create a TapParameterDialog
59 // class based on WiresharkDialog and subclass it here.
62 // - Add tap parameters? SCSI SRT uses PARAM_ENUM. Everything appears to use
63 // PARAM_FILTER. Nothing uses _UINT, _STRING, or _UUID.
64 // - Update to match bug 9452 / r53657.
65 // - Create a TapParameterTreeWidgetItem class?
66 // - Better / more usable XML output.
68 const int expand_all_threshold_ = 100; // Arbitrary
70 static QHash<const QString, tpdCreator> cfg_str_to_creator_;
71 const QString TapParameterDialog::action_name_ = "TapParameterAction";
73 TapParameterDialog::TapParameterDialog(QWidget &parent, CaptureFile &cf, int help_topic) :
74 WiresharkDialog(parent, cf),
75 ui(new Ui::TapParameterDialog),
76 help_topic_(help_topic)
80 // Only show a hint label if a subclass provides a hint.
81 ui->hintLabel->hide();
83 ctx_menu_.addAction(ui->actionCopyToClipboard);
84 ctx_menu_.addAction(ui->actionSaveAs);
87 button = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
88 connect(button, SIGNAL(clicked()), this, SLOT(on_actionCopyToClipboard_triggered()));
90 button = ui->buttonBox->addButton(tr("Save as" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ActionRole);
91 connect(button, SIGNAL(clicked()), this, SLOT(on_actionSaveAs_triggered()));
93 connect(ui->displayFilterLineEdit, SIGNAL(textChanged(QString)),
94 this, SLOT(updateWidgets()));
96 ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
98 if (help_topic_ < 1) {
99 ui->buttonBox->button(QDialogButtonBox::Help)->hide();
102 if (!ui->displayFilterLineEdit->text().isEmpty()) {
103 QString filter = ui->displayFilterLineEdit->text();
104 emit updateFilter(filter);
106 show_timer_ = new QTimer(this);
107 setRetapOnShow(true);
110 TapParameterDialog::~TapParameterDialog()
117 void TapParameterDialog::registerDialog(const QString title, const char *cfg_abbr, register_stat_group_t group, stat_tap_init_cb tap_init_cb, tpdCreator creator)
121 ui_info.group = group;
122 ui_info.title = title.toUtf8().constData();
123 ui_info.cli_string = cfg_abbr;
124 ui_info.tap_init_cb = tap_init_cb;
125 ui_info.nparams = 0; // We'll need this for SCSI SRT
126 ui_info.params = NULL;
127 register_stat_tap_ui(&ui_info, NULL);
129 QString cfg_str = cfg_abbr;
130 cfg_str_to_creator_[cfg_str] = creator;
132 QAction *tpd_action = new QAction(title, NULL);
133 tpd_action->setObjectName(action_name_);
134 tpd_action->setData(cfg_str);
135 wsApp->addDynamicMenuGroupItem(group, tpd_action);
138 TapParameterDialog *TapParameterDialog::showTapParameterStatistics(QWidget &parent, CaptureFile &cf, const QString cfg_str, const QString arg, void *)
140 if (cfg_str_to_creator_.contains(cfg_str)) {
141 TapParameterDialog *tpd = cfg_str_to_creator_[cfg_str](parent, cfg_str, arg, cf);
147 QTreeWidget *TapParameterDialog::statsTreeWidget()
149 return ui->statsTreeWidget;
152 QLineEdit *TapParameterDialog::displayFilterLineEdit()
154 return ui->displayFilterLineEdit;
157 QPushButton *TapParameterDialog::applyFilterButton()
159 return ui->applyFilterButton;
162 QVBoxLayout *TapParameterDialog::verticalLayout()
164 return ui->verticalLayout;
167 QHBoxLayout *TapParameterDialog::filterLayout()
169 return ui->filterLayout;
172 QString TapParameterDialog::displayFilter()
174 return ui->displayFilterLineEdit->text();
177 // This assumes that we're called before signals are connected or show()
179 void TapParameterDialog::setDisplayFilter(const QString &filter)
181 ui->displayFilterLineEdit->setText(filter);
184 void TapParameterDialog::setHint(const QString &hint)
186 ui->hintLabel->setText(hint);
187 ui->hintLabel->show();
190 void TapParameterDialog::setRetapOnShow(bool retap)
194 show_timer_->singleShot(0, this, SLOT(on_applyFilterButton_clicked()));
198 void TapParameterDialog::filterActionTriggered()
200 FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
201 QString filter_expr = filterExpression();
203 if (!fa || filter_expr.isEmpty()) {
207 emit filterAction(filter_expr, fa->action(), fa->actionType());
210 QString TapParameterDialog::itemDataToPlain(QVariant var, int width)
215 switch (var.type()) {
216 case QVariant::String:
221 plain_str = var.toString();
223 case QVariant::Double:
224 plain_str = QString::number(var.toDouble(), 'f', 6);
230 if (plain_str.length() < width) {
231 plain_str = QString("%1").arg(plain_str, width * align_mul);
236 QList<QVariant> TapParameterDialog::treeItemData(QTreeWidgetItem *) const
238 return QList<QVariant>();
241 const QString plain_sep_ = " ";
242 QByteArray TapParameterDialog::getTreeAsString(st_format_type format)
245 QTreeWidgetItemIterator it(ui->statsTreeWidget, QTreeWidgetItemIterator::NotHidden);
247 QList<int> col_widths;
252 case ST_FORMAT_PLAIN:
254 QTreeWidgetItemIterator width_it(it);
255 QString plain_header;
257 QList<QVariant> tid = treeItemData((*width_it));
259 foreach (QVariant var, tid) {
260 if (col_widths.size() <= col) {
261 col_widths.append(ui->statsTreeWidget->headerItem()->text(col).length());
263 if (var.type() == QVariant::String) {
264 col_widths[col] = qMax(col_widths[col], itemDataToPlain(var).length());
270 QStringList ph_parts;
271 for (int col = 0; col < ui->statsTreeWidget->columnCount() && col < col_widths.length(); col++) {
272 ph_parts << ui->statsTreeWidget->headerItem()->text(col);
274 plain_header = ph_parts.join(plain_sep_);
276 QByteArray top_separator;
277 top_separator.fill('=', plain_header.length());
278 top_separator.append('\n');
279 QString file_header = QString("%1 - %2:\n").arg(windowSubtitle(), cap_file_.fileName());
280 footer.fill('-', plain_header.length());
282 plain_header.append('\n');
284 ba.append(top_separator);
285 ba.append(file_header);
286 ba.append(plain_header);
293 QStringList ch_parts;
294 for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) {
295 ch_parts << QString("\"%1\"").arg(ui->statsTreeWidget->headerItem()->text(col));
297 csv_header = ch_parts.join(",");
298 csv_header.append('\n');
299 ba.append(csv_header.toUtf8().constData());
304 // XXX What's a useful format? This mostly conforms to DocBook.
305 ba.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
306 QString title = html_escape(windowSubtitle());
307 QString xml_header = QString("<table>\n<title>%1</title>\n").arg(title);
308 ba.append(xml_header.toUtf8());
309 ba.append("<thead>\n<row>\n");
310 for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) {
311 title = html_escape(ui->statsTreeWidget->headerItem()->text(col));
312 title = QString(" <entry>%1</entry>\n").arg(title);
313 ba.append(title.toUtf8());
315 ba.append("</row>\n</thead>\n");
316 ba.append("<tbody>\n");
317 footer = "</tbody>\n</table>\n";
324 yaml_header = QString("Description: \"%1\"\nFile: \"%2\"\nItems:\n").arg(windowSubtitle()).arg(cap_file_.fileName());
325 ba.append(yaml_header.toUtf8());
334 QList<QVariant> tid = treeItemData((*it));
335 if (tid.length() < 1) {
340 if (tid.length() < ui->statsTreeWidget->columnCount()) {
341 // Assume we have a header
344 // Assume var length == columnCount
349 case ST_FORMAT_PLAIN:
352 foreach (QVariant var, tid) {
353 parts << itemDataToPlain(var, col_widths[i]);
356 line = parts.join(plain_sep_);
361 foreach (QVariant var, tid) {
362 if (var.type() == QVariant::String) {
363 parts << QString("\"%1\"").arg(var.toString());
365 parts << var.toString();
368 line = parts.join(",");
374 foreach (QVariant var, tid) {
375 QString entry = html_escape(var.toString());
376 line.append(QString(" <entry>%1</entry>\n").arg(entry));
378 line.append("</row>\n");
384 QString indent = "-";
385 foreach (QVariant var, tid) {
387 if (var.type() == QVariant::String) {
388 entry = QString("\"%1\"").arg(var.toString());
390 entry = var.toString();
392 line.append(QString(" %1 %2: %3\n").arg(indent).arg(ui->statsTreeWidget->headerItem()->text(col), entry));
402 ba.append(line.toUtf8());
407 ba.append(footer); // plain only?
411 void TapParameterDialog::drawTreeItems()
413 if (ui->statsTreeWidget->model()->rowCount() < expand_all_threshold_) {
414 ui->statsTreeWidget->expandAll();
417 for (int col = 0; col < ui->statsTreeWidget->columnCount(); col++) {
418 ui->statsTreeWidget->resizeColumnToContents(col);
422 void TapParameterDialog::contextMenuEvent(QContextMenuEvent *event)
424 bool enable = filterExpression().length() > 0 ? true : false;
426 foreach (QAction *fa, filter_actions_) {
427 fa->setEnabled(enable);
430 ctx_menu_.exec(event->globalPos());
433 void TapParameterDialog::addFilterActions()
436 QAction *insert_action = ctx_menu_.actions().first();
438 FilterAction::Action cur_action = FilterAction::ActionApply;
439 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
440 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
441 FilterAction *fa = new FilterAction(submenu, cur_action, at);
442 submenu->addAction(fa);
443 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
444 filter_actions_ << fa;
446 ctx_menu_.insertMenu(insert_action, submenu);
448 cur_action = FilterAction::ActionPrepare;
449 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
450 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
451 FilterAction *fa = new FilterAction(submenu, cur_action, at);
452 submenu->addAction(fa);
453 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
454 filter_actions_ << fa;
456 ctx_menu_.insertMenu(insert_action, submenu);
458 cur_action = FilterAction::ActionFind;
459 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
460 foreach (FilterAction::ActionType at, FilterAction::actionTypes(cur_action)) {
461 FilterAction *fa = new FilterAction(submenu, cur_action, at);
462 submenu->addAction(fa);
463 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
464 filter_actions_ << fa;
466 ctx_menu_.insertMenu(insert_action, submenu);
468 cur_action = FilterAction::ActionColorize;
469 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
470 foreach (FilterAction::ActionType at, FilterAction::actionTypes(cur_action)) {
471 FilterAction *fa = new FilterAction(submenu, cur_action, at);
472 submenu->addAction(fa);
473 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
474 filter_actions_ << fa;
476 ctx_menu_.insertMenu(insert_action, submenu);
477 ctx_menu_.insertSeparator(insert_action);
480 void TapParameterDialog::updateWidgets()
482 bool edit_enable = true;
483 bool apply_enable = true;
487 apply_enable = false;
488 } else if (!ui->displayFilterLineEdit->checkFilter()) {
489 // XXX Tell the user why the filter is invalid.
490 apply_enable = false;
492 ui->displayFilterLineEdit->setEnabled(edit_enable);
493 ui->applyFilterButton->setEnabled(apply_enable);
495 WiresharkDialog::updateWidgets();
498 void TapParameterDialog::on_applyFilterButton_clicked()
501 if (!ui->displayFilterLineEdit->checkFilter())
504 QString filter = ui->displayFilterLineEdit->text();
505 emit updateFilter(filter);
506 // If we wanted to be fancy we could add an isRetapping function to
507 // either WiresharkDialog or CaptureFile and use it in updateWidgets
508 // to enable and disable the apply button as needed.
509 // For now we use more simple but less useful logic.
510 bool df_enabled = ui->displayFilterLineEdit->isEnabled();
511 bool af_enabled = ui->applyFilterButton->isEnabled();
512 ui->displayFilterLineEdit->setEnabled(false);
513 ui->applyFilterButton->setEnabled(false);
515 ui->applyFilterButton->setEnabled(af_enabled);
516 ui->displayFilterLineEdit->setEnabled(df_enabled);
520 void TapParameterDialog::on_actionCopyToClipboard_triggered()
522 wsApp->clipboard()->setText(getTreeAsString(ST_FORMAT_PLAIN));
525 void TapParameterDialog::on_actionSaveAs_triggered()
527 QString selectedFilter;
528 st_format_type format;
529 const char *file_ext;
531 bool success = false;
534 QFileDialog SaveAsDialog(this, wsApp->windowTitleString(tr("Save Statistics As" UTF8_HORIZONTAL_ELLIPSIS)),
535 get_last_open_dir());
536 SaveAsDialog.setNameFilter(tr("Plain text file (*.txt);;"
537 "Comma separated values (*.csv);;"
538 "XML document (*.xml);;"
539 "YAML document (*.yaml)"));
540 SaveAsDialog.selectNameFilter(tr("Plain text file (*.txt)"));
541 SaveAsDialog.setAcceptMode(QFileDialog::AcceptSave);
542 if (!SaveAsDialog.exec()) {
545 selectedFilter= SaveAsDialog.selectedNameFilter();
546 if (selectedFilter.contains("*.yaml", Qt::CaseInsensitive)) {
547 format = ST_FORMAT_YAML;
550 else if (selectedFilter.contains("*.xml", Qt::CaseInsensitive)) {
551 format = ST_FORMAT_XML;
554 else if (selectedFilter.contains("*.csv", Qt::CaseInsensitive)) {
555 format = ST_FORMAT_CSV;
559 format = ST_FORMAT_PLAIN;
563 // Get selected filename and add extension of necessary
564 QString file_name = SaveAsDialog.selectedFiles()[0];
565 if (!file_name.endsWith(file_ext, Qt::CaseInsensitive)) {
566 file_name.append(file_ext);
569 QByteArray tree_as_ba = getTreeAsString(format);
571 // actually save the file
572 f = ws_fopen (file_name.toUtf8().constData(), "w");
575 if (fputs(tree_as_ba.data(), f) != EOF) {
582 QMessageBox::warning(this, tr("Error saving file %1").arg(file_name),
583 g_strerror (last_errno));
587 void TapParameterDialog::on_buttonBox_helpRequested()
589 if (help_topic_ > 0) {
590 wsApp->helpTopicAction((topic_action_e) help_topic_);
600 * indent-tabs-mode: nil
603 * ex: set shiftwidth=4 tabstop=8 expandtab:
604 * :indentSize=4:tabSize=8:noTabs=true: