3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
12 #include <ui/qt/proto_tree.h>
13 #include <ui/qt/models/proto_tree_model.h>
15 #include <epan/ftypes/ftypes.h>
16 #include <epan/prefs.h>
17 #include <epan/epan.h>
18 #include <epan/epan_dissect.h>
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/qt/follow_stream_action.h>
29 #include <ui/qt/io_graph_action.h>
30 #include <ui/qt/protocol_preferences_menu.h>
31 #include <ui/all_files_wildcard.h>
32 #include <ui/alert_box.h>
34 #include "main_application.h"
36 #include <QApplication>
37 #include <QContextMenuEvent>
38 #include <QDesktopServices>
39 #include <QHeaderView>
40 #include <QItemSelectionModel>
46 #include <QMessageBox>
47 #include <QJsonDocument>
48 #include <QJsonObject>
51 // - Fix "apply as filter" behavior.
53 ProtoTree::ProtoTree(QWidget
*parent
, epan_dissect_t
*edt_fixed
) :
55 proto_tree_model_(new ProtoTreeModel(this)),
56 column_resize_timer_(0),
60 setAccessibleName(tr("Packet details"));
61 // Leave the uniformRowHeights property as-is (false) since items might
62 // have multiple lines (e.g. packet comments). If this slows things down
63 // too much we should add a custom delegate which handles SizeHintRole
64 // similar to PacketListModel::data.
65 setHeaderHidden(true);
67 #if !defined(Q_OS_WIN)
68 setStyleSheet(QStringLiteral(
69 "QTreeView:item:hover {"
70 " background-color: %1;"
71 " color: palette(text);"
72 "}").arg(ColorUtils::hoverBackground().name(QColor::HexArgb
)));
75 // Shrink down to a small but nonzero size in the main splitter.
76 int one_em
= fontMetrics().height();
77 setMinimumSize(one_em
, one_em
);
79 setModel(proto_tree_model_
);
81 connect(this, SIGNAL(expanded(QModelIndex
)), this, SLOT(syncExpanded(QModelIndex
)));
82 connect(this, SIGNAL(collapsed(QModelIndex
)), this, SLOT(syncCollapsed(QModelIndex
)));
83 connect(this, SIGNAL(clicked(QModelIndex
)),
84 this, SLOT(itemClicked(QModelIndex
)));
85 connect(this, SIGNAL(doubleClicked(QModelIndex
)),
86 this, SLOT(itemDoubleClicked(QModelIndex
)));
88 // resizeColumnToContents checks 1000 items by default. The user might
89 // have scrolled to an area with a different width at this point.
90 connect(verticalScrollBar(), SIGNAL(sliderReleased()),
91 this, SLOT(updateContentWidth()));
93 connect(mainApp
, SIGNAL(appInitialized()), this, SLOT(connectToMainWindow()));
95 viewport()->installEventFilter(this);
98 void ProtoTree::clear() {
99 proto_tree_model_
->setRootNode(NULL
);
100 updateContentWidth();
103 void ProtoTree::connectToMainWindow()
105 if (mainApp
->mainWindow())
107 connect(mainApp
->mainWindow(), SIGNAL(fieldSelected(FieldInformation
*)),
108 this, SLOT(selectedFieldChanged(FieldInformation
*)));
109 connect(mainApp
->mainWindow(), SIGNAL(framesSelected(QList
<int>)),
110 this, SLOT(selectedFrameChanged(QList
<int>)));
114 void ProtoTree::ctxCopyVisibleItems()
116 bool selected_tree
= false;
118 QAction
* send
= qobject_cast
<QAction
*>(sender());
119 if (send
&& send
->property("selected_tree").isValid())
120 selected_tree
= true;
123 if (selected_tree
&& selectionModel()->hasSelection())
124 clip
= toString(selectionModel()->selectedIndexes().first());
128 if (clip
.length() > 0)
129 mainApp
->clipboard()->setText(clip
);
132 void ProtoTree::ctxCopyAsFilter()
134 QModelIndex idx
= selectionModel()->selectedIndexes().first();
135 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(idx
));
138 epan_dissect_t
*edt
= cap_file_
? cap_file_
->edt
: edt_
;
139 char *field_filter
= proto_construct_match_selected_string(finfo
.fieldInfo(), edt
);
140 QString
filter(field_filter
);
141 wmem_free(Q_NULLPTR
, field_filter
);
143 if (filter
.length() > 0)
144 mainApp
->clipboard()->setText(filter
);
148 void ProtoTree::ctxCopySelectedInfo()
152 QAction
* send
= qobject_cast
<QAction
*>(sender());
153 if (send
&& send
->property("field_type").isValid())
154 val
= send
->property("field_type").toInt();
156 QModelIndex idx
= selectionModel()->selectedIndexes().first();
157 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(idx
));
158 if (! finfo
.isValid())
163 case ProtoTree::Name
:
164 clip
.append(finfo
.headerInfo().abbreviation
);
167 case ProtoTree::Description
:
168 clip
= idx
.data(Qt::DisplayRole
).toString();
171 case ProtoTree::Value
:
173 epan_dissect_t
*edt
= cap_file_
? cap_file_
->edt
: edt_
;
174 char* field_str
= get_node_field_value(finfo
.fieldInfo(), edt
);
175 clip
.append(field_str
[0] ? field_str
: "(null)");
183 if (clip
.length() > 0)
184 mainApp
->clipboard()->setText(clip
);
187 void ProtoTree::ctxOpenUrlWiki()
190 bool is_field_reference
= false;
191 QAction
* send
= qobject_cast
<QAction
*>(sender());
192 if (send
&& send
->property("field_reference").isValid())
193 is_field_reference
= send
->property("field_reference").toBool();
194 QModelIndex idx
= selectionModel()->selectedIndexes().first();
195 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(idx
));
197 int field_id
= finfo
.headerInfo().id
;
198 bool protocol_field_selected
= false;
199 if (!proto_registrar_is_protocol(field_id
) && (field_id
!= hf_text_only
)) {
200 protocol_field_selected
= true;
201 field_id
= proto_registrar_get_parent(field_id
);
203 const QString proto_abbrev
= proto_registrar_get_abbrev(field_id
);
205 if (! is_field_reference
)
207 int ret
= QMessageBox::question(this, mainApp
->windowTitleString(tr("Wiki Page for %1").arg(proto_abbrev
)),
208 tr("<p>The Wireshark Wiki is maintained by the community.</p>"
209 "<p>The page you are about to load might be wonderful, "
210 "incomplete, wrong, or nonexistent.</p>"
211 "<p>Proceed to the wiki?</p>"),
212 QMessageBox::Yes
| QMessageBox::No
, QMessageBox::Yes
);
214 if (ret
!= QMessageBox::Yes
) return;
216 url
= QString(WS_WIKI_URL("Protocols/%1")).arg(proto_abbrev
);
220 if (field_id
!= hf_text_only
) {
221 url
= QString(WS_DOCS_URL
"dfref/%1/%2.html")
222 .arg(proto_abbrev
[0])
225 if (protocol_field_selected
)
227 const QString proto_field_abbrev
= proto_registrar_get_abbrev(finfo
.headerInfo().id
);
228 url
.append(QStringLiteral("#%1").arg(proto_field_abbrev
));
231 QMessageBox::information(this, tr("Not a field or protocol"),
232 tr("No field reference available for text labels."),
237 QDesktopServices::openUrl(url
);
240 void ProtoTree::contextMenuEvent(QContextMenuEvent
*event
)
242 QModelIndex index
= indexAt(event
->pos());
243 if (! index
.isValid())
246 // We're in a PacketDialog
247 bool buildForDialog
= false;
248 if (! window()->findChild
<QAction
*>("actionViewExpandSubtrees"))
249 buildForDialog
= true;
251 QMenu
* ctx_menu
= new QMenu(this);
252 ctx_menu
->setAttribute(Qt::WA_DeleteOnClose
);
253 ctx_menu
->setProperty("toolTipsVisible", QVariant::fromValue(true));
255 QMenu
*main_menu_item
, *submenu
;
258 bool have_subtree
= false;
259 FieldInformation
*finfo
= new FieldInformation(proto_tree_model_
->protoNodeFromIndex(index
), ctx_menu
);
260 field_info
* fi
= finfo
->fieldInfo();
261 bool is_selected
= false;
262 epan_dissect_t
*edt
= cap_file_
? cap_file_
->edt
: edt_
;
264 if (cap_file_
&& cap_file_
->finfo_selected
== fi
)
266 else if (! window()->findChild
<QAction
*>("actionViewExpandSubtrees"))
271 if (fi
&& fi
->tree_type
!= -1) {
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();
284 if (! buildForDialog
)
286 if (finfo
->headerInfo().type
== FT_IPv4
|| finfo
->headerInfo().type
== FT_IPv6
) {
287 action
= window()->findChild
<QAction
*>("actionViewEditResolvedName");
288 ctx_menu
->addAction(action
);
289 ctx_menu
->addSeparator();
291 action
= window()->findChild
<QAction
*>("actionAnalyzeApplyAsColumn");
292 ctx_menu
->addAction(action
);
293 ctx_menu
->addSeparator();
296 char * selectedfilter
= proto_construct_match_selected_string(finfo
->fieldInfo(), edt
);
297 bool can_match_selected
= proto_can_match_selected(finfo
->fieldInfo(), edt
);
298 ctx_menu
->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply
, selectedfilter
, can_match_selected
, ctx_menu
));
299 ctx_menu
->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare
, selectedfilter
, can_match_selected
, ctx_menu
));
301 wmem_free(Q_NULLPTR
, selectedfilter
);
303 if (! buildForDialog
)
305 QMenu
*main_conv_menu
= window()->findChild
<QMenu
*>("menuConversationFilter");
306 conv_menu_
.setTitle(main_conv_menu
->title());
308 foreach (QAction
*action
, main_conv_menu
->actions()) {
309 conv_menu_
.addAction(action
);
312 ctx_menu
->addMenu(&conv_menu_
);
314 colorize_menu_
.setTitle(tr("Colorize with Filter"));
315 ctx_menu
->addMenu(&colorize_menu_
);
317 /* XXX: Should we just get a Follow action (if it exists) for the currently
318 * selected field info, similar to preferences and filters?
320 main_menu_item
= window()->findChild
<QMenu
*>("menuFollow");
321 if (main_menu_item
) {
322 submenu
= new QMenu(main_menu_item
->title(), ctx_menu
);
323 ctx_menu
->addMenu(submenu
);
324 foreach (FollowStreamAction
*follow_action
, main_menu_item
->findChildren
<FollowStreamAction
*>()) {
325 if (follow_action
->isEnabled()) {
326 submenu
->addAction(follow_action
);
331 ctx_menu
->addSeparator();
334 ctx_menu
->addMenu(IOGraphAction::createMenu(finfo
->headerInfo(), ctx_menu
));
336 submenu
= ctx_menu
->addMenu(tr("Copy"));
337 submenu
->setToolTipsVisible(true);
338 submenu
->addAction(tr("All Visible Items"), this, SLOT(ctxCopyVisibleItems()));
339 action
= submenu
->addAction(tr("All Visible Selected Tree Items"), this, SLOT(ctxCopyVisibleItems()));
340 action
->setProperty("selected_tree", QVariant::fromValue(true));
341 action
= submenu
->addAction(tr("Description"), this, SLOT(ctxCopySelectedInfo()));
342 action
->setProperty("field_type", ProtoTree::Description
);
343 action
= submenu
->addAction(tr("Field Name"), this, SLOT(ctxCopySelectedInfo()));
344 action
->setProperty("field_type", ProtoTree::Name
);
345 action
= submenu
->addAction(tr("Value"), this, SLOT(ctxCopySelectedInfo()));
346 action
->setProperty("field_type", ProtoTree::Value
);
347 submenu
->addSeparator();
348 submenu
->addAction(tr("As Filter"), this, SLOT(ctxCopyAsFilter()));
349 submenu
->addSeparator();
350 QActionGroup
* copyEntries
= DataPrinter::copyActions(this, finfo
);
351 submenu
->addActions(copyEntries
->actions());
352 ctx_menu
->addSeparator();
354 if (! buildForDialog
)
356 action
= window()->findChild
<QAction
*>("actionAnalyzeShowPacketBytes");
357 ctx_menu
->addAction(action
);
358 action
= window()->findChild
<QAction
*>("actionFileExportPacketBytes");
360 ctx_menu
->addAction(action
);
363 ctx_menu
->addSeparator();
366 int field_id
= finfo
->headerInfo().id
;
367 bool protocol_field_selected
= false;
368 if (!proto_registrar_is_protocol(field_id
) && (field_id
!= hf_text_only
)) {
369 protocol_field_selected
= true;
370 field_id
= proto_registrar_get_parent(field_id
);
372 action
= ctx_menu
->addAction(tr("Wiki Protocol Page"), this, SLOT(ctxOpenUrlWiki()));
373 action
->setProperty("toolTip", QString(WS_WIKI_URL("Protocols/%1")).arg(proto_registrar_get_abbrev(field_id
)));
375 action
= ctx_menu
->addAction(tr("Filter Field Reference"), this, SLOT(ctxOpenUrlWiki()));
376 action
->setProperty("field_reference", QVariant::fromValue(true));
377 if (field_id
!= hf_text_only
) {
378 action
->setEnabled(true);
379 const QString proto_abbrev
= proto_registrar_get_abbrev(field_id
);
380 QString url
= QString(WS_DOCS_URL
"dfref/%1/%2.html")
381 .arg(proto_abbrev
[0])
384 if (protocol_field_selected
)
386 const QString proto_field_abbrev
= proto_registrar_get_abbrev(finfo
->headerInfo().id
);
387 url
.append(QStringLiteral("#%1").arg(proto_field_abbrev
));
389 action
->setProperty("toolTip", url
);
392 action
->setEnabled(false);
393 action
->setProperty("toolTip", tr("No field reference available for text labels."));
396 // The "text only" header field will not give preferences for the selected protocol.
397 // Use parent in this case.
398 ProtoNode
*node
= proto_tree_model_
->protoNodeFromIndex(index
);
399 while (node
&& node
->isValid() && node
->protoNode()->finfo
&& node
->protoNode()->finfo
->hfinfo
&& node
->protoNode()->finfo
->hfinfo
->id
== hf_text_only
) {
400 node
= node
->parentNode();
403 FieldInformation
pref_finfo(node
);
405 ProtocolPreferencesMenu
*proto_prefs_menu
= new ProtocolPreferencesMenu(ctx_menu
);
406 proto_prefs_menu
->setModule(pref_finfo
.moduleName());
408 connect(proto_prefs_menu
, &ProtocolPreferencesMenu::showProtocolPreferences
,
409 this, &ProtoTree::showProtocolPreferences
);
410 connect(proto_prefs_menu
, SIGNAL(editProtocolPreference(pref_t
*,module_t
*)),
411 this, SIGNAL(editProtocolPreference(pref_t
*,module_t
*)));
413 ctx_menu
->addMenu(proto_prefs_menu
);
414 ctx_menu
->addSeparator();
416 if (! buildForDialog
)
418 QAction
*decode_as
= window()->findChild
<QAction
*>("actionAnalyzeDecodeAs");
420 ctx_menu
->addAction(decode_as
);
421 decode_as
->setProperty("create_new", QVariant::fromValue(true));
423 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionGoGoToLinkedPacket"));
424 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionContextShowLinkedPacketInNewWindow"));
428 ctx_menu
->popup(event
->globalPos());
431 void ProtoTree::timerEvent(QTimerEvent
*event
)
433 if (event
->timerId() == column_resize_timer_
) {
434 killTimer(column_resize_timer_
);
435 column_resize_timer_
= 0;
436 resizeColumnToContents(0);
438 QTreeView::timerEvent(event
);
442 // resizeColumnToContents checks 1000 items by default. The user might
443 // have scrolled to an area with a different width at this point.
444 void ProtoTree::keyReleaseEvent(QKeyEvent
*event
)
446 if (event
->isAutoRepeat()) return;
448 switch(event
->key()) {
452 case Qt::Key_PageDown
:
455 updateContentWidth();
462 void ProtoTree::updateContentWidth()
464 if (column_resize_timer_
== 0) {
465 column_resize_timer_
= startTimer(0);
469 void ProtoTree::setMonospaceFont(const QFont
&mono_font
)
475 void ProtoTree::foreachTreeNode(proto_node
*node
, void *proto_tree_ptr
)
477 ProtoTree
*tree_view
= static_cast<ProtoTree
*>(proto_tree_ptr
);
478 ProtoTreeModel
*model
= qobject_cast
<ProtoTreeModel
*>(tree_view
->model());
479 if (!tree_view
|| !model
) {
483 // Related frames - there might be hidden FT_FRAMENUM nodes, so do this
484 // for each proto_node and not just the ProtoNodes in the model
485 if (node
->finfo
->hfinfo
->type
== FT_FRAMENUM
) {
486 ft_framenum_type_t framenum_type
= (ft_framenum_type_t
)GPOINTER_TO_INT(node
->finfo
->hfinfo
->strings
);
487 tree_view
->emitRelatedFrame(fvalue_get_uinteger(node
->finfo
->value
), framenum_type
);
490 proto_tree_children_foreach(node
, foreachTreeNode
, proto_tree_ptr
);
493 // NOLINTNEXTLINE(misc-no-recursion)
494 void ProtoTree::foreachExpand(const QModelIndex
&index
= QModelIndex()) {
496 // Restore expanded state. (Note QModelIndex() refers to the root node)
497 int children
= proto_tree_model_
->rowCount(index
);
498 QModelIndex childIndex
;
499 for (int child
= 0; child
< children
; child
++) {
500 childIndex
= proto_tree_model_
->index(child
, 0, index
);
501 if (childIndex
.isValid()) {
502 ProtoNode
*node
= proto_tree_model_
->protoNodeFromIndex(childIndex
);
503 if (node
&& node
->isValid() && tree_expanded(node
->protoNode()->finfo
->tree_type
)) {
506 // We recurse here, but we're limited by tree depth checks in epan
507 foreachExpand(childIndex
);
512 // setRootNode sets the new contents for the protocol tree and subsequently
513 // restores the previously expanded state.
514 void ProtoTree::setRootNode(proto_node
*root_node
) {
515 // We track item expansion using proto.c:tree_is_expanded.
516 // Replace any existing (possibly invalidated) proto tree by the new tree.
517 // The expanded state will be reset as well and will be re-expanded below.
518 proto_tree_model_
->setRootNode(root_node
);
520 disconnect(this, SIGNAL(expanded(QModelIndex
)), this, SLOT(syncExpanded(QModelIndex
)));
521 proto_tree_children_foreach(root_node
, foreachTreeNode
, this);
523 connect(this, SIGNAL(expanded(QModelIndex
)), this, SLOT(syncExpanded(QModelIndex
)));
525 updateContentWidth();
528 void ProtoTree::emitRelatedFrame(int related_frame
, ft_framenum_type_t framenum_type
)
530 emit
relatedFrame(related_frame
, framenum_type
);
533 void ProtoTree::autoScrollTo(const QModelIndex
&index
)
535 selectionModel()->setCurrentIndex(index
, QItemSelectionModel::ClearAndSelect
);
536 if (!index
.isValid()) {
540 // ensure item is visible (expanding its parents as needed).
544 // XXX We select the first match, which might not be the desired item.
545 void ProtoTree::goToHfid(int hfid
)
547 QModelIndex index
= proto_tree_model_
->findFirstHfid(hfid
);
551 void ProtoTree::selectionChanged(const QItemSelection
&selected
, const QItemSelection
&deselected
)
553 QTreeView::selectionChanged(selected
, deselected
);
554 if (selected
.isEmpty()) {
555 emit
fieldSelected(0);
559 QModelIndex index
= selected
.indexes().first();
560 saveSelectedField(index
);
562 // Find and highlight the protocol bytes. select above won't call
563 // selectionChanged if the current and selected indexes are the same
564 // so we do this here.
565 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(index
), this);
566 if (finfo
.isValid()) {
567 QModelIndex parent
= index
;
568 while (parent
.isValid() && parent
.parent().isValid()) {
569 parent
= parent
.parent();
571 if (parent
.isValid()) {
572 FieldInformation
parent_finfo(proto_tree_model_
->protoNodeFromIndex(parent
));
573 finfo
.setParentField(parent_finfo
.fieldInfo());
575 emit
fieldSelected(&finfo
);
579 void ProtoTree::syncExpanded(const QModelIndex
&index
) {
580 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(index
));
581 if (!finfo
.isValid()) return;
584 * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
585 * are thus presumably leaf nodes and cannot be expanded.
587 if (finfo
.treeType() != -1) {
588 tree_expanded_set(finfo
.treeType(), true);
592 void ProtoTree::syncCollapsed(const QModelIndex
&index
) {
593 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(index
));
594 if (!finfo
.isValid()) return;
597 * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
598 * are thus presumably leaf nodes and cannot be collapsed.
600 if (finfo
.treeType() != -1) {
601 tree_expanded_set(finfo
.treeType(), false);
605 void ProtoTree::expandSubtrees()
607 if (!selectionModel()->hasSelection()) return;
609 QStack
<QModelIndex
> index_stack
;
610 index_stack
.push(selectionModel()->selectedIndexes().first());
612 while (!index_stack
.isEmpty()) {
613 QModelIndex index
= index_stack
.pop();
615 int row_count
= proto_tree_model_
->rowCount(index
);
616 for (int row
= row_count
- 1; row
>= 0; row
--) {
617 QModelIndex child
= proto_tree_model_
->index(row
, 0, index
);
618 if (proto_tree_model_
->hasChildren(child
)) {
619 index_stack
.push(child
);
624 updateContentWidth();
627 void ProtoTree::collapseSubtrees()
629 if (!selectionModel()->hasSelection()) return;
631 QStack
<QModelIndex
> index_stack
;
632 index_stack
.push(selectionModel()->selectedIndexes().first());
634 while (!index_stack
.isEmpty()) {
635 QModelIndex index
= index_stack
.pop();
637 int row_count
= proto_tree_model_
->rowCount(index
);
638 for (int row
= row_count
- 1; row
>= 0; row
--) {
639 QModelIndex child
= proto_tree_model_
->index(row
, 0, index
);
640 if (proto_tree_model_
->hasChildren(child
)) {
641 index_stack
.push(child
);
646 updateContentWidth();
649 void ProtoTree::expandAll()
651 for (int i
= 0; i
< num_tree_types
; i
++) {
652 tree_expanded_set(i
, true);
654 QTreeView::expandAll();
655 updateContentWidth();
658 void ProtoTree::collapseAll()
660 for (int i
= 0; i
< num_tree_types
; i
++) {
661 tree_expanded_set(i
, false);
663 QTreeView::collapseAll();
664 updateContentWidth();
667 void ProtoTree::itemClicked(const QModelIndex
&index
)
669 // selectionChanged() is not issued when some action would select
670 // the same item as currently selected - but we want to make sure
671 // ByteViewText is highlighting that field. The BVT highlighted bytes
672 // might be different, due to hover highlighting or Find Packet "bytes".
674 // Unfortunately, clicked() is singled after selectionChanged(), so
675 // we emit fieldSelected() twice after clicking on a new frame, once
676 // in selectionChanged(), and once here.
678 // We can't get rid of the fieldSelected() handling because there are
679 // non-mouse event ways to select a field, such as keyboard navigation.
681 // All this would be easier if Qt had a signal similar to
682 // selectionChanged() that was triggered even if the new selection
683 // was the same as the old one.
684 if (selectionModel()->selectedIndexes().isEmpty()) {
685 emit
fieldSelected(0);
686 } else if (index
== selectionModel()->selectedIndexes().first()) {
687 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(index
), this);
689 if (finfo
.isValid()) {
690 // Find and highlight the protocol bytes.
691 QModelIndex parent
= index
;
692 while (parent
.isValid() && parent
.parent().isValid()) {
693 parent
= parent
.parent();
695 if (parent
.isValid()) {
696 FieldInformation
parent_finfo(proto_tree_model_
->protoNodeFromIndex(parent
));
697 finfo
.setParentField(parent_finfo
.fieldInfo());
699 emit
fieldSelected(&finfo
);
704 void ProtoTree::itemDoubleClicked(const QModelIndex
&index
)
706 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(index
));
707 if (!finfo
.isValid()) return;
709 if (finfo
.headerInfo().type
== FT_FRAMENUM
) {
710 if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier
) {
711 emit
openPacketInNewWindow(true);
713 mainApp
->gotoFrame(fvalue_get_uinteger(finfo
.fieldInfo()->value
));
716 QString url
= finfo
.url();
717 if (!url
.isEmpty()) {
718 QApplication::clipboard()->setText(url
);
719 QString push_msg
= tr("Copied ") + url
;
720 mainApp
->pushStatus(MainApplication::TemporaryStatus
, push_msg
);
725 void ProtoTree::selectedFrameChanged(QList
<int> frames
)
727 if (frames
.count() == 1 && cap_file_
&& cap_file_
->edt
&& cap_file_
->edt
->tree
) {
728 setRootNode(cap_file_
->edt
->tree
);
730 // Clear the proto tree contents as they have become invalid.
731 proto_tree_model_
->setRootNode(NULL
);
735 // Select a field and bring it into view. Intended to be called by external
736 // components (such as the byte view).
737 void ProtoTree::selectedFieldChanged(FieldInformation
*finfo
)
739 if (finfo
&& finfo
->parent() == this) {
740 // We only want inbound signals.
744 QModelIndex index
= proto_tree_model_
->findFieldInformation(finfo
);
745 setUpdatesEnabled(false);
746 // The new finfo might match the current index. Clear our selection
747 // so that we force a fresh item selection, so that fieldSelected
748 // will in turn be emitted.
749 selectionModel()->clearSelection();
751 setUpdatesEnabled(true);
754 // Remember the currently focussed field based on:
755 // - current hf_id (obviously)
756 // - parent items (to avoid selecting a text item in a different tree)
757 // - the row of each item
758 void ProtoTree::saveSelectedField(QModelIndex
&index
)
760 selected_hfid_path_
.clear();
761 QModelIndex save_index
= index
;
762 while (save_index
.isValid()) {
763 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(save_index
));
764 if (!finfo
.isValid()) break;
765 selected_hfid_path_
.prepend(QPair
<int,int>(save_index
.row(), finfo
.headerInfo().id
));
766 save_index
= save_index
.parent();
770 // Try to focus a tree item which was previously also visible
771 void ProtoTree::restoreSelectedField()
773 if (selected_hfid_path_
.isEmpty()) return;
775 QModelIndex cur_index
= QModelIndex();
776 QPair
<int,int> path_entry
;
777 foreach (path_entry
, selected_hfid_path_
) {
778 int row
= path_entry
.first
;
779 int hf_id
= path_entry
.second
;
780 cur_index
= proto_tree_model_
->index(row
, 0, cur_index
);
781 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(cur_index
));
782 if (!finfo
.isValid() || finfo
.headerInfo().id
!= hf_id
) {
783 // Did not find the selected hfid path in the selected packet
784 cur_index
= QModelIndex();
785 emit
fieldSelected(0);
790 autoScrollTo(cur_index
);
793 // NOLINTNEXTLINE(misc-no-recursion)
794 QString
ProtoTree::traverseTree(const QModelIndex
& travTree
, int identLevel
) const
798 if (travTree
.isValid())
800 result
.append(QStringLiteral(" ").repeated(identLevel
));
801 result
.append(travTree
.data().toString());
804 /* if the element is expanded, we traverse one level down */
805 if (isExpanded(travTree
))
807 int children
= proto_tree_model_
->rowCount(travTree
);
809 for (int child
= 0; child
< children
; child
++)
810 // We recurse here, but we're limited by tree depth checks in epan
811 result
+= traverseTree(proto_tree_model_
->index(child
, 0, travTree
), identLevel
);
818 QString
ProtoTree::toString(const QModelIndex
&start_idx
) const
820 QString tree_string
= "";
821 if (start_idx
.isValid())
822 tree_string
= traverseTree(start_idx
, 0);
825 int children
= proto_tree_model_
->rowCount();
826 for (int child
= 0; child
< children
; child
++)
827 tree_string
+= traverseTree(proto_tree_model_
->index(child
, 0, QModelIndex()), 0);
833 void ProtoTree::setCaptureFile(capture_file
*cf
)
835 // For use by the main view, set the capture file which will later have a
836 // dissection (EDT) ready.
837 // The packet dialog sets a fixed EDT context and MUST NOT use this.
838 Q_ASSERT(edt_
== NULL
);
842 bool ProtoTree::eventFilter(QObject
* obj
, QEvent
* event
)
844 if (event
->type() != QEvent::MouseButtonPress
&& event
->type() != QEvent::MouseMove
)
845 return QTreeView::eventFilter(obj
, event
);
847 /* Mouse was over scrollbar, ignoring */
848 if (qobject_cast
<QScrollBar
*>(obj
))
849 return QTreeView::eventFilter(obj
, event
);
851 if (event
->type() == QEvent::MouseButtonPress
)
853 QMouseEvent
* ev
= (QMouseEvent
*)event
;
855 if (ev
->buttons() & Qt::LeftButton
)
856 drag_start_position_
= ev
->pos();
858 else if (event
->type() == QEvent::MouseMove
)
860 QMouseEvent
* ev
= (QMouseEvent
*)event
;
862 if ((ev
->buttons() & Qt::LeftButton
) && (ev
->pos() - drag_start_position_
).manhattanLength()
863 > QApplication::startDragDistance())
865 QModelIndex idx
= indexAt(drag_start_position_
);
866 FieldInformation
finfo(proto_tree_model_
->protoNodeFromIndex(idx
));
869 /* Hack to prevent QItemSelection taking the item which has been dragged over at start
870 * of drag-drop operation. selectionModel()->blockSignals could have done the trick, but
871 * it does not take in a QTreeWidget (maybe View) */
872 emit
fieldSelected(&finfo
);
873 selectionModel()->select(idx
, QItemSelectionModel::ClearAndSelect
);
875 epan_dissect_t
*edt
= cap_file_
? cap_file_
->edt
: edt_
;
876 char *field_filter
= proto_construct_match_selected_string(finfo
.fieldInfo(), edt
);
877 QString
filter(field_filter
);
878 wmem_free(NULL
, field_filter
);
880 if (filter
.length() > 0)
882 QJsonObject filterData
;
883 filterData
["filter"] = filter
;
884 filterData
["name"] = finfo
.headerInfo().abbreviation
;
885 filterData
["description"] = finfo
.headerInfo().name
;
886 QMimeData
* mimeData
= new QMimeData();
888 mimeData
->setData(WiresharkMimeData::DisplayFilterMimeType
, QJsonDocument(filterData
).toJson());
889 mimeData
->setText(toString(idx
));
891 QDrag
* drag
= new QDrag(this);
892 drag
->setMimeData(mimeData
);
894 QString lblTxt
= QStringLiteral("%1\n%2").arg(finfo
.headerInfo().name
, filter
);
896 DragLabel
* content
= new DragLabel(lblTxt
, this);
898 qreal dpr
= window()->windowHandle()->devicePixelRatio();
899 QPixmap
pixmap(content
->size() * dpr
);
900 pixmap
.setDevicePixelRatio(dpr
);
901 content
->render(&pixmap
);
902 drag
->setPixmap(pixmap
);
904 drag
->exec(Qt::CopyAction
);
912 return QTreeView::eventFilter(obj
, event
);
915 QModelIndex
ProtoTree::moveCursor(QAbstractItemView::CursorAction cursorAction
, Qt::KeyboardModifiers modifiers
)
917 if (cursorAction
== MoveLeft
&& selectionModel()->hasSelection()) {
918 QModelIndex cur_idx
= selectionModel()->selectedIndexes().first();
919 QModelIndex parent
= cur_idx
.parent();
920 if (!isExpanded(cur_idx
) && parent
.isValid() && parent
!= rootIndex()) {
924 return QTreeView::moveCursor(cursorAction
, modifiers
);