Qt: More #include → forward declarations.
[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  * 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.
11  *
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.
16  *
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.
20  */
21
22 #include "protocol_hierarchy_dialog.h"
23 #include "ui_protocol_hierarchy_dialog.h"
24
25 #include "cfile.h"
26
27 #include "ui/proto_hier_stats.h"
28 #include "ui/utf8_entities.h"
29
30 #include "color_utils.h"
31 #include "qt_ui_utils.h"
32 #include "wireshark_application.h"
33
34 #include <QClipboard>
35 #include <QPainter>
36 #include <QPushButton>
37 #include <QTextStream>
38 #include <QTreeWidgetItemIterator>
39
40 /*
41  * @file Protocol Hierarchy Statistics dialog
42  *
43  * Displays tree of protocols with various statistics
44  * Allows filtering on tree items
45  */
46
47 // To do:
48 // - Make "Copy as YAML" output a tree?
49 // - Add time series data to ph_stats_node_t and draw sparklines.
50
51 const int protocol_col_ = 0;
52 const int pct_packets_col_ = 1;
53 const int packets_col_ = 2;
54 const int pct_bytes_col_ = 3;
55 const int bytes_col_ = 4;
56 const int bandwidth_col_ = 5;
57 const int end_packets_col_ = 6;
58 const int end_bytes_col_ = 7;
59 const int end_bandwidth_col_ = 8;
60
61 const int bar_em_width_ = 8;
62 const double bar_blend_ = 0.15;
63 void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
64                                const QModelIndex &index) const
65 {
66     QStyleOptionViewItemV4 optv4 = option;
67     QStyledItemDelegate::initStyleOption(&optv4, index);
68     double value = index.data(Qt::UserRole).toDouble();
69
70     QStyledItemDelegate::paint(painter, option, index);
71
72     painter->save();
73
74     if (QApplication::style()->objectName().contains("vista")) {
75         // QWindowsVistaStyle::drawControl does this internally. Unfortunately there
76         // doesn't appear to be a more general way to do this.
77         optv4.palette.setColor(QPalette::All, QPalette::HighlightedText,
78                                optv4.palette.color(QPalette::Active, QPalette::Text));
79     }
80
81     QColor bar_color = ColorUtils::alphaBlend(optv4.palette.windowText(),
82                                               optv4.palette.window(), bar_blend_);
83     QPalette::ColorGroup cg = optv4.state & QStyle::State_Enabled
84                               ? QPalette::Normal : QPalette::Disabled;
85     if (cg == QPalette::Normal && !(optv4.state & QStyle::State_Active))
86         cg = QPalette::Inactive;
87     if (optv4.state & QStyle::State_Selected) {
88         painter->setPen(optv4.palette.color(cg, QPalette::HighlightedText));
89         bar_color = ColorUtils::alphaBlend(optv4.palette.color(cg, QPalette::Window),
90                                            optv4.palette.color(cg, QPalette::Highlight),
91                                            bar_blend_);
92     } else {
93         painter->setPen(optv4.palette.color(cg, QPalette::Text));
94     }
95
96     QRect pct_rect = option.rect;
97     pct_rect.adjust(1, 1, -1, -1);
98     pct_rect.setWidth(((pct_rect.width() * value) / 100.0) + 0.5);
99     painter->fillRect(pct_rect, bar_color);
100
101     QString pct_str = QString::number(value, 'f', 1);
102     painter->drawText(option.rect, Qt::AlignCenter, pct_str);
103
104     painter->restore();
105 }
106
107 QSize PercentBarDelegate::sizeHint(const QStyleOptionViewItem &option,
108                                    const QModelIndex &index) const
109 {
110     return QSize(option.fontMetrics.height() * bar_em_width_,
111                  QStyledItemDelegate::sizeHint(option, index).height());
112 }
113
114 Q_DECLARE_METATYPE(ph_stats_t*)
115
116 class ProtocolHierarchyTreeWidgetItem : public QTreeWidgetItem
117 {
118 public:
119     ProtocolHierarchyTreeWidgetItem(QTreeWidgetItem *parent, ph_stats_node_t *ph_stats_node) :
120         QTreeWidgetItem(parent),
121         bits_s_(0.0),
122         end_bits_s_(0.0)
123     {
124         if (!ph_stats_node) return;
125
126         filter_name_ = ph_stats_node->hfinfo->abbrev;
127         total_packets_ = ph_stats_node->num_pkts_total;
128         last_packets_ = ph_stats_node->num_pkts_last;
129         total_bytes_ = ph_stats_node->num_bytes_total;
130         last_bytes_ = ph_stats_node->num_bytes_last;
131
132         if (!parent) return;
133         ph_stats_t *ph_stats = parent->treeWidget()->invisibleRootItem()->data(0, Qt::UserRole).value<ph_stats_t*>();
134
135         if (!ph_stats || ph_stats->tot_packets < 1) return;
136         percent_packets_ = total_packets_ * 100.0 / ph_stats->tot_packets;
137         percent_bytes_ = total_bytes_ * 100.0 / ph_stats->tot_bytes;
138
139         double seconds = ph_stats->last_time - ph_stats->first_time;
140
141         if (seconds > 0.0) {
142             bits_s_ = total_bytes_ * 8.0 / seconds;
143             end_bits_s_ = last_bytes_ * 8.0 / seconds;
144         }
145
146         setText(protocol_col_, ph_stats_node->hfinfo->name);
147         setData(pct_packets_col_, Qt::UserRole, percent_packets_);
148         setText(packets_col_, QString::number(total_packets_));
149         setData(pct_bytes_col_, Qt::UserRole, percent_bytes_);
150         setText(bytes_col_, QString::number(total_bytes_));
151         setText(bandwidth_col_, seconds > 0.0 ? bits_s_to_qstring(bits_s_) : UTF8_EM_DASH);
152         setText(end_packets_col_, QString::number(last_packets_));
153         setText(end_bytes_col_, QString::number(last_bytes_));
154         setText(end_bandwidth_col_, seconds > 0.0 ? bits_s_to_qstring(end_bits_s_) : UTF8_EM_DASH);
155     }
156
157     // Return a QString, int, double, or invalid QVariant representing the raw column data.
158     QVariant colData(int col) const {
159         switch(col) {
160         case protocol_col_:
161             return text(col);
162         case (pct_packets_col_):
163             return percent_packets_;
164         case (packets_col_):
165             return total_packets_;
166         case (pct_bytes_col_):
167             return percent_bytes_;
168         case (bytes_col_):
169             return total_bytes_;
170         case (bandwidth_col_):
171             return bits_s_;
172         case (end_packets_col_):
173             return last_packets_;
174         case (end_bytes_col_):
175             return last_bytes_;
176         case (end_bandwidth_col_):
177             return end_bits_s_;
178         default:
179             break;
180         }
181         return QVariant();
182     }
183
184     bool operator< (const QTreeWidgetItem &other) const
185     {
186         const ProtocolHierarchyTreeWidgetItem &other_phtwi = dynamic_cast<const ProtocolHierarchyTreeWidgetItem&>(other);
187
188         switch (treeWidget()->sortColumn()) {
189         case pct_packets_col_:
190             return percent_packets_ < other_phtwi.percent_packets_;
191         case packets_col_:
192             return total_packets_ < other_phtwi.total_packets_;
193         case pct_bytes_col_:
194             return percent_packets_ < other_phtwi.percent_packets_;
195         case bytes_col_:
196             return total_bytes_ < other_phtwi.total_bytes_;
197         case bandwidth_col_:
198             return bits_s_ < other_phtwi.bits_s_;
199         case end_packets_col_:
200             return last_packets_ < other_phtwi.last_packets_;
201         case end_bytes_col_:
202             return last_bytes_ < other_phtwi.last_bytes_;
203         case end_bandwidth_col_:
204             return end_bits_s_ < other_phtwi.end_bits_s_;
205         default:
206             break;
207         }
208
209         // Fall back to string comparison
210         return QTreeWidgetItem::operator <(other);
211     }
212
213     const QString filterName(void) { return filter_name_; }
214
215 private:
216     QString filter_name_;
217     unsigned total_packets_;
218     unsigned last_packets_;
219     unsigned total_bytes_;
220     unsigned last_bytes_;
221
222     double percent_packets_;
223     double percent_bytes_;
224     double bits_s_;
225     double end_bits_s_;
226 };
227
228 ProtocolHierarchyDialog::ProtocolHierarchyDialog(QWidget &parent, CaptureFile &cf) :
229     WiresharkDialog(parent, cf),
230     ui(new Ui::ProtocolHierarchyDialog)
231 {
232     ui->setupUi(this);
233     setWindowSubtitle(tr("Protocol Hierarchy Statistics"));
234
235     // XXX Use recent settings instead
236     resize(parent.width() * 4 / 5, parent.height() * 4 / 5);
237
238     ui->hierStatsTreeWidget->setItemDelegateForColumn(pct_packets_col_, &percent_bar_delegate_);
239     ui->hierStatsTreeWidget->setItemDelegateForColumn(pct_bytes_col_, &percent_bar_delegate_);
240     ph_stats_t *ph_stats = ph_stats_new(cap_file_.capFile());
241     if (ph_stats) {
242         ui->hierStatsTreeWidget->invisibleRootItem()->setData(0, Qt::UserRole, qVariantFromValue(ph_stats));
243         g_node_children_foreach(ph_stats->stats_tree, G_TRAVERSE_ALL, addTreeNode, ui->hierStatsTreeWidget->invisibleRootItem());
244         ph_stats_free(ph_stats);
245     }
246
247     ui->hierStatsTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
248     connect(ui->hierStatsTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
249                 SLOT(showProtoHierMenu(QPoint)));
250
251     ui->hierStatsTreeWidget->setSortingEnabled(true);
252     ui->hierStatsTreeWidget->expandAll();
253
254     for (int i = 0; i < ui->hierStatsTreeWidget->columnCount(); i++) {
255         ui->hierStatsTreeWidget->resizeColumnToContents(i);
256     }
257
258     QMenu *submenu;
259
260     FilterAction::Action cur_action = FilterAction::ActionApply;
261     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
262     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
263         FilterAction *fa = new FilterAction(submenu, cur_action, at);
264         submenu->addAction(fa);
265         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
266     }
267
268     cur_action = FilterAction::ActionPrepare;
269     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
270     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
271         FilterAction *fa = new FilterAction(submenu, cur_action, at);
272         submenu->addAction(fa);
273         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
274     }
275
276     FilterAction *fa = new FilterAction(&ctx_menu_, FilterAction::ActionFind);
277     ctx_menu_.addAction(fa);
278     connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
279
280     fa = new FilterAction(&ctx_menu_, FilterAction::ActionColorize);
281     ctx_menu_.addAction(fa);
282
283     ctx_menu_.addSeparator();
284     ctx_menu_.addAction(ui->actionCopyAsCsv);
285     ctx_menu_.addAction(ui->actionCopyAsYaml);
286
287     copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ApplyRole);
288
289     QMenu *copy_menu = new QMenu();
290     QAction *ca;
291     ca = copy_menu->addAction(tr("as CSV"));
292     ca->setToolTip(ui->actionCopyAsCsv->toolTip());
293     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered()));
294     ca = copy_menu->addAction(tr("as YAML"));
295     ca->setToolTip(ui->actionCopyAsYaml->toolTip());
296     connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered()));
297     copy_button_->setMenu(copy_menu);
298
299
300     display_filter_ = cap_file_.capFile()->dfilter;
301     updateWidgets();
302 }
303
304 ProtocolHierarchyDialog::~ProtocolHierarchyDialog()
305 {
306     delete ui;
307 }
308
309 void ProtocolHierarchyDialog::showProtoHierMenu(QPoint pos)
310 {
311     bool enable = ui->hierStatsTreeWidget->currentItem() != NULL && !file_closed_ ? true : false;
312
313     foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
314         submenu->setEnabled(enable);
315     }
316     foreach (QAction *action, ctx_menu_.actions()) {
317         if (action != ui->actionCopyAsCsv && action != ui->actionCopyAsYaml) {
318             action->setEnabled(enable);
319         }
320     }
321
322     ctx_menu_.popup(ui->hierStatsTreeWidget->viewport()->mapToGlobal(pos));
323 }
324
325 void ProtocolHierarchyDialog::filterActionTriggered()
326 {
327     ProtocolHierarchyTreeWidgetItem *phti = static_cast<ProtocolHierarchyTreeWidgetItem *>(ui->hierStatsTreeWidget->currentItem());
328     FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
329
330     if (!fa || !phti) {
331         return;
332     }
333     QString filter_name(phti->filterName());
334
335     emit filterAction(filter_name, fa->action(), fa->actionType());
336 }
337
338 void ProtocolHierarchyDialog::addTreeNode(GNode *node, gpointer data)
339 {
340     ph_stats_node_t *stats = (ph_stats_node_t *)node->data;
341     if (!stats) return;
342
343     QTreeWidgetItem *parent_ti = static_cast<QTreeWidgetItem *>(data);
344     if (!parent_ti) return;
345
346     ProtocolHierarchyTreeWidgetItem *phti = new ProtocolHierarchyTreeWidgetItem(parent_ti, stats);
347
348     g_node_children_foreach(node, G_TRAVERSE_ALL, addTreeNode, phti);
349
350 }
351
352 void ProtocolHierarchyDialog::updateWidgets()
353 {
354     QString hint = "<small><i>";
355     if (display_filter_.isEmpty()) {
356         hint += tr("No display filter.");
357     } else {
358         hint += tr("Display filter: %1").arg(display_filter_);
359     }
360     hint += "</i></small>";
361     ui->hintLabel->setText(hint);
362 }
363
364 QList<QVariant> ProtocolHierarchyDialog::protoHierRowData(QTreeWidgetItem *item) const
365 {
366     QList<QVariant> row_data;
367
368     for (int col = 0; col < ui->hierStatsTreeWidget->columnCount(); col++) {
369         if (!item) {
370             row_data << ui->hierStatsTreeWidget->headerItem()->text(col);
371         } else {
372             ProtocolHierarchyTreeWidgetItem *phti = static_cast<ProtocolHierarchyTreeWidgetItem*>(item);
373             if (phti) {
374                 row_data << phti->colData(col);
375             }
376         }
377     }
378     return row_data;
379 }
380
381 void ProtocolHierarchyDialog::on_actionCopyAsCsv_triggered()
382 {
383     QString csv;
384     QTextStream stream(&csv, QIODevice::Text);
385     QTreeWidgetItemIterator iter(ui->hierStatsTreeWidget);
386     bool first = true;
387
388     while (*iter) {
389         QStringList separated_value;
390         QTreeWidgetItem *item = first ? NULL : (*iter);
391
392         foreach (QVariant v, protoHierRowData(item)) {
393             if (!v.isValid()) {
394                 separated_value << "\"\"";
395             } else if ((int) v.type() == (int) QMetaType::QString) {
396                 separated_value << QString("\"%1\"").arg(v.toString());
397             } else {
398                 separated_value << v.toString();
399             }
400         }
401         stream << separated_value.join(",") << endl;
402
403         if (!first) iter++;
404         first = false;
405     }
406     wsApp->clipboard()->setText(stream.readAll());
407 }
408
409 void ProtocolHierarchyDialog::on_actionCopyAsYaml_triggered()
410 {
411     QString yaml;
412     QTextStream stream(&yaml, QIODevice::Text);
413     QTreeWidgetItemIterator iter(ui->hierStatsTreeWidget);
414     bool first = true;
415
416     stream << "---" << endl;
417     while (*iter) {
418         QTreeWidgetItem *item = first ? NULL : (*iter);
419
420         stream << "-" << endl;
421         foreach (QVariant v, protoHierRowData(item)) {
422             stream << " - " << v.toString() << endl;
423         }
424         if (!first) iter++;
425         first = false;
426     }
427     wsApp->clipboard()->setText(stream.readAll());
428 }
429
430 void ProtocolHierarchyDialog::on_buttonBox_helpRequested()
431 {
432     wsApp->helpTopicAction(HELP_STATS_PROTO_HIERARCHY_DIALOG);
433 }
434
435 /*
436  * Editor modelines
437  *
438  * Local Variables:
439  * c-basic-offset: 4
440  * tab-width: 8
441  * indent-tabs-mode: nil
442  * End:
443  *
444  * ex: set shiftwidth=4 tabstop=8 expandtab:
445  * :indentSize=4:tabSize=8:noTabs=true:
446  */