Bug Summary

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