replace SPDX identifier GPL-2.0+ with GPL-2.0-or-later.
[metze/wireshark/wip.git] / ui / qt / protocol_hierarchy_dialog.cpp
1 /* protocol_hierarchy_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 #include "protocol_hierarchy_dialog.h"
10 #include <ui_protocol_hierarchy_dialog.h>
11
12 #include "cfile.h"
13
14 #include "ui/proto_hier_stats.h"
15
16 #include <ui/qt/utils/variant_pointer.h>
17
18 #include <wsutil/utf8_entities.h>
19
20 #include <ui/qt/utils/qt_ui_utils.h>
21 #include "wireshark_application.h"
22
23 #include <QClipboard>
24 #include <QPushButton>
25 #include <QTextStream>
26 #include <QTreeWidgetItemIterator>
27
28 /*
29  * @file Protocol Hierarchy Statistics dialog
30  *
31  * Displays tree of protocols with various statistics
32  * Allows filtering on tree items
33  */
34
35 // To do:
36 // - Make "Copy as YAML" output a tree?
37 // - Add time series data to ph_stats_node_t and draw sparklines.
38
39 const int protocol_col_ = 0;
40 const int pct_packets_col_ = 1;
41 const int packets_col_ = 2;
42 const int pct_bytes_col_ = 3;
43 const int bytes_col_ = 4;
44 const int bandwidth_col_ = 5;
45 const int end_packets_col_ = 6;
46 const int end_bytes_col_ = 7;
47 const int end_bandwidth_col_ = 8;
48
49 class ProtocolHierarchyTreeWidgetItem : public QTreeWidgetItem
50 {
51 public:
52     ProtocolHierarchyTreeWidgetItem(QTreeWidgetItem *parent, ph_stats_node_t& ph_stats_node) :
53         QTreeWidgetItem(parent),
54         total_packets_(ph_stats_node.num_pkts_total),
55         last_packets_(ph_stats_node.num_pkts_last),
56         total_bytes_(ph_stats_node.num_bytes_total),
57         last_bytes_(ph_stats_node.num_bytes_last),
58         percent_packets_(0),
59         percent_bytes_(0),
60         bits_s_(0.0),
61         end_bits_s_(0.0)
62     {
63         filter_name_ = ph_stats_node.hfinfo->abbrev;
64
65         if (!parent) return;
66         ph_stats_t *ph_stats = VariantPointer<ph_stats_t>::asPtr(parent->treeWidget()->invisibleRootItem()->data(0, Qt::UserRole));
67
68         if (!ph_stats || ph_stats->tot_packets < 1) return;
69         percent_packets_ = total_packets_ * 100.0 / ph_stats->tot_packets;
70         percent_bytes_ = total_bytes_ * 100.0 / ph_stats->tot_bytes;
71
72         double seconds = ph_stats->last_time - ph_stats->first_time;
73
74         if (seconds > 0.0) {
75             bits_s_ = total_bytes_ * 8.0 / seconds;
76             end_bits_s_ = last_bytes_ * 8.0 / seconds;
77         }
78
79         setText(protocol_col_, ph_stats_node.hfinfo->name);
80         setData(pct_packets_col_, Qt::UserRole, percent_packets_);
81         setText(packets_col_, QString::number(total_packets_));
82         setData(pct_bytes_col_, Qt::UserRole, percent_bytes_);
83         setText(bytes_col_, QString::number(total_bytes_));
84         setText(bandwidth_col_, seconds > 0.0 ? bits_s_to_qstring(bits_s_) : UTF8_EM_DASH);
85         setText(end_packets_col_, QString::number(last_packets_));
86         setText(end_bytes_col_, QString::number(last_bytes_));
87         setText(end_bandwidth_col_, seconds > 0.0 ? bits_s_to_qstring(end_bits_s_) : UTF8_EM_DASH);
88     }
89
90     // Return a QString, int, double, or invalid QVariant representing the raw column data.
91     QVariant colData(int col) const {
92         switch(col) {
93         case protocol_col_:
94             return text(col);
95         case (pct_packets_col_):
96             return percent_packets_;
97         case (packets_col_):
98             return total_packets_;
99         case (pct_bytes_col_):
100             return percent_bytes_;
101         case (bytes_col_):
102             return total_bytes_;
103         case (bandwidth_col_):
104             return bits_s_;
105         case (end_packets_col_):
106             return last_packets_;
107         case (end_bytes_col_):
108             return last_bytes_;
109         case (end_bandwidth_col_):
110             return end_bits_s_;
111         default:
112             break;
113         }
114         return QVariant();
115     }
116
117     bool operator< (const QTreeWidgetItem &other) const
118     {
119         const ProtocolHierarchyTreeWidgetItem &other_phtwi = dynamic_cast<const ProtocolHierarchyTreeWidgetItem&>(other);
120
121         switch (treeWidget()->sortColumn()) {
122         case pct_packets_col_:
123             return percent_packets_ < other_phtwi.percent_packets_;
124         case packets_col_:
125             return total_packets_ < other_phtwi.total_packets_;
126         case pct_bytes_col_:
127             return percent_packets_ < other_phtwi.percent_packets_;
128         case bytes_col_:
129             return total_bytes_ < other_phtwi.total_bytes_;
130         case bandwidth_col_:
131             return bits_s_ < other_phtwi.bits_s_;
132         case end_packets_col_:
133             return last_packets_ < other_phtwi.last_packets_;
134         case end_bytes_col_:
135             return last_bytes_ < other_phtwi.last_bytes_;
136         case end_bandwidth_col_:
137             return end_bits_s_ < other_phtwi.end_bits_s_;
138         default:
139             break;
140         }
141
142         // Fall back to string comparison
143         return QTreeWidgetItem::operator <(other);
144     }
145
146     const QString filterName(void) { return filter_name_; }
147
148 private:
149     QString filter_name_;
150     unsigned total_packets_;
151     unsigned last_packets_;
152     unsigned total_bytes_;
153     unsigned last_bytes_;
154
155     double percent_packets_;
156     double percent_bytes_;
157     double bits_s_;
158     double end_bits_s_;
159 };
160
161 ProtocolHierarchyDialog::ProtocolHierarchyDialog(QWidget &parent, CaptureFile &cf) :
162     WiresharkDialog(parent, cf),
163     ui(new Ui::ProtocolHierarchyDialog)
164 {
165     ui->setupUi(this);
166     loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
167     setWindowSubtitle(tr("Protocol Hierarchy Statistics"));
168
169     ui->hierStatsTreeWidget->setItemDelegateForColumn(pct_packets_col_, &percent_bar_delegate_);
170     ui->hierStatsTreeWidget->setItemDelegateForColumn(pct_bytes_col_, &percent_bar_delegate_);
171     ph_stats_t *ph_stats = ph_stats_new(cap_file_.capFile());
172     if (ph_stats) {
173         ui->hierStatsTreeWidget->invisibleRootItem()->setData(0, Qt::UserRole, VariantPointer<ph_stats_t>::asQVariant(ph_stats));
174         g_node_children_foreach(ph_stats->stats_tree, G_TRAVERSE_ALL, addTreeNode, ui->hierStatsTreeWidget->invisibleRootItem());
175         ph_stats_free(ph_stats);
176     }
177
178     ui->hierStatsTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
179     connect(ui->hierStatsTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
180                 SLOT(showProtoHierMenu(QPoint)));
181
182     ui->hierStatsTreeWidget->setSortingEnabled(true);
183     ui->hierStatsTreeWidget->expandAll();
184
185     for (int i = 0; i < ui->hierStatsTreeWidget->columnCount(); i++) {
186         ui->hierStatsTreeWidget->resizeColumnToContents(i);
187     }
188
189     QMenu *submenu;
190
191     FilterAction::Action cur_action = FilterAction::ActionApply;
192     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
193     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
194         FilterAction *fa = new FilterAction(submenu, cur_action, at);
195         submenu->addAction(fa);
196         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
197     }
198
199     cur_action = FilterAction::ActionPrepare;
200     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
201     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
202         FilterAction *fa = new FilterAction(submenu, cur_action, at);
203         submenu->addAction(fa);
204         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
205     }
206
207     FilterAction *fa = new FilterAction(&ctx_menu_, FilterAction::ActionFind);
208     ctx_menu_.addAction(fa);
209     connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
210
211     fa = new FilterAction(&ctx_menu_, FilterAction::ActionColorize);
212     ctx_menu_.addAction(fa);
213     connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
214
215     ctx_menu_.addSeparator();
216     ctx_menu_.addAction(ui->actionCopyAsCsv);
217     ctx_menu_.addAction(ui->actionCopyAsYaml);
218
219     copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ApplyRole);
220
221     QMenu *copy_menu = new QMenu(copy_button_);
222     QAction *ca;
223     ca = copy_menu->addAction(tr("as CSV"));
224     ca->setToolTip(ui->actionCopyAsCsv->toolTip());
225     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered()));
226     ca = copy_menu->addAction(tr("as YAML"));
227     ca->setToolTip(ui->actionCopyAsYaml->toolTip());
228     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
229     copy_button_->setMenu(copy_menu);
230
231     QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
232     if (close_bt) {
233         close_bt->setDefault(true);
234     }
235
236     display_filter_ = cap_file_.capFile()->dfilter;
237     updateWidgets();
238 }
239
240 ProtocolHierarchyDialog::~ProtocolHierarchyDialog()
241 {
242     delete ui;
243 }
244
245 void ProtocolHierarchyDialog::showProtoHierMenu(QPoint pos)
246 {
247     bool enable = ui->hierStatsTreeWidget->currentItem() != NULL && !file_closed_ ? true : false;
248
249     foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
250         submenu->setEnabled(enable);
251     }
252     foreach (QAction *action, ctx_menu_.actions()) {
253         if (action != ui->actionCopyAsCsv && action != ui->actionCopyAsYaml) {
254             action->setEnabled(enable);
255         }
256     }
257
258     ctx_menu_.popup(ui->hierStatsTreeWidget->viewport()->mapToGlobal(pos));
259 }
260
261 void ProtocolHierarchyDialog::filterActionTriggered()
262 {
263     ProtocolHierarchyTreeWidgetItem *phti = static_cast<ProtocolHierarchyTreeWidgetItem *>(ui->hierStatsTreeWidget->currentItem());
264     FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
265
266     if (!fa || !phti) {
267         return;
268     }
269     QString filter_name(phti->filterName());
270
271     emit filterAction(filter_name, fa->action(), fa->actionType());
272 }
273
274 void ProtocolHierarchyDialog::addTreeNode(GNode *node, gpointer data)
275 {
276     ph_stats_node_t *stats = (ph_stats_node_t *)node->data;
277     if (!stats) return;
278
279     QTreeWidgetItem *parent_ti = static_cast<QTreeWidgetItem *>(data);
280     if (!parent_ti) return;
281
282     ProtocolHierarchyTreeWidgetItem *phti = new ProtocolHierarchyTreeWidgetItem(parent_ti, *stats);
283
284     g_node_children_foreach(node, G_TRAVERSE_ALL, addTreeNode, phti);
285
286 }
287
288 void ProtocolHierarchyDialog::updateWidgets()
289 {
290     QString hint = "<small><i>";
291     if (display_filter_.isEmpty()) {
292         hint += tr("No display filter.");
293     } else {
294         hint += tr("Display filter: %1").arg(display_filter_);
295     }
296     hint += "</i></small>";
297     ui->hintLabel->setText(hint);
298
299     WiresharkDialog::updateWidgets();
300 }
301
302 QList<QVariant> ProtocolHierarchyDialog::protoHierRowData(QTreeWidgetItem *item) const
303 {
304     QList<QVariant> row_data;
305
306     for (int col = 0; col < ui->hierStatsTreeWidget->columnCount(); col++) {
307         if (!item) {
308             row_data << ui->hierStatsTreeWidget->headerItem()->text(col);
309         } else {
310             ProtocolHierarchyTreeWidgetItem *phti = static_cast<ProtocolHierarchyTreeWidgetItem*>(item);
311             if (phti) {
312                 row_data << phti->colData(col);
313             }
314         }
315     }
316     return row_data;
317 }
318
319 void ProtocolHierarchyDialog::on_actionCopyAsCsv_triggered()
320 {
321     QString csv;
322     QTextStream stream(&csv, QIODevice::Text);
323     QTreeWidgetItemIterator iter(ui->hierStatsTreeWidget);
324     bool first = true;
325
326     while (*iter) {
327         QStringList separated_value;
328         QTreeWidgetItem *item = first ? NULL : (*iter);
329
330         foreach (QVariant v, protoHierRowData(item)) {
331             if (!v.isValid()) {
332                 separated_value << "\"\"";
333             } else if (v.type() == QVariant::String) {
334                 separated_value << QString("\"%1\"").arg(v.toString());
335             } else {
336                 separated_value << v.toString();
337             }
338         }
339         stream << separated_value.join(",") << endl;
340
341         if (!first) ++iter;
342         first = false;
343     }
344     wsApp->clipboard()->setText(stream.readAll());
345 }
346
347 void ProtocolHierarchyDialog::on_actionCopyAsYaml_triggered()
348 {
349     QString yaml;
350     QTextStream stream(&yaml, QIODevice::Text);
351     QTreeWidgetItemIterator iter(ui->hierStatsTreeWidget);
352     bool first = true;
353
354     stream << "---" << endl;
355     while (*iter) {
356         QTreeWidgetItem *item = first ? NULL : (*iter);
357
358         stream << "-" << endl;
359         foreach (QVariant v, protoHierRowData(item)) {
360             stream << " - " << v.toString() << endl;
361         }
362         if (!first) ++iter;
363         first = false;
364     }
365     wsApp->clipboard()->setText(stream.readAll());
366 }
367
368 void ProtocolHierarchyDialog::on_buttonBox_helpRequested()
369 {
370     wsApp->helpTopicAction(HELP_STATS_PROTO_HIERARCHY_DIALOG);
371 }
372
373 /*
374  * Editor modelines
375  *
376  * Local Variables:
377  * c-basic-offset: 4
378  * tab-width: 8
379  * indent-tabs-mode: nil
380  * End:
381  *
382  * ex: set shiftwidth=4 tabstop=8 expandtab:
383  * :indentSize=4:tabSize=8:noTabs=true:
384  */