Bug Summary

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