Bug Summary

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