1 /* expert_info_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 "expert_info_dialog.h"
11 #include <ui_expert_info_dialog.h>
12
13 #include "file.h"
14
15 #include <epan/epan_dissect.h>
16 #include <epan/expert.h>
17 #include <epan/stat_tap_ui.h>
18 #include <epan/tap.h>
19
20 #include "wireshark_application.h"
21
22 #include <QAction>
23 #include <QHash>
24 #include <QMenu>
25 #include <QMessageBox>
26 #include <QPushButton>
27
28 // To do:
29 // - Test with custom expert levels (Preferences -> Expert).
30 // - Test with large captures.
31 // - Promote to a fourth pane in the main window?
32 // - Make colors configurable? In theory we could condense image/expert_indicators.svg,
33 // down to one item, make sure it uses a single (or a few) base color(s), and generate
34 // icons on the fly.
35
ExpertInfoDialog(QWidget & parent,CaptureFile & capture_file)36 ExpertInfoDialog::ExpertInfoDialog(QWidget &parent, CaptureFile &capture_file) :
37 WiresharkDialog(parent, capture_file),
38 ui(new Ui::ExpertInfoDialog),
39 expert_info_model_(new ExpertInfoModel(capture_file)),
40 proxyModel_(new ExpertInfoProxyModel(this)),
41 display_filter_(QString())
42 {
43 ui->setupUi(this);
44
45 proxyModel_->setSourceModel(expert_info_model_);
46 ui->expertInfoTreeView->setModel(proxyModel_);
47
48 setWindowSubtitle(tr("Expert Information"));
49
50 // Clicking on an item jumps to its associated packet. Make the dialog
51 // narrow so that we avoid obscuring the packet list.
52 int dlg_width = parent.width() * 3 / 5;
53 if (dlg_width < width()) dlg_width = width();
54 loadGeometry(dlg_width, parent.height());
55
56 int one_em = fontMetrics().height();
57 ui->expertInfoTreeView->setColumnWidth(ExpertInfoProxyModel::colProxySummary, one_em * 25); // Arbitrary
58
59 //Unfortunately this has to be done manually and not through .ui
60 ui->severitiesPushButton->setMenu(ui->menuShowExpert);
61
62 ui->expertInfoTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
63 connect(ui->expertInfoTreeView, SIGNAL(customContextMenuRequested(QPoint)),
64 SLOT(showExpertInfoMenu(QPoint)));
65
66 QMenu *submenu;
67
68 FilterAction::Action cur_action = FilterAction::ActionApply;
69 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
70 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
71 FilterAction *fa = new FilterAction(submenu, cur_action, at);
72 submenu->addAction(fa);
73 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
74 }
75
76 cur_action = FilterAction::ActionPrepare;
77 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
78 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
79 FilterAction *fa = new FilterAction(submenu, cur_action, at);
80 submenu->addAction(fa);
81 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
82 }
83
84 FilterAction *fa;
85 QList<FilterAction::Action> extra_actions =
86 QList<FilterAction::Action>() << FilterAction::ActionFind
87 << FilterAction::ActionColorize
88 << FilterAction::ActionWebLookup
89 << FilterAction::ActionCopy;
90
91 foreach (FilterAction::Action extra_action, extra_actions) {
92 fa = new FilterAction(&ctx_menu_, extra_action);
93 ctx_menu_.addAction(fa);
94 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
95 }
96
97 //Add collapse/expand all menu options
98 QAction *collapse = new QAction(tr("Collapse All"), this);
99 ctx_menu_.addAction(collapse);
100 connect(collapse, SIGNAL(triggered()), this, SLOT(collapseTree()));
101
102 QAction *expand = new QAction(tr("Expand All"), this);
103 ctx_menu_.addAction(expand);
104 connect(expand, SIGNAL(triggered()), this, SLOT(expandTree()));
105
106 connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
107 this, SLOT(captureEvent(CaptureEvent)));
108 setDisplayFilter();
109 QTimer::singleShot(0, this, SLOT(retapPackets()));
110 }
111
~ExpertInfoDialog()112 ExpertInfoDialog::~ExpertInfoDialog()
113 {
114 delete ui;
115 delete proxyModel_;
116 delete expert_info_model_;
117 }
118
clearAllData()119 void ExpertInfoDialog::clearAllData()
120 {
121 expert_info_model_->clear();
122 }
123
setDisplayFilter(const QString & display_filter)124 void ExpertInfoDialog::setDisplayFilter(const QString &display_filter)
125 {
126 display_filter_ = display_filter;
127 updateWidgets();
128 }
129
getExpertInfoView()130 ExpertInfoTreeView* ExpertInfoDialog::getExpertInfoView()
131 {
132 return ui->expertInfoTreeView;
133 }
134
retapPackets()135 void ExpertInfoDialog::retapPackets()
136 {
137 if (file_closed_) return;
138
139 clearAllData();
140 removeTapListeners();
141
142 if (!registerTapListener("expert",
143 expert_info_model_,
144 ui->limitCheckBox->isChecked() ? display_filter_.toUtf8().constData(): NULL,
145 TL_REQUIRES_COLUMNS,
146 ExpertInfoModel::tapReset,
147 ExpertInfoModel::tapPacket,
148 ExpertInfoModel::tapDraw)) {
149 return;
150 }
151
152 cap_file_.retapPackets();
153 }
154
captureEvent(CaptureEvent e)155 void ExpertInfoDialog::captureEvent(CaptureEvent e)
156 {
157 if (e.captureContext() == CaptureEvent::Retap)
158 {
159 switch (e.eventType())
160 {
161 case CaptureEvent::Started:
162 ui->limitCheckBox->setEnabled(false);
163 ui->groupBySummaryCheckBox->setEnabled(false);
164 break;
165 case CaptureEvent::Finished:
166 updateWidgets();
167 break;
168 default:
169 break;
170 }
171 }
172 }
173
updateWidgets()174 void ExpertInfoDialog::updateWidgets()
175 {
176 ui->limitCheckBox->setEnabled(! file_closed_ && ! display_filter_.isEmpty());
177
178 ui->actionShowError->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityError) > 0);
179 ui->actionShowWarning->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityWarn) > 0);
180 ui->actionShowNote->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityNote) > 0);
181 ui->actionShowChat->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityChat) > 0);
182 ui->actionShowComment->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityComment) > 0);
183
184 QString tooltip;
185 QString hint;
186
187 if (file_closed_) {
188 tooltip = tr("Capture file closed.");
189 hint = tr("Capture file closed.");
190 } else if (display_filter_.isEmpty()) {
191 tooltip = tr("No display filter");
192 hint = tr("No display filter set.");
193 } else {
194 tooltip = tr("Limit information to \"%1\".").arg(display_filter_);
195 hint = tr("Display filter: \"%1\"").arg(display_filter_);
196 }
197
198 ui->limitCheckBox->setToolTip(tooltip);
199 hint.prepend("<small><i>");
200 hint.append("</i></small>");
201 ui->hintLabel->setText(hint);
202
203 ui->groupBySummaryCheckBox->setEnabled(!file_closed_);
204 }
205
on_actionShowError_toggled(bool checked)206 void ExpertInfoDialog::on_actionShowError_toggled(bool checked)
207 {
208 proxyModel_->setSeverityFilter(PI_ERROR, !checked);
209 updateWidgets();
210 }
211
on_actionShowWarning_toggled(bool checked)212 void ExpertInfoDialog::on_actionShowWarning_toggled(bool checked)
213 {
214 proxyModel_->setSeverityFilter(PI_WARN, !checked);
215 updateWidgets();
216 }
217
on_actionShowNote_toggled(bool checked)218 void ExpertInfoDialog::on_actionShowNote_toggled(bool checked)
219 {
220 proxyModel_->setSeverityFilter(PI_NOTE, !checked);
221 updateWidgets();
222 }
223
on_actionShowChat_toggled(bool checked)224 void ExpertInfoDialog::on_actionShowChat_toggled(bool checked)
225 {
226 proxyModel_->setSeverityFilter(PI_CHAT, !checked);
227 updateWidgets();
228 }
229
on_actionShowComment_toggled(bool checked)230 void ExpertInfoDialog::on_actionShowComment_toggled(bool checked)
231 {
232 proxyModel_->setSeverityFilter(PI_COMMENT, !checked);
233 updateWidgets();
234 }
235
236
showExpertInfoMenu(QPoint pos)237 void ExpertInfoDialog::showExpertInfoMenu(QPoint pos)
238 {
239 bool enable = true;
240 QModelIndex expertIndex = ui->expertInfoTreeView->indexAt(pos);
241 if (!expertIndex.isValid()) {
242 return;
243 }
244
245 if (proxyModel_->data(expertIndex.sibling(expertIndex.row(), ExpertInfoModel::colHf), Qt::DisplayRole).toInt() < 0) {
246 enable = false;
247 }
248
249 foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
250 submenu->setEnabled(enable && !file_closed_);
251 }
252 foreach (QAction *action, ctx_menu_.actions()) {
253 FilterAction *fa = qobject_cast<FilterAction *>(action);
254 bool action_enable = enable && !file_closed_;
255 if (fa && (fa->action() == FilterAction::ActionWebLookup || fa->action() == FilterAction::ActionCopy)) {
256 action_enable = enable;
257 }
258 action->setEnabled(action_enable);
259 }
260
261 ctx_menu_.popup(ui->expertInfoTreeView->viewport()->mapToGlobal(pos));
262 }
263
filterActionTriggered()264 void ExpertInfoDialog::filterActionTriggered()
265 {
266 QModelIndex modelIndex = ui->expertInfoTreeView->currentIndex();
267 FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
268
269 if (!fa || !modelIndex.isValid()) {
270 return;
271 }
272
273 int hf_index = proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colHf), Qt::DisplayRole).toInt();
274
275 if (hf_index > -1) {
276 QString filter_string;
277 if (fa->action() == FilterAction::ActionWebLookup) {
278 filter_string = QString("%1 %2")
279 .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colProtocol), Qt::DisplayRole).toString())
280 .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colSummary), Qt::DisplayRole).toString());
281 } else if (fa->action() == FilterAction::ActionCopy) {
282 filter_string = QString("%1 %2: %3")
283 .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colPacket), Qt::DisplayRole).toUInt())
284 .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colProtocol), Qt::DisplayRole).toString())
285 .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colSummary), Qt::DisplayRole).toString());
286 } else {
287 filter_string = proto_registrar_get_abbrev(hf_index);
288 }
289
290 if (! filter_string.isEmpty()) {
291 emit filterAction(filter_string, fa->action(), fa->actionType());
292 }
293 }
294 }
295
collapseTree()296 void ExpertInfoDialog::collapseTree()
297 {
298 ui->expertInfoTreeView->collapseAll();
299 }
300
expandTree()301 void ExpertInfoDialog::expandTree()
302 {
303 ui->expertInfoTreeView->expandAll();
304 }
305
on_limitCheckBox_toggled(bool)306 void ExpertInfoDialog::on_limitCheckBox_toggled(bool)
307 {
308 retapPackets();
309 }
310
on_groupBySummaryCheckBox_toggled(bool)311 void ExpertInfoDialog::on_groupBySummaryCheckBox_toggled(bool)
312 {
313 expert_info_model_->setGroupBySummary(ui->groupBySummaryCheckBox->isChecked());
314 }
315
316 // Show child (packet list) items that match the contents of searchLineEdit.
on_searchLineEdit_textChanged(const QString & search_re)317 void ExpertInfoDialog::on_searchLineEdit_textChanged(const QString &search_re)
318 {
319 proxyModel_->setSummaryFilter(search_re);
320 }
321
on_buttonBox_helpRequested()322 void ExpertInfoDialog::on_buttonBox_helpRequested()
323 {
324 wsApp->helpTopicAction(HELP_EXPERT_INFO_DIALOG);
325 }
326
327 // Stat command + args
328
329 static void
expert_info_init(const char *,void *)330 expert_info_init(const char *, void*) {
331 wsApp->emitStatCommandSignal("ExpertInfo", NULL, NULL);
332 }
333
334 static stat_tap_ui expert_info_stat_ui = {
335 REGISTER_STAT_GROUP_GENERIC,
336 NULL,
337 "expert",
338 expert_info_init,
339 0,
340 NULL
341 };
342
343 extern "C" {
344
345 void register_tap_listener_qt_expert_info(void);
346
347 void
register_tap_listener_qt_expert_info(void)348 register_tap_listener_qt_expert_info(void)
349 {
350 register_stat_tap_ui(&expert_info_stat_ui, NULL);
351 }
352
353 }
354