Fix comment end after SPDX identifier
[metze/wireshark/wip.git] / ui / qt / traffic_table_dialog.cpp
1 /* traffic_table_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9
10 #include "log.h"
11 #include "traffic_table_dialog.h"
12 #include <ui_traffic_table_dialog.h>
13
14 #include <epan/addr_resolv.h>
15 #include <epan/prefs.h>
16
17 #include "ui/recent.h"
18
19 #include "progress_frame.h"
20 #include "wireshark_application.h"
21
22 #include <QCheckBox>
23 #include <QClipboard>
24 #include <QContextMenuEvent>
25 #include <QDialogButtonBox>
26 #include <QList>
27 #include <QMap>
28 #include <QMessageBox>
29 #include <QPushButton>
30 #include <QTabWidget>
31 #include <QTreeWidget>
32 #include <QTextStream>
33 #include <QToolButton>
34
35 // To do:
36 // - Add "copy" items to the menu.
37
38 // Bugs:
39 // - Tabs and menu items don't always end up in the same order.
40 // - Columns don't resize correctly.
41 // - Closing the capture file clears conversation data.
42
43 TrafficTableDialog::TrafficTableDialog(QWidget &parent, CaptureFile &cf, const char *filter, const QString &table_name) :
44     WiresharkDialog(parent, cf),
45     ui(new Ui::TrafficTableDialog),
46     cap_file_(cf),
47     file_closed_(false),
48     filter_(filter),
49     nanosecond_timestamps_(false)
50 {
51     ui->setupUi(this);
52     loadGeometry(parent.width(), parent.height() * 3 / 4);
53
54     ui->enabledTypesPushButton->setText(tr("%1 Types").arg(table_name));
55     ui->absoluteTimeCheckBox->hide();
56     setWindowSubtitle(QString("%1s").arg(table_name));
57
58     copy_bt_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
59     QMenu *copy_menu = new QMenu(copy_bt_);
60     QAction *ca;
61     ca = copy_menu->addAction(tr("as CSV"));
62     ca->setToolTip(tr("Copy all values of this page to the clipboard in CSV (Comma Separated Values) format."));
63     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsCsv()));
64     ca = copy_menu->addAction(tr("as YAML"));
65     ca->setToolTip(tr("Copy all values of this page to the clipboard in the YAML data serialization format."));
66     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsYaml()));
67     copy_bt_->setMenu(copy_menu);
68
69     ui->enabledTypesPushButton->setMenu(&traffic_type_menu_);
70     ui->trafficTableTabWidget->setFocus();
71
72     if (cf.timestampPrecision() == WTAP_TSPREC_NSEC) {
73         nanosecond_timestamps_ = true;
74     }
75
76     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(currentTabChanged()));
77     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(updateWidgets()));
78     connect(ui->trafficTableTabWidget, SIGNAL(currentChanged(int)),
79             this, SLOT(currentTabChanged()));
80     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
81             this, SLOT(captureEvent(CaptureEvent)));
82 }
83
84 TrafficTableDialog::~TrafficTableDialog()
85 {
86     delete ui;
87 }
88
89 bool TrafficTableDialog::absoluteStartTime()
90 {
91     return absoluteTimeCheckBox()->isChecked();
92 }
93
94 const QList<int> TrafficTableDialog::defaultProtos() const
95 {
96     // Reasonable defaults?
97     return QList<int>() << proto_get_id_by_filter_name("eth") << proto_get_id_by_filter_name("ip")
98                         << proto_get_id_by_filter_name("ipv6") << proto_get_id_by_filter_name("tcp")
99                         << proto_get_id_by_filter_name("udp");
100 }
101
102 class fillTypeMenuData
103 {
104 public:
105     fillTypeMenuData(TrafficTableDialog* dialog, QList<int> &enabled_protos)
106     : dialog_(dialog),
107     enabled_protos_(enabled_protos)
108     {
109     }
110
111     TrafficTableDialog* dialog_;
112     QList<int> &enabled_protos_;
113 };
114
115 gboolean TrafficTableDialog::fillTypeMenuFunc(const void *key, void *value, void *userdata)
116 {
117     register_ct_t* ct = (register_ct_t*)value;
118     const QString title = (const gchar*)key;
119     fillTypeMenuData* data = (fillTypeMenuData*)userdata;
120     int proto_id = get_conversation_proto_id(ct);
121
122     QAction *endp_action = new QAction(title, data->dialog_);
123     endp_action->setData(QVariant::fromValue(proto_id));
124     endp_action->setCheckable(true);
125     endp_action->setChecked(data->enabled_protos_.contains(proto_id));
126     data->dialog_->connect(endp_action, SIGNAL(triggered()), data->dialog_, SLOT(toggleTable()));
127     data->dialog_->traffic_type_menu_.addAction(endp_action);
128
129     return FALSE;
130 }
131
132 void TrafficTableDialog::fillTypeMenu(QList<int> &enabled_protos)
133 {
134     fillTypeMenuData data(this, enabled_protos);
135
136     conversation_table_iterate_tables(fillTypeMenuFunc, &data);
137 }
138
139 void TrafficTableDialog::addProgressFrame(QObject *parent)
140 {
141     ProgressFrame::addToButtonBox(ui->buttonBox, parent);
142 }
143
144 QDialogButtonBox *TrafficTableDialog::buttonBox() const
145 {
146     return ui->buttonBox;
147 }
148
149 QTabWidget *TrafficTableDialog::trafficTableTabWidget() const
150 {
151     return ui->trafficTableTabWidget;
152 }
153
154 QCheckBox *TrafficTableDialog::displayFilterCheckBox() const
155 {
156     return ui->displayFilterCheckBox;
157 }
158
159 QCheckBox *TrafficTableDialog::nameResolutionCheckBox() const
160 {
161     return ui->nameResolutionCheckBox;
162 }
163
164 QCheckBox *TrafficTableDialog::absoluteTimeCheckBox() const
165 {
166     return ui->absoluteTimeCheckBox;
167 }
168
169 QPushButton *TrafficTableDialog::enabledTypesPushButton() const
170 {
171     return ui->enabledTypesPushButton;
172 }
173
174 void TrafficTableDialog::currentTabChanged()
175 {
176     bool has_resolution = false;
177     TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
178     if (cur_tree) has_resolution = cur_tree->hasNameResolution();
179
180     bool block = blockSignals(true);
181     if (has_resolution) {
182         // Don't change the actual setting.
183         ui->nameResolutionCheckBox->setEnabled(true);
184     } else {
185         ui->nameResolutionCheckBox->setChecked(false);
186         ui->nameResolutionCheckBox->setEnabled(false);
187     }
188     blockSignals(block);
189
190     if (cur_tree) cur_tree->setNameResolutionEnabled(ui->nameResolutionCheckBox->isChecked());
191 }
192
193 void TrafficTableDialog::on_nameResolutionCheckBox_toggled(bool)
194 {
195     QWidget *cw = ui->trafficTableTabWidget->currentWidget();
196     if (cw) cw->update();
197 }
198
199 void TrafficTableDialog::on_displayFilterCheckBox_toggled(bool checked)
200 {
201     if (!cap_file_.isValid()) {
202         return;
203     }
204
205     QByteArray filter_utf8;
206     const char *filter = NULL;
207     if (checked) {
208         filter = cap_file_.capFile()->dfilter;
209     } else if (!filter_.isEmpty()) {
210         filter_utf8 = filter_.toUtf8();
211         filter = filter_utf8.constData();
212     }
213
214     for (int i = 0; i < ui->trafficTableTabWidget->count(); i++) {
215         TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->widget(i));
216         set_tap_dfilter(cur_tree->trafficTreeHash(), filter);
217     }
218
219     cap_file_.retapPackets();
220 }
221
222 void TrafficTableDialog::captureEvent(CaptureEvent e)
223 {
224     if (e.captureContext() == CaptureEvent::Retap)
225     {
226         switch (e.eventType())
227         {
228         case CaptureEvent::Started:
229             ui->displayFilterCheckBox->setEnabled(false);
230             break;
231         case CaptureEvent::Finished:
232             ui->displayFilterCheckBox->setEnabled(true);
233             break;
234         default:
235             break;
236         }
237     }
238
239 }
240
241 void TrafficTableDialog::setTabText(QWidget *tree, const QString &text)
242 {
243     // Could use QObject::sender as well
244     int index = ui->trafficTableTabWidget->indexOf(tree);
245     if (index >= 0) {
246         ui->trafficTableTabWidget->setTabText(index, text);
247     }
248 }
249
250 void TrafficTableDialog::toggleTable()
251 {
252     QAction *ca = qobject_cast<QAction *>(QObject::sender());
253     if (!ca) {
254         return;
255     }
256
257     int proto_id = ca->data().value<int>();
258     register_ct_t* table = get_conversation_by_proto_id(proto_id);
259
260     bool new_table = addTrafficTable(table);
261     updateWidgets();
262
263     if (ca->isChecked()) {
264         ui->trafficTableTabWidget->setCurrentWidget(proto_id_to_tree_[proto_id]);
265     }
266
267     if (new_table) {
268         cap_file_.retapPackets();
269     }
270 }
271
272 void TrafficTableDialog::updateWidgets()
273 {
274     QWidget *cur_w = ui->trafficTableTabWidget->currentWidget();
275     ui->trafficTableTabWidget->setUpdatesEnabled(false);
276     ui->trafficTableTabWidget->clear();
277
278     foreach (QAction *ca, traffic_type_menu_.actions()) {
279         int proto_id = ca->data().value<int>();
280         if (proto_id_to_tree_.contains(proto_id) && ca->isChecked()) {
281             ui->trafficTableTabWidget->addTab(proto_id_to_tree_[proto_id],
282                                               proto_id_to_tree_[proto_id]->trafficTreeTitle());
283         }
284     }
285     ui->trafficTableTabWidget->setCurrentWidget(cur_w);
286     ui->trafficTableTabWidget->setUpdatesEnabled(true);
287
288     WiresharkDialog::updateWidgets();
289 }
290
291 QList<QVariant> TrafficTableDialog::curTreeRowData(int row) const
292 {
293     TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
294     if (!cur_tree) {
295         return QList<QVariant>();
296     }
297
298     return cur_tree->rowData(row);
299 }
300
301 void TrafficTableDialog::copyAsCsv()
302 {
303     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
304     if (!cur_tree) {
305         return;
306     }
307
308     QString csv;
309     QTextStream stream(&csv, QIODevice::Text);
310     for (int row = -1; row < cur_tree->topLevelItemCount(); row ++) {
311         QStringList rdsl;
312         foreach (QVariant v, curTreeRowData(row)) {
313             if (!v.isValid()) {
314                 rdsl << "\"\"";
315             } else if (v.type() == QVariant::String) {
316                 rdsl << QString("\"%1\"").arg(v.toString());
317             } else {
318                 rdsl << v.toString();
319             }
320         }
321         stream << rdsl.join(",") << endl;
322     }
323     wsApp->clipboard()->setText(stream.readAll());
324 }
325
326 void TrafficTableDialog::copyAsYaml()
327 {
328     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
329     if (!cur_tree) {
330         return;
331     }
332
333     QString yaml;
334     QTextStream stream(&yaml, QIODevice::Text);
335     stream << "---" << endl;
336     for (int row = -1; row < cur_tree->topLevelItemCount(); row ++) {
337         stream << "-" << endl;
338         foreach (QVariant v, curTreeRowData(row)) {
339             stream << " - " << v.toString() << endl;
340         }
341     }
342     wsApp->clipboard()->setText(stream.readAll());
343 }
344
345 TrafficTableTreeWidget::TrafficTableTreeWidget(QWidget *parent, register_ct_t *table) :
346     QTreeWidget(parent),
347     table_(table),
348     hash_(),
349     resolve_names_(false)
350 {
351     setRootIsDecorated(false);
352     sortByColumn(0, Qt::AscendingOrder);
353
354     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(updateItemsForSettingChange()));
355 }
356
357 TrafficTableTreeWidget::~TrafficTableTreeWidget()
358 {
359     remove_tap_listener(&hash_);
360 }
361
362 QList<QVariant> TrafficTableTreeWidget::rowData(int row) const
363 {
364     QList<QVariant> row_data;
365
366     if (row >= topLevelItemCount()) {
367         return row_data;
368     }
369
370     for (int col = 0; col < columnCount(); col++) {
371         if (isColumnHidden(col)) {
372             continue;
373         }
374         if (row < 0) {
375             row_data << headerItem()->text(col);
376         } else {
377             TrafficTableTreeWidgetItem *ti = static_cast<TrafficTableTreeWidgetItem *>(topLevelItem(row));
378             if (ti) {
379                 row_data << ti->colData(col, resolve_names_);
380             }
381         }
382     }
383     return row_data;
384 }
385
386 // True if name resolution is enabled for the table's address type, false
387 // otherwise.
388 // XXX We need a more reliable method of fetching the address type(s) for
389 // a table.
390 bool TrafficTableTreeWidget::hasNameResolution() const
391 {
392     if (!table_) return false;
393
394     QStringList mac_protos = QStringList() << "eth" << "tr"<< "wlan";
395     QStringList net_protos = QStringList() << "ip" << "ipv6" << "jxta"
396                                            << "mptcp" << "rsvp" << "sctp"
397                                            << "tcp" << "udp";
398
399     QString table_proto = proto_get_protocol_filter_name(get_conversation_proto_id(table_));
400
401     if (mac_protos.contains(table_proto) && gbl_resolv_flags.mac_name) return true;
402     if (net_protos.contains(table_proto) && gbl_resolv_flags.network_name) return true;
403
404     return false;
405 }
406
407 void TrafficTableTreeWidget::setNameResolutionEnabled(bool enable)
408 {
409     if (resolve_names_ != enable) {
410         resolve_names_ = enable;
411         updateItems();
412     }
413 }
414
415 void TrafficTableTreeWidget::contextMenuEvent(QContextMenuEvent *event)
416 {
417     bool enable = currentItem() != NULL ? true : false;
418
419     foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
420         submenu->setEnabled(enable);
421     }
422
423     ctx_menu_.exec(event->globalPos());
424
425 }
426
427 void TrafficTableTreeWidget::updateItemsForSettingChange()
428 {
429     updateItems();
430 }
431
432 /*
433  * Editor modelines
434  *
435  * Local Variables:
436  * c-basic-offset: 4
437  * tab-width: 8
438  * indent-tabs-mode: nil
439  * End:
440  *
441  * ex: set shiftwidth=4 tabstop=8 expandtab:
442  * :indentSize=4:tabSize=8:noTabs=true:
443  */