Bug Summary

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