Bug Summary

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