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