Bug Summary

File:builds/wireshark/wireshark/ui/qt/packet_list.cpp
Warning:line 1498, column 16
Potential leak of memory pointed to by 'drag_label'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name packet_list.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -fno-delete-null-pointer-checks -mframe-pointer=all -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -ffloat16-excess-precision=fast -fbfloat16-excess-precision=fast -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/builds/wireshark/wireshark/build -fcoverage-compilation-dir=/builds/wireshark/wireshark/build -resource-dir /usr/lib/llvm-22/lib/clang/22 -isystem /usr/include/glib-2.0 -isystem /usr/lib/x86_64-linux-gnu/glib-2.0/include -isystem /builds/wireshark/wireshark/build/ui/qt -isystem /builds/wireshark/wireshark/ui/qt -isystem /builds/wireshark/wireshark/ui/qt/lua_debugger -isystem /usr/include/x86_64-linux-gnu/qt6/QtConcurrent -isystem /usr/include/x86_64-linux-gnu/qt6 -isystem /usr/include/x86_64-linux-gnu/qt6/QtCore -isystem /usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -isystem /usr/include/x86_64-linux-gnu/qt6/QtNetwork -isystem /usr/include/x86_64-linux-gnu/qt6/QtPrintSupport -isystem /usr/include/x86_64-linux-gnu/qt6/QtGui -isystem /usr/include/x86_64-linux-gnu/qt6/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt6/QtSvg -isystem /usr/include/x86_64-linux-gnu/qt6/QtMultimedia -isystem /usr/include/x86_64-linux-gnu/qt6/QtDBus -D CARES_NO_DEPRECATED -D G_DISABLE_DEPRECATED -D G_DISABLE_SINGLE_INCLUDES -D QT_CONCURRENT_LIB -D QT_CORE_LIB -D QT_DBUS_LIB -D QT_GUI_LIB -D QT_MULTIMEDIA_LIB -D QT_NETWORK_LIB -D QT_PRINTSUPPORT_LIB -D QT_SVG_LIB -D QT_WIDGETS_LIB -D WS_DEBUG -D WS_DEBUG_UTF_8 -I /builds/wireshark/wireshark/build/ui/qt/qtui_autogen/include -I /builds/wireshark/wireshark/build -I /builds/wireshark/wireshark -I /builds/wireshark/wireshark/include -D _GLIBCXX_ASSERTIONS -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-22/lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fmacro-prefix-map=/builds/wireshark/wireshark/= -fmacro-prefix-map=/builds/wireshark/wireshark/build/= -fmacro-prefix-map=../= -Wno-format-nonliteral -std=c++17 -fdeprecated-macro -ferror-limit 19 -fwrapv -fwrapv-pointer -fstrict-flex-arrays=3 -stack-protector 2 -fstack-clash-protection -fcf-protection=full -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fcolor-diagnostics -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /builds/wireshark/wireshark/sbout/2026-10-07-100537-3663-1 -x c++ /builds/wireshark/wireshark/ui/qt/packet_list.cpp
1/* packet_list.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include <ui/qt/packet_list.h>
11
12#include "file.h"
13
14#include <epan/epan.h>
15#include <epan/epan_dissect.h>
16
17#include <epan/column.h>
18#include <epan/expert.h>
19#include <epan/packet.h>
20#include <epan/prefs.h>
21#include <epan/proto.h>
22
23#include "ui/main_statusbar.h"
24#include "ui/packet_list_utils.h"
25#include "ui/preference_utils.h"
26#include "ui/recent.h"
27#include "ui/simple_dialog.h"
28#include <wsutil/utf8_entities.h>
29
30#include "wiretap/wtap_opttypes.h"
31#include "app/application_flavor.h"
32#include "wsutil/str_util.h"
33#include <wsutil/wslog.h>
34
35#include <epan/color_filters.h>
36
37#include <ui/qt/utils/color_utils.h>
38#include <ui/qt/utils/theme_manager.h>
39#include <ui/qt/utils/font_manager.h>
40#include <ui/qt/widgets/overlay_scroll_bar.h>
41#include "proto_tree.h"
42#include <ui/qt/utils/qt_ui_utils.h>
43#include "main_application.h"
44#include <ui/qt/utils/data_printer.h>
45#include <ui/qt/utils/frame_information.h>
46#include <ui/qt/utils/profile_switcher.h>
47#include <ui/qt/utils/variant_pointer.h>
48#include <ui/qt/models/pref_models.h>
49#include <ui/qt/widgets/packet_list_header.h>
50#include <ui/qt/widgets/pinned_overlay_view.h>
51#include <ui/qt/utils/wireshark_mime_data.h>
52#include <ui/qt/widgets/drag_label.h>
53#include <ui/qt/filter_action.h>
54#include <ui/qt/follow_stream_action.h>
55#include <ui/qt/decode_as_dialog.h>
56#include <ui/qt/wireshark_main_window.h>
57
58#include <QAction>
59#include <QActionGroup>
60#include <QApplication>
61#include <QDesktopServices>
62#include <QUrl>
63#include <QClipboard>
64#include <QContextMenuEvent>
65#include <QtCore/qmath.h>
66#include <QElapsedTimer>
67#include <QFontMetrics>
68#include <QHeaderView>
69#include <QKeyEvent>
70#include <QMessageBox>
71#include <QPainter>
72#include <QScreen>
73#include <QScrollBar>
74#include <QWheelEvent>
75#include <QTabWidget>
76#include <QTextEdit>
77#include <QTimerEvent>
78#include <QTreeWidget>
79#include <QWindow>
80#include <QJsonObject>
81#include <QJsonDocument>
82
83#ifdef Q_OS_WIN
84#include "wsutil/file_util.h"
85#include <QSysInfo>
86#include <uxtheme.h>
87#endif
88
89// To do:
90// - Fix "apply as filter" behavior.
91// - Add colorize conversation.
92// - Use a timer to trigger automatic scrolling.
93
94// If we ever add the ability to open multiple capture files we might be
95// able to use something like QMap<capture_file *, PacketList *> to match
96// capture files against packet lists and models.
97static PacketList *gbl_cur_packet_list;
98
99const int max_comments_to_fetch_ = 20000000; // Arbitrary
100const int overlay_update_interval_ = 100; // 250; // Milliseconds.
101// Turbo mode: If the user holds down the down or up key longer than the
102// activation delay, switch from the operating system's autorepeat rate to
103// our own, faster rate.
104const int turbo_activation_delay_ = 1500; // Milliseconds, somewhat arbitrary.
105// Our goal is to move to the next or previous packet as fast as possible
106// while still allowing the user to recognize changes in the hex view.
107// According to this paper on Rapid Serial Visual Presentation (RSVP), humans
108// can recognize images presented as fast as 13ms.
109// https://web.archive.org/web/20250216195443/https://mollylab-1.mit.edu/sites/default/files/documents/FastDetect2014withFigures.pdf
110// Hopefully 15ms is plenty.
111const int turbo_nav_interval_ = 15; // Milliseconds
112
113
114/*
115 * Given a frame_data structure, scroll to and select the row in the
116 * packet list corresponding to that frame. If there is no such
117 * row, return false, otherwise return true.
118 */
119bool
120packet_list_select_row_from_data(frame_data *fdata_needle)
121{
122 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
123 return false;
124
125 PacketListProxyModel* model = qobject_cast<PacketListProxyModel*>(gbl_cur_packet_list->model());
126
127 if (!model)
128 return false;
129
130 model->flushVisibleRows();
131 int row = -1;
132 if (!fdata_needle)
133 row = 0;
134 else
135 row = model->visibleIndexOf(fdata_needle);
136
137 if (row >= 0) {
138 /* Calling ClearAndSelect with setCurrentIndex clears the "current"
139 * item, but doesn't clear the "selected" item. We want to clear
140 * the "selected" item as well so that selectionChanged() will be
141 * emitted in order to force an update of the packet details and
142 * packet bytes after a search.
143 */
144 gbl_cur_packet_list->selectionModel()->clearSelection();
145 gbl_cur_packet_list->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
146 gbl_cur_packet_list->scrollTo(gbl_cur_packet_list->currentIndex(), PacketList::PositionAtCenter);
147 return true;
148 }
149
150 return false;
151}
152
153/*
154 * Given a field_info, select the field (which will scroll to it in
155 * the main ProtoTree, etc.) This is kind of an odd place for it,
156 * but we call this when performing Find Packet in lieu of changing the
157 * selected frame (the function above), because we found a match in the
158 * same frame as the currently selected one.
159 */
160bool
161packet_list_select_finfo(field_info *fi)
162{
163 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
164 return false;
165
166 if (fi) {
167 FieldInformation finfo(fi, gbl_cur_packet_list);
168 emit gbl_cur_packet_list->fieldSelected(&finfo);
169 } else {
170 emit gbl_cur_packet_list->fieldSelected(0);
171 }
172 return true;
173}
174
175void
176packet_list_clear(void)
177{
178 if (gbl_cur_packet_list) {
179 gbl_cur_packet_list->clear();
180 }
181}
182
183void
184packet_list_freeze(void)
185{
186 if (gbl_cur_packet_list) {
187 gbl_cur_packet_list->freeze();
188 }
189}
190
191void
192packet_list_thaw(void)
193{
194 if (gbl_cur_packet_list) {
195 gbl_cur_packet_list->thaw();
196 }
197
198 packets_bar_update();
199}
200
201/* Redraw the packet list *and* currently-selected detail */
202void
203packet_list_queue_draw(void)
204{
205 if (gbl_cur_packet_list)
206 gbl_cur_packet_list->redrawVisiblePackets();
207}
208
209void
210packet_list_recent_write_all(FILE *rf) {
211 if (!gbl_cur_packet_list)
212 return;
213
214 gbl_cur_packet_list->writeRecent(rf);
215}
216
217bool
218packet_list_multi_select_active(void)
219{
220 if (gbl_cur_packet_list) {
221 return gbl_cur_packet_list->multiSelectActive();
222 }
223 return false;
224}
225
226#define MIN_COL_WIDTH_STR"MMMMMM" "MMMMMM"
227
228PacketList::PacketList(QWidget *parent) :
229 QTreeView(parent),
230 proto_tree_(nullptr),
231 cap_file_(nullptr),
232 ctx_column_(-1),
233 ctx_from_pinned_row_strip_(false),
234 overlay_timer_id_(0),
235 turbo_timer_id_(0),
236 turbo_key_(Qt::Key(0)),
237 held_key_(Qt::Key(0)),
238 create_near_overlay_(true),
239 create_far_overlay_(true),
240 mouse_pressed_at_(QModelIndex()),
241 capture_in_progress_(false),
242 tail_at_end_(0),
243 columns_changed_(false),
244 set_column_visibility_(false),
245 set_style_sheet_(false),
246 frozen_current_row_(QModelIndex()),
247 frozen_selected_rows_(QModelIndexList()),
248 pinned_rows_model_(nullptr),
249 pinned_column_boundary_(0),
250 pinned_column_view_(nullptr),
251 pinned_row_view_(nullptr),
252 pinned_row_corner_view_(nullptr),
253 hovered_frame_num_(-1),
254 cur_history_(-1),
255 in_history_(false),
256 finfo_array(nullptr),
257 profile_switcher_(nullptr)
258{
259 setItemsExpandable(false);
260 setRootIsDecorated(false);
261 setSortingEnabled(prefs.gui_packet_list_sortable);
262 setUniformRowHeights(true);
263 setFocusPolicy(Qt::StrongFocus);
264 // Needed so mouseMoveEvent() fires on a plain hover (no button held),
265 // to track hovered_frame_num_ for the cross-pane hover highlight.
266 setMouseTracking(true);
267
268#ifdef Q_OS_MAC
269 setAttribute(Qt::WA_MacShowFocusRect, true);
270#endif
271
272 verticalScrollBar()->setFocusPolicy(Qt::NoFocus);
273 horizontalScrollBar()->setFocusPolicy(Qt::NoFocus);
274
275 packet_list_header_ = new PacketListHeader(header()->orientation());
276 connect(packet_list_header_, &PacketListHeader::resetColumnWidth, this, &PacketList::setRecentColumnWidth);
277 connect(packet_list_header_, &PacketListHeader::updatePackets, this, &PacketList::updatePackets);
278 connect(packet_list_header_, &PacketListHeader::showColumnPreferences, this, &PacketList::showProtocolPreferences);
279 connect(packet_list_header_, &PacketListHeader::editColumn, this, &PacketList::editColumn);
280 connect(packet_list_header_, &PacketListHeader::columnsChanged, this, &PacketList::columnsChanged);
281 setHeader(packet_list_header_);
282
283 header()->setFirstSectionMovable(true);
284
285 setSelectionMode(QAbstractItemView::ExtendedSelection);
286
287 // Shrink down to a small but nonzero size in the main splitter.
288 int one_em = fontMetrics().height();
289 setMinimumSize(one_em, one_em);
290
291 overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
292 setVerticalScrollBar(overlay_sb_);
293
294 header()->setSortIndicator(-1, Qt::AscendingOrder);
295
296 packet_list_model_ = new PacketListModel(this, cap_file_);
297 packet_list_proxy_model_ = new PacketListProxyModel(this);
298 packet_list_proxy_model_->setSourceModel(packet_list_model_);
299 setModel(packet_list_proxy_model_);
300
301 Q_ASSERT(gbl_cur_packet_list == Q_NULLPTR)((gbl_cur_packet_list == nullptr) ? static_cast<void>(0
) : ::qt_assert("gbl_cur_packet_list == Q_NULLPTR", "ui/qt/packet_list.cpp"
, 301))
;
302 gbl_cur_packet_list = this;
303
304 connect(packet_list_proxy_model_, &PacketListProxyModel::goToPacket, this, [=](int packet) { goToPacket(packet); });
305 connect(mainApp, &MainApplication::addressResolutionChanged, this, &PacketList::redrawVisiblePacketsDontSelectCurrent);
306 connect(mainApp, &MainApplication::columnDataChanged, this, &PacketList::redrawVisiblePacketsDontSelectCurrent);
307 connect(mainApp, &MainApplication::preferencesChanged, this, [=]() {
308 /* The pref is a uint but QCache maxCost is a signed int (/
309 * qsizetype in Qt 6). Note that QAbstractItemModel row numbers
310 * are ints, not unsigned ints, so we're limited to INT_MAX
311 * rows anyway.
312 */
313 PacketListRecord::setMaxCache(prefs.gui_packet_list_cached_rows_max > INT_MAX2147483647 ? INT_MAX2147483647 : prefs.gui_packet_list_cached_rows_max);
314 if ((bool) (prefs.gui_packet_list_sortable) != isSortingEnabled()) {
315 setSortingEnabled(prefs.gui_packet_list_sortable);
316 }
317 });
318
319 connect(header(), &QHeaderView::sectionResized, this, &PacketList::sectionResized);
320 connect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
321 // The frozen column view's own header is a separate QHeaderView and
322 // doesn't automatically follow this one's sort indicator (the arrow
323 // icon), so keep it mirrored whenever a click on the real header (not
324 // just sortByColumnFromOverlay()) changes it.
325 connect(header(), &QHeaderView::sortIndicatorChanged, this, [this](int column, Qt::SortOrder order) {
326 if (pinned_column_view_) {
327 pinned_column_view_->mirrorSortIndicator(column, order);
328 }
329 });
330
331 connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &PacketList::vScrollBarActionTriggered);
332
333 connect(packet_list_header_, &PacketListHeader::freezeColumnsToHere, this, &PacketList::setPinnedColumnBoundary);
334 // While the real header is being dragged, let it show through the
335 // frozen-column overlay so the drag is visible when crossing the freeze.
336 connect(packet_list_header_, &PacketListHeader::dragActiveChanged, this, [this](bool active) {
337 header_drag_active_ = active;
338 updateFrozenOverlayMask();
339 });
340 connect(packet_list_header_, &PacketListHeader::unfreezeColumns, this, [this]() { setPinnedColumnBoundary(0); });
341
342 pinned_rows_model_ = new PinnedRowsModel(this);
343 pinned_rows_model_->setSourceModel(packet_list_proxy_model_);
344 // Filtering (modelReset) and sorting (layoutChanged) on the source
345 // model both need the pinned set re-resolved/re-ordered to match.
346 connect(packet_list_proxy_model_, &QAbstractItemModel::modelReset, this, &PacketList::updatePinnedRowVisibility);
347 connect(packet_list_proxy_model_, &QAbstractItemModel::layoutChanged, this, &PacketList::updatePinnedRowVisibility);
348
349 pinned_column_view_ = new PinnedColumnView(this, this);
350 pinned_column_view_->setModel(packet_list_proxy_model_);
351 pinned_column_view_->setSelectionModel(selectionModel());
352
353 connect(verticalScrollBar(), &QScrollBar::valueChanged, pinned_column_view_, &PinnedColumnView::setVerticalScrollValue);
354
355 // Own the font: seed it now and follow the FontManager for later changes.
356 connect(FontManager::instance(), &FontManager::monospaceFontChanged, this, &PacketList::setMonospaceFont);
357 connect(FontManager::instance(), &FontManager::applicationFontChanged, this, &PacketList::setRegularFont);
358 setMonospaceFont(FontManager::zoomedMonospaceFont());
359 setRegularFont(FontManager::zoomedFont());
360
361 // isActiveWindow()/QEvent::ActivationChange can lag behind the actual
362 // activation state on some platforms until the next event loop
363 // iteration, so also track focus moving to/from the application
364 // entirely to keep the pinned overlay views' selection color synced.
365 connect(qApp(static_cast<QApplication *>(QCoreApplication::instance
()))
, &QApplication::focusChanged, this, &PacketList::applicationFocusChanged);
366}
367
368void PacketList::setPinnedRowViews(PinnedRowView *row_view, PinnedRowView *corner_view)
369{
370 pinned_row_view_ = row_view;
371 pinned_row_corner_view_ = corner_view;
372
373 if (pinned_row_view_) {
374 pinned_row_view_->setModel(pinned_rows_model_);
375 connect(horizontalScrollBar(), &QScrollBar::valueChanged, pinned_row_view_, &PinnedRowView::setHorizontalScrollValue);
376 }
377 if (pinned_row_corner_view_) {
378 pinned_row_corner_view_->setModel(pinned_rows_model_);
379 }
380
381 // Push this view's already-computed column delegates (including the tag
382 // column's) onto the newly attached pinned views immediately, rather than
383 // waiting for the next setColumnVisibility() call (e.g. a column resize),
384 // which is the only other place delegates get propagated to
385 // pinnedOverlayViews().
386 setColumnDelegate();
387}
388
389QList<QTreeView *> PacketList::pinnedOverlayViews() const
390{
391 QList<QTreeView *> views;
392 if (pinned_column_view_) {
393 views << pinned_column_view_;
394 }
395 if (pinned_row_view_) {
396 views << pinned_row_view_;
397 }
398 if (pinned_row_corner_view_) {
399 views << pinned_row_corner_view_;
400 }
401 return views;
402}
403
404void PacketList::repaintPinnedOverlays()
405{
406 for (QTreeView *view : pinnedOverlayViews()) {
407 view->viewport()->update();
408 }
409}
410
411void PacketList::mirrorSectionWidthToOverlays(int column, int width)
412{
413 if (pinned_column_view_) {
414 pinned_column_view_->mirrorSectionWidth(column, width);
415 }
416 if (pinned_row_view_) {
417 pinned_row_view_->mirrorSectionWidth(column, width);
418 }
419 if (pinned_row_corner_view_) {
420 pinned_row_corner_view_->mirrorSectionWidth(column, width);
421 }
422}
423
424PacketList::~PacketList()
425{
426 if (finfo_array)
427 {
428 g_ptr_array_free(finfo_array, true);
429 }
430}
431
432void PacketList::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
433{
434 /* QAbstractItemView doesn't have a way to indicate "auto scroll, but
435 * only vertically." So just restore the horizontal scroll value whenever
436 * it scrolls.
437 */
438 setUpdatesEnabled(false);
439 int horizVal = horizontalScrollBar()->value();
440 QTreeView::scrollTo(index, hint);
441 horizontalScrollBar()->setValue(horizVal);
442 setUpdatesEnabled(true);
443}
444
445void PacketList::colorsChanged()
446{
447 const QString c_active = "active";
448 const QString c_inactive = "!active";
449
450 const QString flat_style_format =
451 "QTreeView::item:selected:%1 {"
452 " color: %2;"
453 " background-color: %3;"
454 "}";
455
456 // Same as flat_style_format but with no :active/:!active selector at
457 // all, for the overlay panes (see applyOverlayActiveState()).
458 const QString plain_style_format =
459 "QTreeView::item:selected {"
460 " color: %1;"
461 " background-color: %2;"
462 "}";
463
464 ThemeManager *tm = ThemeManager::instance();
465 QColor active_bg = tm->color(ThemeManager::PacketsSelection);
466 QColor inactive_bg = tm->color(ThemeManager::PacketsInactive);
467 QColor active_fg = tm->color(ThemeManager::PacketsSelectionText);
468 QColor inactive_fg = tm->color(ThemeManager::PacketsInactiveText);
469
470 QString active_style = flat_style_format.arg(
471 c_active,
472 active_fg.name(),
473 active_bg.name());
474 QString inactive_style = flat_style_format.arg(
475 c_inactive,
476 inactive_fg.name(),
477 inactive_bg.name());
478
479 // Hover highlighting is painted manually in drawRow() (driven by
480 // hovered_frame_num_) rather than through a :hover stylesheet
481 // selector, since the mouse hovering a pinned overlay view needs to
482 // highlight the corresponding row in every pane, not just the one
483 // widget Qt considers "hovered".
484 QString full_style = active_style + inactive_style;
485
486 // Cache the plain flat colors (no :active/:!active selector -- see
487 // applyOverlayActiveState()) so window-activation changes can pick the
488 // right one without recomputing from ThemeManager every time.
489 overlay_active_flat_style_ = plain_style_format.arg(active_fg.name(), active_bg.name());
490 overlay_inactive_flat_style_ = plain_style_format.arg(inactive_fg.name(), inactive_bg.name());
491
492 set_style_sheet_ = true;
493 setStyleSheet(full_style);
494 set_style_sheet_ = false;
495
496 applyOverlayActiveState();
497#if \
498 ( \
499 (QT_VERSION((6<<16)|(10<<8)|(2)) >= QT_VERSION_CHECK(6, 5, 4)((6<<16)|(5<<8)|(4)) && QT_VERSION((6<<16)|(10<<8)|(2)) < QT_VERSION_CHECK(6, 6, 0)((6<<16)|(6<<8)|(0))) \
500 || (QT_VERSION((6<<16)|(10<<8)|(2)) >= QT_VERSION_CHECK(6, 6, 1)((6<<16)|(6<<8)|(1))) \
501 )
502 // https://bugreports.qt.io/browse/QTBUG-122109
503 // Affects Qt 6.5.4 and later, 6.6.1 and later.
504 // When setting the style sheet, all visible sections are set
505 // to the new minimum DefaultSectionSize (even if it hasn't
506 // changed.) So make sure the new widths aren't saved to recent
507 // and then restore from recent.
508 applyRecentColumnWidths();
509 setColumnVisibility();
510#endif
511}
512
513void PacketList::applyOverlayActiveState()
514{
515 // Match the plain flat_style_format(":active"/:!active") selectors
516 // applied to this view itself, which are keyed to whether THIS widget
517 // has focus (not just whether the window is active) -- Qt's
518 // :active/:!active tracks the same thing per-widget. Clicking into the
519 // packet details/bytes pane keeps the window active but moves focus
520 // away from PacketList, and the overlay panes should dim along with
521 // it, not stay "focused" on their own.
522 const QString &style = (hasFocus() && isActiveWindow()) ? overlay_active_flat_style_ : overlay_inactive_flat_style_;
523 if (pinned_column_view_) {
524 pinned_column_view_->setStyleSheet(style);
525 }
526 if (pinned_row_view_) {
527 pinned_row_view_->setStyleSheet(style);
528 }
529 if (pinned_row_corner_view_) {
530 pinned_row_corner_view_->setStyleSheet(style);
531 }
532}
533
534void PacketList::changeEvent(QEvent *event)
535{
536 QTreeView::changeEvent(event);
537
538 if (event->type() == QEvent::ActivationChange) {
539 applyOverlayActiveState();
540 }
541}
542
543void PacketList::applicationFocusChanged(QWidget *, QWidget *)
544{
545 applyOverlayActiveState();
546}
547
548QString PacketList::joinSummaryRow(QStringList col_parts, int row, SummaryCopyType type)
549{
550 QString copy_text;
551 switch (type) {
552 case CopyAsCSV:
553 copy_text = "\"";
554 copy_text += col_parts.join("\",\"");
555 copy_text += "\"";
556 break;
557 case CopyAsYAML:
558 copy_text = "----\n";
559 copy_text += QStringLiteral("# Packet %1 from %2\n")(QString(QtPrivate::qMakeStringPrivate(u"" "# Packet %1 from %2\n"
)))
.arg(row).arg(cap_file_->filename);
560 copy_text += "- ";
561 copy_text += col_parts.join("\n- ");
562 copy_text += "\n";
563 break;
564 case CopyAsText:
565 default:
566 copy_text = col_parts.join("\t");
567 }
568
569 return copy_text;
570}
571
572void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
573{
574 QTreeView::drawRow(painter, option, index);
575
576 // The hover highlight is driven by hovered_frame_num_ rather than Qt's
577 // native per-widget hover state, since the mouse hovering a pinned
578 // overlay view needs to highlight this row here too -- native :hover
579 // styling only ever reacts to the mouse being over this specific
580 // widget. Painted even on a selected row so hover wins over selection,
581 // matching the pre-ThemeManager behavior (see the old :selected:hover
582 // stylesheet rule this replaced -- "background-color: ...;
583 // color: palette(text);"). Uses ColorUtils::paintFlatCell() rather
584 // than re-invoking each column's delegate with overridden palette
585 // roles -- QStyledItemDelegate::paint() calls initStyleOption(),
586 // which re-fetches Qt::BackgroundRole from the model and clobbers any
587 // backgroundBrush/palette override made beforehand, so a colored,
588 // marked, or ignored row's own background always won that fight and
589 // showed through instead of the flat hover color.
590 if (prefs.gui_packet_list_hover_style && index.isValid()) {
591 frame_data *fdata = getFDataForRow(index.row());
592 if (fdata && (int)fdata->num == hovered_frame_num_) {
593 QColor hover_bg = ColorUtils::hoverBackground();
594 QColor text_color = palette().color(QPalette::Text);
595
596 for (int visual_col = 0; visual_col < header()->count(); visual_col++) {
597 int logical_col = header()->logicalIndex(visual_col);
598 if (isColumnHidden(logical_col)) {
599 continue;
600 }
601 QModelIndex col_index = index.siblingAtColumn(logical_col);
602 QStyleOptionViewItem col_option = option;
603 col_option.rect = visualRect(col_index);
604 if (get_column_format(logical_col) == COL_TAG) {
605 // The tag column has its own delegate (emoji font, centered
606 // layout, per-segment links) that paintFlatCell()'s generic
607 // CE_ItemViewItem drawing can't reproduce. Fill the hover
608 // background here, then paint only the tag content on top
609 // (not the delegate's own background fill, which would
610 // clobber the hover color with the model's background role).
611 painter->save();
612 painter->fillRect(col_option.rect, hover_bg);
613 painter->restore();
614 tag_column_delegate_.paintContent(painter, col_option, col_index);
615 continue;
616 }
617 ColorUtils::paintFlatCell(painter, this, col_option, col_index, hover_bg, text_color);
618 }
619 }
620 }
621
622 if (prefs.gui_packet_list_separator) {
623 QRect rect = visualRect(index);
624
625 painter->setPen(QColor(Qt::white));
626 painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
627 }
628}
629
630void PacketList::setProtoTree (ProtoTree *proto_tree) {
631 proto_tree_ = proto_tree;
632
633 connect(proto_tree_, &ProtoTree::goToPacket, this, [=](int packet) { goToPacket(packet); });
634 connect(proto_tree_, &ProtoTree::relatedFrame,
635 &related_packet_delegate_, &RelatedPacketDelegate::addRelatedFrame);
636}
637
638bool PacketList::uniqueSelectActive()
639{
640 return selectionModel()->selectedRows(0).count() == 1 ? true : false;
641}
642
643bool PacketList::multiSelectActive()
644{
645 return selectionModel()->selectedRows(0).count() > 1 ? true : false;
646}
647
648QList<int> PacketList::selectedRows(bool useFrameNum)
649{
650 QList<int> rows;
651 if (selectionModel() && selectionModel()->hasSelection())
652 {
653 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_653 = QtPrivate::qMakeForeachContainer(selectionModel
()->selectedRows(0)); _container_653.i != _container_653.e
; ++_container_653.i) if (QModelIndex idx = *_container_653.i
; false) {} else
654 {
655 if (idx.isValid())
656 {
657 if (! useFrameNum)
658 rows << idx.row();
659 else if (useFrameNum)
660 {
661 frame_data * frame = getFDataForRow(idx.row());
662 if (frame)
663 rows << frame->num;
664 }
665 }
666 }
667 }
668 else if (currentIndex().isValid())
669 {
670 //
671 // XXX - will we ever have a current index but not a selection
672 // model?
673 //
674 if (! useFrameNum)
675 rows << currentIndex().row();
676 else
677 {
678 frame_data *frame = getFDataForRow(currentIndex().row());
679 if (frame)
680 rows << frame->num;
681 }
682 }
683
684 return rows;
685}
686
687int PacketList::currentFrameNum() const
688{
689 if (!cap_file_ || !cap_file_->current_frame) {
690 return -1;
691 }
692 return (int)cap_file_->current_frame->num;
693}
694
695frame_data *PacketList::filteredOutSelectedFrame() const
696{
697 if (!cap_file_ || !cap_file_->current_frame || !packet_list_model_ || !pinned_rows_model_) {
698 return nullptr;
699 }
700 if (selectionModel() && selectionModel()->hasSelection()) {
701 return nullptr;
702 }
703 int frame_num = (int)cap_file_->current_frame->num;
704 if (!pinned_rows_model_->isPinned(frame_num) || packet_list_proxy_model_->packetNumberToRow(frame_num) >= 0) {
705 return nullptr;
706 }
707 return cap_file_->current_frame;
708}
709
710QModelIndexList PacketList::sourceIndexes(const QModelIndexList &indexes) const
711{
712 QModelIndexList source_indexes;
713 for (const QModelIndex &idx : indexes) {
714 source_indexes << packet_list_proxy_model_->mapToSource(idx);
715 }
716 return source_indexes;
717}
718
719void PacketList::refreshFilteredOutFrame(frame_data *fdata)
720{
721 // Re-selecting re-dissects the frame; drawCurrentPacket() would instead
722 // unselect it, since there is no row here to resolve it from.
723 packet_list_model_->invalidateAllColumnStrings();
724 selectFrameFromOverlay((int)fdata->num);
725 create_far_overlay_ = true;
726 packets_bar_update();
727}
728
729void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected)
730{
731 QTreeView::selectionChanged(selected, deselected);
732
733 // The pinned overlay views share this view's selection model, but each
734 // is a separate QAbstractItemView with its own viewport; force a
735 // repaint there too so the highlight always shows on both sides of a
736 // freeze boundary, regardless of which view the selection change
737 // originated from.
738 repaintPinnedOverlays();
739
740 // We shouldn't need to scroll because QTreeView already does that if
741 // a single packet is newly selected. If we have a single selected packet
742 // because we just deselected the second to last packet, we probably don't
743 // want to scroll to the remaining frame.
744 drawCurrentPacket(false);
745}
746
747void PacketList::contextMenuEvent(QContextMenuEvent *event)
748{
749 const char *module_name = NULL__null;
750
751 if (finfo_array)
752 {
753 g_ptr_array_free(finfo_array, true);
754 finfo_array = NULL__null;
755 }
756
757 QModelIndex ctxIndex = indexAt(event->pos());
758
759 if (selectionModel() && selectionModel()->selectedRows(0).count() > 1)
760 selectionModel()->select(ctxIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
761
762 // ctxIndex is invalid when this is called via showContextMenuForFrame()
763 // for a pinned packet that's been filtered out of this view entirely
764 // (no row here to resolve via indexAt()/getRowFdata()) -- fall back to
765 // whatever frame was actually selected (by selectFrameFromOverlay(),
766 // just before this runs), which for that case is exactly the pinned
767 // packet's own frame, resolved independently of any row here.
768 frame_data *ctx_row_fdata = ctxIndex.isValid() ?
769 packet_list_proxy_model_->getRowFdata(ctxIndex.row()) :
770 (cap_file_ ? cap_file_->current_frame : nullptr);
771
772 // frameData will be owned by one of the submenus, see below.
773 FrameInformation * frameData =
774 new FrameInformation(new CaptureFile(this, cap_file_), ctx_row_fdata);
775
776 QMenu * ctx_menu = new QMenu(this);
777 ctx_menu->setAttribute(Qt::WA_DeleteOnClose);
778 // XXX We might want to reimplement setParent() and fill in the context
779 // menu there.
780 ctx_menu->addAction(window()->findChild<QAction *>("actionEditMarkSelected"));
781 ctx_menu->addAction(window()->findChild<QAction *>("actionEditIgnoreSelected"));
782 ctx_menu->addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
783 ctx_menu->addAction(window()->findChild<QAction *>("actionEditTimeShift"));
784 frame_data *ctx_fdata = ctx_row_fdata;
785 if (ctx_fdata) {
786 bool rowPinned = pinned_rows_model_->isPinned((int)ctx_fdata->num);
787 bool maxReached = !rowPinned && pinned_rows_model_->pinnedCount() >= PinnedRowsModel::kMaxPinnedRows;
788 QString pin_label = rowPinned ? tr("Unpin Row")
789 : maxReached ? tr("Pin Row to Top (max %1 reached)").arg(PinnedRowsModel::kMaxPinnedRows)
790 : tr("Pin Row to Top");
791 QAction *pin_action = ctx_menu->addAction(pin_label);
792 pin_action->setEnabled(!maxReached);
793 connect(pin_action, &QAction::triggered, this, [this, rowPinned]() {
794 if (rowPinned) {
795 unpinSelectedRows();
796 } else {
797 pinSelectedRows();
798 }
799 });
800 }
801
802 // Shown only for a context menu request that actually originated from
803 // the pinned-row strip (PinnedRowView) itself -- not the frozen-column
804 // overlay or a direct right-click on the primary view -- since
805 // scrolling the main view to a packet already visible there (or
806 // clearing every pinned row) doesn't make sense as an action offered
807 // from either of those.
808 if (ctx_from_pinned_row_strip_ && ctx_fdata) {
809 int ctx_frame_num = ctx_fdata->num;
810 QAction *goto_action = ctx_menu->addAction(tr("Go to Packet"));
811 connect(goto_action, &QAction::triggered, this, [this, ctx_frame_num]() {
812 goToPacket(ctx_frame_num);
813 });
814 }
815 if (ctx_from_pinned_row_strip_ && pinned_rows_model_->pinnedCount() > 0) {
816 QAction *unpin_all_action = ctx_menu->addAction(tr("Unpin All Rows"));
817 connect(unpin_all_action, &QAction::triggered, this, &PacketList::unpinAllRows);
818 }
819 ctx_menu->addMenu(window()->findChild<QMenu *>("menuPacketComment"));
820
821 ctx_menu->addSeparator();
822
823 // Code for custom context menus from Lua's register_packet_menu()
824 MainWindow * mainWindow = mainApp->mainWindow();
825 // N.B., will only call for a single frame selection,
826 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
827 finfo_array = proto_all_finfos(cap_file_->edt->tree);
828 if (mainWindow) {
829 bool insertedPacketMenu = mainWindow->addPacketMenus(ctx_menu, finfo_array);
830 if (insertedPacketMenu) {
831 ctx_menu->addSeparator();
832 }
833 }
834 }
835
836 ctx_menu->addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
837
838 // Reset for the next context menu request: showContextMenuForRow()/
839 // showContextMenuForFrame() set this immediately before calling here,
840 // but a direct right-click on the primary view calls this override
841 // straight from Qt's own event dispatch, bypassing both wrappers
842 // entirely -- so it can't be reset at entry the way ctx_column_ is,
843 // only after this one use.
844 ctx_from_pinned_row_strip_ = false;
845
846 ctx_menu->addSeparator();
847
848 QString selectedfilter = getFilterFromRowAndColumn(currentIndex());
849
850 if (! hasFocus() && cap_file_ && cap_file_->finfo_selected) {
851 char *tmp_field = proto_construct_match_selected_string(cap_file_->finfo_selected, cap_file_->edt);
852 selectedfilter = QString(tmp_field);
853 wmem_free(NULL__null, tmp_field);
854 }
855
856 bool have_filter_expr = !selectedfilter.isEmpty();
857 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, have_filter_expr, ctx_menu));
858 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, have_filter_expr, ctx_menu));
859
860 const char *conv_menu_name = "menuConversationFilter";
861 QMenu * main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
862 conv_menu_.setTitle(main_menu_item->title());
863 conv_menu_.setObjectName(conv_menu_name);
864 ctx_menu->addMenu(&conv_menu_);
865
866 const char *colorize_menu_name = "menuColorizeConversation";
867 main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
868 colorize_menu_.setTitle(main_menu_item->title());
869 colorize_menu_.setObjectName(colorize_menu_name);
870 ctx_menu->addMenu(&colorize_menu_);
871
872 QMenu * submenu;
873 main_menu_item = window()->findChild<QMenu *>("menuSCTP");
874 if (main_menu_item) {
875 submenu = new QMenu(main_menu_item->title(), ctx_menu);
876 ctx_menu->addMenu(submenu);
877 submenu->addAction(window()->findChild<QAction *>("actionSCTPAnalyseThisAssociation"));
878 submenu->addAction(window()->findChild<QAction *>("actionSCTPShowAllAssociations"));
879 submenu->addAction(window()->findChild<QAction *>("actionSCTPFilterThisAssociation"));
880 }
881
882 main_menu_item = window()->findChild<QMenu *>("menuFollow");
883 if (main_menu_item) {
884 submenu = new QMenu(main_menu_item->title(), ctx_menu);
885 ctx_menu->addMenu(submenu);
886 foreach (FollowStreamAction *follow_action, main_menu_item->findChildren<FollowStreamAction *>())for (auto _container_886 = QtPrivate::qMakeForeachContainer(main_menu_item
->findChildren<FollowStreamAction *>()); _container_886
.i != _container_886.e; ++_container_886.i) if (FollowStreamAction
*follow_action = *_container_886.i; false) {} else
{
887 /* XXX: We could, like the prefs above, walk the protocols/layers
888 * and add the follow actions in the order they appear in the packet.
889 */
890 if (follow_action->isEnabled()) {
891 submenu->addAction(follow_action);
892 }
893 }
894 }
895
896 // "Links" submenu — one entry per matching tag rule that has a URL
897 if (ctxIndex.isValid()) {
898 PacketListRecord *record = static_cast<PacketListRecord *>(ctxIndex.internalPointer());
899 if (record) {
900 const TagSegmentList &links = record->tagLinkList();
901 if (!links.isEmpty()) {
902 QMenu *linkSubmenu = new QMenu(tr("Tag Links"), ctx_menu);
903 for (const auto &link : links) {
904 QString displayUrl = link.second.length() > 50
905 ? link.second.left(50) + "..." : link.second;
906 QString label = link.first + ": " + displayUrl;
907 QAction *linkAction = linkSubmenu->addAction(label);
908 QString url = link.second;
909 connect(linkAction, &QAction::triggered, this, [url]() {
910 QDesktopServices::openUrl(QUrl(url));
911 });
912 }
913 ctx_menu->addMenu(linkSubmenu);
914 }
915 }
916 }
917
918 ctx_menu->addSeparator();
919
920 main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
921 submenu = new QMenu(main_menu_item->title(), ctx_menu);
922 submenu->setToolTipsVisible(true);
923 ctx_menu->addMenu(submenu);
924
925 QAction * action = submenu->addAction(tr("Summary as Text"));
926 action->setData(CopyAsText);
927 connect(action, &QAction::triggered, this, &PacketList::copySummary);
928 action = submenu->addAction(tr("…as CSV"));
929 action->setData(CopyAsCSV);
930 connect(action, &QAction::triggered, this, &PacketList::copySummary);
931 action = submenu->addAction(tr("…as YAML"));
932 action->setData(CopyAsYAML);
933 connect(action, &QAction::triggered, this, &PacketList::copySummary);
934 action = submenu->addAction(tr("…as HTML"));
935 action->setData(CopyAsHTML);
936 connect(action, &QAction::triggered, this, &PacketList::copySummary);
937 submenu->addSeparator();
938
939 submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
940 submenu->addSeparator();
941
942 QActionGroup * copyEntries = DataPrinter::copyActions(this, frameData);
943 submenu->addActions(copyEntries->actions());
944 copyEntries->setParent(submenu);
945 frameData->setParent(submenu);
946
947 if (application_flavor_is_wireshark()) {
948 /* i.e., Wireshark only */
949 ctx_menu->addSeparator();
950 QMenu *proto_prefs_menus = new QMenu(ProtocolPreferencesMenu::tr("Protocol Preferences"), ctx_menu);
951
952 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
953 QList<QString> added_proto_prefs;
954 // N.B. finfo_array will be assigned above
955 for (unsigned i = 0; i < finfo_array->len; i++) {
956 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i)((finfo_array)->pdata)[i];
957 const header_field_info *hfinfo = fi->hfinfo;
958
959 if (prefs_is_registered_protocol(hfinfo->abbrev)) {
960 if (hfinfo->parent == -1) {
961 module_name = hfinfo->abbrev;
962 } else {
963 module_name = proto_registrar_get_abbrev(hfinfo->parent);
964 }
965
966 if (added_proto_prefs.contains(module_name)) {
967 continue;
968 }
969
970 ProtocolPreferencesMenu *proto_prefs_menu = new ProtocolPreferencesMenu(hfinfo->name, module_name, proto_prefs_menus);
971
972 connect(proto_prefs_menu, &ProtocolPreferencesMenu::showProtocolPreferences,
973 this, &PacketList::showProtocolPreferences);
974 connect(proto_prefs_menu, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)"
"\0" "ui/qt/packet_list.cpp" ":" "974")
,
975 this, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)"
"\0" "ui/qt/packet_list.cpp" ":" "975")
);
976
977 proto_prefs_menus->addMenu(proto_prefs_menu);
978 added_proto_prefs << module_name;
979 }
980 }
981 }
982 ctx_menu->addMenu(proto_prefs_menus);
983 action = ctx_menu->addAction(tr("Decode As…"));
984 action->setProperty("create_new", QVariant(true));
985 connect(action, &QAction::triggered, this, &PacketList::ctxDecodeAsDialog);
986 // "Print" not ported intentionally
987 action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
988 ctx_menu->addAction(action);
989 }
990
991 // Set menu sensitivity for the current column and set action data.
992 if (frameData)
993 emit framesSelected(QList<int>() << frameData->frameNum());
994 else
995 emit framesSelected(QList<int>());
996
997 ctx_menu->popup(event->globalPos());
998}
999
1000void PacketList::ctxDecodeAsDialog()
1001{
1002 QAction *da_action = qobject_cast<QAction*>(sender());
1003 if (! da_action)
1004 return;
1005 bool create_new = da_action->property("create_new").toBool();
1006
1007 DecodeAsDialog *da_dialog = new DecodeAsDialog(this, cap_file_, create_new);
1008 connect(da_dialog, &DecodeAsDialog::destroyed, mainApp, &MainApplication::flushAppSignals);
1009 da_dialog->setWindowModality(Qt::ApplicationModal);
1010 da_dialog->setAttribute(Qt::WA_DeleteOnClose);
1011 da_dialog->show();
1012}
1013
1014void PacketList::timerEvent(QTimerEvent *event)
1015{
1016 if (event->timerId() == overlay_timer_id_) {
1017 if (!capture_in_progress_ && model() != nullptr) {
1018 if (create_near_overlay_) drawNearOverlay();
1019 if (create_far_overlay_) drawFarOverlay();
1020 }
1021 } else if (event->timerId() == turbo_timer_id_) {
1022 QKeyEvent turbo_step(QEvent::KeyPress, turbo_key_, Qt::NoModifier, QString(), true);
1023 QTreeView::keyPressEvent(&turbo_step);
1024 } else {
1025 QTreeView::timerEvent(event);
1026 }
1027}
1028
1029void PacketList::paintEvent(QPaintEvent *event)
1030{
1031 // XXX This is overkill, but there are quite a few events that
1032 // require a new overlay, e.g. page up/down, scrolling, column
1033 // resizing, etc.
1034 create_near_overlay_ = true;
1035 QTreeView::paintEvent(event);
1036}
1037
1038void PacketList::mousePressEvent(QMouseEvent *event)
1039{
1040 QTreeView::mousePressEvent(event);
1041
1042 QModelIndex curIndex = indexAt(event->pos());
1043 mouse_pressed_at_ = curIndex;
1044
1045 bool midButton = (event->buttons() & Qt::MiddleButton) == Qt::MiddleButton;
1046 if (midButton && cap_file_ && packet_list_model_)
1047 {
1048 packet_list_model_->toggleFrameMark(QModelIndexList() << packet_list_proxy_model_->mapToSource(curIndex));
1049
1050 // Make sure the packet list's frame.marked related field text is updated.
1051 redrawVisiblePackets();
1052
1053 create_far_overlay_ = true;
1054 packets_bar_update();
1055 }
1056}
1057
1058void PacketList::mouseReleaseEvent(QMouseEvent *event) {
1059 QTreeView::mouseReleaseEvent(event);
1060
1061 mouse_pressed_at_ = QModelIndex();
1062}
1063
1064// The pinned overlay views (PinnedColumnView/PinnedRowView) are separate
1065// QTreeViews that only ever show a slice of the real content, laid out
1066// independently of the primary view. Translating pixel coordinates from an
1067// overlay's click into this view's coordinate space is unreliable -- their
1068// row geometry can drift by a row even when the two appear visually
1069// aligned (e.g. differing sub-pixel scroll remainders). Instead, the
1070// overlay resolves the row itself via its own (always-correct, since it's
1071// local) indexAt(), and these entry points act on that row/index directly
1072// within this view, exactly as a real click here would.
1073void PacketList::selectRowFromOverlay(int row, int column, Qt::MouseButtons buttons,
1074 Qt::KeyboardModifiers modifiers)
1075{
1076 QModelIndex index = model()->index(row, column);
1077 if (!index.isValid()) {
1078 return;
1079 }
1080
1081 // Selecting a pinned packet (the whole point of pinning it) shouldn't
1082 // yank the primary view's scroll position to wherever that packet
1083 // happens to be -- setCurrentIndex() below scrolls to make the new
1084 // current index visible (QAbstractItemView's normal behavior for
1085 // e.g. arrow-key navigation), which is exactly what a click in the
1086 // pinned strip should NOT do. Save and restore the vertical scroll
1087 // position around it, the same way scrollTo() already does for the
1088 // horizontal one.
1089 int vert_scroll_value = verticalScrollBar()->value();
1090
1091 mouse_pressed_at_ = index;
1092
1093 // Mirrors the same three cases native ExtendedSelection handles for a
1094 // plain click (no modifier/Ctrl/Shift) -- this view's own
1095 // mousePressEvent() can't be relied on here since the click actually
1096 // landed in a different, overlay widget (PinnedRowView/
1097 // PinnedColumnView), which forwards here instead of letting
1098 // QAbstractItemView's native handling run.
1099 //
1100 // setCurrentIndex(QModelIndex) (the view-level convenience method) always
1101 // re-applies its own ClearAndSelect internally, which would immediately
1102 // clobber the Toggle/Select calls below -- moving "current" without
1103 // touching the selection just made requires going through
1104 // QItemSelectionModel::setCurrentIndex() directly with an explicit
1105 // NoUpdate flag instead.
1106 if (modifiers & Qt::ShiftModifier) {
1107 QModelIndex from = currentIndex().isValid() ? currentIndex() : index;
1108 selectionModel()->select(QItemSelection(from, index),
1109 QItemSelectionModel::Select | QItemSelectionModel::Rows);
1110 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate | QItemSelectionModel::Current);
1111 } else if (modifiers & Qt::ControlModifier) {
1112 selectionModel()->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
1113 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate | QItemSelectionModel::Current);
1114 } else {
1115 setCurrentIndex(index);
1116 selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1117 }
1118
1119 verticalScrollBar()->setValue(vert_scroll_value);
1120 // Keep keyboard focus on the primary view so the selection highlight
1121 // always renders in its "active" color, regardless of which pane the
1122 // click that produced this selection landed in.
1123 setFocus();
1124
1125 // The pinned-row views draw their own selection highlight by reading
1126 // currentFrameNum()/selectedRows() directly (see PinnedRowView::drawRow()),
1127 // rather than sharing this view's QItemSelectionModel, so updating that
1128 // state above doesn't itself trigger a repaint there -- request one
1129 // explicitly, the same way setHoveredFrameNum() already does for hover.
1130 repaintPinnedOverlays();
1131
1132 if (buttons & Qt::MiddleButton) {
1133 packet_list_model_->toggleFrameMark(QModelIndexList() << packet_list_proxy_model_->mapToSource(index));
1134 redrawVisiblePackets();
1135 create_far_overlay_ = true;
1136 packets_bar_update();
1137 }
1138}
1139
1140void PacketList::selectFramesFromOverlay(const QList<int> &frame_nums)
1141{
1142 if (!packet_list_model_) {
1143 return;
1144 }
1145
1146 // Builds one QItemSelection out of each frame's own (single-row)
1147 // selection range rather than one spanning QItemSelection(first, last):
1148 // a single range only ever expresses a contiguous rectangle in one
1149 // model's row space, so there's no way to ask it for "just these
1150 // sparse primary-view rows" directly -- each frame number (already
1151 // resolved by the caller to just the pinned rows actually within a
1152 // strip-scoped Shift-click range, see
1153 // PinnedRowView::mousePressEvent()) is resolved to its own primary-view
1154 // row here, which naturally excludes any unpinned row that happens to
1155 // sit between them there. Accumulated into one QItemSelection and
1156 // applied via a single select() call (rather than clearSelection() +
1157 // one select() per frame) so selectionChanged() -- which does real
1158 // work per call, e.g. rebuilding and emitting framesSelected() -- only
1159 // fires once for the whole batch, not once per frame.
1160 QItemSelection combined;
1161 QModelIndex last_valid_index;
1162 for (int frame_num : frame_nums) {
1163 int row = packet_list_proxy_model_->packetNumberToRow(frame_num);
1164 if (row < 0) {
1165 // Filtered out of the primary view -- no row here to add to
1166 // the shared QItemSelectionModel for it, same documented
1167 // limitation selectFrameFromOverlay() already carries for a
1168 // single filtered-out pinned row.
1169 continue;
1170 }
1171 QModelIndex row_index = model()->index(row, 0);
1172 combined.select(row_index, row_index);
1173 last_valid_index = row_index;
1174 }
1175
1176 // Matches selectRowFromOverlay()/selectFrameFromOverlay(), which both
1177 // update or clear this: stale drag-select state left over from an
1178 // earlier real click on this view itself shouldn't be mistaken for a
1179 // drag matching whatever row a later mouseMoveEvent() lands on (see
1180 // mouse_pressed_at_'s own comment and its use at line ~1431).
1181 mouse_pressed_at_ = QModelIndex();
1182 selectionModel()->select(combined, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1183
1184 if (last_valid_index.isValid()) {
1185 selectionModel()->setCurrentIndex(last_valid_index,
1186 QItemSelectionModel::NoUpdate | QItemSelectionModel::Current);
1187 }
1188 setFocus();
1189
1190 // The pinned-row views draw their own selection highlight by reading
1191 // currentFrameNum()/selectedRows() directly (see PinnedRowView::drawRow()),
1192 // rather than sharing this view's QItemSelectionModel, so updating that
1193 // state above doesn't itself trigger a repaint there -- request one
1194 // explicitly, the same way setHoveredFrameNum() already does for hover.
1195 repaintPinnedOverlays();
1196}
1197
1198void PacketList::selectFrameFromOverlay(int frame_num, Qt::KeyboardModifiers /* modifiers */)
1199{
1200 if (!cap_file_ || !packet_list_model_) {
1201 return;
1202 }
1203
1204 PacketListRecord *record = packet_list_model_->physicalRecordForFrameNum(frame_num);
1205 frame_data *fdata = record ? record->frameData() : nullptr;
1206 if (!fdata) {
1207 return;
1208 }
1209
1210 // Clear any real selection in this view's own model: the frame being
1211 // selected may not even have a row here (it can be filtered out), so
1212 // there's no index to select instead. Mirrors the "nothing selected"
1213 // path selectionChanged() takes for row < 0.
1214 selectionModel()->clearSelection();
1215 setCurrentIndex(QModelIndex());
1216 mouse_pressed_at_ = QModelIndex();
1217 setFocus();
1218
1219 // The pinned-row views draw their own selection highlight by reading
1220 // currentFrameNum()/selectedRows() directly (see PinnedRowView::drawRow()),
1221 // rather than sharing this view's QItemSelectionModel, so updating that
1222 // state above doesn't itself trigger a repaint there -- request one
1223 // explicitly, the same way setHoveredFrameNum() already does for hover.
1224 repaintPinnedOverlays();
1225
1226 // cf_select_packet() works directly against the capture file's own
1227 // frame array and is independent of the display filter/QModelIndex
1228 // entirely -- unlike selectionChanged()'s row-based path, which can
1229 // only ever resolve a frame that currently has a row in this view.
1230 cf_select_packet(cap_file_, fdata);
1231
1232 // This adds a frame to the history even if it's filtered out and not in
1233 // the main view. Such a frame will be skipped by have[Next|Previous]History
1234 // and thus go[Next|Previous]HistoryPacket, but is preserved in the history
1235 // for when the filter changes. To make goToHistory work for frames which
1236 // are filtered out but still pinned would take more work.
1237 if (cap_file_->current_frame) {
1238 updateHistory(cap_file_->current_frame->num);
1239 }
1240
1241 related_packet_delegate_.clear();
1242
1243 // The previous dissection state has been invalidated by
1244 // cf_select_packet() above; receivers must clear the previous state
1245 // and apply the updated one.
1246 emit framesSelected(QList<int>() << frame_num);
1247
1248 if (!cap_file_->edt) {
1249 viewport()->update();
1250 emit fieldSelected(0);
1251 return;
1252 }
1253
1254 if (cap_file_->edt->tree) {
1255 packet_info *pi = &cap_file_->edt->pi;
1256 related_packet_delegate_.setCurrentFrame(pi->num);
1257 conversation_t *conv = find_conversation_pinfo_ro(pi, 0);
1258 if (conv) {
1259 related_packet_delegate_.setConversation(conv);
1260 }
1261 viewport()->update();
1262 }
1263
1264 if (proto_tree_) {
1265 proto_tree_->restoreSelectedField();
1266 } else {
1267 emit fieldSelected(0);
1268 }
1269}
1270
1271void PacketList::showContextMenuForRow(int row, const QPoint &global_pos, bool from_pinned_row_strip)
1272{
1273 QModelIndex index = model()->index(row, 0);
1274 if (!index.isValid()) {
1275 return;
1276 }
1277
1278 ctx_from_pinned_row_strip_ = from_pinned_row_strip;
1279
1280 // visualRect() is this view's own geometry query, so it can't drift
1281 // the way a translated cross-view pixel position could; it guarantees
1282 // contextMenuEvent()'s own indexAt() resolves back to this exact row.
1283 QPoint local_pos = visualRect(index).center();
1284 QContextMenuEvent translated(QContextMenuEvent::Mouse, local_pos, global_pos);
1285 contextMenuEvent(&translated);
1286}
1287
1288void PacketList::showContextMenuForFrame(int frame_num, const QPoint &global_pos, bool from_pinned_row_strip)
1289{
1290 ctx_from_pinned_row_strip_ = from_pinned_row_strip;
1291
1292 // Select the frame first so cap_file_->current_frame/edt reflect it --
1293 // contextMenuEvent() falls back to cap_file_->current_frame whenever
1294 // indexAt() resolves to an invalid index (see below), which is
1295 // exactly what happens for a pinned packet with no row in this view
1296 // at all (i.e. filtered out).
1297 selectFrameFromOverlay(frame_num);
1298
1299 // A position guaranteed to resolve to an invalid index via
1300 // indexAt(), same idea as showContextMenuForRow() using a real row's
1301 // visualRect() to guarantee the opposite. contextMenuEvent() then
1302 // uses the ctx_row_fdata fallback to cap_file_->current_frame set by
1303 // selectFrameFromOverlay() above instead of a row-based lookup.
1304 QPoint local_pos(-1, -1);
1305 QContextMenuEvent translated(QContextMenuEvent::Mouse, local_pos, global_pos);
1306 contextMenuEvent(&translated);
1307}
1308
1309// header_pos must already be in packet_list_header_'s (the real, primary
1310// header's) own coordinate space by the time it reaches each forwarder
1311// below -- every caller (PinnedColumnHeader, and PacketListPane's
1312// duplicate_header_corner_/duplicate_header_main_ event filter) is
1313// responsible for translating its own local position into that space
1314// first, since each has a different relationship to it (PinnedColumnHeader
1315// and duplicate_header_corner_ both start at the frozen boundary the same
1316// way packet_list_header_'s own unscrolled columns [0, boundary) do;
1317// duplicate_header_main_ instead starts at the boundary itself and must
1318// add its own width). See PacketList::frozenHeaderPosToReal() for the
1319// shared translation helper used by PinnedColumnHeader.
1320QPoint PacketList::frozenHeaderPosToReal(const QPoint &frozen_pos) const
1321{
1322 int logical_index = pinned_column_view_->header()->logicalIndexAt(frozen_pos);
1323 if (logical_index < 0) {
1324 return frozen_pos;
1325 }
1326 int real_x = packet_list_header_->sectionViewportPosition(logical_index)
1327 + (frozen_pos.x() - pinned_column_view_->header()->sectionViewportPosition(logical_index));
1328 return QPoint(real_x, frozen_pos.y());
1329}
1330
1331void PacketList::forwardHeaderContextMenu(QContextMenuEvent *event, const QPoint &header_pos)
1332{
1333 // event->globalPos() (from the original click, wherever it actually
1334 // happened on screen) rather than packet_list_header_->mapToGlobal(
1335 // header_pos): the latter assumes the real header is on-screen at its
1336 // usual position, which isn't true when this request originated from
1337 // duplicate_header_ (PacketListPane's stand-in header shown above the
1338 // pinned-rows strip while the real one is hidden) -- that would pop
1339 // the menu up at the real header's location instead of where the user
1340 // actually right-clicked.
1341 QContextMenuEvent translated(event->reason(), header_pos, event->globalPos(), event->modifiers());
1342 packet_list_header_->showContextMenuAt(&translated);
1343}
1344
1345void PacketList::forwardHeaderMousePress(QMouseEvent *event, const QPoint &header_pos)
1346{
1347 // Temporarily disable click-to-sort on the real header: forwarding
1348 // press/release here is only meant to drive its resize-drag state
1349 // machine (needed so a frozen column's resize actually changes the
1350 // shared column width). QHeaderView's mouseReleaseEvent does its own
1351 // native click-to-sort detection as a side effect of the same event,
1352 // which would otherwise double up with the explicit
1353 // sortByColumnFromOverlay() call and cancel it out.
1354 packet_list_header_->setSectionsClickable(false);
1355 QMouseEvent translated(event->type(), header_pos, packet_list_header_->mapToGlobal(header_pos),
1356 event->button(), event->buttons(), event->modifiers());
1357 packet_list_header_->forwardMousePressEvent(&translated);
1358}
1359
1360void PacketList::forwardHeaderMouseMove(QMouseEvent *event, const QPoint &header_pos)
1361{
1362 QMouseEvent translated(event->type(), header_pos, packet_list_header_->mapToGlobal(header_pos),
1363 event->button(), event->buttons(), event->modifiers());
1364 packet_list_header_->forwardMouseMoveEvent(&translated);
1365}
1366
1367void PacketList::forwardHeaderMouseRelease(QMouseEvent *event, const QPoint &header_pos)
1368{
1369 QMouseEvent translated(event->type(), header_pos, packet_list_header_->mapToGlobal(header_pos),
1370 event->button(), event->buttons(), event->modifiers());
1371 packet_list_header_->forwardMouseReleaseEvent(&translated);
1372 packet_list_header_->setSectionsClickable(true);
1373}
1374
1375void PacketList::forwardWheelEvent(QWheelEvent *event)
1376{
1377 QTreeView::wheelEvent(event);
1378}
1379
1380void PacketList::sortByColumnFromOverlay(int column)
1381{
1382 if (!isSortingEnabled()) {
1383 return;
1384 }
1385
1386 Qt::SortOrder order = Qt::AscendingOrder;
1387 if (header()->sortIndicatorSection() == column) {
1388 order = (header()->sortIndicatorOrder() == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder;
1389 }
1390 header()->setSortIndicator(column, order);
1391
1392 // The frozen column view's own header is a separate QHeaderView, so it
1393 // needs its sort indicator (the arrow icon) set explicitly to match --
1394 // it doesn't automatically follow the primary header's indicator.
1395 if (pinned_column_view_) {
1396 pinned_column_view_->mirrorSortIndicator(column, order);
1397 }
1398}
1399
1400void PacketList::mouseMoveEvent (QMouseEvent *event)
1401{
1402 QModelIndex curIndex = indexAt(event->pos());
1403 frame_data *hovered_fdata = curIndex.isValid() ? getFDataForRow(curIndex.row()) : nullptr;
1404 int new_hovered_frame_num = hovered_fdata ? (int)hovered_fdata->num : -1;
1405 if (new_hovered_frame_num != hovered_frame_num_) {
1406 hovered_frame_num_ = new_hovered_frame_num;
1407 viewport()->update();
1408 repaintPinnedOverlays();
1409 }
1410
1411 if (event->buttons() & Qt::LeftButton && curIndex.isValid() && curIndex == mouse_pressed_at_)
1412 {
1413 startCellDrag(packet_list_model_ ? packet_list_proxy_model_->getRowFdata(curIndex.row()) : nullptr,
1414 curIndex.column(), model()->data(curIndex).toString());
1415 }
1416}
1417
1418void PacketList::startCellDragFromOverlay(int row, int column)
1419{
1420 QModelIndex idx = model()->index(row, column);
1421 if (!idx.isValid() || !packet_list_model_) {
1422 return;
1423 }
1424 startCellDrag(packet_list_proxy_model_->getRowFdata(row), column, model()->data(idx).toString());
1425}
1426
1427void PacketList::startCellDragForFrameFromOverlay(int frame_num, int column)
1428{
1429 if (!packet_list_model_) {
1
Assuming field 'packet_list_model_' is non-null
2
Taking false branch
1430 return;
1431 }
1432 PacketListRecord *record = packet_list_model_->physicalRecordForFrameNum(frame_num);
1433 startCellDrag(record ? record->frameData() : nullptr, column, QString());
3
Assuming 'record' is non-null
4
'?' condition is true
5
Calling 'PacketList::startCellDrag'
1434}
1435
1436void PacketList::startCellDrag(frame_data *fdata, int column, const QString &cell_text)
1437{
1438 ctx_column_ = column;
1439 QMimeData * mimeData = new QMimeData();
1440 DragLabel * drag_label = nullptr;
1441
1442 QString filter = getFilterFromFdataAndColumn(fdata, column);
1443 QList<int> rows = selectedRows();
1444 if (rows.count() > 1)
6
Assuming the condition is false
7
Taking false branch
1445 {
1446 QStringList entries;
1447 foreach (int row, rows)for (auto _container_1447 = QtPrivate::qMakeForeachContainer(
rows); _container_1447.i != _container_1447.e; ++_container_1447
.i) if (int row = *_container_1447.i; false) {} else
1448 {
1449 QModelIndex idx = model()->index(row, 0);
1450 if (! idx.isValid())
1451 continue;
1452
1453 QString entry = createSummaryText(idx, CopyAsText);
1454 entries << entry;
1455 }
1456
1457 if (entries.count() > 0)
1458 mimeData->setText(entries.join("\n"));
1459 }
1460 else if (! filter.isEmpty())
8
Assuming the condition is true
9
Taking true branch
1461 {
1462 QString name = model()->headerData(column, header()->orientation()).toString();
1463 QString abbrev = filter.left(filter.indexOf(' '));
1464
1465 mimeData->setText(filter);
1466
1467 QJsonObject filterData;
1468 filterData["filter"] = filter;
1469 filterData["name"] = abbrev;
1470 filterData["description"] = name;
1471
1472 mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
1473 drag_label = new DragLabel(QStringLiteral("%1\n%2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1\n%2"))).arg(name, abbrev), this);
10
Memory is allocated
1474 }
1475 else if (! cell_text.isEmpty())
1476 {
1477 mimeData->setText(cell_text);
1478 }
1479
1480 if (mimeData->hasText() || mimeData->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
11
Assuming the condition is false
12
Assuming the condition is false
13
Taking false branch
1481 {
1482 QDrag * drag = new QDrag(this);
1483 drag->setMimeData(mimeData);
1484 if (drag_label)
1485 {
1486 qreal dpr = window()->windowHandle()->devicePixelRatio();
1487 QPixmap pixmap= QPixmap(drag_label->size() * dpr);
1488 pixmap.setDevicePixelRatio(dpr);
1489 drag_label->render(&pixmap);
1490 drag->setPixmap(pixmap);
1491 delete drag_label;
1492 }
1493
1494 drag->exec(Qt::CopyAction);
1495 }
1496 else
1497 {
1498 delete mimeData;
14
Potential leak of memory pointed to by 'drag_label'
1499 }
1500}
1501
1502void PacketList::leaveEvent(QEvent *event)
1503{
1504 QTreeView::leaveEvent(event);
1505
1506 if (hovered_frame_num_ != -1) {
1507 hovered_frame_num_ = -1;
1508 viewport()->update();
1509 repaintPinnedOverlays();
1510 }
1511}
1512
1513void PacketList::setHoveredRowFromOverlay(int row)
1514{
1515 frame_data *fdata = (row >= 0) ? getFDataForRow(row) : nullptr;
1516 setHoveredFrameNum(fdata ? (int)fdata->num : -1);
1517}
1518
1519void PacketList::setHoveredFrameNum(int frame_num)
1520{
1521 if (frame_num == hovered_frame_num_) {
1522 return;
1523 }
1524
1525 hovered_frame_num_ = frame_num;
1526 viewport()->update();
1527 repaintPinnedOverlays();
1528}
1529
1530void PacketList::keyPressEvent(QKeyEvent *event)
1531{
1532 // XXX Add support for Qt::Key_F7 and Qt::Key_F8
1533 bool nav_key = (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down);
1534
1535 if (nav_key && event->isAutoRepeat()) {
1536 Qt::Key key = static_cast<Qt::Key>(event->key());
1537
1538 if (turbo_timer_id_ != 0 && key == turbo_key_) {
1539 // We're handling autorepeat ourselves.
1540 event->accept();
1541 return;
1542 }
1543
1544 if (turbo_timer_id_ == 0 && key == held_key_ && key_hold_elapsed_.isValid() &&
1545 key_hold_elapsed_.hasExpired(turbo_activation_delay_)) {
1546 turbo_key_ = key;
1547 turbo_timer_id_ = startTimer(turbo_nav_interval_);
1548 event->accept();
1549 return;
1550 }
1551 }
1552
1553 QTreeView::keyPressEvent(event);
1554
1555 if (nav_key && !event->isAutoRepeat()) {
1556 held_key_ = static_cast<Qt::Key>(event->key());
1557 key_hold_elapsed_.start();
1558 }
1559
1560 if (event->matches(QKeySequence::Copy))
1561 {
1562 QStringList content, htmlContent;
1563 if (model() && selectionModel() && selectionModel()->hasSelection())
1564 {
1565 QList<int> rows;
1566 QModelIndexList selRows = selectionModel()->selectedRows(0);
1567 foreach(QModelIndex row, selRows)for (auto _container_1567 = QtPrivate::qMakeForeachContainer(
selRows); _container_1567.i != _container_1567.e; ++_container_1567
.i) if (QModelIndex row = *_container_1567.i; false) {} else
1568 rows.append(row.row());
1569
1570 QStringList hdr_parts;
1571 QList<int> align_parts, size_parts;
1572
1573 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
1574 case COPY_FORMAT_TEXT:
1575 case COPY_FORMAT_HTML:
1576 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
1577 hdr_parts = createHeaderPartsForAligned();
1578 align_parts = createAlignmentPartsForAligned();
1579 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
1580 }
1581 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
1582 htmlContent << createDefaultStyleForHtml();
1583 htmlContent << createOpeningTagForHtml();
1584 }
1585 break;
1586 case COPY_FORMAT_CSV:
1587 case COPY_FORMAT_YAML:
1588 break;
1589 }
1590
1591 QList<QStringList> entries;
1592 foreach(int row, rows)for (auto _container_1592 = QtPrivate::qMakeForeachContainer(
rows); _container_1592.i != _container_1592.e; ++_container_1592
.i) if (int row = *_container_1592.i; false) {} else
1593 {
1594 QModelIndex idx = model()->index(row, 0);
1595 if (! idx.isValid())
1596 continue;
1597
1598 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
1599 case COPY_FORMAT_TEXT:
1600 case COPY_FORMAT_HTML:
1601 if (prefs.gui_packet_list_copy_text_with_aligned_columns)
1602 content << createSummaryForAligned(idx, align_parts, size_parts);
1603 else
1604 content << createSummaryText(idx, CopyAsText);
1605 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML)
1606 htmlContent << createSummaryForHtml(idx);
1607 break;
1608 case COPY_FORMAT_CSV:
1609 content << createSummaryText(idx, CopyAsCSV);
1610 break;
1611 case COPY_FORMAT_YAML:
1612 content << createSummaryText(idx, CopyAsYAML);
1613 break;
1614 }
1615 }
1616 }
1617
1618 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
1619 // htmlContent will never be empty as they will always have style and table tags
1620 QMimeData *mimeData = new QMimeData;
1621 htmlContent << createClosingTagForHtml();
1622 mimeData->setHtml(htmlContent.join('\n'));
1623 mimeData->setText(content.join('\n').append("\n"));
1624 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1625 }
1626 else {
1627 if (content.count() > 0) {
1628 QString copy_text;
1629 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_YAML) {
1630 copy_text = content.join("");
1631 }
1632 else {
1633 copy_text = content.join("\n");
1634 copy_text += "\n";
1635 }
1636 mainApp->clipboard()->setText(copy_text);
1637 }
1638 }
1639 }
1640}
1641
1642void PacketList::keyReleaseEvent(QKeyEvent *event)
1643{
1644 if (!event->isAutoRepeat() && event->key() == held_key_) {
1645 if (turbo_timer_id_ != 0 && event->key() == turbo_key_) {
1646 killTimer(turbo_timer_id_);
1647 turbo_timer_id_ = 0;
1648 }
1649 held_key_ = Qt::Key(0);
1650 key_hold_elapsed_.invalidate();
1651 }
1652
1653 QTreeView::keyReleaseEvent(event);
1654}
1655
1656void PacketList::focusInEvent(QFocusEvent *event)
1657{
1658 QTreeView::focusInEvent(event);
1659
1660 if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) {
1661 if (model() && model()->rowCount() > 0 && selectionModel()) {
1662 if (!selectionModel()->hasSelection()) {
1663 QModelIndex first = model()->index(0, 0);
1664 if (first.isValid()) {
1665 selectionModel()->setCurrentIndex(first, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1666 setCurrentIndex(first);
1667 }
1668 }
1669
1670 // ALWAYS scroll to the current index if we have one
1671 if (currentIndex().isValid()) {
1672 scrollTo(currentIndex());
1673 }
1674 }
1675 }
1676}
1677
1678void PacketList::focusOutEvent(QFocusEvent *event)
1679{
1680 if (turbo_timer_id_ != 0) {
1681 killTimer(turbo_timer_id_);
1682 turbo_timer_id_ = 0;
1683 }
1684 held_key_ = Qt::Key(0);
1685 key_hold_elapsed_.invalidate();
1686
1687 QTreeView::focusOutEvent(event);
1688}
1689
1690void PacketList::resizeEvent(QResizeEvent *event)
1691{
1692 create_near_overlay_ = true;
1693 create_far_overlay_ = true;
1694 QTreeView::resizeEvent(event);
1695 layoutPinnedOverlays();
1696}
1697
1698void PacketList::setColumnVisibility()
1699{
1700 set_column_visibility_ = true;
1701 for (unsigned i = 0; i < prefs.num_cols; i++) {
1702 bool hidden = get_column_visible(i) ? false : true;
1703 bool frozen = isColumnFrozen((int)i, pinned_column_boundary_);
1704 setColumnHidden(i, hidden);
1705 if (pinned_column_view_) {
1706 pinned_column_view_->setColumnHidden(i, hidden || !frozen);
1707 }
1708 if (pinned_row_view_) {
1709 pinned_row_view_->setColumnHidden(i, hidden || frozen);
1710 }
1711 if (pinned_row_corner_view_) {
1712 pinned_row_corner_view_->setColumnHidden(i, hidden || !frozen);
1713 }
1714 emit columnHiddenChanged(i, hidden);
1715 }
1716 setColumnDelegate();
1717 set_column_visibility_ = false;
1718}
1719
1720void PacketList::setColumnDelegate()
1721{
1722 for (unsigned i = 0; i < prefs.num_cols; i++) {
1723 setItemDelegateForColumn(i, nullptr); // Reset all delegates
1724 }
1725
1726 // Multi-color delegate takes precedence over related packet delegate (row stripes only, not scrollbar-only mode)
1727 if (prefs.gui_packet_list_multi_color_mode == PACKET_LIST_MULTI_COLOR_MODE_FULL ||
1728 prefs.gui_packet_list_multi_color_mode == PACKET_LIST_MULTI_COLOR_MODE_SHIFT_RIGHT) {
1729 for (unsigned i = 0; i < prefs.num_cols; i++) {
1730 if (get_column_visible(i)) {
1731 setItemDelegateForColumn(i, &multi_color_delegate_);
1732 }
1733 }
1734 }
1735 else if (prefs.gui_packet_list_show_related) {
1736 for (unsigned i = 0; i < prefs.num_cols; i++) {
1737 if (get_column_visible(i)) {
1738 setItemDelegateForColumn(i, &related_packet_delegate_);
1739 break; // Set the delegate only on the first visible column
1740 }
1741 }
1742 }
1743
1744 // Tag column delegate is installed on every COL_TAG column regardless of other delegates
1745 for (unsigned i = 0; i < prefs.num_cols; i++) {
1746 if (get_column_format(i) == COL_TAG) {
1747 setItemDelegateForColumn(i, &tag_column_delegate_);
1748 }
1749 }
1750
1751 auto row_height = [this]() { return pinnedRowHeight(); };
1752 for (QTreeView *view : pinnedOverlayViews()) {
1753 for (unsigned i = 0; i < prefs.num_cols; i++) {
1754 view->setItemDelegateForColumn(i, nullptr);
1755 }
1756 QList<QObject *> old_wrappers;
1757 for (QObject *child : view->children()) {
1758 if (dynamic_cast<PinnedOverlayView::FixedRowHeightDelegate *>(child)) {
1759 old_wrappers << child;
1760 }
1761 }
1762 view->setItemDelegate(new PinnedOverlayView::FixedRowHeightDelegate(nullptr, row_height, view));
1763 qDeleteAll(old_wrappers);
1764 for (unsigned i = 0; i < prefs.num_cols; i++) {
1765 QAbstractItemDelegate *col_delegate = itemDelegateForColumn(i);
1766 if (col_delegate) {
1767 view->setItemDelegateForColumn(i, new PinnedOverlayView::FixedRowHeightDelegate(col_delegate, row_height, view));
1768 }
1769 }
1770 }
1771}
1772
1773void PacketList::setRecentColumnWidth(int col)
1774{
1775 int col_width = recent_get_column_width(col);
1776
1777 if (col_width < 1) {
1778 int fmt = get_column_format(col);
1779 const char *long_str = get_column_width_string(fmt, col);
1780
1781 // TODO: a column's natural width is model data, not a view concern.
1782 // PacketListModel should hint it instead of the view measuring against
1783 // the font here.
1784 QFontMetrics fm = QFontMetrics(FontManager::monospaceFont());
1785 if (long_str) {
1786 col_width = fm.horizontalAdvance(long_str);
1787 } else {
1788 col_width = fm.horizontalAdvance(MIN_COL_WIDTH_STR"MMMMMM");
1789 }
1790 // Custom delegate padding
1791 if (itemDelegateForColumn(col)) {
1792 QStyleOptionViewItem option;
1793 initViewItemOption(&option);
1794 // This is adding "how much width hinted for an empty index, plus
1795 // the decoration, plus any padding between the decoration and
1796 // normal display?" Many styles however have a non zero hint for
1797 // an empty index, so this isn't quite right. What we really want
1798 // is a size hint for an index whose data is the string above, and
1799 // to just use that for the width.
1800 col_width += itemDelegateForColumn(col)->sizeHint(option, QModelIndex()).width();
1801 }
1802 }
1803
1804 setColumnWidth(col, col_width);
1805}
1806
1807void PacketList::drawCurrentPacket(bool scroll)
1808{
1809 // Update the dissection details for the current frame.
1810 //
1811 // It might make sense to call this from dataChanged if a single frame
1812 // is selected and its index is among those changed, instead of, e.g.,
1813 // markFrame() having to call this directly. Note however that
1814 // PacketListModel calls layoutChanged instead of dataChanged due
1815 // to a Qt 6 bug (that appears to have been mitigated in Qt 6.9),
1816 // so we perhaps have to listen to LayoutChanged as well.
1817 if (!cap_file_) return;
1818
1819 int row = -1;
1820 static bool multiSelect = false;
1821 QList<int> selectedFrames;
1822
1823 if (selectionModel())
1824 {
1825 QModelIndexList selRows = selectionModel()->selectedRows(0);
1826 if (selRows.count() > 1)
1827 {
1828 if (packet_list_model_) {
1829 foreach (QModelIndex idx, selRows)for (auto _container_1829 = QtPrivate::qMakeForeachContainer(
selRows); _container_1829.i != _container_1829.e; ++_container_1829
.i) if (QModelIndex idx = *_container_1829.i; false) {} else
1830 {
1831 frame_data * fdata = packet_list_proxy_model_->getRowFdata(idx);
1832 if (fdata)
1833 selectedFrames << fdata->num;
1834 }
1835 }
1836
1837 emit framesSelected(selectedFrames);
1838 emit fieldSelected(0);
1839 cf_unselect_packet(cap_file_);
1840
1841 /* We have to repaint the content while changing state, as some delegates react to multi-select */
1842 if (! multiSelect)
1843 {
1844 related_packet_delegate_.clear();
1845 viewport()->update();
1846 }
1847
1848 multiSelect = true;
1849
1850 return;
1851 }
1852 else if (selRows.count() > 0 && selRows.at(0).isValid())
1853 {
1854 /* One row selected. Note this is *not* necessarily the row of the
1855 * currentIndex, if two rows were selected and one was deselected
1856 * via Ctrl-Click. Then the currentIndex is the deselected row. */
1857 multiSelect = false;
1858 row = selRows.at(0).row();
1859 if (scroll) {
1860 if (currentIndex().siblingAtColumn(0) != selRows.at(0)) {
1861 // Not the currentIndex. Set the current index, which will
1862 // scroll to it. Use NoUpdate to not change the selection
1863 // and possibly create a loop.
1864 selectionModel()->setCurrentIndex(selRows.at(0), QItemSelectionModel::NoUpdate);
1865 } else {
1866 // The currentIndex. Scroll to it if necessary.
1867 scrollTo(currentIndex());
1868 }
1869 }
1870 }
1871
1872 /* Handling empty selection */
1873 if (selRows.count() <= 0)
1874 {
1875 /* Nothing selected, but multiSelect is still active */
1876 if (multiSelect)
1877 {
1878 multiSelect = false;
1879 if (currentIndex().isValid())
1880 {
1881 selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
1882 return;
1883 }
1884 }
1885 /* Nothing selected, so in WS <= 3.0 nothing was indicated as well */
1886 else if (currentIndex().isValid())
1887 {
1888 setCurrentIndex(QModelIndex());
1889 }
1890 }
1891 }
1892
1893 if (row < 0 || !packet_list_model_)
1894 cf_unselect_packet(cap_file_);
1895 else {
1896 frame_data * fdata = packet_list_proxy_model_->getRowFdata(row);
1897 cf_select_packet(cap_file_, fdata);
1898 if (fdata)
1899 selectedFrames << fdata->num;
1900 }
1901
1902 if (cap_file_->current_frame) {
1903 // Add this to the end of the history if it's not already
1904 updateHistory(cap_file_->current_frame->num);
1905 }
1906
1907 related_packet_delegate_.clear();
1908
1909 // The previous dissection state has been invalidated by cf_select_packet
1910 // above, receivers must clear the previous state and apply the updated one.
1911 emit framesSelected(selectedFrames);
1912
1913 if (!cap_file_->edt) {
1914 viewport()->update();
1915 emit fieldSelected(0);
1916 return;
1917 }
1918
1919 if (cap_file_->edt->tree) {
1920 packet_info *pi = &cap_file_->edt->pi;
1921 related_packet_delegate_.setCurrentFrame(pi->num);
1922 conversation_t *conv = find_conversation_pinfo_ro(pi, 0);
1923 if (conv) {
1924 related_packet_delegate_.setConversation(conv);
1925 }
1926 viewport()->update();
1927 }
1928
1929 // Figure out what field should be selected.
1930 // XXX - All this should be done in ProtoTree in response to framesSelected
1931 if (cap_file_->search_in_progress) {
1932 field_info *fi = NULL__null;
1933
1934 if (cap_file_->string && cap_file_->decode_data) {
1935 // The tree where the target string matched one of the labels was discarded in
1936 // match_protocol_tree() so we have to search again in the latest tree.
1937 fi = cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree);
1938 } else if (cap_file_->search_len != 0) {
1939 // Find the finfo that corresponds to our byte.
1940 // The match can span multiple fields (and a single byte can
1941 // match more than one field.) Our behavior is to find the last
1942 // field in the tree (so hopefully spanning fewer bytes) that
1943 // matches the last byte in the search match.
1944 // (regex search can find a zero length match not at the
1945 // start of the frame if lookbehind is used, but
1946 // proto_find_field_from_offset doesn't match such a field
1947 // and it's not clear which field we would want to match.)
1948 fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos + cap_file_->search_len - 1,
1949 cap_file_->edt->tvb);
1950 }
1951
1952 if (fi) {
1953 FieldInformation finfo(fi, this);
1954 emit fieldSelected(&finfo);
1955 } else {
1956 emit fieldSelected(0);
1957 }
1958 } else if (proto_tree_) {
1959 proto_tree_->restoreSelectedField();
1960 }
1961}
1962
1963// Redraw the packet list and detail.
1964// Causes the UI to scroll to the current packet.
1965// Called from many places.
1966void PacketList::redrawVisiblePackets() {
1967 packet_list_model_->invalidateAllColumnStrings();
1968 drawCurrentPacket();
1969}
1970
1971// Redraw the packet list and detail.
1972// Does not scroll back to the selected packet.
1973void PacketList::redrawVisiblePacketsDontSelectCurrent() {
1974 packet_list_model_->invalidateAllColumnStrings();
1975 drawCurrentPacket(false);
1976}
1977
1978void PacketList::resetColumns()
1979{
1980 packet_list_model_->resetColumns();
1981}
1982
1983// Return true if we have a visible packet further along in the history.
1984bool PacketList::haveNextHistory(bool update_cur)
1985{
1986 if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
1987 return false;
1988 }
1989
1990 for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
1991 if (packet_list_proxy_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1992 if (update_cur) {
1993 cur_history_ = i;
1994 }
1995 return true;
1996 }
1997 }
1998 return false;
1999}
2000
2001// Return true if we have a visible packet back in the history.
2002bool PacketList::havePreviousHistory(bool update_cur)
2003{
2004 if (selection_history_.size() < 1 || cur_history_ < 1) {
2005 return false;
2006 }
2007
2008 for (int i = cur_history_ - 1; i >= 0; i--) {
2009 if (packet_list_proxy_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
2010 if (update_cur) {
2011 cur_history_ = i;
2012 }
2013 return true;
2014 }
2015 }
2016 return false;
2017}
2018
2019void PacketList::updateHistory(int frame_num)
2020{
2021 // We want to call this whenever cap_file_->current_frame changes.
2022 // (We don't keep a history of frames that were selected as part of
2023 // multiselect.) That is not the same thing as the currentIndex. First,
2024 // because the pinned rows can set a frame as the current frame that
2025 // isn't in the main view (and thus never becomes the currentIndex.)
2026 // Secondly, because with ExtendedSelection an index can become the
2027 // currentIndex by being in a multi-selection and then *deselected*
2028 // with Ctrl-Click. (Also, if two frames were selected, the remaining
2029 // frame then becomes cap_file_->current_frame but not CurrentIndex.)
2030 if (in_history_)
2031 return;
2032
2033 if (!selection_history_.isEmpty()) {
2034 if (frame_num == selection_history_.constLast()) {
2035 return;
2036 }
2037 }
2038
2039 cur_history_++;
2040 selection_history_.append(frame_num);
2041}
2042
2043void PacketList::setProfileSwitcher(ProfileSwitcher *profile_switcher)
2044{
2045 profile_switcher_ = profile_switcher;
2046 if (profile_switcher) {
2047 connect(packet_list_model_, &PacketListModel::packetAppended, profile_switcher_, &ProfileSwitcher::checkPacket);
2048 }
2049}
2050
2051frame_data *PacketList::getFDataForRow(int row) const
2052{
2053 return packet_list_proxy_model_->getRowFdata(row);
2054}
2055
2056// prefs.col_list has changed.
2057void PacketList::columnsChanged()
2058{
2059 columns_changed_ = true;
2060 column_register_fields();
2061 mainApp->emitAppSignal(MainApplication::FieldsChanged);
2062 if (!cap_file_) {
2063 // Keep columns_changed_ = true until we load a capture file.
2064 return;
2065 }
2066
2067 prefs.num_cols = g_list_length(prefs.col_list);
2068 col_cleanup(&cap_file_->cinfo);
2069 build_column_format_array(&cap_file_->cinfo, prefs.num_cols, false);
2070 create_far_overlay_ = true;
2071 resetColumns();
2072 applyRecentColumnWidths();
2073 setColumnVisibility();
2074 // Freezing is defined purely positionally ("the first N columns"), and
2075 // this signal doesn't say which column(s) actually changed -- a
2076 // removal, addition, or reorder can each change which logical columns
2077 // now sit at positions [0, pinned_column_boundary_), so there's no way
2078 // to tell from here whether the frozen set still means what the user
2079 // last chose. Unfreezing unconditionally is conservative (it also
2080 // clears a freeze that a change elsewhere in the column list didn't
2081 // actually affect), but guarantees the frozen set is never silently
2082 // wrong. setPinnedColumnBoundary() also re-syncs the pinned overlays
2083 // and duplicate header pair to match.
2084 if (pinned_column_boundary_ > 0) {
2085 setPinnedColumnBoundary(0);
2086 }
2087 columns_changed_ = false;
2088}
2089
2090// Fields have changed, update custom columns
2091void PacketList::fieldsChanged(capture_file *cf)
2092{
2093 // hfids used by custom columns or by the _ws.col fields may have changed,
2094 // so recreate the columns. We shouldn't need to if prefs.col_list is NULL,
2095 // since that doesn't register and deregister _ws.col fields.
2096 // If the column pref changes to or from NULL, that triggers columnsChanged
2097 // above, so we don't need to do it twice.
2098 //
2099 // XXX - If we knew exactly which fields changed, we could rebuild the
2100 // columns only if a field used by the columns changed.
2101 if (prefs.col_list) {
2102 prefs.num_cols = g_list_length(prefs.col_list);
2103 col_cleanup(&cf->cinfo);
2104 build_column_format_array(&cf->cinfo, prefs.num_cols, false);
2105 resetColumns();
2106 }
2107}
2108
2109// Column widths should
2110// - Load from recent when we load a new profile (including at starting up).
2111// - Reapply when changing columns.
2112// - Persist across freezes and thaws.
2113// - Persist across file closing and opening.
2114// - Save to recent when we save our profile (including shutting down).
2115// - Not be affected by the behavior of stretchLastSection. (XXX: We
2116// still save the stretched value to recent, sectionResized doesn't
2117// distinguish between a resize from being stretched and a manual change.)
2118void PacketList::applyRecentColumnWidths()
2119{
2120 // Either we've just started up or a profile has changed. Read
2121 // the recent settings, apply them, and save the header state.
2122
2123 for (unsigned col = 0; col < prefs.num_cols; col++) {
2124 // The column must be shown before setting column width.
2125 // Visibility will be updated in setColumnVisibility().
2126 setColumnHidden(col, false);
2127 setRecentColumnWidth(col);
2128 }
2129}
2130
2131void PacketList::preferencesChanged()
2132{
2133 // Previously driven by the separate MainApplication::colorsChanged
2134 // signal, which has been removed — every colorsChanged emit was
2135 // paired with a preferencesChanged emit anyway. Rebuilding the
2136 // selection stylesheet on every pref change is cheap (single
2137 // setStyleSheet call) and idempotent.
2138 colorsChanged();
2139
2140 // Intelligent scroll bar (minimap)
2141 if (prefs.gui_packet_list_show_minimap) {
2142 if (overlay_timer_id_ == 0) {
2143 overlay_timer_id_ = startTimer(overlay_update_interval_);
2144 }
2145 } else {
2146 if (overlay_timer_id_ != 0) {
2147 killTimer(overlay_timer_id_);
2148 overlay_timer_id_ = 0;
2149 }
2150 }
2151
2152 // Elide mode.
2153 // This sets the mode for the entire view. If we want to make this setting
2154 // per-column we'll either have to generalize RelatedPacketDelegate so that
2155 // we can set it for entire rows or create another delegate.
2156 Qt::TextElideMode elide_mode = Qt::ElideRight;
2157 switch (prefs.gui_packet_list_elide_mode) {
2158 case ELIDE_LEFT:
2159 elide_mode = Qt::ElideLeft;
2160 break;
2161 case ELIDE_MIDDLE:
2162 elide_mode = Qt::ElideMiddle;
2163 break;
2164 case ELIDE_NONE:
2165 elide_mode = Qt::ElideNone;
2166 break;
2167 default:
2168 break;
2169 }
2170 setTextElideMode(elide_mode);
2171}
2172
2173void PacketList::freezePacketList(bool changing_profile)
2174{
2175 changing_profile_ = changing_profile;
2176 freeze(true);
2177}
2178
2179void PacketList::recolorPackets()
2180{
2181 packet_list_model_->resetColorized();
2182 redrawVisiblePackets();
2183}
2184
2185// Enable autoscroll.
2186void PacketList::setVerticalAutoScroll(bool enabled)
2187{
2188 tail_at_end_ = enabled;
2189 if (enabled && capture_in_progress_) {
2190 scrollToBottom();
2191 }
2192}
2193
2194// Called when we finish reading, reloading, rescanning, and retapping
2195// packets.
2196void PacketList::captureFileReadFinished()
2197{
2198 packet_list_proxy_model_->flushVisibleRows();
2199 packet_list_proxy_model_->dissectIdle(true);
2200 // Invalidating the column strings picks up and request/response
2201 // tracking changes. We might just want to call it from flushVisibleRows.
2202 packet_list_model_->invalidateAllColumnStrings();
2203 // Sort *after* invalidating the column strings
2204 if (isSortingEnabled()) {
2205 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
2206 }
2207}
2208
2209bool PacketList::freeze(bool keep_current_frame)
2210{
2211 if (!cap_file_ || model() == Q_NULLPTRnullptr) {
2212 // No capture file or already frozen
2213 return false;
2214 }
2215
2216 frame_data *current_frame = cap_file_->current_frame;
2217 column_state_ = header()->saveState();
2218 setHeaderHidden(true);
2219 frozen_current_row_ = currentIndex();
2220 frozen_selected_rows_ = selectionModel()->selectedRows();
2221 selectionModel()->clear();
2222 setModel(Q_NULLPTRnullptr);
2223 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
2224 // call selectionChanged.
2225 related_packet_delegate_.clear();
2226
2227 if (keep_current_frame) {
2228 cap_file_->current_frame = current_frame;
2229 }
2230
2231 /* Clears packet list as well as byteview */
2232 emit framesSelected(QList<int>());
2233
2234 return true;
2235}
2236
2237bool PacketList::thaw(bool restore_selection)
2238{
2239 if (!cap_file_ || model() != Q_NULLPTRnullptr) {
2240 // No capture file or not frozen
2241 return false;
2242 }
2243
2244 setHeaderHidden(false);
2245 // Note that if we have a current sort status set in the header,
2246 // this will automatically try to sort the model (we don't want
2247 // that to happen if we're in the middle of reading the file).
2248 setModel(packet_list_proxy_model_);
2249
2250 // setModel() always creates a brand-new default selection model, even
2251 // when passed the same model pointer, discarding the one we'd shared
2252 // with pinned_column_view_ back in the constructor. Re-share it so its
2253 // selection highlighting doesn't silently go stale. (The pinned-row
2254 // overlays use a separate proxy model -- PinnedRowsModel -- and so
2255 // never shared this selection model to begin with.)
2256 if (pinned_column_view_) {
2257 pinned_column_view_->setSelectionModel(selectionModel());
2258 }
2259
2260 if (changing_profile_) {
2261 // When changing profile the new recent settings must be applied to the columns.
2262 applyRecentColumnWidths();
2263 setColumnVisibility();
2264 changing_profile_ = false;
2265 } else {
2266 // Resetting the model resets our column widths so we restore them here.
2267 // We don't reapply the recent settings because the user could have
2268 // resized the columns manually since they were initially loaded.
2269 header()->restoreState(column_state_);
2270 }
2271
2272 if (restore_selection && frozen_selected_rows_.length() > 0 && selectionModel()) {
2273 /* This updates our selection, which redissects the current packet,
2274 * which is needed when we're called from MainWindow::layoutPanes.
2275 * Also, this resets all ProtoTree and ByteView data */
2276 clearSelection();
2277 setCurrentIndex(frozen_current_row_);
2278 foreach (QModelIndex idx, frozen_selected_rows_)for (auto _container_2278 = QtPrivate::qMakeForeachContainer(
frozen_selected_rows_); _container_2278.i != _container_2278.
e; ++_container_2278.i) if (QModelIndex idx = *_container_2278
.i; false) {} else
{
2279 selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
2280 }
2281 scrollTo(currentIndex(), PositionAtCenter);
2282 }
2283 frozen_current_row_ = QModelIndex();
2284 frozen_selected_rows_ = QModelIndexList();
2285
2286 // setHeaderHidden(false) above unconditionally re-shows the native
2287 // header, but if rows are still pinned, PacketListPane needs the
2288 // native header hidden again in favor of its duplicate header pair.
2289 // Resync now so that invariant holds after every freeze/thaw cycle
2290 // (filter apply, reload, retap), not just after pin/unpin.
2291 updatePinnedRowVisibility();
2292
2293 return true;
2294}
2295
2296void PacketList::clear() {
2297 // Cleared before packet_list_model_ below: that call emits modelReset,
2298 // which updatePinnedRowVisibility() (connected to it) reacts to by
2299 // calling pinned_rows_model_->refresh() -- resolving pinned frame
2300 // numbers against packet_list_model_ while it's already been cleared,
2301 // if pinned_rows_model_ itself hadn't been cleared first. Every
2302 // consumer of that resolution currently null-checks defensively, so
2303 // this ordering wasn't otherwise observably wrong, but doing the
2304 // pinned-state clear first removes the transient window entirely
2305 // rather than relying on those checks to mask it.
2306 pinned_rows_model_->clear();
2307 setPinnedColumnBoundary(0);
2308
2309 related_packet_delegate_.clear();
2310 selectionModel()->clear();
2311 packet_list_model_->clear();
2312 proto_tree_->clear();
2313 selection_history_.clear();
2314 cur_history_ = -1;
2315 in_history_ = false;
2316
2317 QImage overlay;
2318 overlay_sb_->setNearOverlayImage(overlay);
2319 overlay_sb_->setMarkedPacketImage(overlay);
2320 create_near_overlay_ = true;
2321 create_far_overlay_ = true;
2322
2323 updatePinnedRowVisibility();
2324}
2325
2326void PacketList::writeRecent(FILE *rf) {
2327 int width, col_fmt;
2328 char xalign;
2329
2330 fprintf (rf, "%s:\n", RECENT_KEY_COL_WIDTH"column.width");
2331 for (unsigned col = 0; col < prefs.num_cols; col++) {
2332 if (col > 0) {
2333 fprintf (rf, ",\n");
2334 }
2335 col_fmt = get_column_format(col);
2336 if (col_fmt == COL_CUSTOM) {
2337 fprintf (rf, " \"%%Cus:%s\",", get_column_custom_fields(col));
2338 } else {
2339 fprintf (rf, " %s,", col_format_to_string(col_fmt));
2340 }
2341 width = recent_get_column_width (col);
2342 xalign = recent_get_column_xalign (col);
2343 fprintf (rf, " %d", width);
2344 if (xalign != COLUMN_XALIGN_DEFAULT0) {
2345 fprintf (rf, ":%c", xalign);
2346 }
2347 }
2348 fprintf (rf, "\n");
2349}
2350
2351bool PacketList::contextMenuActive()
2352{
2353 return ctx_column_ >= 0 ? true : false;
2354}
2355
2356QString PacketList::getFilterFromRowAndColumn(QModelIndex idx)
2357{
2358 if (! idx.isValid() || !packet_list_model_)
2359 return QString();
2360
2361 return getFilterFromFdataAndColumn(packet_list_proxy_model_->getRowFdata(idx.row()), idx.column());
2362}
2363
2364QString PacketList::getFilterFromFdataAndColumn(frame_data *fdata, int column)
2365{
2366 QString filter;
2367
2368 if (!cap_file_ || !packet_list_model_ || column < 0 || (unsigned)column >= cap_file_->cinfo.num_cols)
2369 return filter;
2370
2371 if (fdata != NULL__null) {
2372 epan_dissect_t edt;
2373 wtap_rec rec; /* Record information */
2374
2375 wtap_rec_init(&rec, DEFAULT_INIT_BUFFER_SIZE_2048(2 * 1024));
2376 if (!cf_read_record(cap_file_, fdata, &rec)) {
2377 wtap_rec_cleanup(&rec);
2378 return filter; /* error reading the record */
2379 }
2380 /* proto tree, visible. We need a proto tree if there's custom columns */
2381 epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), false);
2382 col_custom_prime_edt(&edt, &cap_file_->cinfo);
2383
2384 epan_dissect_run(&edt, cap_file_->cd_t, &rec, fdata, &cap_file_->cinfo);
2385
2386 if (cap_file_->cinfo.columns[column].col_fmt == COL_CUSTOM) {
2387 filter.append(gchar_free_to_qstring(col_custom_get_filter(&edt, &cap_file_->cinfo, (unsigned)column)));
2388 } else {
2389 /* We don't need to fill in the custom columns, as we get their
2390 * filters above.
2391 */
2392 col_fill_in(&edt.pi, true, true);
2393 if (strlen(cap_file_->cinfo.col_expr.col_expr[column]) != 0 &&
2394 strlen(cap_file_->cinfo.col_expr.col_expr_val[column]) != 0) {
2395 bool is_string_value = false;
2396 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.col_expr.col_expr[column]);
2397 if (hfi && FT_IS_STRING(hfi->type)((hfi->type) == FT_STRING || (hfi->type) == FT_STRINGZ ||
(hfi->type) == FT_STRINGZPAD || (hfi->type) == FT_STRINGZTRUNC
|| (hfi->type) == FT_UINT_STRING || (hfi->type) == FT_AX25
)
) {
2398 /* Could be an address type such as usb.src which must be quoted. */
2399 is_string_value = true;
2400 }
2401
2402 if (filter.isEmpty()) {
2403 if (is_string_value) {
2404 filter.append(QStringLiteral("%1 == \"%2\"")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 == \"%2\"")))
2405 .arg(cap_file_->cinfo.col_expr.col_expr[column])
2406 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
2407 } else {
2408 filter.append(QStringLiteral("%1 == %2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 == %2")))
2409 .arg(cap_file_->cinfo.col_expr.col_expr[column])
2410 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
2411 }
2412 }
2413 }
2414 }
2415
2416 epan_dissect_cleanup(&edt);
2417 wtap_rec_cleanup(&rec);
2418 }
2419
2420 return filter;
2421}
2422
2423void PacketList::resetColorized()
2424{
2425 packet_list_model_->resetColorized();
2426 update();
2427}
2428
2429QString PacketList::getPacketComment(unsigned c_number)
2430{
2431 int row = currentIndex().row();
2432 const frame_data *fdata;
2433 char *pkt_comment;
2434 wtap_opttype_return_val result;
2435 QString ret_val = NULL__null;
2436
2437 if (!cap_file_ || !packet_list_model_) return NULL__null;
2438
2439 fdata = packet_list_proxy_model_->getRowFdata(row);
2440 if (!fdata) fdata = filteredOutSelectedFrame();
2441
2442 if (!fdata) return NULL__null;
2443
2444 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
2445 result = wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT1, c_number, &pkt_comment);
2446 if (result == WTAP_OPTTYPE_SUCCESS) {
2447 ret_val = QString(pkt_comment);
2448 }
2449 wtap_block_unref(pkt_block);
2450 return ret_val;
2451}
2452
2453void PacketList::addPacketComment(QString new_comment)
2454{
2455 if (!cap_file_ || !packet_list_model_) return;
2456 if (new_comment.isEmpty()) return;
2457
2458 QByteArray ba = new_comment.toUtf8();
2459
2460 /*
2461 * Make sure this would fit in a pcapng option.
2462 *
2463 * XXX - 65535 is the maximum size for an option in pcapng;
2464 * what if another capture file format supports larger
2465 * comments?
2466 */
2467 if (ba.size() > 65535) {
2468 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK0x01,
2469 "That comment is too large to save in a capture file.");
2470 return;
2471 }
2472
2473 if (frame_data *hidden = filteredOutSelectedFrame()) {
2474 packet_list_model_->addFrameComment(packet_list_model_->physicalRecordForFrameNum((int)hidden->num), ba);
2475 refreshFilteredOutFrame(hidden);
2476 } else if (selectionModel() && selectionModel()->hasSelection()) {
2477 packet_list_model_->addFrameComment(sourceIndexes(selectionModel()->selectedRows()), ba);
2478 drawCurrentPacket();
2479 }
2480}
2481
2482void PacketList::setPacketComment(unsigned c_number, QString new_comment)
2483{
2484 QModelIndex curIndex = currentIndex();
2485
2486 if (!cap_file_ || !packet_list_model_) return;
2487
2488 QByteArray ba = new_comment.toUtf8();
2489 /*
2490 * Make sure this would fit in a pcapng option.
2491 *
2492 * XXX - 65535 is the maximum size for an option in pcapng;
2493 * what if another capture file format supports larger
2494 * comments?
2495 */
2496 if (ba.size() > 65535) {
2497 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK0x01,
2498 "That comment is too large to save in a capture file.");
2499 return;
2500 }
2501
2502 if (frame_data *hidden = filteredOutSelectedFrame()) {
2503 packet_list_model_->setFrameComment(packet_list_model_->physicalRecordForFrameNum((int)hidden->num), ba, c_number);
2504 refreshFilteredOutFrame(hidden);
2505 return;
2506 }
2507
2508 packet_list_model_->setFrameComment(packet_list_proxy_model_->mapToSource(curIndex), ba, c_number);
2509 drawCurrentPacket();
2510}
2511
2512QString PacketList::allPacketComments()
2513{
2514 uint32_t framenum;
2515 frame_data *fdata;
2516 QString buf_str;
2517
2518 if (!cap_file_) return buf_str;
2519
2520 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
2521 fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum);
2522
2523 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
2524
2525 if (pkt_block) {
2526 unsigned n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT1);
2527 for (unsigned i = 0; i < n_comments; i++) {
2528 char *comment_text;
2529 if (WTAP_OPTTYPE_SUCCESS == wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT1, i, &comment_text)) {
2530 buf_str.append(tr("Frame %1: %2\n\n").arg(framenum).arg(comment_text));
2531 if (buf_str.length() > max_comments_to_fetch_) {
2532 buf_str.append(tr("[ Comment text exceeds %1. Stopping. ]")
2533 .arg(format_size(max_comments_to_fetch_, FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)format_size_wmem(__null, max_comments_to_fetch_, FORMAT_SIZE_UNIT_BYTES
, (1 << 0))
));
2534 return buf_str;
2535 }
2536 }
2537 }
2538 }
2539 }
2540 return buf_str;
2541}
2542
2543void PacketList::deleteCommentsFromPackets()
2544{
2545 if (!cap_file_ || !packet_list_model_) return;
2546
2547 if (frame_data *hidden = filteredOutSelectedFrame()) {
2548 packet_list_model_->deleteFrameComments(packet_list_model_->physicalRecordForFrameNum((int)hidden->num));
2549 refreshFilteredOutFrame(hidden);
2550 } else if (selectionModel() && selectionModel()->hasSelection()) {
2551 packet_list_model_->deleteFrameComments(sourceIndexes(selectionModel()->selectedRows()));
2552 drawCurrentPacket();
2553 }
2554}
2555
2556void PacketList::deleteAllPacketComments()
2557{
2558 if (!cap_file_ || !packet_list_model_) return;
2559
2560 packet_list_model_->deleteAllFrameComments();
2561 drawCurrentPacket();
2562}
2563
2564
2565// Slots
2566
2567void PacketList::setCaptureFile(capture_file *cf)
2568{
2569 cap_file_ = cf;
2570 packet_list_model_->setCaptureFile(cf);
2571 if (cap_file_ && columns_changed_) {
2572 columnsChanged();
2573 }
2574 create_near_overlay_ = true;
2575 changing_profile_ = false;
2576 sortByColumn(-1, Qt::AscendingOrder);
2577}
2578
2579void PacketList::setMonospaceFont(const QFont &mono_font)
2580{
2581 setFont(mono_font);
2582 for (QTreeView *view : pinnedOverlayViews()) {
2583 view->setFont(mono_font);
2584 }
2585 layoutPinnedOverlays();
2586}
2587
2588void PacketList::setRegularFont(const QFont &regular_font)
2589{
2590 header()->setFont(regular_font);
2591 header()->viewport()->setFont(regular_font);
2592 // pinned_column_view_'s own header isn't covered by setMonospaceFont()
2593 // looping over pinnedOverlayViews() (that sets the *view's* font, for
2594 // cell content -- QHeaderView doesn't automatically inherit a
2595 // different font set on its own separately-headed QTreeView the way
2596 // the primary view's header is explicitly given the regular font
2597 // above), so without this it silently kept whatever font QHeaderView
2598 // fell back to, differing from the primary header's.
2599 if (pinned_column_view_) {
2600 pinned_column_view_->header()->setFont(regular_font);
2601 }
2602}
2603
2604void PacketList::goNextPacket(void)
2605{
2606 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
2607 // Alt+toolbar
2608 goNextHistoryPacket();
2609 return;
2610 }
2611
2612 if (selectionModel()->hasSelection()) {
2613 selectionModel()->setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2614 } else {
2615 // First visible packet.
2616 selectionModel()->setCurrentIndex(indexAt(viewport()->rect().topLeft()), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2617 }
2618
2619 scrollViewChanged(false);
2620}
2621
2622void PacketList::goPreviousPacket(void)
2623{
2624 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
2625 // Alt+toolbar
2626 goPreviousHistoryPacket();
2627 return;
2628 }
2629
2630 if (selectionModel()->hasSelection()) {
2631 selectionModel()->setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2632 } else {
2633 // Last visible packet.
2634 QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
2635 if (last_idx.isValid()) {
2636 selectionModel()->setCurrentIndex(last_idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2637 } else {
2638 goLastPacket();
2639 }
2640 }
2641
2642 scrollViewChanged(false);
2643}
2644
2645void PacketList::goFirstPacket(void) {
2646 if (packet_list_proxy_model_->rowCount() < 1) return;
2647 selectionModel()->setCurrentIndex(packet_list_proxy_model_->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2648 scrollTo(currentIndex());
2649
2650 scrollViewChanged(false);
2651}
2652
2653void PacketList::goLastPacket(void) {
2654 if (packet_list_proxy_model_->rowCount() < 1) return;
2655 selectionModel()->setCurrentIndex(packet_list_proxy_model_->index(packet_list_proxy_model_->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2656 scrollTo(currentIndex());
2657
2658 scrollViewChanged(false);
2659}
2660
2661void PacketList::goToPacket(int packet, int hf_id)
2662{
2663 if (!cf_goto_frame(cap_file_, packet, false))
2664 return;
2665
2666 // cf_goto_frame only returns true if packet_list_select_row_from_data
2667 // succeeds, the latter has already selected and scrolled to the frame.
2668 if (hf_id > 0) {
2669 proto_tree_->goToHfid(hf_id);
2670 }
2671
2672 scrollViewChanged(false);
2673}
2674
2675void PacketList::goNextHistoryPacket()
2676{
2677 if (haveNextHistory(true)) {
2678 in_history_ = true;
2679 goToPacket(selection_history_.at(cur_history_));
2680 in_history_ = false;
2681 }
2682}
2683
2684void PacketList::goPreviousHistoryPacket()
2685{
2686 if (havePreviousHistory(true)) {
2687 in_history_ = true;
2688 goToPacket(selection_history_.at(cur_history_));
2689 in_history_ = false;
2690 }
2691}
2692
2693void PacketList::markFrame()
2694{
2695 if (!cap_file_ || !packet_list_model_) return;
2696
2697 if (frame_data *hidden = filteredOutSelectedFrame()) {
2698 packet_list_model_->toggleFrameMark(packet_list_model_->physicalRecordForFrameNum((int)hidden->num));
2699 refreshFilteredOutFrame(hidden);
2700 return;
2701 }
2702
2703 QModelIndexList frames;
2704
2705 if (selectionModel() && selectionModel()->hasSelection())
2706 {
2707 QModelIndexList selRows = selectionModel()->selectedRows(0);
2708 foreach (QModelIndex idx, selRows)for (auto _container_2708 = QtPrivate::qMakeForeachContainer(
selRows); _container_2708.i != _container_2708.e; ++_container_2708
.i) if (QModelIndex idx = *_container_2708.i; false) {} else
2709 {
2710 if (idx.isValid())
2711 {
2712 frames << idx;
2713 }
2714 }
2715 }
2716 else
2717 frames << currentIndex();
2718
2719 packet_list_model_->toggleFrameMark(sourceIndexes(frames));
2720
2721 // Make sure the packet list's frame.marked related field text is updated.
2722 redrawVisiblePackets();
2723
2724 create_far_overlay_ = true;
2725 packets_bar_update();
2726}
2727
2728void PacketList::markAllDisplayedFrames(bool set)
2729{
2730 if (!cap_file_ || !packet_list_model_) return;
2731
2732 packet_list_proxy_model_->setDisplayedFrameMark(set);
2733
2734 // Make sure the packet list's frame.marked related field text is updated.
2735 redrawVisiblePackets();
2736
2737 create_far_overlay_ = true;
2738 packets_bar_update();
2739}
2740
2741void PacketList::ignoreFrame()
2742{
2743 if (!cap_file_ || !packet_list_model_) return;
2744
2745 if (frame_data *hidden = filteredOutSelectedFrame()) {
2746 packet_list_model_->toggleFrameIgnore(packet_list_model_->physicalRecordForFrameNum((int)hidden->num));
2747 refreshFilteredOutFrame(hidden);
2748 emit packetDissectionChanged();
2749 return;
2750 }
2751
2752 QModelIndexList frames;
2753
2754 if (selectionModel() && selectionModel()->hasSelection())
2755 {
2756 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_2756 = QtPrivate::qMakeForeachContainer(
selectionModel()->selectedRows(0)); _container_2756.i != _container_2756
.e; ++_container_2756.i) if (QModelIndex idx = *_container_2756
.i; false) {} else
2757 {
2758 if (idx.isValid())
2759 {
2760 frames << idx;
2761 }
2762 }
2763 }
2764 else
2765 frames << currentIndex();
2766
2767
2768 packet_list_model_->toggleFrameIgnore(sourceIndexes(frames));
2769 create_far_overlay_ = true;
2770 int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
2771 setUpdatesEnabled(false);
2772 emit packetDissectionChanged();
2773 setUpdatesEnabled(true);
2774 verticalScrollBar()->setValue(sb_val);
2775}
2776
2777void PacketList::ignoreAllDisplayedFrames(bool set)
2778{
2779 if (!cap_file_ || !packet_list_model_) return;
2780
2781 packet_list_proxy_model_->setDisplayedFrameIgnore(set);
2782 create_far_overlay_ = true;
2783 emit packetDissectionChanged();
2784}
2785
2786void PacketList::setTimeReference()
2787{
2788 if (!cap_file_ || !packet_list_model_) return;
2789
2790 if (frame_data *hidden = filteredOutSelectedFrame()) {
2791 packet_list_model_->toggleFrameRefTime(packet_list_model_->physicalRecordForFrameNum((int)hidden->num));
2792 refreshFilteredOutFrame(hidden);
2793 return;
2794 }
2795
2796 QModelIndexList frames;
2797
2798 if (selectionModel() && selectionModel()->hasSelection())
2799 {
2800 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_2800 = QtPrivate::qMakeForeachContainer(
selectionModel()->selectedRows(0)); _container_2800.i != _container_2800
.e; ++_container_2800.i) if (QModelIndex idx = *_container_2800
.i; false) {} else
2801 {
2802 if (idx.isValid())
2803 {
2804 frames << idx;
2805 }
2806 }
2807 }
2808 else
2809 frames << currentIndex();
2810
2811 packet_list_model_->toggleFrameRefTime(sourceIndexes(frames));
2812 create_far_overlay_ = true;
2813}
2814
2815void PacketList::unsetAllTimeReferences()
2816{
2817 if (!cap_file_ || !packet_list_model_) return;
2818 packet_list_model_->unsetAllFrameRefTime();
2819 create_far_overlay_ = true;
2820}
2821
2822void PacketList::applyTimeShift()
2823{
2824 packet_list_model_->resetColumns();
2825 redrawVisiblePackets();
2826 emit packetDissectionChanged();
2827}
2828
2829void PacketList::updatePackets(bool redraw)
2830{
2831 if (redraw) {
2832 packet_list_model_->resetColumns();
2833 redrawVisiblePackets();
2834 } else {
2835 update();
2836 }
2837}
2838
2839void PacketList::columnVisibilityTriggered()
2840{
2841 QAction *ha = qobject_cast<QAction*>(sender());
2842 if (!ha) return;
2843
2844 int col = ha->data().toInt();
2845 set_column_visible(col, ha->isChecked());
2846 setColumnVisibility();
2847 if (ha->isChecked()) {
2848 setRecentColumnWidth(col);
2849 }
2850 prefs_main_write();
2851}
2852
2853void PacketList::sectionResized(int col, int, int new_width)
2854{
2855 if (isVisible() && !columns_changed_ && !set_column_visibility_ && !set_style_sheet_ && new_width > 0) {
2856 // Column 1 gets an invalid value (32 on macOS) when we're not yet
2857 // visible.
2858 //
2859 // Don't set column width when columns changed or setting column
2860 // visibility because we may get a sectionResized() from QTreeView
2861 // with values from a old columns layout.
2862 //
2863 // Don't set column width when hiding a column.
2864
2865 recent_set_column_width(col, new_width);
2866 }
2867
2868 mirrorSectionWidthToOverlays(col, new_width);
2869 layoutPinnedOverlays();
2870}
2871
2872// The user moved a column. Make sure prefs.col_list, the column format
2873// array, and the header's visual and logical indices all agree.
2874// gtk/packet_list.c:column_dnd_changed_cb
2875void PacketList::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
2876{
2877 GList *new_col_list = NULL__null;
2878 GList *new_recent_col_list = NULL__null;
2879 QList<int> saved_sizes;
2880 int sort_idx;
2881
2882 // Since we undo the move below, these should always stay in sync.
2883 // Otherwise the order of columns can be unexpected after drag and drop.
2884 if (logicalIndex != oldVisualIndex) {
2885 ws_warning("Column moved from an unexpected state (%d, %d, %d)",do { if (true) { ws_log_full("", LOG_LEVEL_WARNING, "ui/qt/packet_list.cpp"
, 2886, __func__, "Column moved from an unexpected state (%d, %d, %d)"
, logicalIndex, oldVisualIndex, newVisualIndex); } } while (0
)
2886 logicalIndex, oldVisualIndex, newVisualIndex)do { if (true) { ws_log_full("", LOG_LEVEL_WARNING, "ui/qt/packet_list.cpp"
, 2886, __func__, "Column moved from an unexpected state (%d, %d, %d)"
, logicalIndex, oldVisualIndex, newVisualIndex); } } while (0
)
;
2887 }
2888
2889 // Remember which column should be sorted. Use the visual index since this
2890 // points to the current GUI state rather than the outdated column order
2891 // (indicated by the logical index).
2892 sort_idx = header()->sortIndicatorSection();
2893 if (sort_idx != -1) {
2894 sort_idx = header()->visualIndex(sort_idx);
2895 }
2896
2897 // Build a new column list based on the header's logical order.
2898 for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
2899 int log_idx = header()->logicalIndex(vis_idx);
2900 saved_sizes << header()->sectionSize(log_idx);
2901
2902 void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
2903 if (pref_data) {
2904 new_col_list = g_list_append(new_col_list, pref_data);
2905 }
2906
2907 pref_data = g_list_nth_data(recent.col_width_list, log_idx);
2908 if (pref_data) {
2909 new_recent_col_list = g_list_append(new_recent_col_list, pref_data);
2910 }
2911 }
2912
2913 // Undo move to ensure that the logical indices map to the visual indices,
2914 // otherwise the column order is changed twice (once via the modified
2915 // col_list, once because of the visual/logical index mismatch).
2916 disconnect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
2917 header()->moveSection(newVisualIndex, oldVisualIndex);
2918 connect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
2919
2920 // Clear and rebuild our (and the header's) model. There doesn't appear
2921 // to be another way to reset the logical index.
2922 freeze();
2923
2924 g_list_free(prefs.col_list);
2925 prefs.col_list = new_col_list;
2926 g_list_free(recent.col_width_list);
2927 recent.col_width_list = new_recent_col_list;
2928
2929 thaw(true);
2930
2931 for (int i = 0; i < saved_sizes.length(); i++) {
2932 if (saved_sizes[i] < 1) continue;
2933 header()->resizeSection(i, saved_sizes[i]);
2934 }
2935
2936 prefs_main_write();
2937
2938 mainApp->emitAppSignal(MainApplication::ColumnsChanged);
2939
2940 // If the column with the sort indicator got shifted, mark the new column
2941 // after updating the columns contents (via ColumnsChanged) to ensure that
2942 // the columns are sorted using the intended column contents.
2943 int left_col = MIN(oldVisualIndex, newVisualIndex)(((oldVisualIndex) < (newVisualIndex)) ? (oldVisualIndex) :
(newVisualIndex))
;
2944 int right_col = MAX(oldVisualIndex, newVisualIndex)(((oldVisualIndex) > (newVisualIndex)) ? (oldVisualIndex) :
(newVisualIndex))
;
2945 if (left_col <= sort_idx && sort_idx <= right_col) {
2946 header()->setSortIndicator(sort_idx, header()->sortIndicatorOrder());
2947 }
2948
2949 layoutPinnedOverlays();
2950}
2951
2952void PacketList::pinSelectedRows()
2953{
2954 if (!pinned_rows_model_ || !packet_list_model_) return;
2955
2956 QModelIndexList frames;
2957 if (selectionModel() && selectionModel()->hasSelection()) {
2958 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_2958 = QtPrivate::qMakeForeachContainer(
selectionModel()->selectedRows(0)); _container_2958.i != _container_2958
.e; ++_container_2958.i) if (QModelIndex idx = *_container_2958
.i; false) {} else
{
2959 if (idx.isValid()) frames << idx;
2960 }
2961 } else {
2962 frames << currentIndex();
2963 }
2964
2965 foreach (QModelIndex idx, frames)for (auto _container_2965 = QtPrivate::qMakeForeachContainer(
frames); _container_2965.i != _container_2965.e; ++_container_2965
.i) if (QModelIndex idx = *_container_2965.i; false) {} else
{
2966 frame_data *fdata = packet_list_proxy_model_->getRowFdata(idx.row());
2967 if (fdata) {
2968 pinned_rows_model_->pinFrame((int)fdata->num);
2969 }
2970 }
2971 updatePinnedRowVisibility();
2972}
2973
2974void PacketList::unpinSelectedRows()
2975{
2976 if (!pinned_rows_model_ || !packet_list_model_) return;
2977
2978 QModelIndexList frames;
2979 if (selectionModel() && selectionModel()->hasSelection()) {
2980 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_2980 = QtPrivate::qMakeForeachContainer(
selectionModel()->selectedRows(0)); _container_2980.i != _container_2980
.e; ++_container_2980.i) if (QModelIndex idx = *_container_2980
.i; false) {} else
{
2981 if (idx.isValid()) frames << idx;
2982 }
2983 } else {
2984 frames << currentIndex();
2985 }
2986
2987 foreach (QModelIndex idx, frames)for (auto _container_2987 = QtPrivate::qMakeForeachContainer(
frames); _container_2987.i != _container_2987.e; ++_container_2987
.i) if (QModelIndex idx = *_container_2987.i; false) {} else
{
2988 frame_data *fdata = packet_list_proxy_model_->getRowFdata(idx.row());
2989 if (fdata) {
2990 pinned_rows_model_->unpinFrame((int)fdata->num);
2991 }
2992 }
2993 updatePinnedRowVisibility();
2994}
2995
2996void PacketList::unpinAllRows()
2997{
2998 pinned_rows_model_->clear();
2999 updatePinnedRowVisibility();
3000}
3001
3002int PacketList::pinnedRowHeight() const
3003{
3004 int row_height = sizeHintForRow(0);
3005 if (row_height > 0) {
3006 return row_height;
3007 }
3008 // No rows laid out yet (e.g. empty capture); fall back to font-based
3009 // spacing so the strip still sizes sensibly rather than to 0.
3010 return fontMetrics().height() + 2;
3011}
3012
3013void PacketList::setPinnedColumnBoundary(int column_count)
3014{
3015 pinned_column_boundary_ = column_count;
3016 emit pinnedColumnBoundaryChanged(pinned_column_boundary_);
3017
3018 // Seed widths for the newly-pinned range; afterward sectionResized()
3019 // keeps these in sync as the user resizes columns.
3020 for (int i = 0; i < pinned_column_boundary_; i++) {
3021 int width = header()->sectionSize(i);
3022 if (pinned_column_view_) {
3023 pinned_column_view_->mirrorSectionWidth(i, width);
3024 }
3025 if (pinned_row_corner_view_) {
3026 pinned_row_corner_view_->mirrorSectionWidth(i, width);
3027 }
3028 }
3029
3030 if (pinned_column_view_) {
3031 pinned_column_view_->setFrozenColumnCount(pinned_column_boundary_);
3032 }
3033 if (packet_list_header_) {
3034 packet_list_header_->setFrozenColumnCount(pinned_column_boundary_);
3035 }
3036
3037 updatePinnedRowVisibility();
3038 layoutPinnedOverlays();
3039}
3040
3041void PacketList::updatePinnedRowVisibility()
3042{
3043 pinned_rows_model_->refresh();
3044
3045 bool have_pinned_rows = pinned_rows_model_->pinnedCount() > 0;
3046
3047 for (unsigned i = 0; i < prefs.num_cols; i++) {
3048 int width = header()->sectionSize(i);
3049 if (pinned_row_view_) {
3050 pinned_row_view_->mirrorSectionWidth(i, width);
3051 }
3052 if (pinned_row_corner_view_) {
3053 pinned_row_corner_view_->mirrorSectionWidth(i, width);
3054 }
3055 }
3056
3057 if (pinned_row_view_) {
3058 pinned_row_view_->setColumnRange(pinned_column_boundary_, -1);
3059 pinned_row_view_->setVisible(have_pinned_rows);
3060 pinned_row_view_->updateGeometry();
3061 }
3062 if (pinned_row_corner_view_) {
3063 bool show_corner = have_pinned_rows && pinned_column_boundary_ > 0;
3064 if (show_corner) {
3065 pinned_row_corner_view_->setColumnRange(0, pinned_column_boundary_ - 1);
3066 }
3067 pinned_row_corner_view_->setVisible(show_corner);
3068 pinned_row_corner_view_->updateGeometry();
3069 }
3070
3071 layoutPinnedOverlays();
3072}
3073
3074void PacketList::updateFrozenOverlayMask()
3075{
3076 if (!pinned_column_view_) {
3077 return;
3078 }
3079 if (header_drag_active_ && pinned_column_view_->isVisible()) {
3080 int header_height = header()->height();
3081 pinned_column_view_->setMask(QRegion(0, header_height, pinned_column_view_->width(),
3082 pinned_column_view_->height() - header_height));
3083 } else {
3084 pinned_column_view_->clearMask();
3085 }
3086}
3087
3088void PacketList::layoutPinnedOverlays()
3089{
3090 // Width of the frozen-column portion, shared between the column-freeze
3091 // overlay below and the pinned-row strip's "corner" widget (whose
3092 // sizing PacketListPane keeps in sync via pinnedRowsCornerWidthChanged()).
3093 int corner_width = 0;
3094 for (int i = 0; i < pinned_column_boundary_; i++) {
3095 corner_width += header()->sectionSize(i);
3096 }
3097 bool have_pinned_rows = pinned_rows_model_ && pinned_rows_model_->pinnedCount() > 0;
3098 emit pinnedRowsCornerWidthChanged(corner_width, have_pinned_rows);
3099
3100 if (!pinned_column_view_) {
3101 return;
3102 }
3103
3104 // Derive the real on-screen offsets from the primary view's own child
3105 // widgets rather than assuming (0, 0): QTreeView's default frame
3106 // border/margin means header()/viewport() do not actually start flush
3107 // with PacketList's own top-left corner.
3108 int left_offset = header()->pos().x();
3109 int top_offset = header()->pos().y();
3110 int header_height = header()->height();
3111
3112 if (pinned_column_boundary_ > 0) {
3113 pinned_column_view_->mirrorHeaderHeight(header_height);
3114 // Span from the header's own top offset (not the viewport's) so
3115 // this view's own header covers the primary header's frozen-column
3116 // titles, which would otherwise scroll horizontally with the rest
3117 // of the header.
3118 QSize new_size(corner_width, header_height + viewport()->height());
3119 pinned_column_view_->setGeometry(left_offset, top_offset,
3120 new_size.width(), new_size.height());
3121 pinned_column_view_->setVisible(true);
3122 pinned_column_view_->raise();
3123 // refreshLayout() forces a full row-geometry recompute
3124 // (doItemsLayout()), which must run after setGeometry() since
3125 // QTreeView computes its vertical scroll range from the current
3126 // viewport size -- but only the height actually affects that
3127 // range, so skip the recompute when only corner_width (the
3128 // column-resize-driven part of new_size) changed. Without this,
3129 // dragging a column resize handle while frozen columns are active
3130 // re-runs a full O(rows) layout on every mouse-move tick.
3131 if (new_size.height() != pinned_column_view_size_.height()) {
3132 pinned_column_view_->refreshLayout();
3133 }
3134 pinned_column_view_size_ = new_size;
3135 updateFrozenOverlayMask();
3136 pinned_column_view_->setVerticalScrollValue(verticalScrollBar()->value());
3137 } else {
3138 pinned_column_view_->setVisible(false);
3139 }
3140}
3141
3142QString PacketList::createSummaryText(QModelIndex idx, SummaryCopyType type)
3143{
3144 if (! idx.isValid())
3145 return "";
3146
3147 QStringList col_parts;
3148 int row = idx.row();
3149 for (int col = 0; col < packet_list_proxy_model_->columnCount(); col++) {
3150 if (get_column_visible(col)) {
3151 col_parts << packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, col), Qt::DisplayRole).toString();
3152 }
3153 }
3154 return joinSummaryRow(col_parts, row, type);
3155}
3156
3157QString PacketList::createHeaderSummaryText(SummaryCopyType type)
3158{
3159 QStringList col_parts;
3160 for (int col = 0; col < packet_list_proxy_model_->columnCount(); ++col) {
3161 if (get_column_visible(col)) {
3162 col_parts << packet_list_proxy_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
3163 }
3164 }
3165 return joinSummaryRow(col_parts, 0, type);
3166}
3167
3168QStringList PacketList::createHeaderPartsForAligned()
3169{
3170 QStringList hdr_parts;
3171 for (int col = 0; col < packet_list_proxy_model_->columnCount(); ++col) {
3172 if (get_column_visible(col)) {
3173 hdr_parts << packet_list_proxy_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
3174 }
3175 }
3176 return hdr_parts;
3177}
3178
3179QList<int> PacketList::createAlignmentPartsForAligned()
3180{
3181 QList<int> align_parts;
3182 for (int col = 0; col < packet_list_proxy_model_->columnCount(); col++) {
3183 if (get_column_visible(col)) {
3184 align_parts << packet_list_proxy_model_->data(packet_list_proxy_model_->index(0, col), Qt::TextAlignmentRole).toInt();
3185 }
3186 }
3187 return align_parts;
3188}
3189
3190QList<int> PacketList::createSizePartsForAligned(bool useHeader, QStringList hdr_parts, QList<int> rows)
3191{
3192 QList<int> size_parts;
3193
3194 for (int i = 0; i < hdr_parts.size(); ++i) {
3195 if (useHeader)
3196 size_parts << static_cast<int>(hdr_parts.at(i).size());
3197 else
3198 size_parts << 0;
3199 }
3200
3201 foreach(int row, rows)for (auto _container_3201 = QtPrivate::qMakeForeachContainer(
rows); _container_3201.i != _container_3201.e; ++_container_3201
.i) if (int row = *_container_3201.i; false) {} else
3202 {
3203 QModelIndex idx = model()->index(row, 0);
3204 if (! idx.isValid())
3205 continue;
3206
3207 QStringList col_parts;
3208 for (int col = 0; col < packet_list_proxy_model_->columnCount(); col++) {
3209 if (get_column_visible(col)) {
3210 col_parts << (packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, col), Qt::DisplayRole).toString());
3211 }
3212 }
3213
3214 for (int i = 0; i < col_parts.size(); ++i) {
3215 if (col_parts.at(i).size() > size_parts.at(i)) {
3216 size_parts[i] = static_cast<int>(col_parts.at(i).size());
3217 }
3218 }
3219 col_parts.clear();
3220 }
3221
3222 return size_parts;
3223}
3224
3225QString PacketList::createHeaderSummaryForAligned(QStringList hdr_parts, QList<int> align_parts, QList<int> size_parts)
3226{
3227 QString hdr_text;
3228 for (int i = 0; i < hdr_parts.size(); ++i) {
3229 if (align_parts.at(i) == Qt::AlignLeft) {
3230 hdr_text += hdr_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
3231 }
3232 else if (align_parts.at(i) == Qt::AlignRight) {
3233 hdr_text += hdr_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
3234 }
3235 }
3236 return QStringLiteral("-%1")(QString(QtPrivate::qMakeStringPrivate(u"" "-%1"))).arg(hdr_text).trimmed().mid(1);
3237}
3238
3239QString PacketList::createSummaryForAligned(QModelIndex idx, QList<int> align_parts, QList<int> size_parts)
3240{
3241 if (! idx.isValid())
3242 return "";
3243
3244 QStringList col_parts;
3245 int row = idx.row();
3246 for (int col = 0; col < packet_list_proxy_model_->columnCount(); col++) {
3247 if (get_column_visible(col)) {
3248 col_parts << packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, col), Qt::DisplayRole).toString();
3249 }
3250 }
3251
3252 QString col_text;
3253 for (int i = 0; i < col_parts.size(); ++i) {
3254 if (align_parts.at(i) == Qt::AlignLeft) {
3255 col_text += col_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
3256 }
3257 else if (align_parts.at(i) == Qt::AlignRight) {
3258 col_text += col_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
3259 }
3260 }
3261
3262 return QStringLiteral("-%1")(QString(QtPrivate::qMakeStringPrivate(u"" "-%1"))).arg(col_text).trimmed().mid(1);
3263}
3264
3265QString PacketList::createDefaultStyleForHtml()
3266{
3267 QString fontFamily = QString(prefs.gui_font_name).split(",")[0];
3268 QString fontSize = QString(prefs.gui_font_name).split(",")[1];
3269
3270 return QStringLiteral("<style>"(QString(QtPrivate::qMakeStringPrivate(u"" "<style>" "table{font-family:%1;font-size:%2pt;}"
"th{background-color:#000000;color:#ffffff;text-align:left;}"
"th,td{padding:%3pt}" "</style>")))
3271 "table{font-family:%1;font-size:%2pt;}"(QString(QtPrivate::qMakeStringPrivate(u"" "<style>" "table{font-family:%1;font-size:%2pt;}"
"th{background-color:#000000;color:#ffffff;text-align:left;}"
"th,td{padding:%3pt}" "</style>")))
3272 "th{background-color:#000000;color:#ffffff;text-align:left;}"(QString(QtPrivate::qMakeStringPrivate(u"" "<style>" "table{font-family:%1;font-size:%2pt;}"
"th{background-color:#000000;color:#ffffff;text-align:left;}"
"th,td{padding:%3pt}" "</style>")))
3273 "th,td{padding:%3pt}"(QString(QtPrivate::qMakeStringPrivate(u"" "<style>" "table{font-family:%1;font-size:%2pt;}"
"th{background-color:#000000;color:#ffffff;text-align:left;}"
"th,td{padding:%3pt}" "</style>")))
3274 "</style>")(QString(QtPrivate::qMakeStringPrivate(u"" "<style>" "table{font-family:%1;font-size:%2pt;}"
"th{background-color:#000000;color:#ffffff;text-align:left;}"
"th,td{padding:%3pt}" "</style>")))
.arg(fontFamily, fontSize).arg(fontSize.toInt() / 2);
3275}
3276
3277QString PacketList::createOpeningTagForHtml()
3278{
3279 return "<table>";
3280}
3281
3282QString PacketList::createHeaderSummaryForHtml()
3283{
3284 QString hdr_text;
3285 hdr_text += "<tr>";
3286 for (int col = 0; col < packet_list_proxy_model_->columnCount(); ++col) {
3287 if (get_column_visible(col)) {
3288 hdr_text += "<th>";
3289 hdr_text += packet_list_proxy_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
3290 hdr_text += "</th>";
3291 }
3292 }
3293 hdr_text += "</tr>";
3294 return hdr_text;
3295}
3296
3297QString PacketList::createSummaryForHtml(QModelIndex idx)
3298{
3299 if (! idx.isValid())
3300 return "";
3301
3302 int row = idx.row();
3303 QString col_text;
3304
3305 QString bg_color = packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, 0), Qt::BackgroundRole).toString();
3306 QString fg_color = packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, 0), Qt::ForegroundRole).toString();
3307 col_text += "<tr style=\"background-color:" + bg_color + ";color:" + fg_color + ";\">";
3308
3309 QString alignment[] = {"left", "right", "center", "justify"};
3310
3311 for (int col = 0; col < packet_list_proxy_model_->columnCount(); col++) {
3312 if (get_column_visible(col)) {
3313 col_text += "<td style=\"text-align:" + alignment[packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, col), Qt::TextAlignmentRole).toInt() / 2] + ";\">";
3314 col_text += packet_list_proxy_model_->data(packet_list_proxy_model_->index(row, col), Qt::DisplayRole).toString();
3315 col_text += "</td>";
3316 }
3317 }
3318
3319 col_text += "</tr>";
3320 return col_text;
3321}
3322
3323QString PacketList::createClosingTagForHtml()
3324{
3325 return "</table>";
3326}
3327
3328void PacketList::copySummary()
3329{
3330 if (!currentIndex().isValid()) return;
3331
3332 QAction *ca = qobject_cast<QAction*>(sender());
3333 if (!ca) return;
3334
3335 QVariant type = ca->data();
3336 if (! type.canConvert<SummaryCopyType>())
3337 return;
3338 SummaryCopyType copy_type = type.value<SummaryCopyType>();
3339
3340 QString copy_text;
3341 if (type == CopyAsText || type == CopyAsHTML) {
3342 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
3343 QList<int> rows;
3344 rows << currentIndex().row();
3345 QStringList hdr_parts;
3346 QList<int> align_parts, size_parts;
3347 hdr_parts = createHeaderPartsForAligned();
3348 align_parts = createAlignmentPartsForAligned();
3349 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
3350 copy_text = createSummaryForAligned(currentIndex(), align_parts, size_parts);
3351 }
3352 else {
3353 copy_text = createSummaryText(currentIndex(), CopyAsText);
3354 }
3355 copy_text += "\n";
3356 if (type == CopyAsHTML) {
3357 QStringList htmlContent;
3358 htmlContent << createDefaultStyleForHtml();
3359 htmlContent << createOpeningTagForHtml();
3360 htmlContent << createSummaryForHtml(currentIndex());
3361 htmlContent << createClosingTagForHtml();
3362 // htmlContent will never be empty as they will always have
3363 // style and table tags
3364 QMimeData *mimeData = new QMimeData;
3365 mimeData->setHtml(htmlContent.join('\n'));
3366 mimeData->setText(copy_text);
3367 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
3368 }
3369 else {
3370 mainApp->clipboard()->setText(copy_text);
3371 }
3372 }
3373 else {
3374 copy_text = createSummaryText(currentIndex(), copy_type);
3375 if (type != CopyAsYAML)
3376 copy_text += "\n";
3377 mainApp->clipboard()->setText(copy_text);
3378 }
3379}
3380
3381// We need to tell when the user has scrolled the packet list, either to
3382// the end or anywhere other than the end.
3383void PacketList::vScrollBarActionTriggered(int)
3384{
3385 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
3386 // past the end.
3387 tail_at_end_ = (overlay_sb_->sliderPosition() >= overlay_sb_->maximum());
3388
3389 scrollViewChanged(tail_at_end_);
3390}
3391
3392void PacketList::scrollViewChanged(bool at_end)
3393{
3394 if (capture_in_progress_) {
3395 // We want to start auto scrolling when the user scrolls to (or past)
3396 // the end only if recent.capture_auto_scroll is set.
3397 // We want to stop autoscrolling if the user scrolls up or uses
3398 // Go to Packet regardless of the preference setting.
3399 if (recent.capture_auto_scroll || !at_end) {
3400 emit packetListScrolled(at_end);
3401 }
3402 }
3403}
3404
3405// Goal: Overlay the packet list scroll bar with the colors of all of the
3406// packets.
3407// Try 1: Average packet colors in each scroll bar raster line. This has
3408// two problems: It's easy to wash out colors and we dissect every packet.
3409// Try 2: Color across a 5000 or 10000 packet window. We still end up washing
3410// out colors.
3411// Try 3: One packet per vertical scroll bar pixel. This seems to work best
3412// but has the smallest window.
3413// Try 4: Use a multiple of the scroll bar height and scale the image down
3414// using Qt::SmoothTransformation. This gives us more packets per raster
3415// line.
3416
3417// Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
3418// of 9 washed out colors a little too much.
3419//const int height_multiplier_ = 7;
3420void PacketList::drawNearOverlay()
3421{
3422 if (create_near_overlay_) {
3423 create_near_overlay_ = false;
3424 }
3425
3426 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
3427
3428 if (!prefs.gui_packet_list_show_minimap) return;
3429
3430 qreal dp_ratio = overlay_sb_->devicePixelRatio();
3431 int o_height = overlay_sb_->height() * dp_ratio;
3432 int o_rows = qMin(packet_list_proxy_model_->rowCount(), o_height);
3433 QFontMetricsF fmf(mainApp->font());
3434 int o_width = ((static_cast<int>(fmf.height())) * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
3435
3436 if (recent.packet_list_colorize && o_rows > 0) {
3437 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
3438
3439 QPainter painter(&overlay);
3440
3441 overlay.fill(Qt::transparent);
3442
3443 int cur_line = 0;
3444 int start = 0;
3445
3446 if (packet_list_proxy_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
3447 start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_proxy_model_->rowCount() - o_rows);
3448 }
3449 int end = start + o_rows;
3450 for (int row = start; row < end; row++) {
3451 packet_list_proxy_model_->ensureRowColorized(row);
3452
3453 frame_data *fdata = packet_list_proxy_model_->getRowFdata(row);
3454 int next_line = (row - start + 1) * o_height / o_rows;
3455 int row_height = next_line - cur_line;
3456
3457 // Multi-color support in minimap (enabled for all non-Off modes)
3458 if (prefs.gui_packet_list_multi_color_mode != PACKET_LIST_MULTI_COLOR_MODE_OFF) {
3459 QModelIndex idx = packet_list_proxy_model_->index(row, 0);
3460 PacketListRecord *record = static_cast<PacketListRecord*>(idx.internalPointer());
3461 // Conversation color filters take full precedence — skip multi-color stripe rendering
3462 bool is_conversation_color = fdata->color_filter &&
3463 strncmp(((const color_filter_t *)fdata->color_filter)->filter_name,
3464 CONVERSATION_COLOR_PREFIX"___conversation_color_filter___", strlen(CONVERSATION_COLOR_PREFIX"___conversation_color_filter___")) == 0;
3465 if (record && record->hasMultipleColors() && !is_conversation_color) {
3466 // Draw vertical stripes for multiple colors (skip paused filters)
3467 const GSList *filters = record->matchingColorFilters();
3468
3469 // Count non-paused filters
3470 int num_active_colors = 0;
3471 for (const GSList *item = filters; item != NULL__null; item = g_slist_next(item)((item) ? (((GSList *)(item))->next) : __null)) {
3472 const color_filter_t *colorf = (const color_filter_t *)item->data;
3473 if (!color_filter_is_session_disabled(colorf->filter_name)) {
3474 num_active_colors++;
3475 }
3476 }
3477
3478 if (num_active_colors > 0) {
3479 int stripe_width = o_width / num_active_colors;
3480 int x_pos = 0;
3481 int stripe_idx = 0;
3482
3483 for (const GSList *item = filters; item != NULL__null; item = g_slist_next(item)((item) ? (((GSList *)(item))->next) : __null)) {
3484 const color_filter_t *colorf = (const color_filter_t *)item->data;
3485 // Skip paused filters
3486 if (!color_filter_is_session_disabled(colorf->filter_name)) {
3487 QColor color(ColorUtils::fromColorT(&colorf->bg_color));
3488 int width = (stripe_idx == num_active_colors - 1) ? (o_width - x_pos) : stripe_width;
3489 painter.fillRect(x_pos, cur_line, width, row_height, color);
3490 x_pos += stripe_width;
3491 stripe_idx++;
3492 }
3493 }
3494 } else {
3495 // All filters paused, draw no color
3496 painter.fillRect(0, cur_line, o_width, row_height, QColor(Qt::white));
3497 }
3498 } else if (fdata->color_filter) {
3499 // Single color fallback
3500 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
3501 QColor color(ColorUtils::fromColorT(&color_filter->bg_color));
3502 painter.fillRect(0, cur_line, o_width, row_height, color);
3503 }
3504 } else {
3505 // Original single-color behavior
3506 const color_t *bgcolor = NULL__null;
3507 if (fdata->color_filter) {
3508 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
3509 bgcolor = &color_filter->bg_color;
3510 }
3511
3512 if (bgcolor) {
3513 QColor color(ColorUtils::fromColorT(bgcolor));
3514 painter.fillRect(0, cur_line, o_width, row_height, color);
3515 }
3516 }
3517
3518 cur_line = next_line;
3519 }
3520
3521 // If the selected packet is in the overlay set selected_pos
3522 // accordingly. Otherwise, pin it to either the top or bottom.
3523 QList<int> positions;
3524 if (selectionModel()->hasSelection()) {
3525
3526 QModelIndexList selRows = selectionModel()->selectedRows(0);
3527 int last_row = -1;
3528 int last_pos = -1;
3529 foreach (QModelIndex idx, selRows)for (auto _container_3529 = QtPrivate::qMakeForeachContainer(
selRows); _container_3529.i != _container_3529.e; ++_container_3529
.i) if (QModelIndex idx = *_container_3529.i; false) {} else
3530 {
3531 int selected_pos = -1;
3532 int sel_row = idx.row();
3533 if (sel_row < start) {
3534 selected_pos = 0;
3535 } else if (sel_row >= end) {
3536 selected_pos = overlay.height() - 1;
3537 } else {
3538 selected_pos = (sel_row - start) * o_height / o_rows;
3539 }
3540
3541 /* Due to the difference in the display height, we sometimes get empty positions
3542 * inbetween consecutive valid rows. If those are detected, they are signaled as
3543 * being selected as well */
3544 if (last_pos >= 0 && selected_pos > (last_pos + 1) && (last_row + 1) == sel_row)
3545 {
3546 for (int pos = (last_pos + 1); pos < selected_pos; pos++)
3547 {
3548 if (! positions.contains(pos))
3549 positions << pos;
3550 }
3551 }
3552 else if (selected_pos != -1 && ! positions.contains(selected_pos))
3553 positions << selected_pos;
3554
3555 last_row = sel_row;
3556 last_pos = selected_pos;
3557 }
3558 }
3559
3560 overlay_sb_->setNearOverlayImage(overlay, packet_list_proxy_model_->rowCount(), start, end, positions, (o_height / o_rows));
3561 } else {
3562 QImage overlay;
3563 overlay_sb_->setNearOverlayImage(overlay);
3564 }
3565}
3566
3567void PacketList::drawFarOverlay()
3568{
3569 if (create_far_overlay_) {
3570 create_far_overlay_ = false;
3571 }
3572
3573 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
3574
3575 if (!prefs.gui_packet_list_show_minimap) return;
3576
3577 QSize groove_size = overlay_sb_->grooveRect().size();
3578 qreal dp_ratio = overlay_sb_->devicePixelRatio();
3579 groove_size *= dp_ratio;
3580 int o_width = groove_size.width();
3581 int o_height = groove_size.height();
3582 int pl_rows = packet_list_proxy_model_->rowCount();
3583 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
3584 bool have_marked_image = false;
3585
3586 // If only there were references from popular culture about getting into
3587 // some sort of groove.
3588 if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
3589
3590 QPainter painter(&overlay);
3591
3592 // Draw text-colored tick marks on a transparent background.
3593 // Hopefully no themes use the text color for the groove color.
3594 overlay.fill(Qt::transparent);
3595
3596 QColor tick_color = palette().text().color();
3597 tick_color.setAlphaF(0.3f);
3598 painter.setPen(tick_color);
3599
3600 for (int row = 0; row < pl_rows; row++) {
3601
3602 frame_data *fdata = packet_list_proxy_model_->getRowFdata(row);
3603 if (fdata->marked || fdata->ref_time || fdata->ignored) {
3604 int new_line = row * o_height / pl_rows;
3605 int tick_width = o_width / 3;
3606 // Marked or ignored: left side, time refs: right side.
3607 // XXX Draw ignored ticks in the middle?
3608 int x1 = fdata->ref_time ? o_width - tick_width : 1;
3609 int x2 = fdata->ref_time ? o_width - 1 : tick_width;
3610
3611 painter.drawLine(x1, new_line, x2, new_line);
3612 have_marked_image = true;
3613 }
3614 }
3615
3616 if (have_marked_image) {
3617 overlay_sb_->setMarkedPacketImage(overlay);
3618 return;
3619 }
3620 }
3621
3622 if (!have_marked_image) {
3623 QImage null_overlay;
3624 overlay_sb_->setMarkedPacketImage(null_overlay);
3625 }
3626}
3627
3628// Auto scroll if:
3629// - We are capturing
3630// - actionGoAutoScroll in the main UI is checked.
3631
3632// actionGoAutoScroll in the main UI:
3633// - Is set to the value of recent.capture_auto_scroll when beginning a capture
3634// - Can be triggered manually by the user
3635// - Is turned on if the last user-set vertical scrollbar position is at the
3636// end and recent.capture_auto_scroll is enabled
3637// - Is turned off if the last user-set vertical scrollbar is not at the end,
3638// or if one of the Go to Packet actions is used (XXX: Should keyboard
3639// navigation in keyPressEvent turn it off for similar reasons?)
3640void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
3641{
3642 QTreeView::rowsInserted(parent, start, end);
3643 bool aggregation_mode = recent.aggregation_view && prefs.aggregation_fields_num > 0;
3644 if (aggregation_mode) {
3645 // In Aggregation mode, we might have replaced the currently selected
3646 // packets with new ones matching the aggregation fields, so we need
3647 // to redissect the details if a single frame is selected.
3648 drawCurrentPacket(false);
3649 }
3650 if (capture_in_progress_ && tail_at_end_) {
3651 scrollToBottom();
3652 }
3653}
3654
3655void PacketList::resizeAllColumns(bool onlyTimeFormatted)
3656{
3657 if (!cap_file_ || cap_file_->state == FILE_CLOSED || cap_file_->state == FILE_READ_PENDING)
3658 return;
3659
3660 for (unsigned col = 0; col < cap_file_->cinfo.num_cols; col++) {
3661 if (! onlyTimeFormatted || col_has_time_fmt(&cap_file_->cinfo, col)) {
3662 resizeColumnToContents(col);
3663 }
3664 }
3665}