3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include <ui/qt/packet_list.h>
16 #include <epan/epan.h>
17 #include <epan/epan_dissect.h>
19 #include <epan/column.h>
20 #include <epan/expert.h>
21 #include <epan/ipproto.h>
22 #include <epan/packet.h>
23 #include <epan/prefs.h>
24 #include <epan/proto.h>
26 #include "ui/main_statusbar.h"
27 #include "ui/packet_list_utils.h"
28 #include "ui/preference_utils.h"
29 #include "ui/recent.h"
30 #include "ui/recent_utils.h"
31 #include "ui/ws_ui_util.h"
32 #include "ui/simple_dialog.h"
33 #include <wsutil/utf8_entities.h>
36 #include "wiretap/wtap_opttypes.h"
37 #include "wsutil/filesystem.h"
38 #include "wsutil/str_util.h"
39 #include <wsutil/wslog.h>
41 #include <epan/color_filters.h>
42 #include "frame_tvbuff.h"
44 #include <ui/qt/utils/color_utils.h>
45 #include <ui/qt/widgets/overlay_scroll_bar.h>
46 #include "proto_tree.h"
47 #include <ui/qt/utils/qt_ui_utils.h>
48 #include "main_application.h"
49 #include <ui/qt/utils/data_printer.h>
50 #include <ui/qt/utils/frame_information.h>
51 #include <ui/qt/utils/profile_switcher.h>
52 #include <ui/qt/utils/variant_pointer.h>
53 #include <ui/qt/models/pref_models.h>
54 #include <ui/qt/widgets/packet_list_header.h>
55 #include <ui/qt/utils/wireshark_mime_data.h>
56 #include <ui/qt/widgets/drag_label.h>
57 #include <ui/qt/filter_action.h>
58 #include <ui/qt/follow_stream_action.h>
59 #include <ui/qt/decode_as_dialog.h>
60 #include <ui/qt/wireshark_main_window.h>
63 #include <QActionGroup>
65 #include <QContextMenuEvent>
66 #include <QtCore/qmath.h>
67 #include <QElapsedTimer>
68 #include <QFontMetrics>
69 #include <QHeaderView>
70 #include <QMessageBox>
76 #include <QTimerEvent>
77 #include <QTreeWidget>
79 #include <QJsonObject>
80 #include <QJsonDocument>
83 #include "wsutil/file_util.h"
89 // - Fix "apply as filter" behavior.
90 // - Add colorize conversation.
91 // - Use a timer to trigger automatic scrolling.
93 // If we ever add the ability to open multiple capture files we might be
94 // able to use something like QMap<capture_file *, PacketList *> to match
95 // capture files against packet lists and models.
96 static PacketList
*gbl_cur_packet_list
;
98 const int max_comments_to_fetch_
= 20000000; // Arbitrary
99 const int overlay_update_interval_
= 100; // 250; // Milliseconds.
103 * Given a frame_data structure, scroll to and select the row in the
104 * packet list corresponding to that frame. If there is no such
105 * row, return false, otherwise return true.
108 packet_list_select_row_from_data(frame_data
*fdata_needle
)
110 if (! gbl_cur_packet_list
|| ! gbl_cur_packet_list
->model())
113 PacketListModel
* model
= qobject_cast
<PacketListModel
*>(gbl_cur_packet_list
->model());
118 model
->flushVisibleRows();
123 row
= model
->visibleIndexOf(fdata_needle
);
126 /* Calling ClearAndSelect with setCurrentIndex clears the "current"
127 * item, but doesn't clear the "selected" item. We want to clear
128 * the "selected" item as well so that selectionChanged() will be
129 * emitted in order to force an update of the packet details and
130 * packet bytes after a search.
132 gbl_cur_packet_list
->selectionModel()->clearSelection();
133 gbl_cur_packet_list
->selectionModel()->setCurrentIndex(model
->index(row
, 0), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
134 gbl_cur_packet_list
->scrollTo(gbl_cur_packet_list
->currentIndex(), PacketList::PositionAtCenter
);
142 * Given a field_info, select the field (which will scroll to it in
143 * the main ProtoTree, etc.) This is kind of an odd place for it,
144 * but we call this when performing Find Packet in lieu of changing the
145 * selected frame (the function above), because we found a match in the
146 * same frame as the currently selected one.
149 packet_list_select_finfo(field_info
*fi
)
151 if (! gbl_cur_packet_list
|| ! gbl_cur_packet_list
->model())
155 FieldInformation
finfo(fi
, gbl_cur_packet_list
);
156 emit gbl_cur_packet_list
->fieldSelected(&finfo
);
158 emit gbl_cur_packet_list
->fieldSelected(0);
164 packet_list_clear(void)
166 if (gbl_cur_packet_list
) {
167 gbl_cur_packet_list
->clear();
172 packet_list_freeze(void)
174 if (gbl_cur_packet_list
) {
175 gbl_cur_packet_list
->freeze();
180 packet_list_thaw(void)
182 if (gbl_cur_packet_list
) {
183 gbl_cur_packet_list
->thaw();
186 packets_bar_update();
189 /* Redraw the packet list *and* currently-selected detail */
191 packet_list_queue_draw(void)
193 if (gbl_cur_packet_list
)
194 gbl_cur_packet_list
->redrawVisiblePackets();
198 packet_list_recent_write_all(FILE *rf
) {
199 if (!gbl_cur_packet_list
)
202 gbl_cur_packet_list
->writeRecent(rf
);
206 packet_list_multi_select_active(void)
208 if (gbl_cur_packet_list
) {
209 return gbl_cur_packet_list
->multiSelectActive();
214 #define MIN_COL_WIDTH_STR "MMMMMM"
216 PacketList::PacketList(QWidget
*parent
) :
218 proto_tree_(nullptr),
221 overlay_timer_id_(0),
222 create_near_overlay_(true),
223 create_far_overlay_(true),
224 mouse_pressed_at_(QModelIndex()),
225 capture_in_progress_(false),
227 columns_changed_(false),
228 set_column_visibility_(false),
229 set_style_sheet_(false),
230 frozen_current_row_(QModelIndex()),
231 frozen_selected_rows_(QModelIndexList()),
234 finfo_array(nullptr),
235 profile_switcher_(nullptr)
237 setItemsExpandable(false);
238 setRootIsDecorated(false);
239 setSortingEnabled(prefs
.gui_packet_list_sortable
);
240 setUniformRowHeights(true);
241 setAccessibleName("Packet list");
243 packet_list_header_
= new PacketListHeader(header()->orientation());
244 connect(packet_list_header_
, &PacketListHeader::resetColumnWidth
, this, &PacketList::setRecentColumnWidth
);
245 connect(packet_list_header_
, &PacketListHeader::updatePackets
, this, &PacketList::updatePackets
);
246 connect(packet_list_header_
, &PacketListHeader::showColumnPreferences
, this, &PacketList::showProtocolPreferences
);
247 connect(packet_list_header_
, &PacketListHeader::editColumn
, this, &PacketList::editColumn
);
248 connect(packet_list_header_
, &PacketListHeader::columnsChanged
, this, &PacketList::columnsChanged
);
249 setHeader(packet_list_header_
);
251 header()->setFirstSectionMovable(true);
253 setSelectionMode(QAbstractItemView::ExtendedSelection
);
255 // Shrink down to a small but nonzero size in the main splitter.
256 int one_em
= fontMetrics().height();
257 setMinimumSize(one_em
, one_em
);
259 overlay_sb_
= new OverlayScrollBar(Qt::Vertical
, this);
260 setVerticalScrollBar(overlay_sb_
);
262 header()->setSortIndicator(-1, Qt::AscendingOrder
);
264 packet_list_model_
= new PacketListModel(this, cap_file_
);
265 setModel(packet_list_model_
);
267 Q_ASSERT(gbl_cur_packet_list
== Q_NULLPTR
);
268 gbl_cur_packet_list
= this;
270 connect(packet_list_model_
, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
271 connect(packet_list_model_
, SIGNAL(itemHeightChanged(const QModelIndex
&)), this, SLOT(updateRowHeights(const QModelIndex
&)));
272 connect(mainApp
, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
273 connect(mainApp
, SIGNAL(columnDataChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
274 connect(mainApp
, &MainApplication::preferencesChanged
, this, [=]() {
275 /* The pref is a uint but QCache maxCost is a signed int (/
276 * qsizetype in Qt 6). Note that QAbstractItemModel row numbers
277 * are ints, not unsigned ints, so we're limited to INT_MAX
280 PacketListRecord::setMaxCache(prefs
.gui_packet_list_cached_rows_max
> INT_MAX
? INT_MAX
: prefs
.gui_packet_list_cached_rows_max
);
281 if ((bool) (prefs
.gui_packet_list_sortable
) != isSortingEnabled()) {
282 setSortingEnabled(prefs
.gui_packet_list_sortable
);
286 connect(header(), SIGNAL(sectionResized(int,int,int)),
287 this, SLOT(sectionResized(int,int,int)));
288 connect(header(), SIGNAL(sectionMoved(int,int,int)),
289 this, SLOT(sectionMoved(int,int,int)));
291 connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
294 PacketList::~PacketList()
298 g_ptr_array_free(finfo_array
, true);
302 void PacketList::scrollTo(const QModelIndex
&index
, QAbstractItemView::ScrollHint hint
)
304 /* QAbstractItemView doesn't have a way to indicate "auto scroll, but
305 * only vertically." So just restore the horizontal scroll value whenever
308 setUpdatesEnabled(false);
309 int horizVal
= horizontalScrollBar()->value();
310 QTreeView::scrollTo(index
, hint
);
311 horizontalScrollBar()->setValue(horizVal
);
312 setUpdatesEnabled(true);
315 void PacketList::colorsChanged()
317 const QString c_active
= "active";
318 const QString c_inactive
= "!active";
320 QString flat_style_format
=
321 "QTreeView::item:selected:%1 {"
323 " background-color: %3;"
326 QString gradient_style_format
=
327 "QTreeView::item:selected:%1 {"
329 " background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1 stop: 0 %4, stop: 0.5 %3, stop: 1 %4);"
333 #if !defined(Q_OS_WIN)
334 hover_style
= QString(
335 "QTreeView:item:hover {"
336 " background-color: %1;"
337 " color: palette(text);"
338 "}").arg(ColorUtils::hoverBackground().name(QColor::HexArgb
));
341 QString active_style
= QString();
342 QString inactive_style
= QString();
344 if (prefs
.gui_active_style
== COLOR_STYLE_DEFAULT
) {
346 } else if (prefs
.gui_active_style
== COLOR_STYLE_FLAT
) {
348 QColor foreground
= ColorUtils::fromColorT(prefs
.gui_active_fg
);
349 QColor background
= ColorUtils::fromColorT(prefs
.gui_active_bg
);
351 active_style
= flat_style_format
.arg(
355 } else if (prefs
.gui_active_style
== COLOR_STYLE_GRADIENT
) {
357 QColor foreground
= ColorUtils::fromColorT(prefs
.gui_active_fg
);
358 QColor background1
= ColorUtils::fromColorT(prefs
.gui_active_bg
);
359 QColor background2
= QColor::fromRgb(ColorUtils::alphaBlend(foreground
, background1
, COLOR_STYLE_ALPHA
));
361 active_style
= gradient_style_format
.arg(
368 // INACTIVE style sheet settings
369 if (prefs
.gui_inactive_style
== COLOR_STYLE_DEFAULT
) {
370 // INACTIVE = Default
371 } else if (prefs
.gui_inactive_style
== COLOR_STYLE_FLAT
) {
373 QColor foreground
= ColorUtils::fromColorT(prefs
.gui_inactive_fg
);
374 QColor background
= ColorUtils::fromColorT(prefs
.gui_inactive_bg
);
376 inactive_style
= flat_style_format
.arg(
380 } else if (prefs
.gui_inactive_style
== COLOR_STYLE_GRADIENT
) {
381 // INACTIVE = Gradient
382 QColor foreground
= ColorUtils::fromColorT(prefs
.gui_inactive_fg
);
383 QColor background1
= ColorUtils::fromColorT(prefs
.gui_inactive_bg
);
384 QColor background2
= QColor::fromRgb(ColorUtils::alphaBlend(foreground
, background1
, COLOR_STYLE_ALPHA
));
386 inactive_style
= gradient_style_format
.arg(
393 // Set the style sheet
394 set_style_sheet_
= true;
395 if(prefs
.gui_packet_list_hover_style
) {
396 setStyleSheet(active_style
+ inactive_style
+ hover_style
);
398 setStyleSheet(active_style
+ inactive_style
);
400 set_style_sheet_
= false;
403 (QT_VERSION >= QT_VERSION_CHECK(6, 5, 4) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) \
404 || (QT_VERSION >= QT_VERSION_CHECK(6, 6, 1)) \
406 // https://bugreports.qt.io/browse/QTBUG-122109
407 // Affects Qt 6.5.4 and later, 6.6.1 and later.
408 // When setting the style sheet, all visible sections are set
409 // to the new minimum DefaultSectionSize (even if it hasn't
410 // changed.) So make sure the new widths aren't saved to recent
411 // and then restore from recent.
412 applyRecentColumnWidths();
413 setColumnVisibility();
417 QString
PacketList::joinSummaryRow(QStringList col_parts
, int row
, SummaryCopyType type
)
423 copy_text
+= col_parts
.join("\",\"");
427 copy_text
= "----\n";
428 copy_text
+= QStringLiteral("# Packet %1 from %2\n").arg(row
).arg(cap_file_
->filename
);
430 copy_text
+= col_parts
.join("\n- ");
435 copy_text
= col_parts
.join("\t");
441 void PacketList::drawRow (QPainter
*painter
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
443 QTreeView::drawRow(painter
, option
, index
);
445 if (prefs
.gui_packet_list_separator
) {
446 QRect rect
= visualRect(index
);
448 painter
->setPen(QColor(Qt::white
));
449 painter
->drawLine(0, rect
.y() + rect
.height() - 1, width(), rect
.y() + rect
.height() - 1);
453 void PacketList::setProtoTree (ProtoTree
*proto_tree
) {
454 proto_tree_
= proto_tree
;
456 connect(proto_tree_
, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
457 connect(proto_tree_
, SIGNAL(relatedFrame(int,ft_framenum_type_t
)),
458 &related_packet_delegate_
, SLOT(addRelatedFrame(int,ft_framenum_type_t
)));
461 bool PacketList::uniqueSelectActive()
463 return selectionModel()->selectedRows(0).count() == 1 ? true : false;
466 bool PacketList::multiSelectActive()
468 return selectionModel()->selectedRows(0).count() > 1 ? true : false;
471 QList
<int> PacketList::selectedRows(bool useFrameNum
)
474 if (selectionModel() && selectionModel()->hasSelection())
476 foreach (QModelIndex idx
, selectionModel()->selectedRows(0))
482 else if (useFrameNum
)
484 frame_data
* frame
= getFDataForRow(idx
.row());
491 else if (currentIndex().isValid())
494 // XXX - will we ever have a current index but not a selection
498 rows
<< currentIndex().row();
501 frame_data
*frame
= getFDataForRow(currentIndex().row());
510 void PacketList::selectionChanged (const QItemSelection
& selected
, const QItemSelection
& deselected
)
512 QTreeView::selectionChanged(selected
, deselected
);
514 if (!cap_file_
) return;
517 static bool multiSelect
= false;
519 if (selectionModel())
521 QModelIndexList selRows
= selectionModel()->selectedRows(0);
522 if (selRows
.count() > 1)
525 foreach (QModelIndex idx
, selRows
)
531 emit
framesSelected(rows
);
532 emit
fieldSelected(0);
533 cf_unselect_packet(cap_file_
);
535 /* We have to repaint the content while changing state, as some delegates react to multi-select */
538 related_packet_delegate_
.clear();
539 viewport()->update();
546 else if (selRows
.count() > 0 && selRows
.at(0).isValid())
549 row
= selRows
.at(0).row();
552 /* Handling empty selection */
553 if (selRows
.count() <= 0)
555 /* Nothing selected, but multiSelect is still active */
559 if (currentIndex().isValid())
561 selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
565 /* Nothing selected, so in WS <= 3.0 nothing was indicated as well */
566 else if (currentIndex().isValid())
568 setCurrentIndex(QModelIndex());
573 if (row
< 0 || !packet_list_model_
)
574 cf_unselect_packet(cap_file_
);
576 frame_data
* fdata
= packet_list_model_
->getRowFdata(row
);
577 cf_select_packet(cap_file_
, fdata
);
580 if (!in_history_
&& cap_file_
->current_frame
) {
582 selection_history_
.resize(cur_history_
);
583 selection_history_
.append(cap_file_
->current_frame
->num
);
586 related_packet_delegate_
.clear();
588 // The previous dissection state has been invalidated by cf_select_packet
589 // above, receivers must clear the previous state and apply the updated one.
590 emit
framesSelected(QList
<int>() << row
);
592 if (!cap_file_
->edt
) {
593 viewport()->update();
594 emit
fieldSelected(0);
598 if (cap_file_
->edt
->tree
) {
599 packet_info
*pi
= &cap_file_
->edt
->pi
;
600 related_packet_delegate_
.setCurrentFrame(pi
->num
);
601 conversation_t
*conv
= find_conversation_pinfo_ro(pi
, 0);
603 related_packet_delegate_
.setConversation(conv
);
605 viewport()->update();
608 if (cap_file_
->search_in_progress
) {
609 field_info
*fi
= NULL
;
611 if (cap_file_
->string
&& cap_file_
->decode_data
) {
612 // The tree where the target string matched one of the labels was discarded in
613 // match_protocol_tree() so we have to search again in the latest tree.
614 fi
= cf_find_string_protocol_tree(cap_file_
, cap_file_
->edt
->tree
);
615 } else if (cap_file_
->search_len
!= 0) {
616 // Find the finfo that corresponds to our byte.
617 // The match can span multiple fields (and a single byte can
618 // match more than one field.) Our behavior is to find the last
619 // field in the tree (so hopefully spanning fewer bytes) that
620 // matches the last byte in the search match.
621 // (regex search can find a zero length match not at the
622 // start of the frame if lookbehind is used, but
623 // proto_find_field_from_offset doesn't match such a field
624 // and it's not clear which field we would want to match.)
625 fi
= proto_find_field_from_offset(cap_file_
->edt
->tree
, cap_file_
->search_pos
+ cap_file_
->search_len
- 1,
626 cap_file_
->edt
->tvb
);
630 FieldInformation
finfo(fi
, this);
631 emit
fieldSelected(&finfo
);
633 emit
fieldSelected(0);
635 } else if (proto_tree_
) {
636 proto_tree_
->restoreSelectedField();
640 void PacketList::contextMenuEvent(QContextMenuEvent
*event
)
642 const char *module_name
= NULL
;
646 g_ptr_array_free(finfo_array
, true);
650 QModelIndex ctxIndex
= indexAt(event
->pos());
652 if (selectionModel() && selectionModel()->selectedRows(0).count() > 1)
653 selectionModel()->select(ctxIndex
, QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
655 // frameData will be owned by one of the submenus, see below.
656 FrameInformation
* frameData
=
657 new FrameInformation(new CaptureFile(this, cap_file_
), packet_list_model_
->getRowFdata(ctxIndex
.row()));
659 QMenu
* ctx_menu
= new QMenu(this);
660 ctx_menu
->setAttribute(Qt::WA_DeleteOnClose
);
661 // XXX We might want to reimplement setParent() and fill in the context
663 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionEditMarkSelected"));
664 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionEditIgnoreSelected"));
665 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionEditSetTimeReference"));
666 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionEditTimeShift"));
667 ctx_menu
->addMenu(window()->findChild
<QMenu
*>("menuPacketComment"));
669 ctx_menu
->addSeparator();
671 // Code for custom context menus from Lua's register_packet_menu()
672 MainWindow
* mainWindow
= qobject_cast
<MainWindow
*>(mainApp
->mainWindow());
673 // N.B., will only call for a single frame selection,
674 if (cap_file_
&& cap_file_
->edt
&& cap_file_
->edt
->tree
) {
675 finfo_array
= proto_all_finfos(cap_file_
->edt
->tree
);
677 bool insertedPacketMenu
= mainWindow
->addPacketMenus(ctx_menu
, finfo_array
);
678 if (insertedPacketMenu
) {
679 ctx_menu
->addSeparator();
684 ctx_menu
->addAction(window()->findChild
<QAction
*>("actionViewEditResolvedName"));
685 ctx_menu
->addSeparator();
687 QString selectedfilter
= getFilterFromRowAndColumn(currentIndex());
689 if (! hasFocus() && cap_file_
&& cap_file_
->finfo_selected
) {
690 char *tmp_field
= proto_construct_match_selected_string(cap_file_
->finfo_selected
, cap_file_
->edt
);
691 selectedfilter
= QString(tmp_field
);
692 wmem_free(NULL
, tmp_field
);
695 bool have_filter_expr
= !selectedfilter
.isEmpty();
696 ctx_menu
->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply
, selectedfilter
, have_filter_expr
, ctx_menu
));
697 ctx_menu
->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare
, selectedfilter
, have_filter_expr
, ctx_menu
));
699 const char *conv_menu_name
= "menuConversationFilter";
700 QMenu
* main_menu_item
= window()->findChild
<QMenu
*>(conv_menu_name
);
701 conv_menu_
.setTitle(main_menu_item
->title());
702 conv_menu_
.setObjectName(conv_menu_name
);
703 ctx_menu
->addMenu(&conv_menu_
);
705 const char *colorize_menu_name
= "menuColorizeConversation";
706 main_menu_item
= window()->findChild
<QMenu
*>(colorize_menu_name
);
707 colorize_menu_
.setTitle(main_menu_item
->title());
708 colorize_menu_
.setObjectName(colorize_menu_name
);
709 ctx_menu
->addMenu(&colorize_menu_
);
712 main_menu_item
= window()->findChild
<QMenu
*>("menuSCTP");
713 if (main_menu_item
) {
714 submenu
= new QMenu(main_menu_item
->title(), ctx_menu
);
715 ctx_menu
->addMenu(submenu
);
716 submenu
->addAction(window()->findChild
<QAction
*>("actionSCTPAnalyseThisAssociation"));
717 submenu
->addAction(window()->findChild
<QAction
*>("actionSCTPShowAllAssociations"));
718 submenu
->addAction(window()->findChild
<QAction
*>("actionSCTPFilterThisAssociation"));
721 main_menu_item
= window()->findChild
<QMenu
*>("menuFollow");
722 if (main_menu_item
) {
723 submenu
= new QMenu(main_menu_item
->title(), ctx_menu
);
724 ctx_menu
->addMenu(submenu
);
725 foreach (FollowStreamAction
*follow_action
, main_menu_item
->findChildren
<FollowStreamAction
*>()) {
726 /* XXX: We could, like the prefs above, walk the protocols/layers
727 * and add the follow actions in the order they appear in the packet.
729 if (follow_action
->isEnabled()) {
730 submenu
->addAction(follow_action
);
735 ctx_menu
->addSeparator();
737 main_menu_item
= window()->findChild
<QMenu
*>("menuEditCopy");
738 submenu
= new QMenu(main_menu_item
->title(), ctx_menu
);
739 submenu
->setToolTipsVisible(true);
740 ctx_menu
->addMenu(submenu
);
742 QAction
* action
= submenu
->addAction(tr("Summary as Text"));
743 action
->setData(CopyAsText
);
744 connect(action
, SIGNAL(triggered()), this, SLOT(copySummary()));
745 action
= submenu
->addAction(tr("…as CSV"));
746 action
->setData(CopyAsCSV
);
747 connect(action
, SIGNAL(triggered()), this, SLOT(copySummary()));
748 action
= submenu
->addAction(tr("…as YAML"));
749 action
->setData(CopyAsYAML
);
750 connect(action
, SIGNAL(triggered()), this, SLOT(copySummary()));
751 action
= submenu
->addAction(tr("…as HTML"));
752 action
->setData(CopyAsHTML
);
753 connect(action
, SIGNAL(triggered()), this, SLOT(copySummary()));
754 submenu
->addSeparator();
756 submenu
->addAction(window()->findChild
<QAction
*>("actionEditCopyAsFilter"));
757 submenu
->addSeparator();
759 QActionGroup
* copyEntries
= DataPrinter::copyActions(this, frameData
);
760 submenu
->addActions(copyEntries
->actions());
761 copyEntries
->setParent(submenu
);
762 frameData
->setParent(submenu
);
764 if (is_packet_configuration_namespace()) {
765 /* i.e., Wireshark only */
766 ctx_menu
->addSeparator();
767 QMenu
*proto_prefs_menus
= new QMenu(ProtocolPreferencesMenu::tr("Protocol Preferences"), ctx_menu
);
769 if (cap_file_
&& cap_file_
->edt
&& cap_file_
->edt
->tree
) {
770 QList
<QString
> added_proto_prefs
;
771 // N.B. finfo_array will be assigned above
772 for (unsigned i
= 0; i
< finfo_array
->len
; i
++) {
773 field_info
*fi
= (field_info
*)g_ptr_array_index (finfo_array
, i
);
774 const header_field_info
*hfinfo
= fi
->hfinfo
;
776 if (prefs_is_registered_protocol(hfinfo
->abbrev
)) {
777 if (hfinfo
->parent
== -1) {
778 module_name
= hfinfo
->abbrev
;
780 module_name
= proto_registrar_get_abbrev(hfinfo
->parent
);
783 if (added_proto_prefs
.contains(module_name
)) {
787 ProtocolPreferencesMenu
*proto_prefs_menu
= new ProtocolPreferencesMenu(hfinfo
->name
, module_name
, proto_prefs_menus
);
789 connect(proto_prefs_menu
, SIGNAL(showProtocolPreferences(QString
)),
790 this, SIGNAL(showProtocolPreferences(QString
)));
791 connect(proto_prefs_menu
, SIGNAL(editProtocolPreference(preference
*,pref_module
*)),
792 this, SIGNAL(editProtocolPreference(preference
*,pref_module
*)));
794 proto_prefs_menus
->addMenu(proto_prefs_menu
);
795 added_proto_prefs
<< module_name
;
799 ctx_menu
->addMenu(proto_prefs_menus
);
800 action
= ctx_menu
->addAction(tr("Decode As…"));
801 action
->setProperty("create_new", QVariant(true));
802 connect(action
, &QAction::triggered
, this, &PacketList::ctxDecodeAsDialog
);
803 // "Print" not ported intentionally
804 action
= window()->findChild
<QAction
*>("actionViewShowPacketInNewWindow");
805 ctx_menu
->addAction(action
);
808 // Set menu sensitivity for the current column and set action data.
810 emit
framesSelected(QList
<int>() << frameData
->frameNum());
812 emit
framesSelected(QList
<int>());
814 ctx_menu
->popup(event
->globalPos());
817 void PacketList::ctxDecodeAsDialog()
819 QAction
*da_action
= qobject_cast
<QAction
*>(sender());
822 bool create_new
= da_action
->property("create_new").toBool();
824 DecodeAsDialog
*da_dialog
= new DecodeAsDialog(this, cap_file_
, create_new
);
825 connect(da_dialog
, SIGNAL(destroyed(QObject
*)), mainApp
, SLOT(flushAppSignals()));
826 da_dialog
->setWindowModality(Qt::ApplicationModal
);
827 da_dialog
->setAttribute(Qt::WA_DeleteOnClose
);
831 void PacketList::timerEvent(QTimerEvent
*event
)
833 if (event
->timerId() == overlay_timer_id_
) {
834 if (!capture_in_progress_
) {
835 if (create_near_overlay_
) drawNearOverlay();
836 if (create_far_overlay_
) drawFarOverlay();
839 QTreeView::timerEvent(event
);
843 void PacketList::paintEvent(QPaintEvent
*event
)
845 // XXX This is overkill, but there are quite a few events that
846 // require a new overlay, e.g. page up/down, scrolling, column
848 create_near_overlay_
= true;
849 QTreeView::paintEvent(event
);
852 void PacketList::mousePressEvent (QMouseEvent
*event
)
854 QTreeView::mousePressEvent(event
);
856 QModelIndex curIndex
= indexAt(event
->pos());
857 mouse_pressed_at_
= curIndex
;
859 bool midButton
= (event
->buttons() & Qt::MiddleButton
) == Qt::MiddleButton
;
860 if (midButton
&& cap_file_
&& packet_list_model_
)
862 packet_list_model_
->toggleFrameMark(QModelIndexList() << curIndex
);
864 // Make sure the packet list's frame.marked related field text is updated.
865 redrawVisiblePackets();
867 create_far_overlay_
= true;
868 packets_bar_update();
872 void PacketList::mouseReleaseEvent(QMouseEvent
*event
) {
873 QTreeView::mouseReleaseEvent(event
);
875 mouse_pressed_at_
= QModelIndex();
878 void PacketList::mouseMoveEvent (QMouseEvent
*event
)
880 QModelIndex curIndex
= indexAt(event
->pos());
881 if (event
->buttons() & Qt::LeftButton
&& curIndex
.isValid() && curIndex
== mouse_pressed_at_
)
883 ctx_column_
= curIndex
.column();
884 QMimeData
* mimeData
= new QMimeData();
885 QWidget
* content
= nullptr;
887 QString filter
= getFilterFromRowAndColumn(curIndex
);
888 QList
<int> rows
= selectedRows();
889 if (rows
.count() > 1)
892 foreach (int row
, rows
)
894 QModelIndex idx
= model()->index(row
, 0);
898 QString entry
= createSummaryText(idx
, CopyAsText
);
902 if (content
.count() > 0)
903 mimeData
->setText(content
.join("\n"));
905 else if (! filter
.isEmpty())
908 QString name
= model()->headerData(curIndex
.column(), header()->orientation()).toString();
910 if (! filter
.isEmpty())
912 abbrev
= filter
.left(filter
.indexOf(' '));
916 filter
= model()->data(curIndex
).toString().toLower();
920 mimeData
->setText(filter
);
922 QJsonObject filterData
;
923 filterData
["filter"] = filter
;
924 filterData
["name"] = abbrev
;
925 filterData
["description"] = name
;
927 mimeData
->setData(WiresharkMimeData::DisplayFilterMimeType
, QJsonDocument(filterData
).toJson());
928 content
= new DragLabel(QStringLiteral("%1\n%2").arg(name
, abbrev
), this);
932 QString text
= model()->data(curIndex
).toString();
933 if (! text
.isEmpty())
934 mimeData
->setText(text
);
937 if (mimeData
->hasText() || mimeData
->hasFormat(WiresharkMimeData::DisplayFilterMimeType
))
939 QDrag
* drag
= new QDrag(this);
940 drag
->setMimeData(mimeData
);
943 qreal dpr
= window()->windowHandle()->devicePixelRatio();
944 QPixmap pixmap
= QPixmap(content
->size() * dpr
);
945 pixmap
.setDevicePixelRatio(dpr
);
946 content
->render(&pixmap
);
947 drag
->setPixmap(pixmap
);
950 drag
->exec(Qt::CopyAction
);
959 void PacketList::keyPressEvent(QKeyEvent
*event
)
961 QTreeView::keyPressEvent(event
);
963 if (event
->matches(QKeySequence::Copy
))
965 QStringList content
, htmlContent
;
966 if (model() && selectionModel() && selectionModel()->hasSelection())
969 QModelIndexList selRows
= selectionModel()->selectedRows(0);
970 foreach(QModelIndex row
, selRows
)
971 rows
.append(row
.row());
973 QStringList hdr_parts
;
974 QList
<int> align_parts
, size_parts
;
976 switch (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
) {
977 case COPY_FORMAT_TEXT
:
978 case COPY_FORMAT_HTML
:
979 if (prefs
.gui_packet_list_copy_text_with_aligned_columns
) {
980 hdr_parts
= createHeaderPartsForAligned();
981 align_parts
= createAlignmentPartsForAligned();
982 size_parts
= createSizePartsForAligned(false, hdr_parts
, rows
);
984 if (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
== COPY_FORMAT_HTML
) {
985 htmlContent
<< createDefaultStyleForHtml();
986 htmlContent
<< createOpeningTagForHtml();
989 case COPY_FORMAT_CSV
:
990 case COPY_FORMAT_YAML
:
994 QList
<QStringList
> entries
;
995 foreach(int row
, rows
)
997 QModelIndex idx
= model()->index(row
, 0);
1001 switch (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
) {
1002 case COPY_FORMAT_TEXT
:
1003 case COPY_FORMAT_HTML
:
1004 if (prefs
.gui_packet_list_copy_text_with_aligned_columns
)
1005 content
<< createSummaryForAligned(idx
, align_parts
, size_parts
);
1007 content
<< createSummaryText(idx
, CopyAsText
);
1008 if (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
== COPY_FORMAT_HTML
)
1009 htmlContent
<< createSummaryForHtml(idx
);
1011 case COPY_FORMAT_CSV
:
1012 content
<< createSummaryText(idx
, CopyAsCSV
);
1014 case COPY_FORMAT_YAML
:
1015 content
<< createSummaryText(idx
, CopyAsYAML
);
1021 if (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
== COPY_FORMAT_HTML
) {
1022 // htmlContent will never be empty as they will always have style and table tags
1023 QMimeData
*mimeData
= new QMimeData
;
1024 htmlContent
<< createClosingTagForHtml();
1025 mimeData
->setHtml(htmlContent
.join('\n'));
1026 mimeData
->setText(content
.join('\n').append("\n"));
1027 mainApp
->clipboard()->setMimeData(mimeData
, QClipboard::Clipboard
);
1030 if (content
.count() > 0) {
1032 if (prefs
.gui_packet_list_copy_format_options_for_keyboard_shortcut
== COPY_FORMAT_YAML
) {
1033 copy_text
= content
.join("");
1036 copy_text
= content
.join("\n");
1039 mainApp
->clipboard()->setText(copy_text
);
1045 void PacketList::resizeEvent(QResizeEvent
*event
)
1047 create_near_overlay_
= true;
1048 create_far_overlay_
= true;
1049 QTreeView::resizeEvent(event
);
1052 void PacketList::setColumnVisibility()
1054 set_column_visibility_
= true;
1055 for (int i
= 0; i
< prefs
.num_cols
; i
++) {
1056 setColumnHidden(i
, get_column_visible(i
) ? false : true);
1058 set_column_visibility_
= false;
1061 int PacketList::sizeHintForColumn(int column
) const
1065 // This is a bit hacky but Qt does a fine job of column sizing and
1066 // reimplementing QTreeView::sizeHintForColumn seems like a worse idea.
1067 if (itemDelegateForColumn(column
)) {
1068 QStyleOptionViewItem option
;
1069 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1070 initViewItemOption(&option
);
1072 option
= viewOptions();
1074 // In my (gcc) testing this results in correct behavior on Windows but adds extra space
1075 // on macOS and Linux. We might want to add Q_OS_... #ifdefs accordingly.
1076 size_hint
= itemDelegateForColumn(column
)->sizeHint(option
, QModelIndex()).width();
1078 size_hint
+= QTreeView::sizeHintForColumn(column
); // Decoration padding
1082 void PacketList::setRecentColumnWidth(int col
)
1084 int col_width
= recent_get_column_width(col
);
1086 if (col_width
< 1) {
1087 int fmt
= get_column_format(col
);
1088 const char *long_str
= get_column_width_string(fmt
, col
);
1090 QFontMetrics fm
= QFontMetrics(mainApp
->monospaceFont());
1092 col_width
= fm
.horizontalAdvance(long_str
);
1094 col_width
= fm
.horizontalAdvance(MIN_COL_WIDTH_STR
);
1096 // Custom delegate padding
1097 if (itemDelegateForColumn(col
)) {
1098 QStyleOptionViewItem option
;
1099 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1100 initViewItemOption(&option
);
1102 option
= viewOptions();
1104 col_width
+= itemDelegateForColumn(col
)->sizeHint(option
, QModelIndex()).width();
1108 setColumnWidth(col
, col_width
);
1111 void PacketList::drawCurrentPacket()
1113 // XXX - Update for multi-select? If more than one packet is Selected,
1114 // this changes it so that only the Current packet is Selected.
1115 QModelIndex current_index
= currentIndex();
1116 if (selectionModel() && current_index
.isValid()) {
1117 selectionModel()->clearSelection();
1118 selectionModel()->setCurrentIndex(current_index
, QItemSelectionModel::SelectCurrent
| QItemSelectionModel::Rows
);
1122 // Redraw the packet list and detail. Re-selects the current packet (causes
1123 // the UI to scroll to that packet).
1124 // Called from many places.
1125 void PacketList::redrawVisiblePackets() {
1126 redrawVisiblePacketsDontSelectCurrent();
1127 drawCurrentPacket();
1130 // Redraw the packet list and detail.
1131 // Does not scroll back to the selected packet.
1132 void PacketList::redrawVisiblePacketsDontSelectCurrent() {
1133 packet_list_model_
->invalidateAllColumnStrings();
1136 void PacketList::resetColumns()
1138 packet_list_model_
->resetColumns();
1141 // Return true if we have a visible packet further along in the history.
1142 bool PacketList::haveNextHistory(bool update_cur
)
1144 if (selection_history_
.size() < 1 || cur_history_
>= selection_history_
.size() - 1) {
1148 for (int i
= cur_history_
+ 1; i
< selection_history_
.size(); i
++) {
1149 if (packet_list_model_
->packetNumberToRow(selection_history_
.at(i
)) >= 0) {
1159 // Return true if we have a visible packet back in the history.
1160 bool PacketList::havePreviousHistory(bool update_cur
)
1162 if (selection_history_
.size() < 1 || cur_history_
< 1) {
1166 for (int i
= cur_history_
- 1; i
>= 0; i
--) {
1167 if (packet_list_model_
->packetNumberToRow(selection_history_
.at(i
)) >= 0) {
1177 void PacketList::setProfileSwitcher(ProfileSwitcher
*profile_switcher
)
1179 profile_switcher_
= profile_switcher
;
1180 if (profile_switcher
) {
1181 connect(packet_list_model_
, &PacketListModel::packetAppended
, profile_switcher_
, &ProfileSwitcher::checkPacket
);
1185 frame_data
*PacketList::getFDataForRow(int row
) const
1187 return packet_list_model_
->getRowFdata(row
);
1190 // prefs.col_list has changed.
1191 void PacketList::columnsChanged()
1193 columns_changed_
= true;
1194 column_register_fields();
1195 mainApp
->emitAppSignal(MainApplication::FieldsChanged
);
1197 // Keep columns_changed_ = true until we load a capture file.
1201 prefs
.num_cols
= g_list_length(prefs
.col_list
);
1202 col_cleanup(&cap_file_
->cinfo
);
1203 build_column_format_array(&cap_file_
->cinfo
, prefs
.num_cols
, false);
1204 create_far_overlay_
= true;
1206 applyRecentColumnWidths();
1207 setColumnVisibility();
1208 columns_changed_
= false;
1211 // Fields have changed, update custom columns
1212 void PacketList::fieldsChanged(capture_file
*cf
)
1214 prefs
.num_cols
= g_list_length(prefs
.col_list
);
1215 col_cleanup(&cf
->cinfo
);
1216 build_column_format_array(&cf
->cinfo
, prefs
.num_cols
, false);
1220 // Column widths should
1221 // - Load from recent when we load a new profile (including at starting up).
1222 // - Reapply when changing columns.
1223 // - Persist across freezes and thaws.
1224 // - Persist across file closing and opening.
1225 // - Save to recent when we save our profile (including shutting down).
1226 // - Not be affected by the behavior of stretchLastSection. (XXX: We
1227 // still save the stretched value to recent, sectionResized doesn't
1228 // distinguish between a resize from being stretched and a manual change.)
1229 void PacketList::applyRecentColumnWidths()
1231 // Either we've just started up or a profile has changed. Read
1232 // the recent settings, apply them, and save the header state.
1234 for (int col
= 0; col
< prefs
.num_cols
; col
++) {
1235 // The column must be shown before setting column width.
1236 // Visibility will be updated in setColumnVisibility().
1237 setColumnHidden(col
, false);
1238 setRecentColumnWidth(col
);
1241 column_state_
= header()->saveState();
1244 void PacketList::preferencesChanged()
1246 // Related packet delegate
1247 if (prefs
.gui_packet_list_show_related
) {
1248 setItemDelegateForColumn(0, &related_packet_delegate_
);
1250 setItemDelegateForColumn(0, 0);
1253 // Intelligent scroll bar (minimap)
1254 if (prefs
.gui_packet_list_show_minimap
) {
1255 if (overlay_timer_id_
== 0) {
1256 overlay_timer_id_
= startTimer(overlay_update_interval_
);
1259 if (overlay_timer_id_
!= 0) {
1260 killTimer(overlay_timer_id_
);
1261 overlay_timer_id_
= 0;
1266 // This sets the mode for the entire view. If we want to make this setting
1267 // per-column we'll either have to generalize RelatedPacketDelegate so that
1268 // we can set it for entire rows or create another delegate.
1269 Qt::TextElideMode elide_mode
= Qt::ElideRight
;
1270 switch (prefs
.gui_packet_list_elide_mode
) {
1272 elide_mode
= Qt::ElideLeft
;
1275 elide_mode
= Qt::ElideMiddle
;
1278 elide_mode
= Qt::ElideNone
;
1283 setTextElideMode(elide_mode
);
1286 void PacketList::freezePacketList(bool changing_profile
)
1288 changing_profile_
= changing_profile
;
1292 void PacketList::recolorPackets()
1294 packet_list_model_
->resetColorized();
1295 redrawVisiblePackets();
1298 // Enable autoscroll.
1299 void PacketList::setVerticalAutoScroll(bool enabled
)
1301 tail_at_end_
= enabled
;
1302 if (enabled
&& capture_in_progress_
) {
1307 // Called when we finish reading, reloading, rescanning, and retapping
1309 void PacketList::captureFileReadFinished()
1311 packet_list_model_
->flushVisibleRows();
1312 packet_list_model_
->dissectIdle(true);
1313 // Invalidating the column strings picks up and request/response
1314 // tracking changes. We might just want to call it from flushVisibleRows.
1315 packet_list_model_
->invalidateAllColumnStrings();
1316 // Sort *after* invalidating the column strings
1317 if (isSortingEnabled()) {
1318 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
1322 bool PacketList::freeze(bool keep_current_frame
)
1324 if (!cap_file_
|| model() == Q_NULLPTR
) {
1325 // No capture file or already frozen
1329 frame_data
*current_frame
= cap_file_
->current_frame
;
1330 column_state_
= header()->saveState();
1331 setHeaderHidden(true);
1332 frozen_current_row_
= currentIndex();
1333 frozen_selected_rows_
= selectionModel()->selectedRows();
1334 selectionModel()->clear();
1335 setModel(Q_NULLPTR
);
1336 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
1337 // call selectionChanged.
1338 related_packet_delegate_
.clear();
1340 if (keep_current_frame
) {
1341 cap_file_
->current_frame
= current_frame
;
1344 /* Clears packet list as well as byteview */
1345 emit
framesSelected(QList
<int>());
1350 bool PacketList::thaw(bool restore_selection
)
1352 if (!cap_file_
|| model() != Q_NULLPTR
) {
1353 // No capture file or not frozen
1357 setHeaderHidden(false);
1358 // Note that if we have a current sort status set in the header,
1359 // this will automatically try to sort the model (we don't want
1360 // that to happen if we're in the middle of reading the file).
1361 setModel(packet_list_model_
);
1363 if (changing_profile_
) {
1364 // When changing profile the new recent settings must be applied to the columns.
1365 applyRecentColumnWidths();
1366 setColumnVisibility();
1367 changing_profile_
= false;
1369 // Resetting the model resets our column widths so we restore them here.
1370 // We don't reapply the recent settings because the user could have
1371 // resized the columns manually since they were initially loaded.
1372 header()->restoreState(column_state_
);
1375 if (restore_selection
&& frozen_selected_rows_
.length() > 0 && selectionModel()) {
1376 /* This updates our selection, which redissects the current packet,
1377 * which is needed when we're called from MainWindow::layoutPanes.
1378 * Also, this resets all ProtoTree and ByteView data */
1380 setCurrentIndex(frozen_current_row_
);
1381 foreach (QModelIndex idx
, frozen_selected_rows_
) {
1382 selectionModel()->select(idx
, QItemSelectionModel::Select
| QItemSelectionModel::Rows
);
1384 scrollTo(currentIndex(), PositionAtCenter
);
1386 frozen_current_row_
= QModelIndex();
1387 frozen_selected_rows_
= QModelIndexList();
1392 void PacketList::clear() {
1393 related_packet_delegate_
.clear();
1394 selectionModel()->clear();
1395 packet_list_model_
->clear();
1396 proto_tree_
->clear();
1397 selection_history_
.clear();
1399 in_history_
= false;
1402 overlay_sb_
->setNearOverlayImage(overlay
);
1403 overlay_sb_
->setMarkedPacketImage(overlay
);
1404 create_near_overlay_
= true;
1405 create_far_overlay_
= true;
1408 void PacketList::writeRecent(FILE *rf
) {
1409 int col
, width
, col_fmt
;
1412 fprintf (rf
, "%s:\n", RECENT_KEY_COL_WIDTH
);
1413 for (col
= 0; col
< prefs
.num_cols
; col
++) {
1415 fprintf (rf
, ",\n");
1417 col_fmt
= get_column_format(col
);
1418 if (col_fmt
== COL_CUSTOM
) {
1419 fprintf (rf
, " \"%%Cus:%s\",", get_column_custom_fields(col
));
1421 fprintf (rf
, " %s,", col_format_to_string(col_fmt
));
1423 width
= recent_get_column_width (col
);
1424 xalign
= recent_get_column_xalign (col
);
1425 fprintf (rf
, " %d", width
);
1426 if (xalign
!= COLUMN_XALIGN_DEFAULT
) {
1427 fprintf (rf
, ":%c", xalign
);
1433 bool PacketList::contextMenuActive()
1435 return ctx_column_
>= 0 ? true : false;
1438 QString
PacketList::getFilterFromRowAndColumn(QModelIndex idx
)
1443 if (! idx
.isValid())
1446 int row
= idx
.row();
1447 int column
= idx
.column();
1449 if (!cap_file_
|| !packet_list_model_
|| column
< 0 || column
>= cap_file_
->cinfo
.num_cols
)
1452 fdata
= packet_list_model_
->getRowFdata(row
);
1454 if (fdata
!= NULL
) {
1456 wtap_rec rec
; /* Record metadata */
1457 Buffer buf
; /* Record data */
1459 wtap_rec_init(&rec
);
1460 ws_buffer_init(&buf
, 1514);
1461 if (!cf_read_record(cap_file_
, fdata
, &rec
, &buf
)) {
1462 wtap_rec_cleanup(&rec
);
1463 ws_buffer_free(&buf
);
1464 return filter
; /* error reading the record */
1466 /* proto tree, visible. We need a proto tree if there's custom columns */
1467 epan_dissect_init(&edt
, cap_file_
->epan
, have_custom_cols(&cap_file_
->cinfo
), false);
1468 col_custom_prime_edt(&edt
, &cap_file_
->cinfo
);
1470 epan_dissect_run(&edt
, cap_file_
->cd_t
, &rec
,
1471 frame_tvbuff_new_buffer(&cap_file_
->provider
, fdata
, &buf
),
1472 fdata
, &cap_file_
->cinfo
);
1474 if (cap_file_
->cinfo
.columns
[column
].col_fmt
== COL_CUSTOM
) {
1475 filter
.append(gchar_free_to_qstring(col_custom_get_filter(&edt
, &cap_file_
->cinfo
, column
)));
1477 /* We don't need to fill in the custom columns, as we get their
1480 col_fill_in(&edt
.pi
, true, true);
1481 if (strlen(cap_file_
->cinfo
.col_expr
.col_expr
[column
]) != 0 &&
1482 strlen(cap_file_
->cinfo
.col_expr
.col_expr_val
[column
]) != 0) {
1483 bool is_string_value
= false;
1484 header_field_info
*hfi
= proto_registrar_get_byname(cap_file_
->cinfo
.col_expr
.col_expr
[column
]);
1485 if (hfi
&& FT_IS_STRING(hfi
->type
)) {
1486 /* Could be an address type such as usb.src which must be quoted. */
1487 is_string_value
= true;
1490 if (filter
.isEmpty()) {
1491 if (is_string_value
) {
1492 filter
.append(QStringLiteral("%1 == \"%2\"")
1493 .arg(cap_file_
->cinfo
.col_expr
.col_expr
[column
])
1494 .arg(cap_file_
->cinfo
.col_expr
.col_expr_val
[column
]));
1496 filter
.append(QStringLiteral("%1 == %2")
1497 .arg(cap_file_
->cinfo
.col_expr
.col_expr
[column
])
1498 .arg(cap_file_
->cinfo
.col_expr
.col_expr_val
[column
]));
1504 epan_dissect_cleanup(&edt
);
1505 wtap_rec_cleanup(&rec
);
1506 ws_buffer_free(&buf
);
1512 void PacketList::resetColorized()
1514 packet_list_model_
->resetColorized();
1518 QString
PacketList::getPacketComment(unsigned c_number
)
1520 int row
= currentIndex().row();
1521 const frame_data
*fdata
;
1523 wtap_opttype_return_val result
;
1524 QString ret_val
= NULL
;
1526 if (!cap_file_
|| !packet_list_model_
) return NULL
;
1528 fdata
= packet_list_model_
->getRowFdata(row
);
1530 if (!fdata
) return NULL
;
1532 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
1533 result
= wtap_block_get_nth_string_option_value(pkt_block
, OPT_COMMENT
, c_number
, &pkt_comment
);
1534 if (result
== WTAP_OPTTYPE_SUCCESS
) {
1535 ret_val
= QString(pkt_comment
);
1537 wtap_block_unref(pkt_block
);
1541 void PacketList::addPacketComment(QString new_comment
)
1543 if (!cap_file_
|| !packet_list_model_
) return;
1544 if (new_comment
.isEmpty()) return;
1546 QByteArray ba
= new_comment
.toUtf8();
1549 * Make sure this would fit in a pcapng option.
1551 * XXX - 65535 is the maximum size for an option in pcapng;
1552 * what if another capture file format supports larger
1555 if (ba
.size() > 65535) {
1556 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
1557 "That comment is too large to save in a capture file.");
1561 if (selectionModel() && selectionModel()->hasSelection()) {
1562 packet_list_model_
->addFrameComment(selectionModel()->selectedRows(), ba
);
1563 drawCurrentPacket();
1567 void PacketList::setPacketComment(unsigned c_number
, QString new_comment
)
1569 QModelIndex curIndex
= currentIndex();
1571 if (!cap_file_
|| !packet_list_model_
) return;
1573 QByteArray ba
= new_comment
.toUtf8();
1575 * Make sure this would fit in a pcapng option.
1577 * XXX - 65535 is the maximum size for an option in pcapng;
1578 * what if another capture file format supports larger
1581 if (ba
.size() > 65535) {
1582 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
1583 "That comment is too large to save in a capture file.");
1587 packet_list_model_
->setFrameComment(curIndex
, ba
, c_number
);
1588 drawCurrentPacket();
1591 QString
PacketList::allPacketComments()
1597 if (!cap_file_
) return buf_str
;
1599 for (framenum
= 1; framenum
<= cap_file_
->count
; framenum
++) {
1600 fdata
= frame_data_sequence_find(cap_file_
->provider
.frames
, framenum
);
1602 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
1605 unsigned n_comments
= wtap_block_count_option(pkt_block
, OPT_COMMENT
);
1606 for (unsigned i
= 0; i
< n_comments
; i
++) {
1608 if (WTAP_OPTTYPE_SUCCESS
== wtap_block_get_nth_string_option_value(pkt_block
, OPT_COMMENT
, i
, &comment_text
)) {
1609 buf_str
.append(tr("Frame %1: %2\n\n").arg(framenum
).arg(comment_text
));
1610 if (buf_str
.length() > max_comments_to_fetch_
) {
1611 buf_str
.append(tr("[ Comment text exceeds %1. Stopping. ]")
1612 .arg(format_size(max_comments_to_fetch_
, FORMAT_SIZE_UNIT_BYTES
, FORMAT_SIZE_PREFIX_SI
)));
1622 void PacketList::deleteCommentsFromPackets()
1624 if (!cap_file_
|| !packet_list_model_
) return;
1626 if (selectionModel() && selectionModel()->hasSelection()) {
1627 packet_list_model_
->deleteFrameComments(selectionModel()->selectedRows());
1628 drawCurrentPacket();
1632 void PacketList::deleteAllPacketComments()
1634 if (!cap_file_
|| !packet_list_model_
) return;
1636 packet_list_model_
->deleteAllFrameComments();
1637 drawCurrentPacket();
1643 void PacketList::setCaptureFile(capture_file
*cf
)
1646 packet_list_model_
->setCaptureFile(cf
);
1648 if (columns_changed_
) {
1651 // Restore columns widths and visibility.
1652 header()->restoreState(column_state_
);
1653 setColumnVisibility();
1656 create_near_overlay_
= true;
1657 changing_profile_
= false;
1658 sortByColumn(-1, Qt::AscendingOrder
);
1661 void PacketList::setMonospaceFont(const QFont
&mono_font
)
1666 void PacketList::setRegularFont(const QFont
®ular_font
)
1668 header()->setFont(regular_font
);
1669 header()->viewport()->setFont(regular_font
);
1672 void PacketList::goNextPacket(void)
1674 if (QApplication::keyboardModifiers() & Qt::AltModifier
) {
1676 goNextHistoryPacket();
1680 if (selectionModel()->hasSelection()) {
1681 selectionModel()->setCurrentIndex(moveCursor(MoveDown
, Qt::NoModifier
), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1683 // First visible packet.
1684 selectionModel()->setCurrentIndex(indexAt(viewport()->rect().topLeft()), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1687 scrollViewChanged(false);
1690 void PacketList::goPreviousPacket(void)
1692 if (QApplication::keyboardModifiers() & Qt::AltModifier
) {
1694 goPreviousHistoryPacket();
1698 if (selectionModel()->hasSelection()) {
1699 selectionModel()->setCurrentIndex(moveCursor(MoveUp
, Qt::NoModifier
), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1701 // Last visible packet.
1702 QModelIndex last_idx
= indexAt(viewport()->rect().bottomLeft());
1703 if (last_idx
.isValid()) {
1704 selectionModel()->setCurrentIndex(last_idx
, QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1710 scrollViewChanged(false);
1713 void PacketList::goFirstPacket(void) {
1714 if (packet_list_model_
->rowCount() < 1) return;
1715 selectionModel()->setCurrentIndex(packet_list_model_
->index(0, 0), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1716 scrollTo(currentIndex());
1718 scrollViewChanged(false);
1721 void PacketList::goLastPacket(void) {
1722 if (packet_list_model_
->rowCount() < 1) return;
1723 selectionModel()->setCurrentIndex(packet_list_model_
->index(packet_list_model_
->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
1724 scrollTo(currentIndex());
1726 scrollViewChanged(false);
1729 void PacketList::goToPacket(int packet
, int hf_id
)
1731 if (!cf_goto_frame(cap_file_
, packet
, false))
1734 // cf_goto_frame only returns true if packet_list_select_row_from_data
1735 // succeeds, the latter has already selected and scrolled to the frame.
1737 proto_tree_
->goToHfid(hf_id
);
1740 scrollViewChanged(false);
1743 void PacketList::goNextHistoryPacket()
1745 if (haveNextHistory(true)) {
1747 goToPacket(selection_history_
.at(cur_history_
));
1748 in_history_
= false;
1752 void PacketList::goPreviousHistoryPacket()
1754 if (havePreviousHistory(true)) {
1756 goToPacket(selection_history_
.at(cur_history_
));
1757 in_history_
= false;
1761 void PacketList::markFrame()
1763 if (!cap_file_
|| !packet_list_model_
) return;
1765 QModelIndexList frames
;
1767 if (selectionModel() && selectionModel()->hasSelection())
1769 QModelIndexList selRows
= selectionModel()->selectedRows(0);
1770 foreach (QModelIndex idx
, selRows
)
1779 frames
<< currentIndex();
1781 packet_list_model_
->toggleFrameMark(frames
);
1783 // Make sure the packet list's frame.marked related field text is updated.
1784 redrawVisiblePackets();
1786 create_far_overlay_
= true;
1787 packets_bar_update();
1790 void PacketList::markAllDisplayedFrames(bool set
)
1792 if (!cap_file_
|| !packet_list_model_
) return;
1794 packet_list_model_
->setDisplayedFrameMark(set
);
1796 // Make sure the packet list's frame.marked related field text is updated.
1797 redrawVisiblePackets();
1799 create_far_overlay_
= true;
1800 packets_bar_update();
1803 void PacketList::ignoreFrame()
1805 if (!cap_file_
|| !packet_list_model_
) return;
1807 QModelIndexList frames
;
1809 if (selectionModel() && selectionModel()->hasSelection())
1811 foreach (QModelIndex idx
, selectionModel()->selectedRows(0))
1820 frames
<< currentIndex();
1823 packet_list_model_
->toggleFrameIgnore(frames
);
1824 create_far_overlay_
= true;
1825 int sb_val
= verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1826 setUpdatesEnabled(false);
1827 emit
packetDissectionChanged();
1828 setUpdatesEnabled(true);
1829 verticalScrollBar()->setValue(sb_val
);
1832 void PacketList::ignoreAllDisplayedFrames(bool set
)
1834 if (!cap_file_
|| !packet_list_model_
) return;
1836 packet_list_model_
->setDisplayedFrameIgnore(set
);
1837 create_far_overlay_
= true;
1838 emit
packetDissectionChanged();
1841 void PacketList::setTimeReference()
1843 if (!cap_file_
|| !packet_list_model_
) return;
1844 packet_list_model_
->toggleFrameRefTime(currentIndex());
1845 create_far_overlay_
= true;
1848 void PacketList::unsetAllTimeReferences()
1850 if (!cap_file_
|| !packet_list_model_
) return;
1851 packet_list_model_
->unsetAllFrameRefTime();
1852 create_far_overlay_
= true;
1855 void PacketList::applyTimeShift()
1857 packet_list_model_
->resetColumns();
1858 redrawVisiblePackets();
1859 emit
packetDissectionChanged();
1862 void PacketList::updatePackets(bool redraw
)
1865 packet_list_model_
->resetColumns();
1866 redrawVisiblePackets();
1872 void PacketList::columnVisibilityTriggered()
1874 QAction
*ha
= qobject_cast
<QAction
*>(sender());
1877 int col
= ha
->data().toInt();
1878 set_column_visible(col
, ha
->isChecked());
1879 setColumnVisibility();
1880 if (ha
->isChecked()) {
1881 setRecentColumnWidth(col
);
1886 void PacketList::sectionResized(int col
, int, int new_width
)
1888 if (isVisible() && !columns_changed_
&& !set_column_visibility_
&& !set_style_sheet_
&& new_width
> 0) {
1889 // Column 1 gets an invalid value (32 on macOS) when we're not yet
1892 // Don't set column width when columns changed or setting column
1893 // visibility because we may get a sectionResized() from QTreeView
1894 // with values from a old columns layout.
1896 // Don't set column width when hiding a column.
1898 recent_set_column_width(col
, new_width
);
1902 // The user moved a column. Make sure prefs.col_list, the column format
1903 // array, and the header's visual and logical indices all agree.
1904 // gtk/packet_list.c:column_dnd_changed_cb
1905 void PacketList::sectionMoved(int logicalIndex
, int oldVisualIndex
, int newVisualIndex
)
1907 GList
*new_col_list
= NULL
;
1908 GList
*new_recent_col_list
= NULL
;
1909 QList
<int> saved_sizes
;
1912 // Since we undo the move below, these should always stay in sync.
1913 // Otherwise the order of columns can be unexpected after drag and drop.
1914 if (logicalIndex
!= oldVisualIndex
) {
1915 ws_warning("Column moved from an unexpected state (%d, %d, %d)",
1916 logicalIndex
, oldVisualIndex
, newVisualIndex
);
1919 // Remember which column should be sorted. Use the visual index since this
1920 // points to the current GUI state rather than the outdated column order
1921 // (indicated by the logical index).
1922 sort_idx
= header()->sortIndicatorSection();
1923 if (sort_idx
!= -1) {
1924 sort_idx
= header()->visualIndex(sort_idx
);
1927 // Build a new column list based on the header's logical order.
1928 for (int vis_idx
= 0; vis_idx
< header()->count(); vis_idx
++) {
1929 int log_idx
= header()->logicalIndex(vis_idx
);
1930 saved_sizes
<< header()->sectionSize(log_idx
);
1932 void *pref_data
= g_list_nth_data(prefs
.col_list
, log_idx
);
1934 new_col_list
= g_list_append(new_col_list
, pref_data
);
1937 pref_data
= g_list_nth_data(recent
.col_width_list
, log_idx
);
1939 new_recent_col_list
= g_list_append(new_recent_col_list
, pref_data
);
1943 // Undo move to ensure that the logical indices map to the visual indices,
1944 // otherwise the column order is changed twice (once via the modified
1945 // col_list, once because of the visual/logical index mismatch).
1946 disconnect(header(), SIGNAL(sectionMoved(int,int,int)),
1947 this, SLOT(sectionMoved(int,int,int)));
1948 header()->moveSection(newVisualIndex
, oldVisualIndex
);
1949 connect(header(), SIGNAL(sectionMoved(int,int,int)),
1950 this, SLOT(sectionMoved(int,int,int)));
1952 // Clear and rebuild our (and the header's) model. There doesn't appear
1953 // to be another way to reset the logical index.
1956 g_list_free(prefs
.col_list
);
1957 prefs
.col_list
= new_col_list
;
1958 g_list_free(recent
.col_width_list
);
1959 recent
.col_width_list
= new_recent_col_list
;
1963 for (int i
= 0; i
< saved_sizes
.length(); i
++) {
1964 if (saved_sizes
[i
] < 1) continue;
1965 header()->resizeSection(i
, saved_sizes
[i
]);
1970 mainApp
->emitAppSignal(MainApplication::ColumnsChanged
);
1972 // If the column with the sort indicator got shifted, mark the new column
1973 // after updating the columns contents (via ColumnsChanged) to ensure that
1974 // the columns are sorted using the intended column contents.
1975 int left_col
= MIN(oldVisualIndex
, newVisualIndex
);
1976 int right_col
= MAX(oldVisualIndex
, newVisualIndex
);
1977 if (left_col
<= sort_idx
&& sort_idx
<= right_col
) {
1978 header()->setSortIndicator(sort_idx
, header()->sortIndicatorOrder());
1982 void PacketList::updateRowHeights(const QModelIndex
&ih_index
)
1984 QStyleOptionViewItem option
;
1985 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1986 initViewItemOption(&option
);
1988 option
= viewOptions();
1992 // One of our columns increased the maximum row height. Find out which one.
1993 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
1994 QSize size_hint
= itemDelegate()->sizeHint(option
, packet_list_model_
->index(ih_index
.row(), col
));
1995 max_height
= qMax(max_height
, size_hint
.height());
1998 if (max_height
> 0) {
1999 packet_list_model_
->setMaximumRowHeight(max_height
);
2003 QString
PacketList::createSummaryText(QModelIndex idx
, SummaryCopyType type
)
2005 if (! idx
.isValid())
2008 QStringList col_parts
;
2009 int row
= idx
.row();
2010 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
2011 if (get_column_visible(col
)) {
2012 col_parts
<< packet_list_model_
->data(packet_list_model_
->index(row
, col
), Qt::DisplayRole
).toString();
2015 return joinSummaryRow(col_parts
, row
, type
);
2018 QString
PacketList::createHeaderSummaryText(SummaryCopyType type
)
2020 QStringList col_parts
;
2021 for (int col
= 0; col
< packet_list_model_
->columnCount(); ++col
) {
2022 if (get_column_visible(col
)) {
2023 col_parts
<< packet_list_model_
->headerData(col
, Qt::Orientation::Horizontal
, Qt::DisplayRole
).toString();
2026 return joinSummaryRow(col_parts
, 0, type
);
2029 QStringList
PacketList::createHeaderPartsForAligned()
2031 QStringList hdr_parts
;
2032 for (int col
= 0; col
< packet_list_model_
->columnCount(); ++col
) {
2033 if (get_column_visible(col
)) {
2034 hdr_parts
<< packet_list_model_
->headerData(col
, Qt::Orientation::Horizontal
, Qt::DisplayRole
).toString();
2040 QList
<int> PacketList::createAlignmentPartsForAligned()
2042 QList
<int> align_parts
;
2043 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
2044 if (get_column_visible(col
)) {
2045 align_parts
<< packet_list_model_
->data(packet_list_model_
->index(0, col
), Qt::TextAlignmentRole
).toInt();
2051 QList
<int> PacketList::createSizePartsForAligned(bool useHeader
, QStringList hdr_parts
, QList
<int> rows
)
2053 QList
<int> size_parts
;
2055 for (int i
= 0; i
< hdr_parts
.size(); ++i
) {
2057 size_parts
<< static_cast<int>(hdr_parts
.at(i
).size());
2062 foreach(int row
, rows
)
2064 QModelIndex idx
= model()->index(row
, 0);
2065 if (! idx
.isValid())
2068 QStringList col_parts
;
2069 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
2070 if (get_column_visible(col
)) {
2071 col_parts
<< (packet_list_model_
->data(packet_list_model_
->index(row
, col
), Qt::DisplayRole
).toString());
2075 for (int i
= 0; i
< col_parts
.size(); ++i
) {
2076 if (col_parts
.at(i
).size() > size_parts
.at(i
)) {
2077 size_parts
[i
] = static_cast<int>(col_parts
.at(i
).size());
2086 QString
PacketList::createHeaderSummaryForAligned(QStringList hdr_parts
, QList
<int> align_parts
, QList
<int> size_parts
)
2089 for (int i
= 0; i
< hdr_parts
.size(); ++i
) {
2090 if (align_parts
.at(i
) == Qt::AlignLeft
) {
2091 hdr_text
+= hdr_parts
[i
].leftJustified(size_parts
.at(i
), ' ') + " ";
2093 else if (align_parts
.at(i
) == Qt::AlignRight
) {
2094 hdr_text
+= hdr_parts
[i
].rightJustified(size_parts
.at(i
), ' ') + " ";
2097 return QStringLiteral("-%1").arg(hdr_text
).trimmed().mid(1);
2100 QString
PacketList::createSummaryForAligned(QModelIndex idx
, QList
<int> align_parts
, QList
<int> size_parts
)
2102 if (! idx
.isValid())
2105 QStringList col_parts
;
2106 int row
= idx
.row();
2107 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
2108 if (get_column_visible(col
)) {
2109 col_parts
<< packet_list_model_
->data(packet_list_model_
->index(row
, col
), Qt::DisplayRole
).toString();
2114 for (int i
= 0; i
< col_parts
.size(); ++i
) {
2115 if (align_parts
.at(i
) == Qt::AlignLeft
) {
2116 col_text
+= col_parts
[i
].leftJustified(size_parts
.at(i
), ' ') + " ";
2118 else if (align_parts
.at(i
) == Qt::AlignRight
) {
2119 col_text
+= col_parts
[i
].rightJustified(size_parts
.at(i
), ' ') + " ";
2123 return QStringLiteral("-%1").arg(col_text
).trimmed().mid(1);
2126 QString
PacketList::createDefaultStyleForHtml()
2129 style_text
= "<style>";
2130 QString fontFamily
= QString(prefs
.gui_font_name
).split(",")[0];
2131 QString fontSize
= QString(prefs
.gui_font_name
).split(",")[1];
2132 style_text
+= "table{font-family:" + fontFamily
+ ";font-size:" + fontSize
+ "pt;}";
2133 style_text
+= "th{background-color:#000000;color:#ffffff;text-align:left;}";
2134 style_text
+= "th,td{padding:" + QString::number(fontSize
.toInt() / 2) + "pt}";
2135 style_text
+= "</style>";
2139 QString
PacketList::createOpeningTagForHtml()
2144 QString
PacketList::createHeaderSummaryForHtml()
2148 for (int col
= 0; col
< packet_list_model_
->columnCount(); ++col
) {
2149 if (get_column_visible(col
)) {
2151 hdr_text
+= packet_list_model_
->headerData(col
, Qt::Orientation::Horizontal
, Qt::DisplayRole
).toString();
2152 hdr_text
+= "</th>";
2155 hdr_text
+= "</tr>";
2159 QString
PacketList::createSummaryForHtml(QModelIndex idx
)
2161 if (! idx
.isValid())
2164 int row
= idx
.row();
2167 QString bg_color
= packet_list_model_
->data(packet_list_model_
->index(row
, 0), Qt::BackgroundRole
).toString();
2168 QString fg_color
= packet_list_model_
->data(packet_list_model_
->index(row
, 0), Qt::ForegroundRole
).toString();
2169 col_text
+= "<tr style=\"background-color:" + bg_color
+ ";color:" + fg_color
+ ";\">";
2171 QString alignment
[] = {"left", "right", "center", "justify"};
2173 for (int col
= 0; col
< packet_list_model_
->columnCount(); col
++) {
2174 if (get_column_visible(col
)) {
2175 col_text
+= "<td style=\"text-align:" + alignment
[packet_list_model_
->data(packet_list_model_
->index(row
, col
), Qt::TextAlignmentRole
).toInt() / 2] + ";\">";
2176 col_text
+= packet_list_model_
->data(packet_list_model_
->index(row
, col
), Qt::DisplayRole
).toString();
2177 col_text
+= "</td>";
2181 col_text
+= "</tr>";
2185 QString
PacketList::createClosingTagForHtml()
2190 void PacketList::copySummary()
2192 if (!currentIndex().isValid()) return;
2194 QAction
*ca
= qobject_cast
<QAction
*>(sender());
2197 QVariant type
= ca
->data();
2198 if (! type
.canConvert
<SummaryCopyType
>())
2200 SummaryCopyType copy_type
= type
.value
<SummaryCopyType
>();
2203 if (type
== CopyAsText
|| type
== CopyAsHTML
) {
2204 if (prefs
.gui_packet_list_copy_text_with_aligned_columns
) {
2206 rows
<< currentIndex().row();
2207 QStringList hdr_parts
;
2208 QList
<int> align_parts
, size_parts
;
2209 hdr_parts
= createHeaderPartsForAligned();
2210 align_parts
= createAlignmentPartsForAligned();
2211 size_parts
= createSizePartsForAligned(false, hdr_parts
, rows
);
2212 copy_text
= createSummaryForAligned(currentIndex(), align_parts
, size_parts
);
2215 copy_text
= createSummaryText(currentIndex(), CopyAsText
);
2218 if (type
== CopyAsHTML
) {
2219 QStringList htmlContent
;
2220 htmlContent
<< createDefaultStyleForHtml();
2221 htmlContent
<< createOpeningTagForHtml();
2222 htmlContent
<< createSummaryForHtml(currentIndex());
2223 htmlContent
<< createClosingTagForHtml();
2224 // htmlContent will never be empty as they will always have
2225 // style and table tags
2226 QMimeData
*mimeData
= new QMimeData
;
2227 mimeData
->setHtml(htmlContent
.join('\n'));
2228 mimeData
->setText(copy_text
);
2229 mainApp
->clipboard()->setMimeData(mimeData
, QClipboard::Clipboard
);
2232 mainApp
->clipboard()->setText(copy_text
);
2236 copy_text
= createSummaryText(currentIndex(), copy_type
);
2237 if (type
!= CopyAsYAML
)
2239 mainApp
->clipboard()->setText(copy_text
);
2243 // We need to tell when the user has scrolled the packet list, either to
2244 // the end or anywhere other than the end.
2245 void PacketList::vScrollBarActionTriggered(int)
2247 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
2249 tail_at_end_
= (overlay_sb_
->sliderPosition() >= overlay_sb_
->maximum());
2251 scrollViewChanged(tail_at_end_
);
2254 void PacketList::scrollViewChanged(bool at_end
)
2256 if (capture_in_progress_
) {
2257 // We want to start auto scrolling when the user scrolls to (or past)
2258 // the end only if recent.capture_auto_scroll is set.
2259 // We want to stop autoscrolling if the user scrolls up or uses
2260 // Go to Packet regardless of the preference setting.
2261 if (recent
.capture_auto_scroll
|| !at_end
) {
2262 emit
packetListScrolled(at_end
);
2267 // Goal: Overlay the packet list scroll bar with the colors of all of the
2269 // Try 1: Average packet colors in each scroll bar raster line. This has
2270 // two problems: It's easy to wash out colors and we dissect every packet.
2271 // Try 2: Color across a 5000 or 10000 packet window. We still end up washing
2273 // Try 3: One packet per vertical scroll bar pixel. This seems to work best
2274 // but has the smallest window.
2275 // Try 4: Use a multiple of the scroll bar height and scale the image down
2276 // using Qt::SmoothTransformation. This gives us more packets per raster
2279 // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
2280 // of 9 washed out colors a little too much.
2281 //const int height_multiplier_ = 7;
2282 void PacketList::drawNearOverlay()
2284 if (create_near_overlay_
) {
2285 create_near_overlay_
= false;
2288 if (!cap_file_
|| cap_file_
->state
!= FILE_READ_DONE
) return;
2290 if (!prefs
.gui_packet_list_show_minimap
) return;
2292 qreal dp_ratio
= overlay_sb_
->devicePixelRatio();
2293 int o_height
= overlay_sb_
->height() * dp_ratio
;
2294 int o_rows
= qMin(packet_list_model_
->rowCount(), o_height
);
2295 QFontMetricsF
fmf(mainApp
->font());
2296 int o_width
= ((static_cast<int>(fmf
.height())) * 2 * dp_ratio
) + 2; // 2ems + 1-pixel border on either side.
2298 if (recent
.packet_list_colorize
&& o_rows
> 0) {
2299 QImage
overlay(o_width
, o_height
, QImage::Format_ARGB32_Premultiplied
);
2301 QPainter
painter(&overlay
);
2303 overlay
.fill(Qt::transparent
);
2308 if (packet_list_model_
->rowCount() > o_height
&& overlay_sb_
->maximum() > 0) {
2309 start
+= ((double) overlay_sb_
->value() / overlay_sb_
->maximum()) * (packet_list_model_
->rowCount() - o_rows
);
2311 int end
= start
+ o_rows
;
2312 for (int row
= start
; row
< end
; row
++) {
2313 packet_list_model_
->ensureRowColorized(row
);
2315 frame_data
*fdata
= packet_list_model_
->getRowFdata(row
);
2316 const color_t
*bgcolor
= NULL
;
2317 if (fdata
->color_filter
) {
2318 const color_filter_t
*color_filter
= (const color_filter_t
*) fdata
->color_filter
;
2319 bgcolor
= &color_filter
->bg_color
;
2322 int next_line
= (row
- start
+ 1) * o_height
/ o_rows
;
2324 QColor
color(ColorUtils::fromColorT(bgcolor
));
2325 painter
.fillRect(0, cur_line
, o_width
, next_line
- cur_line
, color
);
2327 cur_line
= next_line
;
2330 // If the selected packet is in the overlay set selected_pos
2331 // accordingly. Otherwise, pin it to either the top or bottom.
2332 QList
<int> positions
;
2333 if (selectionModel()->hasSelection()) {
2335 QModelIndexList selRows
= selectionModel()->selectedRows(0);
2338 foreach (QModelIndex idx
, selRows
)
2340 int selected_pos
= -1;
2341 int sel_row
= idx
.row();
2342 if (sel_row
< start
) {
2344 } else if (sel_row
>= end
) {
2345 selected_pos
= overlay
.height() - 1;
2347 selected_pos
= (sel_row
- start
) * o_height
/ o_rows
;
2350 /* Due to the difference in the display height, we sometimes get empty positions
2351 * inbetween consecutive valid rows. If those are detected, they are signaled as
2352 * being selected as well */
2353 if (last_pos
>= 0 && selected_pos
> (last_pos
+ 1) && (last_row
+ 1) == sel_row
)
2355 for (int pos
= (last_pos
+ 1); pos
< selected_pos
; pos
++)
2357 if (! positions
.contains(pos
))
2361 else if (selected_pos
!= -1 && ! positions
.contains(selected_pos
))
2362 positions
<< selected_pos
;
2365 last_pos
= selected_pos
;
2369 overlay_sb_
->setNearOverlayImage(overlay
, packet_list_model_
->rowCount(), start
, end
, positions
, (o_height
/ o_rows
));
2372 overlay_sb_
->setNearOverlayImage(overlay
);
2376 void PacketList::drawFarOverlay()
2378 if (create_far_overlay_
) {
2379 create_far_overlay_
= false;
2382 if (!cap_file_
|| cap_file_
->state
!= FILE_READ_DONE
) return;
2384 if (!prefs
.gui_packet_list_show_minimap
) return;
2386 QSize groove_size
= overlay_sb_
->grooveRect().size();
2387 qreal dp_ratio
= overlay_sb_
->devicePixelRatio();
2388 groove_size
*= dp_ratio
;
2389 int o_width
= groove_size
.width();
2390 int o_height
= groove_size
.height();
2391 int pl_rows
= packet_list_model_
->rowCount();
2392 QImage
overlay(o_width
, o_height
, QImage::Format_ARGB32_Premultiplied
);
2393 bool have_marked_image
= false;
2395 // If only there were references from popular culture about getting into
2396 // some sort of groove.
2397 if (!overlay
.isNull() && recent
.packet_list_colorize
&& pl_rows
> 0) {
2399 QPainter
painter(&overlay
);
2401 // Draw text-colored tick marks on a transparent background.
2402 // Hopefully no themes use the text color for the groove color.
2403 overlay
.fill(Qt::transparent
);
2405 QColor tick_color
= palette().text().color();
2406 tick_color
.setAlphaF(0.3f
);
2407 painter
.setPen(tick_color
);
2409 for (int row
= 0; row
< pl_rows
; row
++) {
2411 frame_data
*fdata
= packet_list_model_
->getRowFdata(row
);
2412 if (fdata
->marked
|| fdata
->ref_time
|| fdata
->ignored
) {
2413 int new_line
= row
* o_height
/ pl_rows
;
2414 int tick_width
= o_width
/ 3;
2415 // Marked or ignored: left side, time refs: right side.
2416 // XXX Draw ignored ticks in the middle?
2417 int x1
= fdata
->ref_time
? o_width
- tick_width
: 1;
2418 int x2
= fdata
->ref_time
? o_width
- 1 : tick_width
;
2420 painter
.drawLine(x1
, new_line
, x2
, new_line
);
2421 have_marked_image
= true;
2425 if (have_marked_image
) {
2426 overlay_sb_
->setMarkedPacketImage(overlay
);
2431 if (!have_marked_image
) {
2432 QImage null_overlay
;
2433 overlay_sb_
->setMarkedPacketImage(null_overlay
);
2438 // - We are capturing
2439 // - actionGoAutoScroll in the main UI is checked.
2441 // actionGoAutoScroll in the main UI:
2442 // - Is set to the value of recent.capture_auto_scroll when beginning a capture
2443 // - Can be triggered manually by the user
2444 // - Is turned on if the last user-set vertical scrollbar position is at the
2445 // end and recent.capture_auto_scroll is enabled
2446 // - Is turned off if the last user-set vertical scrollbar is not at the end,
2447 // or if one of the Go to Packet actions is used (XXX: Should keyboard
2448 // navigation in keyPressEvent turn it off for similar reasons?)
2449 void PacketList::rowsInserted(const QModelIndex
&parent
, int start
, int end
)
2451 QTreeView::rowsInserted(parent
, start
, end
);
2452 if (capture_in_progress_
&& tail_at_end_
) {
2457 void PacketList::resizeAllColumns(bool onlyTimeFormatted
)
2459 if (!cap_file_
|| cap_file_
->state
== FILE_CLOSED
|| cap_file_
->state
== FILE_READ_PENDING
)
2462 for (int col
= 0; col
< cap_file_
->cinfo
.num_cols
; col
++) {
2463 if (! onlyTimeFormatted
|| col_has_time_fmt(&cap_file_
->cinfo
, col
)) {
2464 resizeColumnToContents(col
);