diff options
Diffstat (limited to 'ui/qt/proto_tree.cpp')
-rw-r--r-- | ui/qt/proto_tree.cpp | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/ui/qt/proto_tree.cpp b/ui/qt/proto_tree.cpp new file mode 100644 index 0000000..77af8d2 --- /dev/null +++ b/ui/qt/proto_tree.cpp @@ -0,0 +1,909 @@ +/* proto_tree.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> + +#include <ui/qt/proto_tree.h> +#include <ui/qt/models/proto_tree_model.h> + +#include <epan/ftypes/ftypes.h> +#include <epan/prefs.h> +#include <epan/epan.h> +#include <epan/epan_dissect.h> +#include <cfile.h> + +#include <ui/qt/utils/color_utils.h> +#include <ui/qt/utils/variant_pointer.h> +#include <ui/qt/utils/wireshark_mime_data.h> +#include <ui/qt/widgets/drag_label.h> +#include <ui/qt/widgets/wireshark_file_dialog.h> +#include <ui/qt/show_packet_bytes_dialog.h> +#include <ui/qt/filter_action.h> +#include <ui/qt/follow_stream_action.h> +#include <ui/all_files_wildcard.h> +#include <ui/alert_box.h> +#include <ui/urls.h> +#include "main_application.h" + +#include <QApplication> +#include <QContextMenuEvent> +#include <QDesktopServices> +#include <QHeaderView> +#include <QItemSelectionModel> +#include <QScrollBar> +#include <QStack> +#include <QUrl> +#include <QClipboard> +#include <QWindow> +#include <QMessageBox> +#include <QJsonDocument> +#include <QJsonObject> + +// To do: +// - Fix "apply as filter" behavior. + +ProtoTree::ProtoTree(QWidget *parent, epan_dissect_t *edt_fixed) : + QTreeView(parent), + proto_tree_model_(new ProtoTreeModel(this)), + column_resize_timer_(0), + cap_file_(NULL), + edt_(edt_fixed) +{ + setAccessibleName(tr("Packet details")); + // Leave the uniformRowHeights property as-is (false) since items might + // have multiple lines (e.g. packet comments). If this slows things down + // too much we should add a custom delegate which handles SizeHintRole + // similar to PacketListModel::data. + setHeaderHidden(true); + +#if !defined(Q_OS_WIN) + setStyleSheet(QString( + "QTreeView:item:hover {" + " background-color: %1;" + " color: palette(text);" + "}").arg(ColorUtils::hoverBackground().name(QColor::HexArgb))); +#endif + + // Shrink down to a small but nonzero size in the main splitter. + int one_em = fontMetrics().height(); + setMinimumSize(one_em, one_em); + + setModel(proto_tree_model_); + + connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex))); + connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(syncCollapsed(QModelIndex))); + connect(this, SIGNAL(clicked(QModelIndex)), + this, SLOT(itemClicked(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), + this, SLOT(itemDoubleClicked(QModelIndex))); + + connect(&proto_prefs_menu_, SIGNAL(showProtocolPreferences(QString)), + this, SIGNAL(showProtocolPreferences(QString))); + connect(&proto_prefs_menu_, SIGNAL(editProtocolPreference(preference*,pref_module*)), + this, SIGNAL(editProtocolPreference(preference*,pref_module*))); + + // resizeColumnToContents checks 1000 items by default. The user might + // have scrolled to an area with a different width at this point. + connect(verticalScrollBar(), SIGNAL(sliderReleased()), + this, SLOT(updateContentWidth())); + + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(connectToMainWindow())); + + viewport()->installEventFilter(this); +} + +void ProtoTree::clear() { + proto_tree_model_->setRootNode(NULL); + updateContentWidth(); +} + +void ProtoTree::connectToMainWindow() +{ + if (mainApp->mainWindow()) + { + connect(mainApp->mainWindow(), SIGNAL(fieldSelected(FieldInformation *)), + this, SLOT(selectedFieldChanged(FieldInformation *))); + connect(mainApp->mainWindow(), SIGNAL(framesSelected(QList<int>)), + this, SLOT(selectedFrameChanged(QList<int>))); + } +} + +void ProtoTree::ctxCopyVisibleItems() +{ + bool selected_tree = false; + + QAction * send = qobject_cast<QAction *>(sender()); + if (send && send->property("selected_tree").isValid()) + selected_tree = true; + + QString clip; + if (selected_tree && selectionModel()->hasSelection()) + clip = toString(selectionModel()->selectedIndexes().first()); + else + clip = toString(); + + if (clip.length() > 0) + mainApp->clipboard()->setText(clip); +} + +void ProtoTree::ctxCopyAsFilter() +{ + QModelIndex idx = selectionModel()->selectedIndexes().first(); + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx)); + if (finfo.isValid()) + { + epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_; + char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt); + QString filter(field_filter); + wmem_free(Q_NULLPTR, field_filter); + + if (filter.length() > 0) + mainApp->clipboard()->setText(filter); + } +} + +void ProtoTree::ctxCopySelectedInfo() +{ + int val = -1; + QString clip; + QAction * send = qobject_cast<QAction *>(sender()); + if (send && send->property("field_type").isValid()) + val = send->property("field_type").toInt(); + + QModelIndex idx = selectionModel()->selectedIndexes().first(); + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx)); + if (! finfo.isValid()) + return; + + switch (val) + { + case ProtoTree::Name: + clip.append(finfo.headerInfo().abbreviation); + break; + + case ProtoTree::Description: + clip = idx.data(Qt::DisplayRole).toString(); + break; + + case ProtoTree::Value: + { + epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_; + gchar* field_str = get_node_field_value(finfo.fieldInfo(), edt); + clip.append(field_str); + g_free(field_str); + } + break; + default: + break; + } + + if (clip.length() > 0) + mainApp->clipboard()->setText(clip); +} + +void ProtoTree::ctxOpenUrlWiki() +{ + QString url; + bool is_field_reference = false; + QAction * send = qobject_cast<QAction *>(sender()); + if (send && send->property("field_reference").isValid()) + is_field_reference = send->property("field_reference").toBool(); + QModelIndex idx = selectionModel()->selectedIndexes().first(); + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx)); + + int field_id = finfo.headerInfo().id; + bool protocol_field_selected = false; + if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) { + protocol_field_selected = true; + field_id = proto_registrar_get_parent(field_id); + } + const QString proto_abbrev = proto_registrar_get_abbrev(field_id); + + if (! is_field_reference) + { + int ret = QMessageBox::question(this, mainApp->windowTitleString(tr("Wiki Page for %1").arg(proto_abbrev)), + tr("<p>The Wireshark Wiki is maintained by the community.</p>" + "<p>The page you are about to load might be wonderful, " + "incomplete, wrong, or nonexistent.</p>" + "<p>Proceed to the wiki?</p>"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (ret != QMessageBox::Yes) return; + + url = QString(WS_WIKI_URL("%1")).arg(proto_abbrev); + } + else + { + if (field_id != hf_text_only) { + url = QString(WS_DOCS_URL "dfref/%1/%2.html") + .arg(proto_abbrev[0]) + .arg(proto_abbrev); + + if (protocol_field_selected) + { + const QString proto_field_abbrev = proto_registrar_get_abbrev(finfo.headerInfo().id); + url.append(QString("#%1").arg(proto_field_abbrev)); + } + } else { + QMessageBox::information(this, tr("Not a field or protocol"), + tr("No field reference available for text labels."), + QMessageBox::Ok); + } + } + + QDesktopServices::openUrl(url); +} + +void ProtoTree::contextMenuEvent(QContextMenuEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (! index.isValid()) + return; + + // We're in a PacketDialog + bool buildForDialog = false; + if (! window()->findChild<QAction *>("actionViewExpandSubtrees")) + buildForDialog = true; + + QMenu * ctx_menu = new QMenu(this); + ctx_menu->setAttribute(Qt::WA_DeleteOnClose); + ctx_menu->setProperty("toolTipsVisible", QVariant::fromValue(true)); + + QMenu *main_menu_item, *submenu; + QAction *action; + + bool have_subtree = false; + FieldInformation *finfo = new FieldInformation(proto_tree_model_->protoNodeFromIndex(index), ctx_menu); + field_info * fi = finfo->fieldInfo(); + bool is_selected = false; + epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_; + + if (cap_file_ && cap_file_->finfo_selected == fi) + is_selected = true; + else if (! window()->findChild<QAction *>("actionViewExpandSubtrees")) + is_selected = true; + + if (is_selected) + { + if (fi && fi->tree_type != -1) { + have_subtree = true; + } + } + + action = ctx_menu->addAction(tr("Expand Subtrees"), this, SLOT(expandSubtrees())); + action->setEnabled(have_subtree); + action = ctx_menu->addAction(tr("Collapse Subtrees"), this, SLOT(collapseSubtrees())); + action->setEnabled(have_subtree); + ctx_menu->addAction(tr("Expand All"), this, SLOT(expandAll())); + ctx_menu->addAction(tr("Collapse All"), this, SLOT(collapseAll())); + ctx_menu->addSeparator(); + + if (! buildForDialog) + { + if (finfo->headerInfo().type == FT_IPv4 || finfo->headerInfo().type == FT_IPv6) { + action = window()->findChild<QAction *>("actionViewEditResolvedName"); + ctx_menu->addAction(action); + ctx_menu->addSeparator(); + } + action = window()->findChild<QAction *>("actionAnalyzeApplyAsColumn"); + ctx_menu->addAction(action); + ctx_menu->addSeparator(); + } + + char * selectedfilter = proto_construct_match_selected_string(finfo->fieldInfo(), edt); + bool can_match_selected = proto_can_match_selected(finfo->fieldInfo(), edt); + ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, can_match_selected, ctx_menu)); + ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, can_match_selected, ctx_menu)); + if (selectedfilter) + wmem_free(Q_NULLPTR, selectedfilter); + + if (! buildForDialog) + { + QMenu *main_conv_menu = window()->findChild<QMenu *>("menuConversationFilter"); + conv_menu_.setTitle(main_conv_menu->title()); + conv_menu_.clear(); + foreach (QAction *action, main_conv_menu->actions()) { + conv_menu_.addAction(action); + } + + ctx_menu->addMenu(&conv_menu_); + + colorize_menu_.setTitle(tr("Colorize with Filter")); + ctx_menu->addMenu(&colorize_menu_); + + /* XXX: Should we just get a Follow action (if it exists) for the currently + * selected field info, similar to preferences and filters? + */ + main_menu_item = window()->findChild<QMenu *>("menuFollow"); + if (main_menu_item) { + submenu = new QMenu(main_menu_item->title(), ctx_menu); + ctx_menu->addMenu(submenu); + foreach (FollowStreamAction *follow_action, main_menu_item->findChildren<FollowStreamAction *>()) { + if (follow_action->isEnabled()) { + submenu->addAction(follow_action); + } + } + } + + ctx_menu->addSeparator(); + } + + submenu = ctx_menu->addMenu(tr("Copy")); + submenu->addAction(tr("All Visible Items"), this, SLOT(ctxCopyVisibleItems())); + action = submenu->addAction(tr("All Visible Selected Tree Items"), this, SLOT(ctxCopyVisibleItems())); + action->setProperty("selected_tree", QVariant::fromValue(true)); + action = submenu->addAction(tr("Description"), this, SLOT(ctxCopySelectedInfo())); + action->setProperty("field_type", ProtoTree::Description); + action = submenu->addAction(tr("Field Name"), this, SLOT(ctxCopySelectedInfo())); + action->setProperty("field_type", ProtoTree::Name); + action = submenu->addAction(tr("Value"), this, SLOT(ctxCopySelectedInfo())); + action->setProperty("field_type", ProtoTree::Value); + submenu->addSeparator(); + submenu->addAction(tr("As Filter"), this, SLOT(ctxCopyAsFilter())); + submenu->addSeparator(); + QActionGroup * copyEntries = DataPrinter::copyActions(this, finfo); + submenu->addActions(copyEntries->actions()); + ctx_menu->addSeparator(); + + if (! buildForDialog) + { + action = window()->findChild<QAction *>("actionAnalyzeShowPacketBytes"); + ctx_menu->addAction(action); + action = window()->findChild<QAction *>("actionFileExportPacketBytes"); + ctx_menu->addAction(action); + + ctx_menu->addSeparator(); + } + + int field_id = finfo->headerInfo().id; + bool protocol_field_selected = false; + if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) { + protocol_field_selected = true; + field_id = proto_registrar_get_parent(field_id); + } + action = ctx_menu->addAction(tr("Wiki Protocol Page"), this, SLOT(ctxOpenUrlWiki())); + action->setProperty("toolTip", QString(WS_WIKI_URL("Protocols/%1")).arg(proto_registrar_get_abbrev(field_id))); + + action = ctx_menu->addAction(tr("Filter Field Reference"), this, SLOT(ctxOpenUrlWiki())); + action->setProperty("field_reference", QVariant::fromValue(true)); + if (field_id != hf_text_only) { + action->setEnabled(true); + const QString proto_abbrev = proto_registrar_get_abbrev(field_id); + QString url = QString(WS_DOCS_URL "dfref/%1/%2.html") + .arg(proto_abbrev[0]) + .arg(proto_abbrev); + + if (protocol_field_selected) + { + const QString proto_field_abbrev = proto_registrar_get_abbrev(finfo->headerInfo().id); + url.append(QString("#%1").arg(proto_field_abbrev)); + } + action->setProperty("toolTip", url); + } + else { + action->setEnabled(false); + action->setProperty("toolTip", tr("No field reference available for text labels.")); + } + ctx_menu->addMenu(&proto_prefs_menu_); + ctx_menu->addSeparator(); + + if (! buildForDialog) + { + QAction *decode_as_ = window()->findChild<QAction *>("actionAnalyzeDecodeAs"); + ctx_menu->addAction(decode_as_); + decode_as_->setProperty("create_new", QVariant::fromValue(true)); + + ctx_menu->addAction(window()->findChild<QAction *>("actionGoGoToLinkedPacket")); + ctx_menu->addAction(window()->findChild<QAction *>("actionContextShowLinkedPacketInNewWindow")); + } + + // The "text only" header field will not give preferences for the selected protocol. + // Use parent in this case. + ProtoNode *node = proto_tree_model_->protoNodeFromIndex(index); + while (node && node->isValid() && node->protoNode()->finfo && node->protoNode()->finfo->hfinfo && node->protoNode()->finfo->hfinfo->id == hf_text_only) { + node = node->parentNode(); + } + + FieldInformation pref_finfo(node); + proto_prefs_menu_.setModule(pref_finfo.moduleName()); + + ctx_menu->popup(event->globalPos()); +} + +void ProtoTree::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == column_resize_timer_) { + killTimer(column_resize_timer_); + column_resize_timer_ = 0; + resizeColumnToContents(0); + } else { + QTreeView::timerEvent(event); + } +} + +// resizeColumnToContents checks 1000 items by default. The user might +// have scrolled to an area with a different width at this point. +void ProtoTree::keyReleaseEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) return; + + switch(event->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Home: + case Qt::Key_End: + updateContentWidth(); + break; + default: + break; + } +} + +void ProtoTree::updateContentWidth() +{ + if (column_resize_timer_ == 0) { + column_resize_timer_ = startTimer(0); + } +} + +void ProtoTree::setMonospaceFont(const QFont &mono_font) +{ + setFont(mono_font); + update(); +} + +void ProtoTree::foreachTreeNode(proto_node *node, gpointer proto_tree_ptr) +{ + ProtoTree *tree_view = static_cast<ProtoTree *>(proto_tree_ptr); + ProtoTreeModel *model = qobject_cast<ProtoTreeModel *>(tree_view->model()); + if (!tree_view || !model) { + return; + } + + // Related frames - there might be hidden FT_FRAMENUM nodes, so do this + // for each proto_node and not just the ProtoNodes in the model + if (node->finfo->hfinfo->type == FT_FRAMENUM) { + ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(node->finfo->hfinfo->strings); + tree_view->emitRelatedFrame(fvalue_get_uinteger(node->finfo->value), framenum_type); + } + + proto_tree_children_foreach(node, foreachTreeNode, proto_tree_ptr); +} + +void ProtoTree::foreachExpand(const QModelIndex &index = QModelIndex()) { + + // Restore expanded state. (Note QModelIndex() refers to the root node) + int children = proto_tree_model_->rowCount(index); + QModelIndex childIndex; + for (int child = 0; child < children; child++) { + childIndex = proto_tree_model_->index(child, 0, index); + if (childIndex.isValid()) { + ProtoNode *node = proto_tree_model_->protoNodeFromIndex(childIndex); + if (node && node->isValid() && tree_expanded(node->protoNode()->finfo->tree_type)) { + expand(childIndex); + } + foreachExpand(childIndex); + } + } +} + +// setRootNode sets the new contents for the protocol tree and subsequently +// restores the previously expanded state. +void ProtoTree::setRootNode(proto_node *root_node) { + // We track item expansion using proto.c:tree_is_expanded. + // Replace any existing (possibly invalidated) proto tree by the new tree. + // The expanded state will be reset as well and will be re-expanded below. + proto_tree_model_->setRootNode(root_node); + + disconnect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex))); + proto_tree_children_foreach(root_node, foreachTreeNode, this); + foreachExpand(); + connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex))); + + updateContentWidth(); +} + +void ProtoTree::emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type) +{ + emit relatedFrame(related_frame, framenum_type); +} + +void ProtoTree::autoScrollTo(const QModelIndex &index) +{ + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + if (!index.isValid()) { + return; + } + + // ensure item is visible (expanding its parents as needed). + scrollTo(index); +} + +// XXX We select the first match, which might not be the desired item. +void ProtoTree::goToHfid(int hfid) +{ + QModelIndex index = proto_tree_model_->findFirstHfid(hfid); + autoScrollTo(index); +} + +void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + QTreeView::selectionChanged(selected, deselected); + if (selected.isEmpty()) { + emit fieldSelected(0); + return; + } + + QModelIndex index = selected.indexes().first(); + saveSelectedField(index); + + // Find and highlight the protocol bytes. select above won't call + // selectionChanged if the current and selected indexes are the same + // so we do this here. + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index), this); + if (finfo.isValid()) { + QModelIndex parent = index; + while (parent.isValid() && parent.parent().isValid()) { + parent = parent.parent(); + } + if (parent.isValid()) { + FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent)); + finfo.setParentField(parent_finfo.fieldInfo()); + } + emit fieldSelected(&finfo); + } +} + +void ProtoTree::syncExpanded(const QModelIndex &index) { + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index)); + if (!finfo.isValid()) return; + + /* + * Nodes with "finfo->tree_type" of -1 have no ett_ value, and + * are thus presumably leaf nodes and cannot be expanded. + */ + if (finfo.treeType() != -1) { + tree_expanded_set(finfo.treeType(), TRUE); + } +} + +void ProtoTree::syncCollapsed(const QModelIndex &index) { + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index)); + if (!finfo.isValid()) return; + + /* + * Nodes with "finfo->tree_type" of -1 have no ett_ value, and + * are thus presumably leaf nodes and cannot be collapsed. + */ + if (finfo.treeType() != -1) { + tree_expanded_set(finfo.treeType(), FALSE); + } +} + +void ProtoTree::expandSubtrees() +{ + if (!selectionModel()->hasSelection()) return; + + QStack<QModelIndex> index_stack; + index_stack.push(selectionModel()->selectedIndexes().first()); + + while (!index_stack.isEmpty()) { + QModelIndex index = index_stack.pop(); + expand(index); + int row_count = proto_tree_model_->rowCount(index); + for (int row = row_count - 1; row >= 0; row--) { + QModelIndex child = proto_tree_model_->index(row, 0, index); + if (proto_tree_model_->hasChildren(child)) { + index_stack.push(child); + } + } + } + + updateContentWidth(); +} + +void ProtoTree::collapseSubtrees() +{ + if (!selectionModel()->hasSelection()) return; + + QStack<QModelIndex> index_stack; + index_stack.push(selectionModel()->selectedIndexes().first()); + + while (!index_stack.isEmpty()) { + QModelIndex index = index_stack.pop(); + collapse(index); + int row_count = proto_tree_model_->rowCount(index); + for (int row = row_count - 1; row >= 0; row--) { + QModelIndex child = proto_tree_model_->index(row, 0, index); + if (proto_tree_model_->hasChildren(child)) { + index_stack.push(child); + } + } + } + + updateContentWidth(); +} + +void ProtoTree::expandAll() +{ + for (int i = 0; i < num_tree_types; i++) { + tree_expanded_set(i, TRUE); + } + QTreeView::expandAll(); + updateContentWidth(); +} + +void ProtoTree::collapseAll() +{ + for (int i = 0; i < num_tree_types; i++) { + tree_expanded_set(i, FALSE); + } + QTreeView::collapseAll(); + updateContentWidth(); +} + +void ProtoTree::itemClicked(const QModelIndex &index) +{ + // selectionChanged() is not issued when some action would select + // the same item as currently selected - but we want to make sure + // ByteViewText is highlighting that field. The BVT highlighted bytes + // might be different, due to hover highlighting or Find Packet "bytes". + // + // Unfortunately, clicked() is singled after selectionChanged(), so + // we emit fieldSelected() twice after clicking on a new frame, once + // in selectionChanged(), and once here. + // + // We can't get rid of the fieldSelected() handling because there are + // non-mouse event ways to select a field, such as keyboard navigation. + // + // All this would be easier if Qt had a signal similar to + // selectionChanged() that was triggered even if the new selection + // was the same as the old one. + if (selectionModel()->selectedIndexes().isEmpty()) { + emit fieldSelected(0); + } else if (index == selectionModel()->selectedIndexes().first()) { + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index), this); + + if (finfo.isValid()) { + // Find and highlight the protocol bytes. + QModelIndex parent = index; + while (parent.isValid() && parent.parent().isValid()) { + parent = parent.parent(); + } + if (parent.isValid()) { + FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent)); + finfo.setParentField(parent_finfo.fieldInfo()); + } + emit fieldSelected(&finfo); + } + } +} + +void ProtoTree::itemDoubleClicked(const QModelIndex &index) +{ + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index)); + if (!finfo.isValid()) return; + + if (finfo.headerInfo().type == FT_FRAMENUM) { + if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) { + emit openPacketInNewWindow(true); + } else { + mainApp->gotoFrame(fvalue_get_uinteger(finfo.fieldInfo()->value)); + } + } else { + QString url = finfo.url(); + if (!url.isEmpty()) { + QApplication::clipboard()->setText(url); + QString push_msg = tr("Copied ") + url; + mainApp->pushStatus(MainApplication::TemporaryStatus, push_msg); + } + } +} + +void ProtoTree::selectedFrameChanged(QList<int> frames) +{ + if (frames.count() == 1 && cap_file_ && cap_file_->edt && cap_file_->edt->tree) { + setRootNode(cap_file_->edt->tree); + } else { + // Clear the proto tree contents as they have become invalid. + proto_tree_model_->setRootNode(NULL); + } +} + +// Select a field and bring it into view. Intended to be called by external +// components (such as the byte view). +void ProtoTree::selectedFieldChanged(FieldInformation *finfo) +{ + if (finfo && finfo->parent() == this) { + // We only want inbound signals. + return; + } + + QModelIndex index = proto_tree_model_->findFieldInformation(finfo); + setUpdatesEnabled(false); + // The new finfo might match the current index. Clear our selection + // so that we force a fresh item selection, so that fieldSelected + // will in turn be emitted. + selectionModel()->clearSelection(); + autoScrollTo(index); + setUpdatesEnabled(true); +} + +// Remember the currently focussed field based on: +// - current hf_id (obviously) +// - parent items (to avoid selecting a text item in a different tree) +// - the row of each item +void ProtoTree::saveSelectedField(QModelIndex &index) +{ + selected_hfid_path_.clear(); + QModelIndex save_index = index; + while (save_index.isValid()) { + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(save_index)); + if (!finfo.isValid()) break; + selected_hfid_path_.prepend(QPair<int,int>(save_index.row(), finfo.headerInfo().id)); + save_index = save_index.parent(); + } +} + +// Try to focus a tree item which was previously also visible +void ProtoTree::restoreSelectedField() +{ + if (selected_hfid_path_.isEmpty()) return; + + QModelIndex cur_index = QModelIndex(); + QPair<int,int> path_entry; + foreach (path_entry, selected_hfid_path_) { + int row = path_entry.first; + int hf_id = path_entry.second; + cur_index = proto_tree_model_->index(row, 0, cur_index); + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(cur_index)); + if (!finfo.isValid() || finfo.headerInfo().id != hf_id) { + // Did not find the selected hfid path in the selected packet + cur_index = QModelIndex(); + emit fieldSelected(0); + break; + } + } + + autoScrollTo(cur_index); +} + +QString ProtoTree::traverseTree(const QModelIndex & travTree, int identLevel) const +{ + QString result = ""; + + if (travTree.isValid()) + { + result.append(QString(" ").repeated(identLevel)); + result.append(travTree.data().toString()); + result.append("\n"); + + /* if the element is expanded, we traverse one level down */ + if (isExpanded(travTree)) + { + int children = proto_tree_model_->rowCount(travTree); + identLevel++; + for (int child = 0; child < children; child++) + result += traverseTree(proto_tree_model_->index(child, 0, travTree), identLevel); + } + } + + return result; +} + +QString ProtoTree::toString(const QModelIndex &start_idx) const +{ + QString tree_string = ""; + if (start_idx.isValid()) + tree_string = traverseTree(start_idx, 0); + else + { + int children = proto_tree_model_->rowCount(); + for (int child = 0; child < children; child++) + tree_string += traverseTree(proto_tree_model_->index(child, 0, QModelIndex()), 0); + } + + return tree_string; +} + +void ProtoTree::setCaptureFile(capture_file *cf) +{ + // For use by the main view, set the capture file which will later have a + // dissection (EDT) ready. + // The packet dialog sets a fixed EDT context and MUST NOT use this. + Q_ASSERT(edt_ == NULL); + cap_file_ = cf; +} + +bool ProtoTree::eventFilter(QObject * obj, QEvent * event) +{ + if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseMove) + return QTreeView::eventFilter(obj, event); + + /* Mouse was over scrollbar, ignoring */ + if (qobject_cast<QScrollBar *>(obj)) + return QTreeView::eventFilter(obj, event); + + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent * ev = (QMouseEvent *)event; + + if (ev->buttons() & Qt::LeftButton) + drag_start_position_ = ev->pos(); + } + else if (event->type() == QEvent::MouseMove) + { + QMouseEvent * ev = (QMouseEvent *)event; + + if ((ev->buttons() & Qt::LeftButton) && (ev->pos() - drag_start_position_).manhattanLength() + > QApplication::startDragDistance()) + { + QModelIndex idx = indexAt(drag_start_position_); + FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx)); + if (finfo.isValid()) + { + /* Hack to prevent QItemSelection taking the item which has been dragged over at start + * of drag-drop operation. selectionModel()->blockSignals could have done the trick, but + * it does not take in a QTreeWidget (maybe View) */ + emit fieldSelected(&finfo); + selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); + + epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_; + char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt); + QString filter(field_filter); + wmem_free(NULL, field_filter); + + if (filter.length() > 0) + { + QJsonObject filterData; + filterData["filter"] = filter; + filterData["name"] = finfo.headerInfo().abbreviation; + filterData["description"] = finfo.headerInfo().name; + QMimeData * mimeData = new QMimeData(); + + mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson()); + mimeData->setText(toString(idx)); + + QDrag * drag = new QDrag(this); + drag->setMimeData(mimeData); + + QString lblTxt = QString("%1\n%2").arg(finfo.headerInfo().name, filter); + + DragLabel * content = new DragLabel(lblTxt, this); + + qreal dpr = window()->windowHandle()->devicePixelRatio(); + QPixmap pixmap(content->size() * dpr); + pixmap.setDevicePixelRatio(dpr); + content->render(&pixmap); + drag->setPixmap(pixmap); + + drag->exec(Qt::CopyAction); + + return true; + } + } + } + } + + return QTreeView::eventFilter(obj, event); +} + +QModelIndex ProtoTree::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + if (cursorAction == MoveLeft && selectionModel()->hasSelection()) { + QModelIndex cur_idx = selectionModel()->selectedIndexes().first(); + QModelIndex parent = cur_idx.parent(); + if (!isExpanded(cur_idx) && parent.isValid() && parent != rootIndex()) { + return parent; + } + } + return QTreeView::moveCursor(cursorAction, modifiers); +} |