Bug Summary

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