Bug Summary

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