1 /* proto_tree.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 <stdio.h>
11
12 #include <ui/qt/proto_tree.h>
13 #include <ui/qt/models/proto_tree_model.h>
14
15 #include <epan/ftypes/ftypes.h>
16 #include <epan/prefs.h>
17 #include <epan/epan.h>
18 #include <epan/epan_dissect.h>
19 #include <cfile.h>
20
21 #include <ui/qt/utils/color_utils.h>
22 #include <ui/qt/utils/variant_pointer.h>
23 #include <ui/qt/utils/wireshark_mime_data.h>
24 #include <ui/qt/widgets/drag_label.h>
25 #include <ui/qt/widgets/wireshark_file_dialog.h>
26 #include <ui/qt/show_packet_bytes_dialog.h>
27 #include <ui/qt/filter_action.h>
28 #include <ui/all_files_wildcard.h>
29 #include <ui/alert_box.h>
30 #include <ui/urls.h>
31 #include "wireshark_application.h"
32
33 #include <QApplication>
34 #include <QContextMenuEvent>
35 #include <QDesktopServices>
36 #include <QHeaderView>
37 #include <QItemSelectionModel>
38 #include <QScrollBar>
39 #include <QStack>
40 #include <QUrl>
41 #include <QClipboard>
42 #include <QWindow>
43 #include <QMessageBox>
44 #include <QJsonDocument>
45 #include <QJsonObject>
46
47 // To do:
48 // - Fix "apply as filter" behavior.
49
ProtoTree(QWidget * parent,epan_dissect_t * edt_fixed)50 ProtoTree::ProtoTree(QWidget *parent, epan_dissect_t *edt_fixed) :
51 QTreeView(parent),
52 proto_tree_model_(new ProtoTreeModel(this)),
53 column_resize_timer_(0),
54 cap_file_(NULL),
55 edt_(edt_fixed)
56 {
57 setAccessibleName(tr("Packet details"));
58 // Leave the uniformRowHeights property as-is (false) since items might
59 // have multiple lines (e.g. packet comments). If this slows things down
60 // too much we should add a custom delegate which handles SizeHintRole
61 // similar to PacketListModel::data.
62 setHeaderHidden(true);
63
64 #if !defined(Q_OS_WIN)
65 #if defined(Q_OS_MAC)
66 QPalette default_pal = QApplication::palette();
67 default_pal.setCurrentColorGroup(QPalette::Active);
68 QColor hover_color = default_pal.highlight().color();
69 #else
70 QColor hover_color = ColorUtils::alphaBlend(palette().window(), palette().highlight(), 0.5);
71 #endif
72
73 setStyleSheet(QString(
74 "QTreeView:item:hover {"
75 " background-color: %1;"
76 " color: palette(text);"
77 "}").arg(hover_color.name(QColor::HexArgb)));
78 #endif
79
80 // Shrink down to a small but nonzero size in the main splitter.
81 int one_em = fontMetrics().height();
82 setMinimumSize(one_em, one_em);
83
84 setModel(proto_tree_model_);
85
86 connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex)));
87 connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(syncCollapsed(QModelIndex)));
88 connect(this, SIGNAL(clicked(QModelIndex)),
89 this, SLOT(itemClicked(QModelIndex)));
90 connect(this, SIGNAL(doubleClicked(QModelIndex)),
91 this, SLOT(itemDoubleClicked(QModelIndex)));
92
93 connect(&proto_prefs_menu_, SIGNAL(showProtocolPreferences(QString)),
94 this, SIGNAL(showProtocolPreferences(QString)));
95 connect(&proto_prefs_menu_, SIGNAL(editProtocolPreference(preference*,pref_module*)),
96 this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
97
98 // resizeColumnToContents checks 1000 items by default. The user might
99 // have scrolled to an area with a different width at this point.
100 connect(verticalScrollBar(), SIGNAL(sliderReleased()),
101 this, SLOT(updateContentWidth()));
102
103 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(connectToMainWindow()));
104
105 viewport()->installEventFilter(this);
106 }
107
clear()108 void ProtoTree::clear() {
109 proto_tree_model_->setRootNode(NULL);
110 updateContentWidth();
111 }
112
connectToMainWindow()113 void ProtoTree::connectToMainWindow()
114 {
115 if (wsApp->mainWindow())
116 {
117 connect(wsApp->mainWindow(), SIGNAL(fieldSelected(FieldInformation *)),
118 this, SLOT(selectedFieldChanged(FieldInformation *)));
119 connect(wsApp->mainWindow(), SIGNAL(framesSelected(QList<int>)),
120 this, SLOT(selectedFrameChanged(QList<int>)));
121 }
122 }
123
ctxCopyVisibleItems()124 void ProtoTree::ctxCopyVisibleItems()
125 {
126 bool selected_tree = false;
127
128 QAction * send = qobject_cast<QAction *>(sender());
129 if (send && send->property("selected_tree").isValid())
130 selected_tree = true;
131
132 QString clip;
133 if (selected_tree && selectionModel()->hasSelection())
134 clip = toString(selectionModel()->selectedIndexes().first());
135 else
136 clip = toString();
137
138 if (clip.length() > 0)
139 wsApp->clipboard()->setText(clip);
140 }
141
ctxCopyAsFilter()142 void ProtoTree::ctxCopyAsFilter()
143 {
144 QModelIndex idx = selectionModel()->selectedIndexes().first();
145 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
146 if (finfo.isValid())
147 {
148 epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
149 char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt);
150 QString filter(field_filter);
151 wmem_free(Q_NULLPTR, field_filter);
152
153 if (filter.length() > 0)
154 wsApp->clipboard()->setText(filter);
155 }
156 }
157
ctxCopySelectedInfo()158 void ProtoTree::ctxCopySelectedInfo()
159 {
160 int val = -1;
161 QString clip;
162 QAction * send = qobject_cast<QAction *>(sender());
163 if (send && send->property("field_type").isValid())
164 val = send->property("field_type").toInt();
165
166 QModelIndex idx = selectionModel()->selectedIndexes().first();
167 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
168 if (! finfo.isValid())
169 return;
170
171 switch (val)
172 {
173 case ProtoTree::Name:
174 clip.append(finfo.headerInfo().abbreviation);
175 break;
176
177 case ProtoTree::Description:
178 clip = idx.data(Qt::DisplayRole).toString();
179 break;
180
181 case ProtoTree::Value:
182 {
183 epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
184 gchar* field_str = get_node_field_value(finfo.fieldInfo(), edt);
185 clip.append(field_str);
186 g_free(field_str);
187 }
188 break;
189 default:
190 break;
191 }
192
193 if (clip.length() > 0)
194 wsApp->clipboard()->setText(clip);
195 }
196
ctxOpenUrlWiki()197 void ProtoTree::ctxOpenUrlWiki()
198 {
199 QUrl url;
200 bool is_field_reference = false;
201 QAction * send = qobject_cast<QAction *>(sender());
202 if (send && send->property("field_reference").isValid())
203 is_field_reference = send->property("field_reference").toBool();
204 QModelIndex idx = selectionModel()->selectedIndexes().first();
205 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
206
207 int field_id = finfo.headerInfo().id;
208 if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) {
209 field_id = proto_registrar_get_parent(field_id);
210 }
211 const QString proto_abbrev = proto_registrar_get_abbrev(field_id);
212
213 if (! is_field_reference)
214 {
215 int ret = QMessageBox::question(this, wsApp->windowTitleString(tr("Wiki Page for %1").arg(proto_abbrev)),
216 tr("<p>The Wireshark Wiki is maintained by the community.</p>"
217 "<p>The page you are about to load might be wonderful, "
218 "incomplete, wrong, or nonexistent.</p>"
219 "<p>Proceed to the wiki?</p>"),
220 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
221
222 if (ret != QMessageBox::Yes) return;
223
224 url = QString(WS_WIKI_URL("Protocols/%1")).arg(proto_abbrev);
225 }
226 else
227 {
228 if (field_id != hf_text_only) {
229 url = QString(WS_DOCS_URL "/dfref/%1/%2")
230 .arg(proto_abbrev[0])
231 .arg(proto_abbrev);
232 } else {
233 QMessageBox::information(this, tr("Not a field or protocol"),
234 tr("No field reference available for text labels."),
235 QMessageBox::Ok);
236 }
237 }
238
239 QDesktopServices::openUrl(url);
240 }
241
contextMenuEvent(QContextMenuEvent * event)242 void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
243 {
244 QModelIndex index = indexAt(event->pos());
245 if (! index.isValid())
246 return;
247
248 // We're in a PacketDialog
249 bool buildForDialog = false;
250 if (! window()->findChild<QAction *>("actionViewExpandSubtrees"))
251 buildForDialog = true;
252
253 QMenu ctx_menu(this);
254
255 QMenu *main_menu_item, *submenu;
256 QAction *action;
257
258 bool have_subtree = false;
259 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
260 field_info * fi = finfo.fieldInfo();
261 bool is_selected = false;
262 epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
263
264 if (cap_file_ && cap_file_->finfo_selected == fi)
265 is_selected = true;
266 else if (! window()->findChild<QAction *>("actionViewExpandSubtrees"))
267 is_selected = true;
268
269 if (is_selected)
270 {
271 if (fi && fi->tree_type != -1) {
272 have_subtree = true;
273 }
274 }
275
276 action = ctx_menu.addAction(tr("Expand Subtrees"), this, SLOT(expandSubtrees()));
277 action->setEnabled(have_subtree);
278 action = ctx_menu.addAction(tr("Collapse Subtrees"), this, SLOT(collapseSubtrees()));
279 action->setEnabled(have_subtree);
280 ctx_menu.addAction(tr("Expand All"), this, SLOT(expandAll()));
281 ctx_menu.addAction(tr("Collapse All"), this, SLOT(collapseAll()));
282 ctx_menu.addSeparator();
283
284 if (! buildForDialog)
285 {
286 action = window()->findChild<QAction *>("actionAnalyzeCreateAColumn");
287 ctx_menu.addAction(action);
288 ctx_menu.addSeparator();
289 }
290
291 char * selectedfilter = proto_construct_match_selected_string(finfo.fieldInfo(), edt);
292 bool can_match_selected = proto_can_match_selected(finfo.fieldInfo(), edt);
293 ctx_menu.addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, can_match_selected, &ctx_menu));
294 ctx_menu.addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, can_match_selected, &ctx_menu));
295 if (selectedfilter)
296 wmem_free(Q_NULLPTR, selectedfilter);
297
298 if (! buildForDialog)
299 {
300 QMenu *main_conv_menu = window()->findChild<QMenu *>("menuConversationFilter");
301 conv_menu_.setTitle(main_conv_menu->title());
302 conv_menu_.clear();
303 foreach (QAction *action, main_conv_menu->actions()) {
304 conv_menu_.addAction(action);
305 }
306
307 ctx_menu.addMenu(&conv_menu_);
308
309 colorize_menu_.setTitle(tr("Colorize with Filter"));
310 ctx_menu.addMenu(&colorize_menu_);
311
312 main_menu_item = window()->findChild<QMenu *>("menuFollow");
313 submenu = new QMenu(main_menu_item->title(), &ctx_menu);
314 ctx_menu.addMenu(submenu);
315 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
316 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
317 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowDCCPStream"));
318 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTLSStream"));
319 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
320 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTP2Stream"));
321 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowQUICStream"));
322 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSIPCall"));
323 ctx_menu.addSeparator();
324 }
325
326 submenu = ctx_menu.addMenu(tr("Copy"));
327 submenu->addAction(tr("All Visible Items"), this, SLOT(ctxCopyVisibleItems()));
328 action = submenu->addAction(tr("All Visible Selected Tree Items"), this, SLOT(ctxCopyVisibleItems()));
329 action->setProperty("selected_tree", QVariant::fromValue(true));
330 action = submenu->addAction(tr("Description"), this, SLOT(ctxCopySelectedInfo()));
331 action->setProperty("field_type", ProtoTree::Description);
332 action = submenu->addAction(tr("Field Name"), this, SLOT(ctxCopySelectedInfo()));
333 action->setProperty("field_type", ProtoTree::Name);
334 action = submenu->addAction(tr("Value"), this, SLOT(ctxCopySelectedInfo()));
335 action->setProperty("field_type", ProtoTree::Value);
336 submenu->addSeparator();
337 submenu->addAction(tr("As Filter"), this, SLOT(ctxCopyAsFilter()));
338 submenu->addSeparator();
339 QActionGroup * copyEntries = DataPrinter::copyActions(this, &finfo);
340 submenu->addActions(copyEntries->actions());
341 ctx_menu.addSeparator();
342
343 if (! buildForDialog)
344 {
345 action = window()->findChild<QAction *>("actionAnalyzeShowPacketBytes");
346 ctx_menu.addAction(action);
347 action = window()->findChild<QAction *>("actionFileExportPacketBytes");
348 ctx_menu.addAction(action);
349
350 ctx_menu.addSeparator();
351 }
352
353 ctx_menu.addAction(tr("Wiki Protocol Page"), this, SLOT(ctxOpenUrlWiki()));
354 action = ctx_menu.addAction(tr("Filter Field Reference"), this, SLOT(ctxOpenUrlWiki()));
355 action->setProperty("field_reference", QVariant::fromValue(true));
356 ctx_menu.addMenu(&proto_prefs_menu_);
357 ctx_menu.addSeparator();
358
359 if (! buildForDialog)
360 {
361 QAction *decode_as_ = window()->findChild<QAction *>("actionAnalyzeDecodeAs");
362 ctx_menu.addAction(decode_as_);
363 decode_as_->setProperty("create_new", QVariant::fromValue(true));
364
365 ctx_menu.addAction(window()->findChild<QAction *>("actionGoGoToLinkedPacket"));
366 ctx_menu.addAction(window()->findChild<QAction *>("actionContextShowLinkedPacketInNewWindow"));
367
368 // The "text only" header field will not give preferences for the selected protocol.
369 // Use parent in this case.
370 proto_node *node = proto_tree_model_->protoNodeFromIndex(index).protoNode();
371 while (node && node->finfo && node->finfo->hfinfo && node->finfo->hfinfo->id == hf_text_only)
372 node = node->parent;
373
374 FieldInformation pref_finfo(node);
375 proto_prefs_menu_.setModule(pref_finfo.moduleName());
376 }
377
378 ctx_menu.exec(event->globalPos());
379 }
380
timerEvent(QTimerEvent * event)381 void ProtoTree::timerEvent(QTimerEvent *event)
382 {
383 if (event->timerId() == column_resize_timer_) {
384 killTimer(column_resize_timer_);
385 column_resize_timer_ = 0;
386 resizeColumnToContents(0);
387 } else {
388 QTreeView::timerEvent(event);
389 }
390 }
391
392 // resizeColumnToContents checks 1000 items by default. The user might
393 // have scrolled to an area with a different width at this point.
keyReleaseEvent(QKeyEvent * event)394 void ProtoTree::keyReleaseEvent(QKeyEvent *event)
395 {
396 if (event->isAutoRepeat()) return;
397
398 switch(event->key()) {
399 case Qt::Key_Up:
400 case Qt::Key_Down:
401 case Qt::Key_PageUp:
402 case Qt::Key_PageDown:
403 case Qt::Key_Home:
404 case Qt::Key_End:
405 updateContentWidth();
406 break;
407 default:
408 break;
409 }
410 }
411
updateContentWidth()412 void ProtoTree::updateContentWidth()
413 {
414 if (column_resize_timer_ == 0) {
415 column_resize_timer_ = startTimer(0);
416 }
417 }
418
setMonospaceFont(const QFont & mono_font)419 void ProtoTree::setMonospaceFont(const QFont &mono_font)
420 {
421 setFont(mono_font);
422 update();
423 }
424
foreachTreeNode(proto_node * node,gpointer proto_tree_ptr)425 void ProtoTree::foreachTreeNode(proto_node *node, gpointer proto_tree_ptr)
426 {
427 ProtoTree *tree_view = static_cast<ProtoTree *>(proto_tree_ptr);
428 ProtoTreeModel *model = qobject_cast<ProtoTreeModel *>(tree_view->model());
429 if (!tree_view || !model) {
430 return;
431 }
432
433 // Expanded state
434 if (tree_expanded(node->finfo->tree_type)) {
435 ProtoNode expand_node = ProtoNode(node);
436 tree_view->expand(model->indexFromProtoNode(expand_node));
437 }
438
439 // Related frames
440 if (node->finfo->hfinfo->type == FT_FRAMENUM) {
441 ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(node->finfo->hfinfo->strings);
442 tree_view->emitRelatedFrame(node->finfo->value.value.uinteger, framenum_type);
443 }
444
445 proto_tree_children_foreach(node, foreachTreeNode, proto_tree_ptr);
446 }
447
448 // setRootNode sets the new contents for the protocol tree and subsequently
449 // restores the previously expanded state.
setRootNode(proto_node * root_node)450 void ProtoTree::setRootNode(proto_node *root_node) {
451 // We track item expansion using proto.c:tree_is_expanded.
452 // Replace any existing (possibly invalidated) proto tree by the new tree.
453 // The expanded state will be reset as well and will be re-expanded below.
454 proto_tree_model_->setRootNode(root_node);
455
456 disconnect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex)));
457 proto_tree_children_foreach(root_node, foreachTreeNode, this);
458 connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex)));
459
460 updateContentWidth();
461 }
462
emitRelatedFrame(int related_frame,ft_framenum_type_t framenum_type)463 void ProtoTree::emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type)
464 {
465 emit relatedFrame(related_frame, framenum_type);
466 }
467
autoScrollTo(const QModelIndex & index)468 void ProtoTree::autoScrollTo(const QModelIndex &index)
469 {
470 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
471 if (!index.isValid()) {
472 return;
473 }
474
475 // ensure item is visible (expanding its parents as needed).
476 scrollTo(index);
477 }
478
479 // XXX We select the first match, which might not be the desired item.
goToHfid(int hfid)480 void ProtoTree::goToHfid(int hfid)
481 {
482 QModelIndex index = proto_tree_model_->findFirstHfid(hfid);
483 autoScrollTo(index);
484 }
485
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)486 void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
487 {
488 QTreeView::selectionChanged(selected, deselected);
489 if (selected.isEmpty()) {
490 emit fieldSelected(0);
491 return;
492 }
493
494 QModelIndex index = selected.indexes().first();
495 saveSelectedField(index);
496
497 // Find and highlight the protocol bytes. select above won't call
498 // selectionChanged if the current and selected indexes are the same
499 // so we do this here.
500 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode(), this);
501 if (finfo.isValid()) {
502 QModelIndex parent = index;
503 while (parent.isValid() && parent.parent().isValid()) {
504 parent = parent.parent();
505 }
506 if (parent.isValid()) {
507 FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent).protoNode());
508 finfo.setParentField(parent_finfo.fieldInfo());
509 }
510 emit fieldSelected(&finfo);
511 }
512 }
513
syncExpanded(const QModelIndex & index)514 void ProtoTree::syncExpanded(const QModelIndex &index) {
515 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
516 if (!finfo.isValid()) return;
517
518 /*
519 * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
520 * are thus presumably leaf nodes and cannot be expanded.
521 */
522 if (finfo.treeType() != -1) {
523 tree_expanded_set(finfo.treeType(), TRUE);
524 }
525 }
526
syncCollapsed(const QModelIndex & index)527 void ProtoTree::syncCollapsed(const QModelIndex &index) {
528 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
529 if (!finfo.isValid()) return;
530
531 /*
532 * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
533 * are thus presumably leaf nodes and cannot be collapsed.
534 */
535 if (finfo.treeType() != -1) {
536 tree_expanded_set(finfo.treeType(), FALSE);
537 }
538 }
539
expandSubtrees()540 void ProtoTree::expandSubtrees()
541 {
542 if (!selectionModel()->hasSelection()) return;
543
544 QStack<QModelIndex> index_stack;
545 index_stack.push(selectionModel()->selectedIndexes().first());
546
547 while (!index_stack.isEmpty()) {
548 QModelIndex index = index_stack.pop();
549 expand(index);
550 int row_count = proto_tree_model_->rowCount(index);
551 for (int row = row_count - 1; row >= 0; row--) {
552 QModelIndex child = proto_tree_model_->index(row, 0, index);
553 if (proto_tree_model_->hasChildren(child)) {
554 index_stack.push(child);
555 }
556 }
557 }
558
559 updateContentWidth();
560 }
561
collapseSubtrees()562 void ProtoTree::collapseSubtrees()
563 {
564 if (!selectionModel()->hasSelection()) return;
565
566 QStack<QModelIndex> index_stack;
567 index_stack.push(selectionModel()->selectedIndexes().first());
568
569 while (!index_stack.isEmpty()) {
570 QModelIndex index = index_stack.pop();
571 collapse(index);
572 int row_count = proto_tree_model_->rowCount(index);
573 for (int row = row_count - 1; row >= 0; row--) {
574 QModelIndex child = proto_tree_model_->index(row, 0, index);
575 if (proto_tree_model_->hasChildren(child)) {
576 index_stack.push(child);
577 }
578 }
579 }
580
581 updateContentWidth();
582 }
583
expandAll()584 void ProtoTree::expandAll()
585 {
586 for (int i = 0; i < num_tree_types; i++) {
587 tree_expanded_set(i, TRUE);
588 }
589 QTreeView::expandAll();
590 updateContentWidth();
591 }
592
collapseAll()593 void ProtoTree::collapseAll()
594 {
595 for (int i = 0; i < num_tree_types; i++) {
596 tree_expanded_set(i, FALSE);
597 }
598 QTreeView::collapseAll();
599 updateContentWidth();
600 }
601
itemClicked(const QModelIndex & index)602 void ProtoTree::itemClicked(const QModelIndex &index)
603 {
604 if (selectionModel()->selectedIndexes().isEmpty()) {
605 emit fieldSelected(0);
606 } else if (index == selectionModel()->selectedIndexes().first()) {
607 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
608
609 if (finfo.isValid()) {
610 emit fieldSelected(&finfo);
611 }
612 }
613 }
614
itemDoubleClicked(const QModelIndex & index)615 void ProtoTree::itemDoubleClicked(const QModelIndex &index)
616 {
617 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
618 if (!finfo.isValid()) return;
619
620 if (finfo.headerInfo().type == FT_FRAMENUM) {
621 if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) {
622 emit openPacketInNewWindow(true);
623 } else {
624 wsApp->gotoFrame(finfo.fieldInfo()->value.value.uinteger);
625 }
626 } else {
627 QString url = finfo.url();
628 if (!url.isEmpty()) {
629 QApplication::clipboard()->setText(url);
630 QString push_msg = tr("Copied ") + url;
631 wsApp->pushStatus(WiresharkApplication::TemporaryStatus, push_msg);
632 }
633 }
634 }
635
selectedFrameChanged(QList<int> frames)636 void ProtoTree::selectedFrameChanged(QList<int> frames)
637 {
638 if (frames.count() == 1 && cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
639 setRootNode(cap_file_->edt->tree);
640 } else {
641 // Clear the proto tree contents as they have become invalid.
642 proto_tree_model_->setRootNode(NULL);
643 }
644 }
645
646 // Select a field and bring it into view. Intended to be called by external
647 // components (such as the byte view).
selectedFieldChanged(FieldInformation * finfo)648 void ProtoTree::selectedFieldChanged(FieldInformation *finfo)
649 {
650 if (finfo && finfo->parent() == this) {
651 // We only want inbound signals.
652 return;
653 }
654
655 QModelIndex index = proto_tree_model_->findFieldInformation(finfo);
656 setUpdatesEnabled(false);
657 // The new finfo might match the current index. Clear our selection
658 // so that we force a fresh item selection, so that fieldSelected
659 // will in turn be emitted.
660 selectionModel()->clearSelection();
661 autoScrollTo(index);
662 setUpdatesEnabled(true);
663 }
664
665 // Remember the currently focussed field based on:
666 // - current hf_id (obviously)
667 // - parent items (to avoid selecting a text item in a different tree)
668 // - the row of each item
saveSelectedField(QModelIndex & index)669 void ProtoTree::saveSelectedField(QModelIndex &index)
670 {
671 selected_hfid_path_.clear();
672 QModelIndex save_index = index;
673 while (save_index.isValid()) {
674 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(save_index).protoNode());
675 if (!finfo.isValid()) break;
676 selected_hfid_path_.prepend(QPair<int,int>(save_index.row(), finfo.headerInfo().id));
677 save_index = save_index.parent();
678 }
679 }
680
681 // Try to focus a tree item which was previously also visible
restoreSelectedField()682 void ProtoTree::restoreSelectedField()
683 {
684 if (selected_hfid_path_.isEmpty()) return;
685
686 QModelIndex cur_index = QModelIndex();
687 QPair<int,int> path_entry;
688 foreach (path_entry, selected_hfid_path_) {
689 int row = path_entry.first;
690 int hf_id = path_entry.second;
691 cur_index = proto_tree_model_->index(row, 0, cur_index);
692 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(cur_index).protoNode());
693 if (!finfo.isValid() || finfo.headerInfo().id != hf_id) {
694 // Did not find the selected hfid path in the selected packet
695 cur_index = QModelIndex();
696 emit fieldSelected(0);
697 break;
698 }
699 }
700
701 autoScrollTo(cur_index);
702 }
703
traverseTree(const QModelIndex & travTree,int identLevel) const704 QString ProtoTree::traverseTree(const QModelIndex & travTree, int identLevel) const
705 {
706 QString result = "";
707
708 if (travTree.isValid())
709 {
710 result.append(QString(" ").repeated(identLevel));
711 result.append(travTree.data().toString());
712 result.append("\n");
713
714 /* if the element is expanded, we traverse one level down */
715 if (isExpanded(travTree))
716 {
717 int children = proto_tree_model_->rowCount(travTree);
718 identLevel++;
719 for (int child = 0; child < children; child++)
720 result += traverseTree(proto_tree_model_->index(child, 0, travTree), identLevel);
721 }
722 }
723
724 return result;
725 }
726
toString(const QModelIndex & start_idx) const727 QString ProtoTree::toString(const QModelIndex &start_idx) const
728 {
729 QString tree_string = "";
730 if (start_idx.isValid())
731 tree_string = traverseTree(start_idx, 0);
732 else
733 {
734 int children = proto_tree_model_->rowCount();
735 for (int child = 0; child < children; child++)
736 tree_string += traverseTree(proto_tree_model_->index(child, 0, QModelIndex()), 0);
737 }
738
739 return tree_string;
740 }
741
setCaptureFile(capture_file * cf)742 void ProtoTree::setCaptureFile(capture_file *cf)
743 {
744 // For use by the main view, set the capture file which will later have a
745 // dissection (EDT) ready.
746 // The packet dialog sets a fixed EDT context and MUST NOT use this.
747 Q_ASSERT(edt_ == NULL);
748 cap_file_ = cf;
749 }
750
eventFilter(QObject * obj,QEvent * event)751 bool ProtoTree::eventFilter(QObject * obj, QEvent * event)
752 {
753 if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseMove)
754 return QTreeView::eventFilter(obj, event);
755
756 /* Mouse was over scrollbar, ignoring */
757 if (qobject_cast<QScrollBar *>(obj))
758 return QTreeView::eventFilter(obj, event);
759
760 if (event->type() == QEvent::MouseButtonPress)
761 {
762 QMouseEvent * ev = (QMouseEvent *)event;
763
764 if (ev->buttons() & Qt::LeftButton)
765 drag_start_position_ = ev->pos();
766 }
767 else if (event->type() == QEvent::MouseMove)
768 {
769 QMouseEvent * ev = (QMouseEvent *)event;
770
771 if ((ev->buttons() & Qt::LeftButton) && (ev->pos() - drag_start_position_).manhattanLength()
772 > QApplication::startDragDistance())
773 {
774 QModelIndex idx = indexAt(drag_start_position_);
775 FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
776 if (finfo.isValid())
777 {
778 /* Hack to prevent QItemSelection taking the item which has been dragged over at start
779 * of drag-drop operation. selectionModel()->blockSignals could have done the trick, but
780 * it does not take in a QTreeWidget (maybe View) */
781 emit fieldSelected(&finfo);
782 selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
783
784 epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
785 char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt);
786 QString filter(field_filter);
787 wmem_free(NULL, field_filter);
788
789 if (filter.length() > 0)
790 {
791 QJsonObject filterData;
792 filterData["filter"] = filter;
793 filterData["name"] = finfo.headerInfo().abbreviation;
794 filterData["description"] = finfo.headerInfo().name;
795 QMimeData * mimeData = new QMimeData();
796
797 mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
798 mimeData->setText(toString(idx));
799
800 QDrag * drag = new QDrag(this);
801 drag->setMimeData(mimeData);
802
803 QString lblTxt = QString("%1\n%2").arg(finfo.headerInfo().name, filter);
804
805 DragLabel * content = new DragLabel(lblTxt, this);
806
807 qreal dpr = window()->windowHandle()->devicePixelRatio();
808 QPixmap pixmap(content->size() * dpr);
809 pixmap.setDevicePixelRatio(dpr);
810 content->render(&pixmap);
811 drag->setPixmap(pixmap);
812
813 drag->exec(Qt::CopyAction);
814
815 return true;
816 }
817 }
818 }
819 }
820
821 return QTreeView::eventFilter(obj, event);
822 }
823
moveCursor(QAbstractItemView::CursorAction cursorAction,Qt::KeyboardModifiers modifiers)824 QModelIndex ProtoTree::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
825 {
826 if (cursorAction == MoveLeft && selectionModel()->hasSelection()) {
827 QModelIndex cur_idx = selectionModel()->selectedIndexes().first();
828 QModelIndex parent = cur_idx.parent();
829 if (!isExpanded(cur_idx) && parent.isValid() && parent != rootIndex()) {
830 return parent;
831 }
832 }
833 return QTreeView::moveCursor(cursorAction, modifiers);
834 }
835