Bug Summary

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