/* packet_list.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "config.h" #include #include "file.h" #include #include #include #include #include #include #include #include #include "ui/main_statusbar.h" #include "ui/packet_list_utils.h" #include "ui/preference_utils.h" #include "ui/recent.h" #include "ui/recent_utils.h" #include "ui/ws_ui_util.h" #include #include "ui/util.h" #include "wsutil/str_util.h" #include #include "frame_tvbuff.h" #include #include #include "proto_tree.h" #include #include "wireshark_application.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include "wsutil/file_util.h" #include #endif // To do: // - Fix "apply as filter" behavior. // - Add colorize conversation. // - Use a timer to trigger automatic scrolling. // If we ever add the ability to open multiple capture files we might be // able to use something like QMap to match // capture files against packet lists and models. static PacketList *gbl_cur_packet_list = NULL; const int max_comments_to_fetch_ = 20000000; // Arbitrary const int tail_update_interval_ = 100; // Milliseconds. const int overlay_update_interval_ = 100; // 250; // Milliseconds. guint packet_list_append(column_info *, frame_data *fdata) { if (!gbl_cur_packet_list) return 0; /* fdata should be filled with the stuff we need * strings are built at display time. */ guint visible_pos; visible_pos = gbl_cur_packet_list->packetListModel()->appendPacket(fdata); return visible_pos; } // Copied from ui/gtk/packet_list.c void packet_list_resize_column(gint col) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->resizeColumnToContents(col); } void packet_list_select_first_row(void) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->goFirstPacket(false); } /* * Given a frame_data structure, scroll to and select the row in the * packet list corresponding to that frame. If there is no such * row, return FALSE, otherwise return TRUE. */ gboolean packet_list_select_row_from_data(frame_data *fdata_needle) { gbl_cur_packet_list->packetListModel()->flushVisibleRows(); int row = gbl_cur_packet_list->packetListModel()->visibleIndexOf(fdata_needle); if (row >= 0) { gbl_cur_packet_list->setCurrentIndex(gbl_cur_packet_list->packetListModel()->index(row,0)); return TRUE; } return FALSE; } gboolean packet_list_check_end(void) { return FALSE; // GTK+ only. } void packet_list_clear(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->clear(); } } void packet_list_enable_color(gboolean) { if (gbl_cur_packet_list) { gbl_cur_packet_list->recolorPackets(); } } void packet_list_freeze(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->freeze(); } } void packet_list_thaw(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->thaw(); } packets_bar_update(); } void packet_list_recreate_visible_rows(void) { if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) { gbl_cur_packet_list->packetListModel()->recreateVisibleRows(); } } frame_data * packet_list_get_row_data(gint row) { if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) { return gbl_cur_packet_list->packetListModel()->getRowFdata(row); } return NULL; } // Called from cf_continue_tail and cf_finish_tail when auto_scroll_live // is enabled. void packet_list_moveto_end(void) { // gbl_cur_packet_list->scrollToBottom(); } /* Redraw the packet list *and* currently-selected detail */ void packet_list_queue_draw(void) { if (gbl_cur_packet_list) gbl_cur_packet_list->redrawVisiblePackets(); } void packet_list_recent_write_all(FILE *rf) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->writeRecent(rf); } #define MIN_COL_WIDTH_STR "MMMMMM" Q_DECLARE_METATYPE(PacketList::ColumnActions) enum copy_summary_type { copy_summary_text_, copy_summary_csv_, copy_summary_yaml_ }; PacketList::PacketList(QWidget *parent) : QTreeView(parent), proto_tree_(NULL), cap_file_(NULL), decode_as_(NULL), ctx_column_(-1), overlay_timer_id_(0), create_near_overlay_(true), create_far_overlay_(true), capture_in_progress_(false), tail_timer_id_(0), rows_inserted_(false), columns_changed_(false), set_column_visibility_(false), frozen_row_(-1), cur_history_(-1), in_history_(false) { setItemsExpandable(false); setRootIsDecorated(false); setSortingEnabled(true); setUniformRowHeights(true); setAccessibleName("Packet list"); overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this); setVerticalScrollBar(overlay_sb_); packet_list_model_ = new PacketListModel(this, cap_file_); setModel(packet_list_model_); sortByColumn(-1, Qt::AscendingOrder); initHeaderContextMenu(); g_assert(gbl_cur_packet_list == NULL); gbl_cur_packet_list = this; bool style_inactive_selected = true; #ifdef Q_OS_WIN // && Qt version >= 4.8.6 if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) { // See if we're running Vista or 7 and we have a theme applied. HMODULE uxtheme_lib = (HMODULE) ws_load_library("uxtheme.dll"); if (uxtheme_lib) { typedef BOOL (WINAPI *IsAppThemedHandler)(void); typedef BOOL (WINAPI *IsThemeActiveHandler)(void); IsAppThemedHandler PIsAppThemed = (IsAppThemedHandler) GetProcAddress(uxtheme_lib, "IsAppThemed"); IsThemeActiveHandler PIsThemeActive = (IsThemeActiveHandler) GetProcAddress(uxtheme_lib, "IsThemeActive"); if (PIsAppThemed && PIsAppThemed() && PIsThemeActive && PIsThemeActive()) { style_inactive_selected = false; } } } #endif if (style_inactive_selected) { // XXX Style the protocol tree as well? QPalette inactive_pal = palette(); inactive_pal.setCurrentColorGroup(QPalette::Inactive); QColor border = QColor::fromRgb(ColorUtils::alphaBlend( inactive_pal.highlightedText(), inactive_pal.highlight(), 0.25)); QColor shadow = QColor::fromRgb(ColorUtils::alphaBlend( inactive_pal.highlightedText(), inactive_pal.highlight(), 0.07)); setStyleSheet(QString( "QTreeView::item:selected:first:!active {" " border-left: 1px solid %1;" "}" "QTreeView::item:selected:last:!active {" " border-right: 1px solid %1;" "}" "QTreeView::item:selected:!active {" " border-top: 1px solid %1;" " border-bottom: 1px solid %1;" " color: %2;" // Try to approximate a subtle box shadow. " background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1" " stop: 0 %4, stop: 0.2 %3, stop: 0.8 %3, stop: 1 %4);" "}") .arg(border.name()) .arg(inactive_pal.highlightedText().color().name()) .arg(inactive_pal.highlight().color().name()) .arg(shadow.name()) ); } connect(packet_list_model_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int))); connect(packet_list_model_, SIGNAL(itemHeightChanged(const QModelIndex&)), this, SLOT(updateRowHeights(const QModelIndex&))); connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent())); header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint))); connect(header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(sectionResized(int,int,int))); connect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(sectionMoved(int,int,int))); connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(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*))); } void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QTreeView::drawRow(painter, option, index); if (prefs.gui_qt_packet_list_separator) { QRect rect = visualRect(index); painter->setPen(QColor(Qt::white)); painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1); } } void PacketList::setProtoTree (ProtoTree *proto_tree) { proto_tree_ = proto_tree; connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int))); connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)), &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t))); } PacketListModel *PacketList::packetListModel() const { return packet_list_model_; } void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected) { QTreeView::selectionChanged(selected, deselected); if (!cap_file_) return; int row = -1; if (selected.isEmpty()) { cf_unselect_packet(cap_file_); } else { row = selected.first().top(); cf_select_packet(cap_file_, row); } if (!in_history_ && cap_file_->current_frame) { cur_history_++; selection_history_.resize(cur_history_); selection_history_.append(cap_file_->current_frame->num); } in_history_ = false; related_packet_delegate_.clear(); if (proto_tree_) proto_tree_->clear(); emit frameSelected(row); if (!cap_file_->edt) { viewport()->update(); return; } if (proto_tree_ && cap_file_->edt->tree) { packet_info *pi = &cap_file_->edt->pi; related_packet_delegate_.setCurrentFrame(pi->num); proto_tree_->setRootNode(cap_file_->edt->tree); conversation_t *conv = find_conversation_pinfo(pi, 0); if (conv) { related_packet_delegate_.setConversation(conv); } viewport()->update(); } if (cap_file_->search_in_progress && (cap_file_->search_pos != 0 || (cap_file_->string && cap_file_->decode_data))) { match_data mdata; field_info *fi = NULL; if (cap_file_->string && cap_file_->decode_data) { // The tree where the target string matched one of the labels was discarded in // match_protocol_tree() so we have to search again in the latest tree. if (cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree, &mdata)) { fi = mdata.finfo; } } else { // Find the finfo that corresponds to our byte. fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos, cap_file_->edt->tvb); } if (fi) { emit fieldSelected(new FieldInformation(fi, this)); } } else if (!cap_file_->search_in_progress && proto_tree_) { proto_tree_->restoreSelectedField(); } } void PacketList::contextMenuEvent(QContextMenuEvent *event) { const char *module_name = NULL; if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) { GPtrArray *finfo_array = proto_all_finfos(cap_file_->edt->tree); for (guint i = finfo_array->len - 1; i > 0 ; i --) { field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i); header_field_info *hfinfo = fi->hfinfo; if (!g_str_has_prefix(hfinfo->abbrev, "text") && !g_str_has_prefix(hfinfo->abbrev, "_ws.expert") && !g_str_has_prefix(hfinfo->abbrev, "_ws.malformed")) { if (hfinfo->parent == -1) { module_name = hfinfo->abbrev; } else { module_name = proto_registrar_get_abbrev(hfinfo->parent); } break; } } } proto_prefs_menu_.setModule(module_name); QModelIndex ctxIndex = indexAt(event->pos()); FrameInformation * frameData = new FrameInformation(new CaptureFile(this, cap_file_), packet_list_model_->getRowFdata(ctxIndex.row())); ctx_menu_.clear(); // XXX We might want to reimplement setParent() and fill in the context // menu there. ctx_menu_.addAction(window()->findChild("actionEditMarkPacket")); ctx_menu_.addAction(window()->findChild("actionEditIgnorePacket")); ctx_menu_.addAction(window()->findChild("actionEditSetTimeReference")); ctx_menu_.addAction(window()->findChild("actionEditTimeShift")); ctx_menu_.addAction(window()->findChild("actionEditPacketComment")); ctx_menu_.addSeparator(); ctx_menu_.addAction(window()->findChild("actionViewEditResolvedName")); ctx_menu_.addSeparator(); QMenu *main_menu_item = window()->findChild("menuApplyAsFilter"); QMenu *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")); const char *conv_menu_name = "menuConversationFilter"; main_menu_item = window()->findChild(conv_menu_name); conv_menu_.setTitle(main_menu_item->title()); conv_menu_.setObjectName(conv_menu_name); ctx_menu_.addMenu(&conv_menu_); const char *colorize_menu_name = "menuColorizeConversation"; main_menu_item = window()->findChild(colorize_menu_name); colorize_menu_.setTitle(main_menu_item->title()); colorize_menu_.setObjectName(colorize_menu_name); ctx_menu_.addMenu(&colorize_menu_); main_menu_item = window()->findChild("menuSCTP"); submenu = new QMenu(main_menu_item->title(), &ctx_menu_); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionSCTPAnalyseThisAssociation")); submenu->addAction(window()->findChild("actionSCTPShowAllAssociations")); submenu->addAction(window()->findChild("actionSCTPFilterThisAssociation")); 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); QAction * action = submenu->addAction(tr("Summary as Text")); action->setData(copy_summary_text_); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as CSV")); action->setData(copy_summary_csv_); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as YAML")); action->setData(copy_summary_yaml_); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); submenu->addSeparator(); submenu->addAction(window()->findChild("actionEditCopyAsFilter")); submenu->addSeparator(); QActionGroup * copyEntries = DataPrinter::copyActions(this, frameData); submenu->addActions(copyEntries->actions()); ctx_menu_.addSeparator(); ctx_menu_.addMenu(&proto_prefs_menu_); decode_as_ = window()->findChild("actionAnalyzeDecodeAs"); ctx_menu_.addAction(decode_as_); // "Print" not ported intentionally action = window()->findChild("actionViewShowPacketInNewWindow"); ctx_menu_.addAction(action); decode_as_->setData(QVariant::fromValue(true)); ctx_column_ = columnAt(event->x()); // Set menu sensitivity for the current column and set action data. if ( frameData ) emit frameSelected(frameData->frameNum()); else emit frameSelected(-1); ctx_menu_.exec(event->globalPos()); ctx_column_ = -1; decode_as_->setData(QVariant()); } // Auto scroll if: // - We're not at the end // - We are capturing // - actionGoAutoScroll in the main UI is checked. // - It's been more than tail_update_interval_ ms since we last scrolled // - The last user-set vertical scrollbar position was at the end. // Using a timer assumes that we can save CPU overhead by updating // periodically. If that's not the case we can dispense with it and call // scrollToBottom() from rowsInserted(). void PacketList::timerEvent(QTimerEvent *event) { if (event->timerId() == tail_timer_id_) { if (rows_inserted_ && capture_in_progress_ && tail_at_end_) { scrollToBottom(); rows_inserted_ = false; } } else if (event->timerId() == overlay_timer_id_) { if (!capture_in_progress_) { if (create_near_overlay_) drawNearOverlay(); if (create_far_overlay_) drawFarOverlay(); } } else { QTreeView::timerEvent(event); } } void PacketList::paintEvent(QPaintEvent *event) { // XXX This is overkill, but there are quite a few events that // require a new overlay, e.g. page up/down, scrolling, column // resizing, etc. create_near_overlay_ = true; QTreeView::paintEvent(event); } void PacketList::mousePressEvent (QMouseEvent *event) { setAutoScroll(false); QTreeView::mousePressEvent(event); setAutoScroll(true); } void PacketList::resizeEvent(QResizeEvent *event) { create_near_overlay_ = true; create_far_overlay_ = true; QTreeView::resizeEvent(event); } void PacketList::setColumnVisibility() { set_column_visibility_ = true; for (int i = 0; i < prefs.num_cols; i++) { setColumnHidden(i, get_column_visible(i) ? false : true); } set_column_visibility_ = false; } int PacketList::sizeHintForColumn(int column) const { int size_hint = 0; // This is a bit hacky but Qt does a fine job of column sizing and // reimplementing QTreeView::sizeHintForColumn seems like a worse idea. if (itemDelegateForColumn(column)) { // In my (gcc) testing this results in correct behavior on Windows but adds extra space // on macOS and Linux. We might want to add Q_OS_... #ifdefs accordingly. size_hint = itemDelegateForColumn(column)->sizeHint(viewOptions(), QModelIndex()).width(); } size_hint += QTreeView::sizeHintForColumn(column); // Decoration padding return size_hint; } void PacketList::setRecentColumnWidth(int col) { int col_width = recent_get_column_width(col); if (col_width < 1) { int fmt = get_column_format(col); const char *long_str = get_column_width_string(fmt, col); QFontMetrics fm = QFontMetrics(wsApp->monospaceFont()); if (long_str) { col_width = fm.width(long_str); } else { col_width = fm.width(MIN_COL_WIDTH_STR); } // Custom delegate padding if (itemDelegateForColumn(col)) { col_width += itemDelegateForColumn(col)->sizeHint(viewOptions(), QModelIndex()).width(); } } setColumnWidth(col, col_width); } void PacketList::initHeaderContextMenu() { header_ctx_menu_.clear(); header_actions_.clear(); // Leave these out for now since Qt doesn't have a "no sort" option // and the user can sort by left-clicking on the header. // header_actions_[] = header_ctx_menu_.addAction(tr("Sort Ascending")); // header_actions_[] = header_ctx_menu_.addAction(tr("Sort Descending")); // header_actions_[] = header_ctx_menu_.addAction(tr("Do Not Sort")); // header_ctx_menu_.addSeparator(); header_actions_[caAlignLeft] = header_ctx_menu_.addAction(tr("Align Left")); header_actions_[caAlignCenter] = header_ctx_menu_.addAction(tr("Align Center")); header_actions_[caAlignRight] = header_ctx_menu_.addAction(tr("Align Right")); header_ctx_menu_.addSeparator(); header_actions_[caColumnPreferences] = header_ctx_menu_.addAction(tr("Column Preferences" UTF8_HORIZONTAL_ELLIPSIS)); header_actions_[caEditColumn] = header_ctx_menu_.addAction(tr("Edit Column")); // XXX Create frame instead of dialog header_actions_[caResizeToContents] = header_ctx_menu_.addAction(tr("Resize To Contents")); header_actions_[caResolveNames] = header_ctx_menu_.addAction(tr("Resolve Names")); header_ctx_menu_.addSeparator(); // header_actions_[caDisplayedColumns] = header_ctx_menu_.addAction(tr("Displayed Columns")); show_hide_separator_ = header_ctx_menu_.addSeparator(); // header_actions_[caHideColumn] = header_ctx_menu_.addAction(tr("Hide This Column")); header_actions_[caRemoveColumn] = header_ctx_menu_.addAction(tr("Remove This Column")); foreach (ColumnActions ca, header_actions_.keys()) { header_actions_[ca]->setData(QVariant::fromValue(ca)); connect(header_actions_[ca], SIGNAL(triggered()), this, SLOT(headerMenuTriggered())); } checkable_actions_ = QList() << caAlignLeft << caAlignCenter << caAlignRight << caResolveNames; foreach (ColumnActions ca, checkable_actions_) { header_actions_[ca]->setCheckable(true); } } void PacketList::drawCurrentPacket() { QModelIndex current_index = currentIndex(); setCurrentIndex(QModelIndex()); if (current_index.isValid()) { setCurrentIndex(current_index); } } // Redraw the packet list and detail. Re-selects the current packet (causes // the UI to scroll to that packet). // Called from many places. void PacketList::redrawVisiblePackets() { update(); header()->update(); drawCurrentPacket(); } // Redraw the packet list and detail. // Does not scroll back to the selected packet. void PacketList::redrawVisiblePacketsDontSelectCurrent() { update(); header()->update(); } void PacketList::resetColumns() { packet_list_model_->resetColumns(); } // Return true if we have a visible packet further along in the history. bool PacketList::haveNextHistory(bool update_cur) { if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) { return false; } for (int i = cur_history_ + 1; i < selection_history_.size(); i++) { if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) { if (update_cur) { cur_history_ = i; } return true; } } return false; } // Return true if we have a visible packet back in the history. bool PacketList::havePreviousHistory(bool update_cur) { if (selection_history_.size() < 1 || cur_history_ < 1) { return false; } for (int i = cur_history_ - 1; i >= 0; i--) { if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) { if (update_cur) { cur_history_ = i; } return true; } } return false; } // prefs.col_list has changed. void PacketList::columnsChanged() { columns_changed_ = true; if (!cap_file_) { // Keep columns_changed_ = true until we load a capture file. return; } prefs.num_cols = g_list_length(prefs.col_list); col_cleanup(&cap_file_->cinfo); build_column_format_array(&cap_file_->cinfo, prefs.num_cols, FALSE); create_far_overlay_ = true; resetColumns(); applyRecentColumnWidths(); setColumnVisibility(); columns_changed_ = false; } // Fields have changed, update custom columns void PacketList::fieldsChanged(capture_file *cf) { prefs.num_cols = g_list_length(prefs.col_list); col_cleanup(&cf->cinfo); build_column_format_array(&cf->cinfo, prefs.num_cols, FALSE); // call packet_list_model_->resetColumns() ? } // Column widths should // - Load from recent when we load a new profile (including at starting up). // - Reapply when changing columns. // - Persist across freezes and thaws. // - Persist across file closing and opening. // - Save to recent when we save our profile (including shutting down). // - Not be affected by the behavior of stretchLastSection. void PacketList::applyRecentColumnWidths() { // Either we've just started up or a profile has changed. Read // the recent settings, apply them, and save the header state. int column_width = 0; for (int col = 0; col < prefs.num_cols; col++) { setRecentColumnWidth(col); column_width += columnWidth(col); } if (column_width > width()) { resize(column_width, height()); } column_state_ = header()->saveState(); } void PacketList::preferencesChanged() { // Related packet delegate if (prefs.gui_packet_list_show_related) { setItemDelegateForColumn(0, &related_packet_delegate_); } else { setItemDelegateForColumn(0, 0); } // Intelligent scroll bar (minimap) if (prefs.gui_packet_list_show_minimap) { if (overlay_timer_id_ == 0) { overlay_timer_id_ = startTimer(overlay_update_interval_); } } else { if (overlay_timer_id_ != 0) { killTimer(overlay_timer_id_); overlay_timer_id_ = 0; } } // Elide mode. // This sets the mode for the entire view. If we want to make this setting // per-column we'll either have to generalize RelatedPacketDelegate so that // we can set it for entire rows or create another delegate. Qt::TextElideMode elide_mode = Qt::ElideRight; switch (prefs.gui_packet_list_elide_mode) { case ELIDE_LEFT: elide_mode = Qt::ElideLeft; break; case ELIDE_MIDDLE: elide_mode = Qt::ElideMiddle; break; case ELIDE_NONE: elide_mode = Qt::ElideNone; break; default: break; } setTextElideMode(elide_mode); } void PacketList::recolorPackets() { packet_list_model_->resetColorized(); redrawVisiblePackets(); } /* Enable autoscroll timer. Note: must be called after the capture is started, * otherwise the timer will not be executed. */ void PacketList::setVerticalAutoScroll(bool enabled) { tail_at_end_ = enabled; if (enabled && capture_in_progress_) { scrollToBottom(); if (tail_timer_id_ == 0) tail_timer_id_ = startTimer(tail_update_interval_); } else if (tail_timer_id_ != 0) { killTimer(tail_timer_id_); tail_timer_id_ = 0; } } // Called when we finish reading, reloading, rescanning, and retapping // packets. void PacketList::captureFileReadFinished() { packet_list_model_->flushVisibleRows(); packet_list_model_->dissectIdle(true); } void PacketList::freeze() { setUpdatesEnabled(false); column_state_ = header()->saveState(); if (currentIndex().isValid()) { frozen_row_ = currentIndex().row(); } else { frozen_row_ = -1; } selectionModel()->clear(); setModel(NULL); // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't // call selectionChanged. related_packet_delegate_.clear(); proto_tree_->clear(); } void PacketList::thaw(bool restore_selection) { setUpdatesEnabled(true); setModel(packet_list_model_); // Resetting the model resets our column widths so we restore them here. // We don't reapply the recent settings because the user could have // resized the columns manually since they were initially loaded. header()->restoreState(column_state_); if (restore_selection && frozen_row_ > -1) { // This updates our selection, which redissects the current packet, // which is needed when we're called from MainWindow::layoutPanes. setCurrentIndex(packet_list_model_->index(frozen_row_, 0)); } frozen_row_ = -1; } void PacketList::clear() { related_packet_delegate_.clear(); selectionModel()->clear(); packet_list_model_->clear(); proto_tree_->clear(); selection_history_.clear(); cur_history_ = -1; in_history_ = false; QImage overlay; overlay_sb_->setNearOverlayImage(overlay); overlay_sb_->setMarkedPacketImage(overlay); create_near_overlay_ = true; create_far_overlay_ = true; } void PacketList::writeRecent(FILE *rf) { gint col, width, col_fmt; gchar xalign; fprintf (rf, "%s:", RECENT_KEY_COL_WIDTH); for (col = 0; col < prefs.num_cols; col++) { if (col > 0) { fprintf (rf, ","); } col_fmt = get_column_format(col); if (col_fmt == COL_CUSTOM) { fprintf (rf, " \"%%Cus:%s\",", get_column_custom_fields(col)); } else { fprintf (rf, " %s,", col_format_to_string(col_fmt)); } width = recent_get_column_width (col); xalign = recent_get_column_xalign (col); fprintf (rf, " %d", width); if (xalign != COLUMN_XALIGN_DEFAULT) { fprintf (rf, ":%c", xalign); } } fprintf (rf, "\n"); } bool PacketList::contextMenuActive() { return ctx_column_ >= 0 ? true : false; } QString PacketList::getFilterFromRowAndColumn() { frame_data *fdata; QString filter; int row = currentIndex().row(); if (!cap_file_ || !packet_list_model_ || ctx_column_ < 0 || ctx_column_ >= cap_file_->cinfo.num_cols) return filter; fdata = packet_list_model_->getRowFdata(row); if (fdata != NULL) { epan_dissect_t edt; if (!cf_read_record(cap_file_, fdata)) return filter; /* error reading the record */ /* proto tree, visible. We need a proto tree if there's custom columns */ epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), FALSE); col_custom_prime_edt(&edt, &cap_file_->cinfo); epan_dissect_run(&edt, cap_file_->cd_t, &cap_file_->phdr, frame_tvbuff_new_buffer(&cap_file_->provider, fdata, &cap_file_->buf), fdata, &cap_file_->cinfo); epan_dissect_fill_in_columns(&edt, TRUE, TRUE); if ((cap_file_->cinfo.columns[ctx_column_].col_custom_occurrence) || (strchr (cap_file_->cinfo.col_expr.col_expr_val[ctx_column_], ',') == NULL)) { /* Only construct the filter when a single occurrence is displayed * otherwise we might end up with a filter like "ip.proto==1,6". * * Or do we want to be able to filter on multiple occurrences so that * the filter might be calculated as "ip.proto==1 && ip.proto==6" * instead? */ if (strlen(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) != 0 && strlen(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]) != 0) { if (cap_file_->cinfo.columns[ctx_column_].col_fmt == COL_CUSTOM) { header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.columns[ctx_column_].col_custom_fields); if (hfi && hfi->parent == -1) { /* Protocol only */ filter.append(cap_file_->cinfo.col_expr.col_expr[ctx_column_]); } else if (hfi && hfi->type == FT_STRING) { /* Custom string, add quotes */ filter.append(QString("%1 == \"%2\"") .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_])); } } if (filter.isEmpty()) { filter.append(QString("%1 == %2") .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_])); } } } epan_dissect_cleanup(&edt); } return filter; } void PacketList::resetColorized() { packet_list_model_->resetColorized(); update(); } QString PacketList::packetComment() { int row = currentIndex().row(); const frame_data *fdata; char *pkt_comment; if (!cap_file_ || !packet_list_model_) return NULL; fdata = packet_list_model_->getRowFdata(row); if (!fdata) return NULL; pkt_comment = cf_get_packet_comment(cap_file_, fdata); return QString(pkt_comment); /* XXX, g_free(pkt_comment) */ } void PacketList::setPacketComment(QString new_comment) { int row = currentIndex().row(); frame_data *fdata; gchar *new_packet_comment; if (!cap_file_ || !packet_list_model_) return; fdata = packet_list_model_->getRowFdata(row); if (!fdata) return; /* Check if we are clearing the comment */ if(new_comment.isEmpty()) { new_packet_comment = NULL; } else { new_packet_comment = qstring_strdup(new_comment); } cf_set_user_packet_comment(cap_file_, fdata, new_packet_comment); g_free(new_packet_comment); redrawVisiblePackets(); } QString PacketList::allPacketComments() { guint32 framenum; frame_data *fdata; QString buf_str; if (!cap_file_) return buf_str; for (framenum = 1; framenum <= cap_file_->count ; framenum++) { fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum); char *pkt_comment = cf_get_packet_comment(cap_file_, fdata); if (pkt_comment) { buf_str.append(QString(tr("Frame %1: %2\n\n")).arg(framenum).arg(pkt_comment)); g_free(pkt_comment); } if (buf_str.length() > max_comments_to_fetch_) { buf_str.append(QString(tr("[ Comment text exceeds %1. Stopping. ]")) .arg(format_size(max_comments_to_fetch_, format_size_unit_bytes|format_size_prefix_si))); return buf_str; } } return buf_str; } void PacketList::deleteAllPacketComments() { guint32 framenum; frame_data *fdata; QString buf_str; if (!cap_file_) return; for (framenum = 1; framenum <= cap_file_->count ; framenum++) { fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum); cf_set_user_packet_comment(cap_file_, fdata, NULL); } redrawVisiblePackets(); } // Slots void PacketList::setCaptureFile(capture_file *cf) { if (cf) { // We're opening. Restore our column widths. header()->restoreState(column_state_); } cap_file_ = cf; if (cap_file_ && columns_changed_) { columnsChanged(); } packet_list_model_->setCaptureFile(cf); create_near_overlay_ = true; sortByColumn(-1, Qt::AscendingOrder); } void PacketList::setMonospaceFont(const QFont &mono_font) { setFont(mono_font); header()->setFont(wsApp->font()); } void PacketList::goNextPacket(void) { if (QApplication::keyboardModifiers() & Qt::AltModifier) { // Alt+toolbar goNextHistoryPacket(); return; } if (selectionModel()->hasSelection()) { setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier)); } else { // First visible packet. setCurrentIndex(indexAt(viewport()->rect().topLeft())); } scrollViewChanged(false); } void PacketList::goPreviousPacket(void) { if (QApplication::keyboardModifiers() & Qt::AltModifier) { // Alt+toolbar goPreviousHistoryPacket(); return; } if (selectionModel()->hasSelection()) { setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier)); } else { // Last visible packet. QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft()); if (last_idx.isValid()) { setCurrentIndex(last_idx); } else { goLastPacket(); } } scrollViewChanged(false); } void PacketList::goFirstPacket(bool user_selected) { if (packet_list_model_->rowCount() < 1) return; setCurrentIndex(packet_list_model_->index(0, 0)); scrollTo(currentIndex()); if (user_selected) { scrollViewChanged(false); } } void PacketList::goLastPacket(void) { if (packet_list_model_->rowCount() < 1) return; setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0)); scrollTo(currentIndex()); scrollViewChanged(false); } // XXX We can jump to the wrong packet if a display filter is applied void PacketList::goToPacket(int packet) { if (!cf_goto_frame(cap_file_, packet)) return; int row = packet_list_model_->packetNumberToRow(packet); if (row >= 0) { setCurrentIndex(packet_list_model_->index(row, 0)); } scrollViewChanged(false); } void PacketList::goToPacket(int packet, int hf_id) { goToPacket(packet); proto_tree_->goToHfid(hf_id); } void PacketList::goNextHistoryPacket() { if (haveNextHistory(true)) { in_history_ = true; goToPacket(selection_history_.at(cur_history_)); in_history_ = false; } } void PacketList::goPreviousHistoryPacket() { if (havePreviousHistory(true)) { in_history_ = true; goToPacket(selection_history_.at(cur_history_)); in_history_ = false; } } void PacketList::markFrame() { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->toggleFrameMark(currentIndex()); create_far_overlay_ = true; packets_bar_update(); } void PacketList::markAllDisplayedFrames(bool set) { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->setDisplayedFrameMark(set); create_far_overlay_ = true; packets_bar_update(); } void PacketList::ignoreFrame() { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->toggleFrameIgnore(currentIndex()); create_far_overlay_ = true; int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position? setUpdatesEnabled(false); emit packetDissectionChanged(); setUpdatesEnabled(true); verticalScrollBar()->setValue(sb_val); } void PacketList::ignoreAllDisplayedFrames(bool set) { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->setDisplayedFrameIgnore(set); create_far_overlay_ = true; emit packetDissectionChanged(); } void PacketList::setTimeReference() { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->toggleFrameRefTime(currentIndex()); create_far_overlay_ = true; } void PacketList::unsetAllTimeReferences() { if (!cap_file_ || !packet_list_model_) return; packet_list_model_->unsetAllFrameRefTime(); create_far_overlay_ = true; } void PacketList::applyTimeShift() { packet_list_model_->applyTimeShift(); redrawVisiblePackets(); // XXX emit packetDissectionChanged(); ? } void PacketList::showHeaderMenu(QPoint pos) { header_ctx_column_ = header()->logicalIndexAt(pos); foreach (ColumnActions ca, checkable_actions_) { header_actions_[ca]->setChecked(false); } switch (recent_get_column_xalign(header_ctx_column_)) { case COLUMN_XALIGN_LEFT: header_actions_[caAlignLeft]->setChecked(true); break; case COLUMN_XALIGN_CENTER: header_actions_[caAlignCenter]->setChecked(true); break; case COLUMN_XALIGN_RIGHT: header_actions_[caAlignRight]->setChecked(true); break; default: break; } bool can_resolve = resolve_column(header_ctx_column_, cap_file_); header_actions_[caResolveNames]->setChecked(can_resolve && get_column_resolved(header_ctx_column_)); header_actions_[caResolveNames]->setEnabled(can_resolve); header_actions_[caRemoveColumn]->setEnabled(header_ctx_column_ >= 0 && header()->count() > 2); foreach (QAction *action, show_hide_actions_) { header_ctx_menu_.removeAction(action); delete action; } show_hide_actions_.clear(); for (int i = 0; i < prefs.num_cols; i++) { QAction *action = new QAction(get_column_title(i), &header_ctx_menu_); action->setCheckable(true); action->setChecked(get_column_visible(i)); action->setData(QVariant::fromValue(i)); connect(action, SIGNAL(triggered()), this, SLOT(columnVisibilityTriggered())); header_ctx_menu_.insertAction(show_hide_separator_, action); show_hide_actions_ << action; } header_ctx_menu_.popup(header()->viewport()->mapToGlobal(pos)); } void PacketList::headerMenuTriggered() { QAction *ha = qobject_cast(sender()); if (!ha) return; bool checked = ha->isChecked(); bool redraw = false; switch(ha->data().value()) { case caAlignLeft: recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_LEFT : COLUMN_XALIGN_DEFAULT); break; case caAlignCenter: recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_CENTER : COLUMN_XALIGN_DEFAULT); break; case caAlignRight: recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_RIGHT : COLUMN_XALIGN_DEFAULT); break; case caColumnPreferences: emit showColumnPreferences(PrefsModel::COLUMNS_PREFERENCE_TREE_NAME); break; case caEditColumn: emit editColumn(header_ctx_column_); break; case caResolveNames: set_column_resolved(header_ctx_column_, checked); packet_list_model_->resetColumns(); if (!prefs.gui_use_pref_save) { prefs_main_write(); } redraw = true; break; case caResizeToContents: resizeColumnToContents(header_ctx_column_); break; case caDisplayedColumns: // No-op break; case caHideColumn: set_column_visible(header_ctx_column_, FALSE); hideColumn(header_ctx_column_); if (!prefs.gui_use_pref_save) { prefs_main_write(); } break; case caRemoveColumn: { if (header()->count() > 2) { column_prefs_remove_nth(header_ctx_column_); columnsChanged(); if (!prefs.gui_use_pref_save) { prefs_main_write(); } } break; } default: break; } if (redraw) { redrawVisiblePackets(); } else { update(); } } void PacketList::columnVisibilityTriggered() { QAction *ha = qobject_cast(sender()); if (!ha) return; int col = ha->data().toInt(); set_column_visible(col, ha->isChecked()); setColumnVisibility(); if (ha->isChecked()) { setRecentColumnWidth(col); } if (!prefs.gui_use_pref_save) { prefs_main_write(); } } void PacketList::sectionResized(int col, int, int new_width) { if (isVisible() && !columns_changed_ && !set_column_visibility_ && new_width > 0) { // Column 1 gets an invalid value (32 on macOS) when we're not yet // visible. // // Don't set column width when columns changed or setting column // visibility because we may get a sectionReized() from QTreeView // with values from a old columns layout. // // Don't set column width when hiding a column. recent_set_column_width(col, new_width); } } // The user moved a column. Make sure prefs.col_list, the column format // array, and the header's visual and logical indices all agree. // gtk/packet_list.c:column_dnd_changed_cb void PacketList::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) { GList *new_col_list = NULL; QList saved_sizes; int sort_idx; // Since we undo the move below, these should always stay in sync. // Otherwise the order of columns can be unexpected after drag and drop. if (logicalIndex != oldVisualIndex) { g_warning("Column moved from an unexpected state (%d, %d, %d)", logicalIndex, oldVisualIndex, newVisualIndex); } // Remember which column should be sorted. Use the visual index since this // points to the current GUI state rather than the outdated column order // (indicated by the logical index). sort_idx = header()->sortIndicatorSection(); if (sort_idx != -1) { sort_idx = header()->visualIndex(sort_idx); } // Build a new column list based on the header's logical order. for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) { int log_idx = header()->logicalIndex(vis_idx); saved_sizes << header()->sectionSize(log_idx); void *pref_data = g_list_nth_data(prefs.col_list, log_idx); if (!pref_data) continue; new_col_list = g_list_append(new_col_list, pref_data); } // Undo move to ensure that the logical indices map to the visual indices, // otherwise the column order is changed twice (once via the modified // col_list, once because of the visual/logical index mismatch). disconnect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(sectionMoved(int,int,int))); header()->moveSection(newVisualIndex, oldVisualIndex); connect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(sectionMoved(int,int,int))); // Clear and rebuild our (and the header's) model. There doesn't appear // to be another way to reset the logical index. freeze(); g_list_free(prefs.col_list); prefs.col_list = new_col_list; thaw(true); for (int i = 0; i < saved_sizes.length(); i++) { if (saved_sizes[i] < 1) continue; header()->resizeSection(i, saved_sizes[i]); } if (!prefs.gui_use_pref_save) { prefs_main_write(); } wsApp->emitAppSignal(WiresharkApplication::ColumnsChanged); // If the column with the sort indicator got shifted, mark the new column // after updating the columns contents (via ColumnsChanged) to ensure that // the columns are sorted using the intended column contents. int left_col = MIN(oldVisualIndex, newVisualIndex); int right_col = MAX(oldVisualIndex, newVisualIndex); if (left_col <= sort_idx && sort_idx <= right_col) { header()->setSortIndicator(sort_idx, header()->sortIndicatorOrder()); } } void PacketList::updateRowHeights(const QModelIndex &ih_index) { QStyleOptionViewItem option = viewOptions(); int max_height = 0; // One of our columns increased the maximum row height. Find out which one. for (int col = 0; col < packet_list_model_->columnCount(); col++) { QSize size_hint = itemDelegate()->sizeHint(option, packet_list_model_->index(ih_index.row(), col)); max_height = qMax(max_height, size_hint.height()); } if (max_height > 0) { packet_list_model_->setMaximiumRowHeight(max_height); } } void PacketList::copySummary() { if (!currentIndex().isValid()) return; QAction *ca = qobject_cast(sender()); if (!ca) return; bool ok = false; int copy_type = ca->data().toInt(&ok); if (!ok) return; QStringList col_parts; int row = currentIndex().row(); for (int col = 0; col < packet_list_model_->columnCount(); col++) { if (get_column_visible(col)) { col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString(); } } QString copy_text; switch (copy_type) { case copy_summary_csv_: copy_text = "\""; copy_text += col_parts.join("\",\""); copy_text += "\""; break; case copy_summary_yaml_: copy_text = "----\n"; copy_text += QString("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename); copy_text += "- "; copy_text += col_parts.join("\n- "); copy_text += "\n"; break; case copy_summary_text_: default: copy_text = col_parts.join("\t"); } wsApp->clipboard()->setText(copy_text); } // We need to tell when the user has scrolled the packet list, either to // the end or anywhere other than the end. void PacketList::vScrollBarActionTriggered(int) { // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up // past the end. tail_at_end_ = (verticalScrollBar()->sliderPosition() >= verticalScrollBar()->maximum()); scrollViewChanged(tail_at_end_); } void PacketList::scrollViewChanged(bool at_end) { if (capture_in_progress_ && prefs.capture_auto_scroll) { emit packetListScrolled(at_end); } } // Goal: Overlay the packet list scroll bar with the colors of all of the // packets. // Try 1: Average packet colors in each scroll bar raster line. This has // two problems: It's easy to wash out colors and we dissect every packet. // Try 2: Color across a 5000 or 10000 packet window. We still end up washing // out colors. // Try 3: One packet per vertical scroll bar pixel. This seems to work best // but has the smallest window. // Try 4: Use a multiple of the scroll bar heigh and scale the image down // using Qt::SmoothTransformation. This gives us more packets per raster // line. // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier // of 9 washed out colors a little too much. //const int height_multiplier_ = 7; void PacketList::drawNearOverlay() { if (create_near_overlay_) { create_near_overlay_ = false; } if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return; if (!prefs.gui_packet_list_show_minimap) return; qreal dp_ratio = 1.0; #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) dp_ratio = overlay_sb_->devicePixelRatio(); #endif int o_height = overlay_sb_->height() * dp_ratio; int o_rows = qMin(packet_list_model_->rowCount(), o_height); int o_width = (wsApp->fontMetrics().height() * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side. int selected_pos = -1; if (recent.packet_list_colorize && o_rows > 0) { QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied); QPainter painter(&overlay); overlay.fill(Qt::transparent); int cur_line = 0; int start = 0; if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) { start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows); } int end = start + o_rows; for (int row = start; row < end; row++) { packet_list_model_->ensureRowColorized(row); frame_data *fdata = packet_list_model_->getRowFdata(row); const color_t *bgcolor = NULL; if (fdata->color_filter) { const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter; bgcolor = &color_filter->bg_color; } int next_line = (row - start) * o_height / o_rows; if (bgcolor) { QColor color(ColorUtils::fromColorT(bgcolor)); painter.fillRect(0, cur_line, o_width, next_line - cur_line, color); } cur_line = next_line; } // If the selected packet is in the overlay set selected_pos // accordingly. Otherwise, pin it to either the top or bottom. if (selectionModel()->hasSelection()) { int sel_row = selectionModel()->currentIndex().row(); if (sel_row < start) { selected_pos = 0; } else if (sel_row >= end) { selected_pos = overlay.height() - 1; } else { selected_pos = (sel_row - start) * o_height / o_rows; } } overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, selected_pos); } else { QImage overlay; overlay_sb_->setNearOverlayImage(overlay); } } void PacketList::drawFarOverlay() { if (create_far_overlay_) { create_far_overlay_ = false; } if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return; if (!prefs.gui_packet_list_show_minimap) return; QSize groove_size = overlay_sb_->grooveRect().size(); #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) qreal dp_ratio = 1.0; dp_ratio = overlay_sb_->devicePixelRatio(); groove_size *= dp_ratio; #endif int o_width = groove_size.width(); int o_height = groove_size.height(); int pl_rows = packet_list_model_->rowCount(); QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied); bool have_marked_image = false; // If only there were references from popular culture about getting into // some sort of groove. if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) { QPainter painter(&overlay); // Draw text-colored tick marks on a transparent background. // Hopefully no themes use the text color for the groove color. overlay.fill(Qt::transparent); QColor tick_color = palette().text().color(); tick_color.setAlphaF(0.3); painter.setPen(tick_color); for (int row = 0; row < pl_rows; row++) { frame_data *fdata = packet_list_model_->getRowFdata(row); if (fdata->flags.marked || fdata->flags.ref_time || fdata->flags.ignored) { int new_line = row * o_height / pl_rows; int tick_width = o_width / 3; // Marked or ignored: left side, time refs: right side. // XXX Draw ignored ticks in the middle? int x1 = fdata->flags.ref_time ? o_width - tick_width : 1; int x2 = fdata->flags.ref_time ? o_width - 1 : tick_width; painter.drawLine(x1, new_line, x2, new_line); have_marked_image = true; } } if (have_marked_image) { overlay_sb_->setMarkedPacketImage(overlay); return; } } if (!have_marked_image) { QImage null_overlay; overlay_sb_->setMarkedPacketImage(null_overlay); } } void PacketList::rowsInserted(const QModelIndex &parent, int start, int end) { QTreeView::rowsInserted(parent, start, end); rows_inserted_ = true; } /* * 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: */