update epan/dissectors/pidl/drsuapi/drsuapi.idl from samba
[wireshark-sm.git] / ui / qt / proto_tree.cpp
blob64ef97060f772509aca0d9077c50c05fad6adecc
1 /* proto_tree.cpp
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
8 */
10 #include <stdio.h>
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>
19 #include <cfile.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>
33 #include <ui/urls.h>
34 #include "main_application.h"
36 #include <QApplication>
37 #include <QContextMenuEvent>
38 #include <QDesktopServices>
39 #include <QHeaderView>
40 #include <QItemSelectionModel>
41 #include <QScrollBar>
42 #include <QStack>
43 #include <QUrl>
44 #include <QClipboard>
45 #include <QWindow>
46 #include <QMessageBox>
47 #include <QJsonDocument>
48 #include <QJsonObject>
50 // To do:
51 // - Fix "apply as filter" behavior.
53 ProtoTree::ProtoTree(QWidget *parent, epan_dissect_t *edt_fixed) :
54 QTreeView(parent),
55 proto_tree_model_(new ProtoTreeModel(this)),
56 column_resize_timer_(0),
57 cap_file_(NULL),
58 edt_(edt_fixed)
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(QString(
69 "QTreeView:item:hover {"
70 " background-color: %1;"
71 " color: palette(text);"
72 "}").arg(ColorUtils::hoverBackground().name(QColor::HexArgb)));
73 #endif
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;
122 QString clip;
123 if (selected_tree && selectionModel()->hasSelection())
124 clip = toString(selectionModel()->selectedIndexes().first());
125 else
126 clip = toString();
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));
136 if (finfo.isValid())
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()
150 int val = -1;
151 QString clip;
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())
159 return;
161 switch (val)
163 case ProtoTree::Name:
164 clip.append(finfo.headerInfo().abbreviation);
165 break;
167 case ProtoTree::Description:
168 clip = idx.data(Qt::DisplayRole).toString();
169 break;
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)");
176 g_free(field_str);
178 break;
179 default:
180 break;
183 if (clip.length() > 0)
184 mainApp->clipboard()->setText(clip);
187 void ProtoTree::ctxOpenUrlWiki()
189 QString url;
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);
218 else
220 if (field_id != hf_text_only) {
221 url = QString(WS_DOCS_URL "dfref/%1/%2.html")
222 .arg(proto_abbrev[0])
223 .arg(proto_abbrev);
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));
230 } else {
231 QMessageBox::information(this, tr("Not a field or protocol"),
232 tr("No field reference available for text labels."),
233 QMessageBox::Ok);
237 QDesktopServices::openUrl(url);
240 void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
242 QModelIndex index = indexAt(event->pos());
243 if (! index.isValid())
244 return;
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;
256 QAction *action;
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)
265 is_selected = true;
266 else if (! window()->findChild<QAction *>("actionViewExpandSubtrees"))
267 is_selected = true;
269 if (is_selected)
271 if (fi && fi->tree_type != -1) {
272 have_subtree = true;
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));
300 if (selectedfilter)
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());
307 conv_menu_.clear();
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");
359 if (action) {
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])
382 .arg(proto_abbrev);
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);
391 else {
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(preference*,pref_module*)),
411 this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
413 ctx_menu->addMenu(proto_prefs_menu);
414 ctx_menu->addSeparator();
416 if (! buildForDialog)
418 QAction *decode_as = window()->findChild<QAction *>("actionAnalyzeDecodeAs");
419 if (decode_as) {
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);
437 } else {
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()) {
449 case Qt::Key_Up:
450 case Qt::Key_Down:
451 case Qt::Key_PageUp:
452 case Qt::Key_PageDown:
453 case Qt::Key_Home:
454 case Qt::Key_End:
455 updateContentWidth();
456 break;
457 default:
458 break;
462 void ProtoTree::updateContentWidth()
464 if (column_resize_timer_ == 0) {
465 column_resize_timer_ = startTimer(0);
469 void ProtoTree::setMonospaceFont(const QFont &mono_font)
471 setFont(mono_font);
472 update();
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) {
480 return;
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)) {
504 expand(childIndex);
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);
522 foreachExpand();
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()) {
537 return;
540 // ensure item is visible (expanding its parents as needed).
541 scrollTo(index);
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);
548 autoScrollTo(index);
551 void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
553 QTreeView::selectionChanged(selected, deselected);
554 if (selected.isEmpty()) {
555 emit fieldSelected(0);
556 return;
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();
614 expand(index);
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();
636 collapse(index);
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);
712 } else {
713 mainApp->gotoFrame(fvalue_get_uinteger(finfo.fieldInfo()->value));
715 } else {
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);
729 } else {
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.
741 return;
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();
750 autoScrollTo(index);
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);
786 break;
790 autoScrollTo(cur_index);
793 // NOLINTNEXTLINE(misc-no-recursion)
794 QString ProtoTree::traverseTree(const QModelIndex & travTree, int identLevel) const
796 QString result = "";
798 if (travTree.isValid())
800 result.append(QStringLiteral(" ").repeated(identLevel));
801 result.append(travTree.data().toString());
802 result.append("\n");
804 /* if the element is expanded, we traverse one level down */
805 if (isExpanded(travTree))
807 int children = proto_tree_model_->rowCount(travTree);
808 identLevel++;
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);
815 return result;
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);
823 else
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);
830 return tree_string;
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);
839 cap_file_ = cf;
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));
867 if (finfo.isValid())
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);
906 return true;
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()) {
921 return parent;
924 return QTreeView::moveCursor(cursorAction, modifiers);