/* proto_tree.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "proto_tree.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif // To do: // - Fix "apply as filter" behavior. /* Fill a single protocol tree item with its string value and set its color. */ static void proto_tree_draw_node(proto_node *node, gpointer data) { field_info *fi = PNODE_FINFO(node); QString label; gboolean is_branch; /* dissection with an invisible proto tree? */ g_assert(fi); if (PROTO_ITEM_IS_HIDDEN(node) && !prefs.display_hidden_proto_items) return; // Fill in our label /* was a free format label produced? */ if (fi->rep) { label = fi->rep->representation; } else { /* no, make a generic label */ gchar label_str[ITEM_LABEL_LENGTH]; proto_item_fill_label(fi, label_str); label = label_str; } if (node->first_child != NULL) { is_branch = TRUE; g_assert(fi->tree_type >= 0 && fi->tree_type < num_tree_types); } else { is_branch = FALSE; } if (PROTO_ITEM_IS_GENERATED(node)) { if (PROTO_ITEM_IS_HIDDEN(node)) { label = QString("<[%1]>").arg(label); } else { label = QString("[%1]").arg(label); } } else if (PROTO_ITEM_IS_HIDDEN(node)) { label = QString("<%1>").arg(label); } QTreeWidgetItem *parentItem = (QTreeWidgetItem *)data; QTreeWidgetItem *item; ProtoTree *proto_tree = qobject_cast(parentItem->treeWidget()); item = new QTreeWidgetItem(parentItem, 0); // Set our colors. QPalette pal = QApplication::palette(); if (fi->hfinfo) { if(fi->hfinfo->type == FT_PROTOCOL) { item->setData(0, Qt::BackgroundRole, pal.window()); item->setData(0, Qt::ForegroundRole, pal.windowText()); } if((fi->hfinfo->type == FT_FRAMENUM) || (FI_GET_FLAG(fi, FI_URL) && IS_FT_STRING(fi->hfinfo->type))) { QFont font = item->font(0); item->setData(0, Qt::ForegroundRole, pal.link()); font.setUnderline(true); item->setData(0, Qt::FontRole, font); if (fi->hfinfo->type == FT_FRAMENUM) { ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(fi->hfinfo->strings); proto_tree->emitRelatedFrame(fi->value.value.uinteger, framenum_type); } } } // XXX - Add routines to get our severity colors. if(FI_GET_FLAG(fi, PI_SEVERITY_MASK)) { switch(FI_GET_FLAG(fi, PI_SEVERITY_MASK)) { case(PI_COMMENT): item->setData(0, Qt::BackgroundRole, ColorUtils::expert_color_comment); break; case(PI_CHAT): item->setData(0, Qt::BackgroundRole, ColorUtils::expert_color_chat); break; case(PI_NOTE): item->setData(0, Qt::BackgroundRole, ColorUtils::expert_color_note); break; case(PI_WARN): item->setData(0, Qt::BackgroundRole, ColorUtils::expert_color_warn); break; case(PI_ERROR): item->setData(0, Qt::BackgroundRole, ColorUtils::expert_color_error); break; default: g_assert_not_reached(); } item->setData(0, Qt::ForegroundRole, ColorUtils::expert_color_foreground); } item->setText(0, label); item->setData(0, Qt::UserRole, VariantPointer::asQVariant(fi)); if (is_branch) { if (tree_expanded(fi->tree_type)) { item->setExpanded(true); } else { item->setExpanded(false); } proto_tree_children_foreach(node, proto_tree_draw_node, item); } } ProtoTree::ProtoTree(QWidget *parent) : QTreeWidget(parent), decode_as_(NULL), column_resize_timer_(0), cap_file_(NULL) { 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 (window()->findChild("actionViewExpandSubtrees")) { // Assume we're a child of the main window. // XXX We might want to reimplement setParent() and fill in the context // menu there. QMenu *main_menu_item, *submenu; QAction *action; ctx_menu_.addAction(window()->findChild("actionViewExpandSubtrees")); ctx_menu_.addAction(window()->findChild("actionViewExpandAll")); ctx_menu_.addAction(window()->findChild("actionViewCollapseAll")); ctx_menu_.addSeparator(); action = window()->findChild("actionAnalyzeCreateAColumn"); ctx_menu_.addAction(action); ctx_menu_.addSeparator(); main_menu_item = window()->findChild("menuApplyAsFilter"); submenu = new QMenu(main_menu_item->title(), &ctx_menu_); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzeAAFSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFNotSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFAndSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFOrSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFAndNotSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFOrNotSelected")); main_menu_item = window()->findChild("menuPrepareAFilter"); submenu = new QMenu(main_menu_item->title(), &ctx_menu_); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzePAFSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFNotSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFAndSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFOrSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFAndNotSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFOrNotSelected")); QMenu *main_conv_menu = window()->findChild("menuConversationFilter"); conv_menu_.setTitle(main_conv_menu->title()); ctx_menu_.addMenu(&conv_menu_); colorize_menu_.setTitle(tr("Colorize with Filter")); ctx_menu_.addMenu(&colorize_menu_); main_menu_item = window()->findChild("menuFollow"); submenu = new QMenu(main_menu_item->title(), &ctx_menu_); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzeFollowTCPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowUDPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowSSLStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowHTTPStream")); ctx_menu_.addSeparator(); main_menu_item = window()->findChild("menuEditCopy"); submenu = new QMenu(main_menu_item->title(), &ctx_menu_); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionCopyAllVisibleItems")); submenu->addAction(window()->findChild("actionCopyAllVisibleSelectedTreeItems")); submenu->addAction(window()->findChild("actionEditCopyDescription")); submenu->addAction(window()->findChild("actionEditCopyFieldName")); submenu->addAction(window()->findChild("actionEditCopyValue")); submenu->addSeparator(); submenu->addAction(window()->findChild("actionEditCopyAsFilter")); submenu->addSeparator(); action = window()->findChild("actionContextCopyBytesHexTextDump"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextCopyBytesHexDump"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextCopyBytesPrintableText"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextCopyBytesHexStream"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextCopyBytesBinary"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextCopyBytesEscapedString"); submenu->addAction(action); copy_actions_ << action; action = window()->findChild("actionContextShowPacketBytes"); ctx_menu_.addAction(action); action = window()->findChild("actionFileExportPacketBytes"); ctx_menu_.addAction(action); ctx_menu_.addSeparator(); action = window()->findChild("actionContextWikiProtocolPage"); ctx_menu_.addAction(action); action = window()->findChild("actionContextFilterFieldReference"); ctx_menu_.addAction(action); // " \n" ctx_menu_.addMenu(&proto_prefs_menu_); ctx_menu_.addSeparator(); decode_as_ = window()->findChild("actionAnalyzeDecodeAs"); ctx_menu_.addAction(decode_as_); // " \n" ctx_menu_.addAction(window()->findChild("actionGoGoToLinkedPacket")); ctx_menu_.addAction(window()->findChild("actionContextShowLinkedPacketInNewWindow")); } else { ctx_menu_.clear(); } connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(updateSelectionStatus(QTreeWidgetItem*))); connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(expand(QModelIndex))); connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(collapse(QModelIndex))); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(itemDoubleClick(QTreeWidgetItem*, int))); 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())); viewport()->installEventFilter(this); } void ProtoTree::closeContextMenu() { ctx_menu_.close(); } void ProtoTree::clear() { updateSelectionStatus(NULL); QTreeWidget::clear(); updateContentWidth(); } void ProtoTree::contextMenuEvent(QContextMenuEvent *event) { if (ctx_menu_.isEmpty()) return; // We're in a PacketDialog QMenu *main_conv_menu = window()->findChild("menuConversationFilter"); conv_menu_.clear(); foreach (QAction *action, main_conv_menu->actions()) { conv_menu_.addAction(action); } FieldInformation * finfo = 0; field_info *fi = NULL; const char *module_name = NULL; if (selectedItems().count() > 0) { fi = VariantPointer::asPtr(selectedItems()[0]->data(0, Qt::UserRole)); if (fi && fi->hfinfo) { if (fi->hfinfo->parent == -1) { module_name = fi->hfinfo->abbrev; } else { module_name = proto_registrar_get_abbrev(fi->hfinfo->parent); } } finfo = new FieldInformation(fi, this); } proto_prefs_menu_.setModule(module_name); foreach (QAction *action, copy_actions_) { action->setProperty("idataprintable_", VariantPointer::asQVariant((IDataPrintable *)finfo)); } decode_as_->setData(qVariantFromValue(true)); // Set menu sensitivity and action data. emit fieldSelected(finfo); ctx_menu_.exec(event->globalPos()); decode_as_->setData(QVariant()); } void ProtoTree::timerEvent(QTimerEvent *event) { if (event->timerId() == column_resize_timer_) { killTimer(column_resize_timer_); column_resize_timer_ = 0; resizeColumnToContents(0); } else { QTreeWidget::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) { mono_font_ = mono_font; setFont(mono_font_); update(); } void ProtoTree::fillProtocolTree(proto_tree *protocol_tree) { clear(); setFont(mono_font_); proto_tree_children_foreach(protocol_tree, proto_tree_draw_node, invisibleRootItem()); updateContentWidth(); } void ProtoTree::emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type) { emit relatedFrame(related_frame, framenum_type); } // XXX We select the first match, which might not be the desired item. void ProtoTree::goToField(int hf_id) { if (hf_id < 0) return; QTreeWidgetItemIterator iter(this); while (*iter) { field_info *fi = VariantPointer::asPtr((*iter)->data(0, Qt::UserRole)); if (fi && fi->hfinfo) { if (fi->hfinfo->id == hf_id) { setCurrentItem(*iter); break; } } ++iter; } } void ProtoTree::updateSelectionStatus(QTreeWidgetItem* item) { if (item) { field_info *fi; QString item_info; fi = VariantPointer::asPtr(item->data(0, Qt::UserRole)); if (!fi || !fi->hfinfo) return; FieldInformation * finfo = new FieldInformation(fi, this); // Find and highlight the protocol bytes QTreeWidgetItem * parent = item->parent(); while (parent && parent->parent()) { parent = parent->parent(); } if (parent) { finfo->setParentField(VariantPointer::asPtr(parent->data(0, Qt::UserRole))); } if ( finfo->isValid() ) { saveSelectedField(item); emit fieldSelected(finfo); } // else the GTK+ version pushes an empty string as described below. /* * Don't show anything if the field name is zero-length; * the pseudo-field for text-only items is such * a field, and we don't want "Text (text)" showing up * on the status line if you've selected such a field. * * XXX - there are zero-length fields for which we *do* * want to show the field name. * * XXX - perhaps the name and abbrev field should be null * pointers rather than null strings for that pseudo-field, * but we'd have to add checks for null pointers in some * places if we did that. * * Or perhaps text-only items should have -1 as the field * index, with no pseudo-field being used, but that might * also require special checks for -1 to be added. */ } } void ProtoTree::expand(const QModelIndex & index) { field_info *fi; fi = VariantPointer::asPtr(index.data(Qt::UserRole)); if (!fi) return; if(prefs.gui_auto_scroll_on_expand) { ScrollHint scroll_hint = PositionAtTop; if (prefs.gui_auto_scroll_percentage > 66) { scroll_hint = PositionAtBottom; } else if (prefs.gui_auto_scroll_percentage >= 33) { scroll_hint = PositionAtCenter; } scrollTo(index, scroll_hint); } /* * Nodes with "finfo->tree_type" of -1 have no ett_ value, and * are thus presumably leaf nodes and cannot be expanded. */ if (fi->tree_type != -1) { g_assert(fi->tree_type >= 0 && fi->tree_type < num_tree_types); tree_expanded_set(fi->tree_type, TRUE); } updateContentWidth(); } void ProtoTree::collapse(const QModelIndex & index) { field_info *fi; fi = VariantPointer::asPtr(index.data(Qt::UserRole)); if (!fi) return; /* * Nodes with "finfo->tree_type" of -1 have no ett_ value, and * are thus presumably leaf nodes and cannot be collapsed. */ if (fi->tree_type != -1) { g_assert(fi->tree_type >= 0 && fi->tree_type < num_tree_types); tree_expanded_set(fi->tree_type, FALSE); } updateContentWidth(); } void ProtoTree::expandSubtrees() { QTreeWidgetItem *top_sel; if (selectedItems().length() < 1) { return; } top_sel = selectedItems()[0]; if (!top_sel) { return; } while (top_sel->parent()) { top_sel = top_sel->parent(); } QTreeWidgetItemIterator iter(top_sel); while (*iter) { if ((*iter) != top_sel && (*iter)->parent() == NULL) { // We found the next top-level item break; } (*iter)->setExpanded(true); ++iter; } updateContentWidth(); } void ProtoTree::expandAll() { int i; for(i=0; i < num_tree_types; i++) { tree_expanded_set(i, TRUE); } QTreeWidget::expandAll(); updateContentWidth(); } void ProtoTree::collapseAll() { int i; for(i=0; i < num_tree_types; i++) { tree_expanded_set(i, FALSE); } QTreeWidget::collapseAll(); updateContentWidth(); } void ProtoTree::itemDoubleClick(QTreeWidgetItem *item, int) { field_info *fi; fi = VariantPointer::asPtr(item->data(0, Qt::UserRole)); if (!fi || !fi->hfinfo) return; if (fi->hfinfo->type == FT_FRAMENUM) { if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) { emit openPacketInNewWindow(true); } else { emit goToPacket(fi->value.value.uinteger); } } else if (FI_GET_FLAG(fi, FI_URL) && IS_FT_STRING(fi->hfinfo->type)) { gchar *url; url = fvalue_to_string_repr(NULL, &fi->value, FTREPR_DISPLAY, fi->hfinfo->display); if(url){ // browser_open_url(url); QDesktopServices::openUrl(QUrl(url)); wmem_free(NULL, url); } } } void ProtoTree::selectedFieldChanged(FieldInformation * finfo) { if ( finfo ) { field_info * fi = finfo->fieldInfo(); QTreeWidgetItemIterator iter(this); while (*iter) { if (fi == VariantPointer::asPtr((*iter)->data(0, Qt::UserRole))) { setCurrentItem(*iter); scrollToItem(*iter); break; } ++iter; } } } // Finds position item at a level, counting only similar fields. static unsigned indexOfField(QTreeWidgetItem *item, header_field_info *hfi) { QTreeWidgetItem *parent = item->parent(); unsigned pos = 0; if (!parent) { // In case multiple top-level layers are present for the same protocol, // try to find its position (this will likely be the first match, zero). QTreeWidget *tree = item->treeWidget(); for (int i = 0; i < tree->topLevelItemCount(); i++) { QTreeWidgetItem *current = tree->topLevelItem(i); if (current == item) { return pos; } if (hfi == VariantPointer::asPtr(current->data(0, Qt::UserRole))->hfinfo) { pos++; } } } else { QTreeWidgetItemIterator iter(parent); while (*iter) { QTreeWidgetItem *current = *iter; if (current == item) { return pos; } if (hfi == VariantPointer::asPtr(current->data(0, Qt::UserRole))->hfinfo) { pos++; } ++iter; } } // should not happen (child is not found at parent?!) return 0; } // Assume about 2^8 items in tree and 2^24 different registered fields. // If there are more of each, then a collision may occur, but since the full // path is matched this is unlikely to be a problem. #define POS_SHIFT 24 #define POS_MASK (((unsigned)-1) << POS_SHIFT) // Remember the currently focussed field based on: // - current hf_id (obviously) // - parent items (to avoid selecting a text item in a different tree) // - position within a tree if there are multiple items static QList serializeAsPath(QTreeWidgetItem *item) { QList path; do { field_info *fi = VariantPointer::asPtr(item->data(0, Qt::UserRole)); unsigned pos = indexOfField(item, fi->hfinfo); path.prepend((pos << POS_SHIFT) | (fi->hfinfo->id & ~POS_MASK)); } while ((item = item->parent())); return path; } void ProtoTree::saveSelectedField(QTreeWidgetItem *item) { selected_field_path_ = serializeAsPath(item); } // Try to focus a tree item which was previously also visible void ProtoTree::restoreSelectedField() { if (selected_field_path_.isEmpty()) { return; } int last_hf_id = selected_field_path_.last() & ~POS_MASK; QTreeWidgetItemIterator iter(this); while (*iter) { field_info *fi = VariantPointer::asPtr((*iter)->data(0, Qt::UserRole)); if (last_hf_id == fi->hfinfo->id && serializeAsPath(*iter) == selected_field_path_) { // focus the first item, but do not expand collapsed trees. QTreeWidgetItem *item = *iter, *target = item; do { if (!item->isExpanded()) { target = item; } } while ((item = item->parent())); setCurrentItem(target); scrollToItem(target); break; } ++iter; } } void ProtoTree::setCaptureFile(capture_file *cf) { cap_file_ = cf; } bool ProtoTree::eventFilter(QObject * obj, QEvent * event) { if ( cap_file_ && event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseMove ) return QTreeWidget::eventFilter(obj, event); /* Mouse was over scrollbar, ignoring */ if ( qobject_cast(obj) ) return QTreeWidget::eventFilter(obj, event); if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent * ev = (QMouseEvent *)event; if ( ev->buttons() & Qt::LeftButton ) dragStartPosition = ev->pos(); } else if ( event->type() == QEvent::MouseMove ) { QMouseEvent * ev = (QMouseEvent *)event; if ( ( ev->buttons() & Qt::LeftButton ) && (ev->pos() - dragStartPosition).manhattanLength() > QApplication::startDragDistance()) { QTreeWidgetItem * item = itemAt(dragStartPosition); if ( item ) { field_info * fi = VariantPointer::asPtr(item->data(0, Qt::UserRole)); if ( fi ) { /* 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) */ QModelIndex idx = indexFromItem(item, 0); emit fieldSelected(new FieldInformation(fi, this)); selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); QString filter = QString(proto_construct_match_selected_string(fi, cap_file_->edt)); if ( filter.length() > 0 ) { DisplayFilterMimeData * dfmd = new DisplayFilterMimeData(QString(fi->hfinfo->name), QString(fi->hfinfo->abbrev), filter); QDrag * drag = new QDrag(this); drag->setMimeData(dfmd); DragLabel * content = new DragLabel(dfmd->labelText(), this); #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) qreal dpr = window()->windowHandle()->devicePixelRatio(); QPixmap pixmap(content->size() * dpr); pixmap.setDevicePixelRatio(dpr); #else QPixmap pixmap(content->size()); #endif content->render(&pixmap); drag->setPixmap(pixmap); drag->exec(Qt::CopyAction); return true; } } } } } return QTreeWidget::eventFilter(obj, event); } /* * Editor modelines * * Local Variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */