Bug Summary

File:builds/wireshark/wireshark/ui/qt/packet_list.cpp
Warning:line 923, 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/QtCore5Compat -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_CORE5COMPAT_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-06-07-100346-3530-1 -x c++ /builds/wireshark/wireshark/ui/qt/packet_list.cpp
1/* packet_list.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include <ui/qt/packet_list.h>
11
12#include "file.h"
13
14#include <epan/epan.h>
15#include <epan/epan_dissect.h>
16
17#include <epan/column.h>
18#include <epan/expert.h>
19#include <epan/packet.h>
20#include <epan/prefs.h>
21#include <epan/proto.h>
22
23#include "ui/main_statusbar.h"
24#include "ui/packet_list_utils.h"
25#include "ui/preference_utils.h"
26#include "ui/recent.h"
27#include "ui/simple_dialog.h"
28#include <wsutil/utf8_entities.h>
29
30#include "wiretap/wtap_opttypes.h"
31#include "app/application_flavor.h"
32#include "wsutil/str_util.h"
33#include <wsutil/wslog.h>
34
35#include <epan/color_filters.h>
36
37#include <ui/qt/utils/color_utils.h>
38#include <ui/qt/utils/theme_manager.h>
39#include <ui/qt/utils/font_manager.h>
40#include <ui/qt/widgets/overlay_scroll_bar.h>
41#include "proto_tree.h"
42#include <ui/qt/utils/qt_ui_utils.h>
43#include "main_application.h"
44#include <ui/qt/utils/data_printer.h>
45#include <ui/qt/utils/frame_information.h>
46#include <ui/qt/utils/profile_switcher.h>
47#include <ui/qt/utils/variant_pointer.h>
48#include <ui/qt/models/pref_models.h>
49#include <ui/qt/widgets/packet_list_header.h>
50#include <ui/qt/utils/wireshark_mime_data.h>
51#include <ui/qt/widgets/drag_label.h>
52#include <ui/qt/filter_action.h>
53#include <ui/qt/follow_stream_action.h>
54#include <ui/qt/decode_as_dialog.h>
55#include <ui/qt/wireshark_main_window.h>
56
57#include <QAction>
58#include <QActionGroup>
59#include <QClipboard>
60#include <QContextMenuEvent>
61#include <QtCore/qmath.h>
62#include <QElapsedTimer>
63#include <QFontMetrics>
64#include <QHeaderView>
65#include <QMessageBox>
66#include <QPainter>
67#include <QScreen>
68#include <QScrollBar>
69#include <QTabWidget>
70#include <QTextEdit>
71#include <QTimerEvent>
72#include <QTreeWidget>
73#include <QWindow>
74#include <QJsonObject>
75#include <QJsonDocument>
76
77#ifdef Q_OS_WIN
78#include "wsutil/file_util.h"
79#include <QSysInfo>
80#include <uxtheme.h>
81#endif
82
83// To do:
84// - Fix "apply as filter" behavior.
85// - Add colorize conversation.
86// - Use a timer to trigger automatic scrolling.
87
88// If we ever add the ability to open multiple capture files we might be
89// able to use something like QMap<capture_file *, PacketList *> to match
90// capture files against packet lists and models.
91static PacketList *gbl_cur_packet_list;
92
93const int max_comments_to_fetch_ = 20000000; // Arbitrary
94const int overlay_update_interval_ = 100; // 250; // Milliseconds.
95
96
97/*
98 * Given a frame_data structure, scroll to and select the row in the
99 * packet list corresponding to that frame. If there is no such
100 * row, return false, otherwise return true.
101 */
102bool
103packet_list_select_row_from_data(frame_data *fdata_needle)
104{
105 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
106 return false;
107
108 PacketListModel* model = qobject_cast<PacketListModel*>(gbl_cur_packet_list->model());
109
110 if (!model)
111 return false;
112
113 model->flushVisibleRows();
114 int row = -1;
115 if (!fdata_needle)
116 row = 0;
117 else
118 row = model->visibleIndexOf(fdata_needle);
119
120 if (row >= 0) {
121 /* Calling ClearAndSelect with setCurrentIndex clears the "current"
122 * item, but doesn't clear the "selected" item. We want to clear
123 * the "selected" item as well so that selectionChanged() will be
124 * emitted in order to force an update of the packet details and
125 * packet bytes after a search.
126 */
127 gbl_cur_packet_list->selectionModel()->clearSelection();
128 gbl_cur_packet_list->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
129 gbl_cur_packet_list->scrollTo(gbl_cur_packet_list->currentIndex(), PacketList::PositionAtCenter);
130 return true;
131 }
132
133 return false;
134}
135
136/*
137 * Given a field_info, select the field (which will scroll to it in
138 * the main ProtoTree, etc.) This is kind of an odd place for it,
139 * but we call this when performing Find Packet in lieu of changing the
140 * selected frame (the function above), because we found a match in the
141 * same frame as the currently selected one.
142 */
143bool
144packet_list_select_finfo(field_info *fi)
145{
146 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
147 return false;
148
149 if (fi) {
150 FieldInformation finfo(fi, gbl_cur_packet_list);
151 emit gbl_cur_packet_list->fieldSelected(&finfo);
152 } else {
153 emit gbl_cur_packet_list->fieldSelected(0);
154 }
155 return true;
156}
157
158void
159packet_list_clear(void)
160{
161 if (gbl_cur_packet_list) {
162 gbl_cur_packet_list->clear();
163 }
164}
165
166void
167packet_list_freeze(void)
168{
169 if (gbl_cur_packet_list) {
170 gbl_cur_packet_list->freeze();
171 }
172}
173
174void
175packet_list_thaw(void)
176{
177 if (gbl_cur_packet_list) {
178 gbl_cur_packet_list->thaw();
179 }
180
181 packets_bar_update();
182}
183
184/* Redraw the packet list *and* currently-selected detail */
185void
186packet_list_queue_draw(void)
187{
188 if (gbl_cur_packet_list)
189 gbl_cur_packet_list->redrawVisiblePackets();
190}
191
192void
193packet_list_recent_write_all(FILE *rf) {
194 if (!gbl_cur_packet_list)
195 return;
196
197 gbl_cur_packet_list->writeRecent(rf);
198}
199
200bool
201packet_list_multi_select_active(void)
202{
203 if (gbl_cur_packet_list) {
204 return gbl_cur_packet_list->multiSelectActive();
205 }
206 return false;
207}
208
209#define MIN_COL_WIDTH_STR"MMMMMM" "MMMMMM"
210
211PacketList::PacketList(QWidget *parent) :
212 QTreeView(parent),
213 proto_tree_(nullptr),
214 cap_file_(nullptr),
215 ctx_column_(-1),
216 overlay_timer_id_(0),
217 create_near_overlay_(true),
218 create_far_overlay_(true),
219 mouse_pressed_at_(QModelIndex()),
220 capture_in_progress_(false),
221 tail_at_end_(0),
222 columns_changed_(false),
223 set_column_visibility_(false),
224 set_style_sheet_(false),
225 frozen_current_row_(QModelIndex()),
226 frozen_selected_rows_(QModelIndexList()),
227 cur_history_(-1),
228 in_history_(false),
229 finfo_array(nullptr),
230 profile_switcher_(nullptr)
231{
232 setItemsExpandable(false);
233 setRootIsDecorated(false);
234 setSortingEnabled(prefs.gui_packet_list_sortable);
235 setUniformRowHeights(true);
236 setFocusPolicy(Qt::StrongFocus);
237
238#ifdef Q_OS_MAC
239 setAttribute(Qt::WA_MacShowFocusRect, true);
240#endif
241
242 verticalScrollBar()->setFocusPolicy(Qt::NoFocus);
243 horizontalScrollBar()->setFocusPolicy(Qt::NoFocus);
244
245 packet_list_header_ = new PacketListHeader(header()->orientation());
246 connect(packet_list_header_, &PacketListHeader::resetColumnWidth, this, &PacketList::setRecentColumnWidth);
247 connect(packet_list_header_, &PacketListHeader::updatePackets, this, &PacketList::updatePackets);
248 connect(packet_list_header_, &PacketListHeader::showColumnPreferences, this, &PacketList::showProtocolPreferences);
249 connect(packet_list_header_, &PacketListHeader::editColumn, this, &PacketList::editColumn);
250 connect(packet_list_header_, &PacketListHeader::columnsChanged, this, &PacketList::columnsChanged);
251 setHeader(packet_list_header_);
252
253 header()->setFirstSectionMovable(true);
254
255 setSelectionMode(QAbstractItemView::ExtendedSelection);
256
257 // Shrink down to a small but nonzero size in the main splitter.
258 int one_em = fontMetrics().height();
259 setMinimumSize(one_em, one_em);
260
261 overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
262 setVerticalScrollBar(overlay_sb_);
263
264 header()->setSortIndicator(-1, Qt::AscendingOrder);
265
266 packet_list_model_ = new PacketListModel(this, cap_file_);
267 setModel(packet_list_model_);
268
269 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"
, 269))
;
270 gbl_cur_packet_list = this;
271
272 connect(packet_list_model_, &PacketListModel::goToPacket, this, [=](int packet) { goToPacket(packet); });
273 connect(mainApp, &MainApplication::addressResolutionChanged, this, &PacketList::redrawVisiblePacketsDontSelectCurrent);
274 connect(mainApp, &MainApplication::columnDataChanged, this, &PacketList::redrawVisiblePacketsDontSelectCurrent);
275 connect(mainApp, &MainApplication::preferencesChanged, this, [=]() {
276 /* The pref is a uint but QCache maxCost is a signed int (/
277 * qsizetype in Qt 6). Note that QAbstractItemModel row numbers
278 * are ints, not unsigned ints, so we're limited to INT_MAX
279 * rows anyway.
280 */
281 PacketListRecord::setMaxCache(prefs.gui_packet_list_cached_rows_max > INT_MAX2147483647 ? INT_MAX2147483647 : prefs.gui_packet_list_cached_rows_max);
282 if ((bool) (prefs.gui_packet_list_sortable) != isSortingEnabled()) {
283 setSortingEnabled(prefs.gui_packet_list_sortable);
284 }
285 });
286
287 connect(header(), &QHeaderView::sectionResized, this, &PacketList::sectionResized);
288 connect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
289
290 connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &PacketList::vScrollBarActionTriggered);
291
292 // Own the font: seed it now and follow the FontManager for later changes.
293 connect(FontManager::instance(), &FontManager::monospaceFontChanged, this, &PacketList::setMonospaceFont);
294 connect(FontManager::instance(), &FontManager::applicationFontChanged, this, &PacketList::setRegularFont);
295 setMonospaceFont(FontManager::zoomedMonospaceFont());
296 setRegularFont(FontManager::zoomedFont());
297}
298
299PacketList::~PacketList()
300{
301 if (finfo_array)
302 {
303 g_ptr_array_free(finfo_array, true);
304 }
305}
306
307void PacketList::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
308{
309 /* QAbstractItemView doesn't have a way to indicate "auto scroll, but
310 * only vertically." So just restore the horizontal scroll value whenever
311 * it scrolls.
312 */
313 setUpdatesEnabled(false);
314 int horizVal = horizontalScrollBar()->value();
315 QTreeView::scrollTo(index, hint);
316 horizontalScrollBar()->setValue(horizVal);
317 setUpdatesEnabled(true);
318}
319
320void PacketList::colorsChanged()
321{
322 const QString c_active = "active";
323 const QString c_inactive = "!active";
324
325 const QString flat_style_format =
326 "QTreeView::item:selected:%1 {"
327 " color: %2;"
328 " background-color: %3;"
329 "}";
330
331 QString hover_style = QStringLiteral((QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {"
" background-color: %1;" " color: palette(text);" "}")))
332 "QTreeView:item:hover {"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {"
" background-color: %1;" " color: palette(text);" "}")))
333 " background-color: %1;"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {"
" background-color: %1;" " color: palette(text);" "}")))
334 " color: palette(text);"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {"
" background-color: %1;" " color: palette(text);" "}")))
335 "}")(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {"
" background-color: %1;" " color: palette(text);" "}")))
.arg(ColorUtils::hoverBackground().name(QColor::HexArgb));
336
337 // A selected row also matches the :selected rules below, which out-rank
338 // the plain hover rule by CSS specificity. Add an equal-specificity
339 // :selected:hover rule, emitted last, so hover wins on a selected row
340 // (matching the proto tree and the pre-ThemeManager behavior).
341 QString selected_hover_style = QStringLiteral((QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView::item:selected:hover {"
" background-color: %1;" " color: palette(text);" "}")))
342 "QTreeView::item:selected:hover {"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView::item:selected:hover {"
" background-color: %1;" " color: palette(text);" "}")))
343 " background-color: %1;"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView::item:selected:hover {"
" background-color: %1;" " color: palette(text);" "}")))
344 " color: palette(text);"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView::item:selected:hover {"
" background-color: %1;" " color: palette(text);" "}")))
345 "}")(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView::item:selected:hover {"
" background-color: %1;" " color: palette(text);" "}")))
.arg(ColorUtils::hoverBackground().name(QColor::HexArgb));
346
347 ThemeManager *tm = ThemeManager::instance();
348 QColor active_bg = tm->color(ThemeManager::PacketsSelection);
349 QColor inactive_bg = tm->color(ThemeManager::PacketsInactive);
350 QColor active_fg = tm->color(ThemeManager::PacketsSelectionText);
351 QColor inactive_fg = tm->color(ThemeManager::PacketsInactiveText);
352
353 QString active_style = flat_style_format.arg(
354 c_active,
355 active_fg.name(),
356 active_bg.name());
357 QString inactive_style = flat_style_format.arg(
358 c_inactive,
359 inactive_fg.name(),
360 inactive_bg.name());
361
362 set_style_sheet_ = true;
363 if (prefs.gui_packet_list_hover_style) {
364 setStyleSheet(active_style + inactive_style + hover_style + selected_hover_style);
365 } else {
366 setStyleSheet(active_style + inactive_style);
367 }
368 set_style_sheet_ = false;
369#if \
370 ( \
371 (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))) \
372 || (QT_VERSION((6<<16)|(10<<8)|(2)) >= QT_VERSION_CHECK(6, 6, 1)((6<<16)|(6<<8)|(1))) \
373 )
374 // https://bugreports.qt.io/browse/QTBUG-122109
375 // Affects Qt 6.5.4 and later, 6.6.1 and later.
376 // When setting the style sheet, all visible sections are set
377 // to the new minimum DefaultSectionSize (even if it hasn't
378 // changed.) So make sure the new widths aren't saved to recent
379 // and then restore from recent.
380 applyRecentColumnWidths();
381 setColumnVisibility();
382#endif
383}
384
385QString PacketList::joinSummaryRow(QStringList col_parts, int row, SummaryCopyType type)
386{
387 QString copy_text;
388 switch (type) {
389 case CopyAsCSV:
390 copy_text = "\"";
391 copy_text += col_parts.join("\",\"");
392 copy_text += "\"";
393 break;
394 case CopyAsYAML:
395 copy_text = "----\n";
396 copy_text += QStringLiteral("# Packet %1 from %2\n")(QString(QtPrivate::qMakeStringPrivate(u"" "# Packet %1 from %2\n"
)))
.arg(row).arg(cap_file_->filename);
397 copy_text += "- ";
398 copy_text += col_parts.join("\n- ");
399 copy_text += "\n";
400 break;
401 case CopyAsText:
402 default:
403 copy_text = col_parts.join("\t");
404 }
405
406 return copy_text;
407}
408
409void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
410{
411 QTreeView::drawRow(painter, option, index);
412
413 if (prefs.gui_packet_list_separator) {
414 QRect rect = visualRect(index);
415
416 painter->setPen(QColor(Qt::white));
417 painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
418 }
419}
420
421void PacketList::setProtoTree (ProtoTree *proto_tree) {
422 proto_tree_ = proto_tree;
423
424 connect(proto_tree_, &ProtoTree::goToPacket, this, [=](int packet) { goToPacket(packet); });
425 connect(proto_tree_, &ProtoTree::relatedFrame,
426 &related_packet_delegate_, &RelatedPacketDelegate::addRelatedFrame);
427}
428
429bool PacketList::uniqueSelectActive()
430{
431 return selectionModel()->selectedRows(0).count() == 1 ? true : false;
432}
433
434bool PacketList::multiSelectActive()
435{
436 return selectionModel()->selectedRows(0).count() > 1 ? true : false;
437}
438
439QList<int> PacketList::selectedRows(bool useFrameNum)
440{
441 QList<int> rows;
442 if (selectionModel() && selectionModel()->hasSelection())
443 {
444 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_444 = QtPrivate::qMakeForeachContainer(selectionModel
()->selectedRows(0)); _container_444.i != _container_444.e
; ++_container_444.i) if (QModelIndex idx = *_container_444.i
; false) {} else
445 {
446 if (idx.isValid())
447 {
448 if (! useFrameNum)
449 rows << idx.row();
450 else if (useFrameNum)
451 {
452 frame_data * frame = getFDataForRow(idx.row());
453 if (frame)
454 rows << frame->num;
455 }
456 }
457 }
458 }
459 else if (currentIndex().isValid())
460 {
461 //
462 // XXX - will we ever have a current index but not a selection
463 // model?
464 //
465 if (! useFrameNum)
466 rows << currentIndex().row();
467 else
468 {
469 frame_data *frame = getFDataForRow(currentIndex().row());
470 if (frame)
471 rows << frame->num;
472 }
473 }
474
475 return rows;
476}
477
478void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected)
479{
480 QTreeView::selectionChanged(selected, deselected);
481
482 if (!cap_file_) return;
483
484 int row = -1;
485 static bool multiSelect = false;
486
487 if (selectionModel())
488 {
489 QModelIndexList selRows = selectionModel()->selectedRows(0);
490 if (selRows.count() > 1)
491 {
492 QList<int> rows;
493 foreach (QModelIndex idx, selRows)for (auto _container_493 = QtPrivate::qMakeForeachContainer(selRows
); _container_493.i != _container_493.e; ++_container_493.i) if
(QModelIndex idx = *_container_493.i; false) {} else
494 {
495 if (idx.isValid())
496 rows << idx.row();
497 }
498
499 emit framesSelected(rows);
500 emit fieldSelected(0);
501 cf_unselect_packet(cap_file_);
502
503 /* We have to repaint the content while changing state, as some delegates react to multi-select */
504 if (! multiSelect)
505 {
506 related_packet_delegate_.clear();
507 viewport()->update();
508 }
509
510 multiSelect = true;
511
512 return;
513 }
514 else if (selRows.count() > 0 && selRows.at(0).isValid())
515 {
516 multiSelect = false;
517 row = selRows.at(0).row();
518 }
519
520 /* Handling empty selection */
521 if (selRows.count() <= 0)
522 {
523 /* Nothing selected, but multiSelect is still active */
524 if (multiSelect)
525 {
526 multiSelect = false;
527 if (currentIndex().isValid())
528 {
529 selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
530 return;
531 }
532 }
533 /* Nothing selected, so in WS <= 3.0 nothing was indicated as well */
534 else if (currentIndex().isValid())
535 {
536 setCurrentIndex(QModelIndex());
537 }
538 }
539 }
540
541 if (row < 0 || !packet_list_model_)
542 cf_unselect_packet(cap_file_);
543 else {
544 frame_data * fdata = packet_list_model_->getRowFdata(row);
545 cf_select_packet(cap_file_, fdata);
546 }
547
548 if (!in_history_ && cap_file_->current_frame) {
549 cur_history_++;
550 selection_history_.resize(cur_history_);
551 selection_history_.append(cap_file_->current_frame->num);
552 }
553
554 related_packet_delegate_.clear();
555
556 // The previous dissection state has been invalidated by cf_select_packet
557 // above, receivers must clear the previous state and apply the updated one.
558 emit framesSelected(QList<int>() << row);
559
560 if (!cap_file_->edt) {
561 viewport()->update();
562 emit fieldSelected(0);
563 return;
564 }
565
566 if (cap_file_->edt->tree) {
567 packet_info *pi = &cap_file_->edt->pi;
568 related_packet_delegate_.setCurrentFrame(pi->num);
569 conversation_t *conv = find_conversation_pinfo_ro(pi, 0);
570 if (conv) {
571 related_packet_delegate_.setConversation(conv);
572 }
573 viewport()->update();
574 }
575
576 if (cap_file_->search_in_progress) {
577 field_info *fi = NULL__null;
578
579 if (cap_file_->string && cap_file_->decode_data) {
580 // The tree where the target string matched one of the labels was discarded in
581 // match_protocol_tree() so we have to search again in the latest tree.
582 fi = cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree);
583 } else if (cap_file_->search_len != 0) {
584 // Find the finfo that corresponds to our byte.
585 // The match can span multiple fields (and a single byte can
586 // match more than one field.) Our behavior is to find the last
587 // field in the tree (so hopefully spanning fewer bytes) that
588 // matches the last byte in the search match.
589 // (regex search can find a zero length match not at the
590 // start of the frame if lookbehind is used, but
591 // proto_find_field_from_offset doesn't match such a field
592 // and it's not clear which field we would want to match.)
593 fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos + cap_file_->search_len - 1,
594 cap_file_->edt->tvb);
595 }
596
597 if (fi) {
598 FieldInformation finfo(fi, this);
599 emit fieldSelected(&finfo);
600 } else {
601 emit fieldSelected(0);
602 }
603 } else if (proto_tree_) {
604 proto_tree_->restoreSelectedField();
605 }
606}
607
608void PacketList::contextMenuEvent(QContextMenuEvent *event)
609{
610 const char *module_name = NULL__null;
611
612 if (finfo_array)
613 {
614 g_ptr_array_free(finfo_array, true);
615 finfo_array = NULL__null;
616 }
617
618 QModelIndex ctxIndex = indexAt(event->pos());
619
620 if (selectionModel() && selectionModel()->selectedRows(0).count() > 1)
621 selectionModel()->select(ctxIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
622
623 // frameData will be owned by one of the submenus, see below.
624 FrameInformation * frameData =
625 new FrameInformation(new CaptureFile(this, cap_file_), packet_list_model_->getRowFdata(ctxIndex.row()));
626
627 QMenu * ctx_menu = new QMenu(this);
628 ctx_menu->setAttribute(Qt::WA_DeleteOnClose);
629 // XXX We might want to reimplement setParent() and fill in the context
630 // menu there.
631 ctx_menu->addAction(window()->findChild<QAction *>("actionEditMarkSelected"));
632 ctx_menu->addAction(window()->findChild<QAction *>("actionEditIgnoreSelected"));
633 ctx_menu->addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
634 ctx_menu->addAction(window()->findChild<QAction *>("actionEditTimeShift"));
635 ctx_menu->addMenu(window()->findChild<QMenu *>("menuPacketComment"));
636
637 ctx_menu->addSeparator();
638
639 // Code for custom context menus from Lua's register_packet_menu()
640 MainWindow * mainWindow = mainApp->mainWindow();
641 // N.B., will only call for a single frame selection,
642 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
643 finfo_array = proto_all_finfos(cap_file_->edt->tree);
644 if (mainWindow) {
645 bool insertedPacketMenu = mainWindow->addPacketMenus(ctx_menu, finfo_array);
646 if (insertedPacketMenu) {
647 ctx_menu->addSeparator();
648 }
649 }
650 }
651
652 ctx_menu->addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
653 ctx_menu->addSeparator();
654
655 QString selectedfilter = getFilterFromRowAndColumn(currentIndex());
656
657 if (! hasFocus() && cap_file_ && cap_file_->finfo_selected) {
658 char *tmp_field = proto_construct_match_selected_string(cap_file_->finfo_selected, cap_file_->edt);
659 selectedfilter = QString(tmp_field);
660 wmem_free(NULL__null, tmp_field);
661 }
662
663 bool have_filter_expr = !selectedfilter.isEmpty();
664 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, have_filter_expr, ctx_menu));
665 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, have_filter_expr, ctx_menu));
666
667 const char *conv_menu_name = "menuConversationFilter";
668 QMenu * main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
669 conv_menu_.setTitle(main_menu_item->title());
670 conv_menu_.setObjectName(conv_menu_name);
671 ctx_menu->addMenu(&conv_menu_);
672
673 const char *colorize_menu_name = "menuColorizeConversation";
674 main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
675 colorize_menu_.setTitle(main_menu_item->title());
676 colorize_menu_.setObjectName(colorize_menu_name);
677 ctx_menu->addMenu(&colorize_menu_);
678
679 QMenu * submenu;
680 main_menu_item = window()->findChild<QMenu *>("menuSCTP");
681 if (main_menu_item) {
682 submenu = new QMenu(main_menu_item->title(), ctx_menu);
683 ctx_menu->addMenu(submenu);
684 submenu->addAction(window()->findChild<QAction *>("actionSCTPAnalyseThisAssociation"));
685 submenu->addAction(window()->findChild<QAction *>("actionSCTPShowAllAssociations"));
686 submenu->addAction(window()->findChild<QAction *>("actionSCTPFilterThisAssociation"));
687 }
688
689 main_menu_item = window()->findChild<QMenu *>("menuFollow");
690 if (main_menu_item) {
691 submenu = new QMenu(main_menu_item->title(), ctx_menu);
692 ctx_menu->addMenu(submenu);
693 foreach (FollowStreamAction *follow_action, main_menu_item->findChildren<FollowStreamAction *>())for (auto _container_693 = QtPrivate::qMakeForeachContainer(main_menu_item
->findChildren<FollowStreamAction *>()); _container_693
.i != _container_693.e; ++_container_693.i) if (FollowStreamAction
*follow_action = *_container_693.i; false) {} else
{
694 /* XXX: We could, like the prefs above, walk the protocols/layers
695 * and add the follow actions in the order they appear in the packet.
696 */
697 if (follow_action->isEnabled()) {
698 submenu->addAction(follow_action);
699 }
700 }
701 }
702
703 ctx_menu->addSeparator();
704
705 main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
706 submenu = new QMenu(main_menu_item->title(), ctx_menu);
707 submenu->setToolTipsVisible(true);
708 ctx_menu->addMenu(submenu);
709
710 QAction * action = submenu->addAction(tr("Summary as Text"));
711 action->setData(CopyAsText);
712 connect(action, &QAction::triggered, this, &PacketList::copySummary);
713 action = submenu->addAction(tr("…as CSV"));
714 action->setData(CopyAsCSV);
715 connect(action, &QAction::triggered, this, &PacketList::copySummary);
716 action = submenu->addAction(tr("…as YAML"));
717 action->setData(CopyAsYAML);
718 connect(action, &QAction::triggered, this, &PacketList::copySummary);
719 action = submenu->addAction(tr("…as HTML"));
720 action->setData(CopyAsHTML);
721 connect(action, &QAction::triggered, this, &PacketList::copySummary);
722 submenu->addSeparator();
723
724 submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
725 submenu->addSeparator();
726
727 QActionGroup * copyEntries = DataPrinter::copyActions(this, frameData);
728 submenu->addActions(copyEntries->actions());
729 copyEntries->setParent(submenu);
730 frameData->setParent(submenu);
731
732 if (application_flavor_is_wireshark()) {
733 /* i.e., Wireshark only */
734 ctx_menu->addSeparator();
735 QMenu *proto_prefs_menus = new QMenu(ProtocolPreferencesMenu::tr("Protocol Preferences"), ctx_menu);
736
737 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
738 QList<QString> added_proto_prefs;
739 // N.B. finfo_array will be assigned above
740 for (unsigned i = 0; i < finfo_array->len; i++) {
741 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i)((finfo_array)->pdata)[i];
742 const header_field_info *hfinfo = fi->hfinfo;
743
744 if (prefs_is_registered_protocol(hfinfo->abbrev)) {
745 if (hfinfo->parent == -1) {
746 module_name = hfinfo->abbrev;
747 } else {
748 module_name = proto_registrar_get_abbrev(hfinfo->parent);
749 }
750
751 if (added_proto_prefs.contains(module_name)) {
752 continue;
753 }
754
755 ProtocolPreferencesMenu *proto_prefs_menu = new ProtocolPreferencesMenu(hfinfo->name, module_name, proto_prefs_menus);
756
757 connect(proto_prefs_menu, &ProtocolPreferencesMenu::showProtocolPreferences,
758 this, &PacketList::showProtocolPreferences);
759 connect(proto_prefs_menu, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)"
"\0" "ui/qt/packet_list.cpp" ":" "759")
,
760 this, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)"
"\0" "ui/qt/packet_list.cpp" ":" "760")
);
761
762 proto_prefs_menus->addMenu(proto_prefs_menu);
763 added_proto_prefs << module_name;
764 }
765 }
766 }
767 ctx_menu->addMenu(proto_prefs_menus);
768 action = ctx_menu->addAction(tr("Decode As…"));
769 action->setProperty("create_new", QVariant(true));
770 connect(action, &QAction::triggered, this, &PacketList::ctxDecodeAsDialog);
771 // "Print" not ported intentionally
772 action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
773 ctx_menu->addAction(action);
774 }
775
776 // Set menu sensitivity for the current column and set action data.
777 if (frameData)
778 emit framesSelected(QList<int>() << frameData->frameNum());
779 else
780 emit framesSelected(QList<int>());
781
782 ctx_menu->popup(event->globalPos());
783}
784
785void PacketList::ctxDecodeAsDialog()
786{
787 QAction *da_action = qobject_cast<QAction*>(sender());
788 if (! da_action)
789 return;
790 bool create_new = da_action->property("create_new").toBool();
791
792 DecodeAsDialog *da_dialog = new DecodeAsDialog(this, cap_file_, create_new);
793 connect(da_dialog, &DecodeAsDialog::destroyed, mainApp, &MainApplication::flushAppSignals);
794 da_dialog->setWindowModality(Qt::ApplicationModal);
795 da_dialog->setAttribute(Qt::WA_DeleteOnClose);
796 da_dialog->show();
797}
798
799void PacketList::timerEvent(QTimerEvent *event)
800{
801 if (event->timerId() == overlay_timer_id_) {
802 if (!capture_in_progress_ && model() != nullptr) {
803 if (create_near_overlay_) drawNearOverlay();
804 if (create_far_overlay_) drawFarOverlay();
805 }
806 } else {
807 QTreeView::timerEvent(event);
808 }
809}
810
811void PacketList::paintEvent(QPaintEvent *event)
812{
813 // XXX This is overkill, but there are quite a few events that
814 // require a new overlay, e.g. page up/down, scrolling, column
815 // resizing, etc.
816 create_near_overlay_ = true;
817 QTreeView::paintEvent(event);
818}
819
820void PacketList::mousePressEvent(QMouseEvent *event)
821{
822 QTreeView::mousePressEvent(event);
823
824 QModelIndex curIndex = indexAt(event->pos());
825 mouse_pressed_at_ = curIndex;
826
827 bool midButton = (event->buttons() & Qt::MiddleButton) == Qt::MiddleButton;
828 if (midButton && cap_file_ && packet_list_model_)
829 {
830 packet_list_model_->toggleFrameMark(QModelIndexList() << curIndex);
831
832 // Make sure the packet list's frame.marked related field text is updated.
833 redrawVisiblePackets();
834
835 create_far_overlay_ = true;
836 packets_bar_update();
837 }
838}
839
840void PacketList::mouseReleaseEvent(QMouseEvent *event) {
841 QTreeView::mouseReleaseEvent(event);
842
843 mouse_pressed_at_ = QModelIndex();
844}
845
846void PacketList::mouseMoveEvent (QMouseEvent *event)
847{
848 QModelIndex curIndex = indexAt(event->pos());
849 if (event->buttons() & Qt::LeftButton && curIndex.isValid() && curIndex == mouse_pressed_at_)
1
Assuming the condition is true
2
Taking true branch
850 {
851 ctx_column_ = curIndex.column();
852 QMimeData * mimeData = new QMimeData();
853 DragLabel * drag_label = nullptr;
854
855 QString filter = getFilterFromRowAndColumn(curIndex);
856 QList<int> rows = selectedRows();
857 if (rows.count() > 1)
3
Assuming the condition is false
4
Taking false branch
858 {
859 QStringList entries;
860 foreach (int row, rows)for (auto _container_860 = QtPrivate::qMakeForeachContainer(rows
); _container_860.i != _container_860.e; ++_container_860.i) if
(int row = *_container_860.i; false) {} else
861 {
862 QModelIndex idx = model()->index(row, 0);
863 if (! idx.isValid())
864 continue;
865
866 QString entry = createSummaryText(idx, CopyAsText);
867 entries << entry;
868 }
869
870 if (entries.count() > 0)
871 mimeData->setText(entries.join("\n"));
872 }
873 else if (! filter.isEmpty())
5
Assuming the condition is true
6
Taking true branch
874 {
875 QString abbrev;
876 QString name = model()->headerData(curIndex.column(), header()->orientation()).toString();
877
878 if (! filter.isEmpty())
7
Assuming the condition is false
8
Taking false branch
879 {
880 abbrev = filter.left(filter.indexOf(' '));
881 }
882 else
883 {
884 filter = model()->data(curIndex).toString().toLower();
885 abbrev = filter;
886 }
887
888 mimeData->setText(filter);
889
890 QJsonObject filterData;
891 filterData["filter"] = filter;
892 filterData["name"] = abbrev;
893 filterData["description"] = name;
894
895 mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
896 drag_label = new DragLabel(QStringLiteral("%1\n%2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1\n%2"))).arg(name, abbrev), this);
9
Memory is allocated
897 }
898 else
899 {
900 QString text = model()->data(curIndex).toString();
901 if (! text.isEmpty())
902 mimeData->setText(text);
903 }
904
905 if (mimeData->hasText() || mimeData->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
10
Assuming the condition is false
11
Assuming the condition is false
12
Taking false branch
906 {
907 QDrag * drag = new QDrag(this);
908 drag->setMimeData(mimeData);
909 if (drag_label)
910 {
911 qreal dpr = window()->windowHandle()->devicePixelRatio();
912 QPixmap pixmap= QPixmap(drag_label->size() * dpr);
913 pixmap.setDevicePixelRatio(dpr);
914 drag_label->render(&pixmap);
915 drag->setPixmap(pixmap);
916 delete drag_label;
917 }
918
919 drag->exec(Qt::CopyAction);
920 }
921 else
922 {
923 delete mimeData;
13
Potential leak of memory pointed to by 'drag_label'
924 }
925 }
926}
927
928void PacketList::keyPressEvent(QKeyEvent *event)
929{
930 QTreeView::keyPressEvent(event);
931
932 if (event->matches(QKeySequence::Copy))
933 {
934 QStringList content, htmlContent;
935 if (model() && selectionModel() && selectionModel()->hasSelection())
936 {
937 QList<int> rows;
938 QModelIndexList selRows = selectionModel()->selectedRows(0);
939 foreach(QModelIndex row, selRows)for (auto _container_939 = QtPrivate::qMakeForeachContainer(selRows
); _container_939.i != _container_939.e; ++_container_939.i) if
(QModelIndex row = *_container_939.i; false) {} else
940 rows.append(row.row());
941
942 QStringList hdr_parts;
943 QList<int> align_parts, size_parts;
944
945 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
946 case COPY_FORMAT_TEXT:
947 case COPY_FORMAT_HTML:
948 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
949 hdr_parts = createHeaderPartsForAligned();
950 align_parts = createAlignmentPartsForAligned();
951 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
952 }
953 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
954 htmlContent << createDefaultStyleForHtml();
955 htmlContent << createOpeningTagForHtml();
956 }
957 break;
958 case COPY_FORMAT_CSV:
959 case COPY_FORMAT_YAML:
960 break;
961 }
962
963 QList<QStringList> entries;
964 foreach(int row, rows)for (auto _container_964 = QtPrivate::qMakeForeachContainer(rows
); _container_964.i != _container_964.e; ++_container_964.i) if
(int row = *_container_964.i; false) {} else
965 {
966 QModelIndex idx = model()->index(row, 0);
967 if (! idx.isValid())
968 continue;
969
970 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
971 case COPY_FORMAT_TEXT:
972 case COPY_FORMAT_HTML:
973 if (prefs.gui_packet_list_copy_text_with_aligned_columns)
974 content << createSummaryForAligned(idx, align_parts, size_parts);
975 else
976 content << createSummaryText(idx, CopyAsText);
977 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML)
978 htmlContent << createSummaryForHtml(idx);
979 break;
980 case COPY_FORMAT_CSV:
981 content << createSummaryText(idx, CopyAsCSV);
982 break;
983 case COPY_FORMAT_YAML:
984 content << createSummaryText(idx, CopyAsYAML);
985 break;
986 }
987 }
988 }
989
990 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
991 // htmlContent will never be empty as they will always have style and table tags
992 QMimeData *mimeData = new QMimeData;
993 htmlContent << createClosingTagForHtml();
994 mimeData->setHtml(htmlContent.join('\n'));
995 mimeData->setText(content.join('\n').append("\n"));
996 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
997 }
998 else {
999 if (content.count() > 0) {
1000 QString copy_text;
1001 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_YAML) {
1002 copy_text = content.join("");
1003 }
1004 else {
1005 copy_text = content.join("\n");
1006 copy_text += "\n";
1007 }
1008 mainApp->clipboard()->setText(copy_text);
1009 }
1010 }
1011 }
1012}
1013
1014void PacketList::focusInEvent(QFocusEvent *event)
1015{
1016 QTreeView::focusInEvent(event);
1017
1018 if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) {
1019 if (model() && model()->rowCount() > 0 && selectionModel()) {
1020 if (!selectionModel()->hasSelection()) {
1021 QModelIndex first = model()->index(0, 0);
1022 if (first.isValid()) {
1023 selectionModel()->setCurrentIndex(first, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1024 setCurrentIndex(first);
1025 }
1026 }
1027
1028 // ALWAYS scroll to the current index if we have one
1029 if (currentIndex().isValid()) {
1030 scrollTo(currentIndex());
1031 }
1032 }
1033 }
1034}
1035
1036void PacketList::resizeEvent(QResizeEvent *event)
1037{
1038 create_near_overlay_ = true;
1039 create_far_overlay_ = true;
1040 QTreeView::resizeEvent(event);
1041}
1042
1043void PacketList::setColumnVisibility()
1044{
1045 set_column_visibility_ = true;
1046 for (unsigned i = 0; i < prefs.num_cols; i++) {
1047 setColumnHidden(i, get_column_visible(i) ? false : true);
1048 }
1049 setColumnDelegate();
1050 set_column_visibility_ = false;
1051}
1052
1053void PacketList::setColumnDelegate()
1054{
1055 for (unsigned i = 0; i < prefs.num_cols; i++) {
1056 setItemDelegateForColumn(i, nullptr); // Reset all delegates
1057 }
1058
1059 // Multi-color delegate takes precedence over related packet delegate (row stripes only, not scrollbar-only mode)
1060 if (prefs.gui_packet_list_multi_color_mode == PACKET_LIST_MULTI_COLOR_MODE_FULL ||
1061 prefs.gui_packet_list_multi_color_mode == PACKET_LIST_MULTI_COLOR_MODE_SHIFT_RIGHT) {
1062 for (unsigned i = 0; i < prefs.num_cols; i++) {
1063 if (get_column_visible(i)) {
1064 setItemDelegateForColumn(i, &multi_color_delegate_);
1065 }
1066 }
1067 }
1068 else if (prefs.gui_packet_list_show_related) {
1069 for (unsigned i = 0; i < prefs.num_cols; i++) {
1070 if (get_column_visible(i)) {
1071 setItemDelegateForColumn(i, &related_packet_delegate_);
1072 break; // Set the delegate only on the first visible column
1073 }
1074 }
1075 }
1076}
1077
1078void PacketList::setRecentColumnWidth(int col)
1079{
1080 int col_width = recent_get_column_width(col);
1081
1082 if (col_width < 1) {
1083 int fmt = get_column_format(col);
1084 const char *long_str = get_column_width_string(fmt, col);
1085
1086 // TODO: a column's natural width is model data, not a view concern.
1087 // PacketListModel should hint it instead of the view measuring against
1088 // the font here.
1089 QFontMetrics fm = QFontMetrics(FontManager::monospaceFont());
1090 if (long_str) {
1091 col_width = fm.horizontalAdvance(long_str);
1092 } else {
1093 col_width = fm.horizontalAdvance(MIN_COL_WIDTH_STR"MMMMMM");
1094 }
1095 // Custom delegate padding
1096 if (itemDelegateForColumn(col)) {
1097 QStyleOptionViewItem option;
1098 initViewItemOption(&option);
1099 // This is adding "how much width hinted for an empty index, plus
1100 // the decoration, plus any padding between the decoration and
1101 // normal display?" Many styles however have a non zero hint for
1102 // an empty index, so this isn't quite right. What we really want
1103 // is a size hint for an index whose data is the string above, and
1104 // to just use that for the width.
1105 col_width += itemDelegateForColumn(col)->sizeHint(option, QModelIndex()).width();
1106 }
1107 }
1108
1109 setColumnWidth(col, col_width);
1110}
1111
1112void PacketList::drawCurrentPacket()
1113{
1114 // XXX - Update for multi-select? If more than one packet is Selected,
1115 // this changes it so that only the Current packet is Selected.
1116 QModelIndex current_index = currentIndex();
1117 if (selectionModel() && current_index.isValid()) {
1118 selectionModel()->clearSelection();
1119 selectionModel()->setCurrentIndex(current_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
1120 }
1121}
1122
1123// Redraw the packet list and detail. Re-selects the current packet (causes
1124// the UI to scroll to that packet).
1125// Called from many places.
1126void PacketList::redrawVisiblePackets() {
1127 redrawVisiblePacketsDontSelectCurrent();
1128 drawCurrentPacket();
1129}
1130
1131// Redraw the packet list and detail.
1132// Does not scroll back to the selected packet.
1133void PacketList::redrawVisiblePacketsDontSelectCurrent() {
1134 packet_list_model_->invalidateAllColumnStrings();
1135}
1136
1137void PacketList::resetColumns()
1138{
1139 packet_list_model_->resetColumns();
1140}
1141
1142// Return true if we have a visible packet further along in the history.
1143bool PacketList::haveNextHistory(bool update_cur)
1144{
1145 if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
1146 return false;
1147 }
1148
1149 for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
1150 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1151 if (update_cur) {
1152 cur_history_ = i;
1153 }
1154 return true;
1155 }
1156 }
1157 return false;
1158}
1159
1160// Return true if we have a visible packet back in the history.
1161bool PacketList::havePreviousHistory(bool update_cur)
1162{
1163 if (selection_history_.size() < 1 || cur_history_ < 1) {
1164 return false;
1165 }
1166
1167 for (int i = cur_history_ - 1; i >= 0; i--) {
1168 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1169 if (update_cur) {
1170 cur_history_ = i;
1171 }
1172 return true;
1173 }
1174 }
1175 return false;
1176}
1177
1178void PacketList::setProfileSwitcher(ProfileSwitcher *profile_switcher)
1179{
1180 profile_switcher_ = profile_switcher;
1181 if (profile_switcher) {
1182 connect(packet_list_model_, &PacketListModel::packetAppended, profile_switcher_, &ProfileSwitcher::checkPacket);
1183 }
1184}
1185
1186frame_data *PacketList::getFDataForRow(int row) const
1187{
1188 return packet_list_model_->getRowFdata(row);
1189}
1190
1191// prefs.col_list has changed.
1192void PacketList::columnsChanged()
1193{
1194 columns_changed_ = true;
1195 column_register_fields();
1196 mainApp->emitAppSignal(MainApplication::FieldsChanged);
1197 if (!cap_file_) {
1198 // Keep columns_changed_ = true until we load a capture file.
1199 return;
1200 }
1201
1202 prefs.num_cols = g_list_length(prefs.col_list);
1203 col_cleanup(&cap_file_->cinfo);
1204 build_column_format_array(&cap_file_->cinfo, prefs.num_cols, false);
1205 create_far_overlay_ = true;
1206 resetColumns();
1207 applyRecentColumnWidths();
1208 setColumnVisibility();
1209 columns_changed_ = false;
1210}
1211
1212// Fields have changed, update custom columns
1213void PacketList::fieldsChanged(capture_file *cf)
1214{
1215 // hfids used by custom columns or by the _ws.col fields may have changed,
1216 // so recreate the columns. We shouldn't need to if prefs.col_list is NULL,
1217 // since that doesn't register and deregister _ws.col fields.
1218 // If the column pref changes to or from NULL, that triggers columnsChanged
1219 // above, so we don't need to do it twice.
1220 //
1221 // XXX - If we knew exactly which fields changed, we could rebuild the
1222 // columns only if a field used by the columns changed.
1223 if (prefs.col_list) {
1224 prefs.num_cols = g_list_length(prefs.col_list);
1225 col_cleanup(&cf->cinfo);
1226 build_column_format_array(&cf->cinfo, prefs.num_cols, false);
1227 resetColumns();
1228 }
1229}
1230
1231// Column widths should
1232// - Load from recent when we load a new profile (including at starting up).
1233// - Reapply when changing columns.
1234// - Persist across freezes and thaws.
1235// - Persist across file closing and opening.
1236// - Save to recent when we save our profile (including shutting down).
1237// - Not be affected by the behavior of stretchLastSection. (XXX: We
1238// still save the stretched value to recent, sectionResized doesn't
1239// distinguish between a resize from being stretched and a manual change.)
1240void PacketList::applyRecentColumnWidths()
1241{
1242 // Either we've just started up or a profile has changed. Read
1243 // the recent settings, apply them, and save the header state.
1244
1245 for (unsigned col = 0; col < prefs.num_cols; col++) {
1246 // The column must be shown before setting column width.
1247 // Visibility will be updated in setColumnVisibility().
1248 setColumnHidden(col, false);
1249 setRecentColumnWidth(col);
1250 }
1251}
1252
1253void PacketList::preferencesChanged()
1254{
1255 // Previously driven by the separate MainApplication::colorsChanged
1256 // signal, which has been removed — every colorsChanged emit was
1257 // paired with a preferencesChanged emit anyway. Rebuilding the
1258 // selection stylesheet on every pref change is cheap (single
1259 // setStyleSheet call) and idempotent.
1260 colorsChanged();
1261
1262 // Intelligent scroll bar (minimap)
1263 if (prefs.gui_packet_list_show_minimap) {
1264 if (overlay_timer_id_ == 0) {
1265 overlay_timer_id_ = startTimer(overlay_update_interval_);
1266 }
1267 } else {
1268 if (overlay_timer_id_ != 0) {
1269 killTimer(overlay_timer_id_);
1270 overlay_timer_id_ = 0;
1271 }
1272 }
1273
1274 // Elide mode.
1275 // This sets the mode for the entire view. If we want to make this setting
1276 // per-column we'll either have to generalize RelatedPacketDelegate so that
1277 // we can set it for entire rows or create another delegate.
1278 Qt::TextElideMode elide_mode = Qt::ElideRight;
1279 switch (prefs.gui_packet_list_elide_mode) {
1280 case ELIDE_LEFT:
1281 elide_mode = Qt::ElideLeft;
1282 break;
1283 case ELIDE_MIDDLE:
1284 elide_mode = Qt::ElideMiddle;
1285 break;
1286 case ELIDE_NONE:
1287 elide_mode = Qt::ElideNone;
1288 break;
1289 default:
1290 break;
1291 }
1292 setTextElideMode(elide_mode);
1293}
1294
1295void PacketList::freezePacketList(bool changing_profile)
1296{
1297 changing_profile_ = changing_profile;
1298 freeze(true);
1299}
1300
1301void PacketList::recolorPackets()
1302{
1303 packet_list_model_->resetColorized();
1304 redrawVisiblePackets();
1305}
1306
1307// Enable autoscroll.
1308void PacketList::setVerticalAutoScroll(bool enabled)
1309{
1310 tail_at_end_ = enabled;
1311 if (enabled && capture_in_progress_) {
1312 scrollToBottom();
1313 }
1314}
1315
1316// Called when we finish reading, reloading, rescanning, and retapping
1317// packets.
1318void PacketList::captureFileReadFinished()
1319{
1320 packet_list_model_->flushVisibleRows();
1321 packet_list_model_->dissectIdle(true);
1322 // Invalidating the column strings picks up and request/response
1323 // tracking changes. We might just want to call it from flushVisibleRows.
1324 packet_list_model_->invalidateAllColumnStrings();
1325 // Sort *after* invalidating the column strings
1326 if (isSortingEnabled()) {
1327 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
1328 }
1329}
1330
1331bool PacketList::freeze(bool keep_current_frame)
1332{
1333 if (!cap_file_ || model() == Q_NULLPTRnullptr) {
1334 // No capture file or already frozen
1335 return false;
1336 }
1337
1338 frame_data *current_frame = cap_file_->current_frame;
1339 column_state_ = header()->saveState();
1340 setHeaderHidden(true);
1341 frozen_current_row_ = currentIndex();
1342 frozen_selected_rows_ = selectionModel()->selectedRows();
1343 selectionModel()->clear();
1344 setModel(Q_NULLPTRnullptr);
1345 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
1346 // call selectionChanged.
1347 related_packet_delegate_.clear();
1348
1349 if (keep_current_frame) {
1350 cap_file_->current_frame = current_frame;
1351 }
1352
1353 /* Clears packet list as well as byteview */
1354 emit framesSelected(QList<int>());
1355
1356 return true;
1357}
1358
1359bool PacketList::thaw(bool restore_selection)
1360{
1361 if (!cap_file_ || model() != Q_NULLPTRnullptr) {
1362 // No capture file or not frozen
1363 return false;
1364 }
1365
1366 setHeaderHidden(false);
1367 // Note that if we have a current sort status set in the header,
1368 // this will automatically try to sort the model (we don't want
1369 // that to happen if we're in the middle of reading the file).
1370 setModel(packet_list_model_);
1371
1372 if (changing_profile_) {
1373 // When changing profile the new recent settings must be applied to the columns.
1374 applyRecentColumnWidths();
1375 setColumnVisibility();
1376 changing_profile_ = false;
1377 } else {
1378 // Resetting the model resets our column widths so we restore them here.
1379 // We don't reapply the recent settings because the user could have
1380 // resized the columns manually since they were initially loaded.
1381 header()->restoreState(column_state_);
1382 }
1383
1384 if (restore_selection && frozen_selected_rows_.length() > 0 && selectionModel()) {
1385 /* This updates our selection, which redissects the current packet,
1386 * which is needed when we're called from MainWindow::layoutPanes.
1387 * Also, this resets all ProtoTree and ByteView data */
1388 clearSelection();
1389 setCurrentIndex(frozen_current_row_);
1390 foreach (QModelIndex idx, frozen_selected_rows_)for (auto _container_1390 = QtPrivate::qMakeForeachContainer(
frozen_selected_rows_); _container_1390.i != _container_1390.
e; ++_container_1390.i) if (QModelIndex idx = *_container_1390
.i; false) {} else
{
1391 selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1392 }
1393 scrollTo(currentIndex(), PositionAtCenter);
1394 }
1395 frozen_current_row_ = QModelIndex();
1396 frozen_selected_rows_ = QModelIndexList();
1397
1398 return true;
1399}
1400
1401void PacketList::clear() {
1402 related_packet_delegate_.clear();
1403 selectionModel()->clear();
1404 packet_list_model_->clear();
1405 proto_tree_->clear();
1406 selection_history_.clear();
1407 cur_history_ = -1;
1408 in_history_ = false;
1409
1410 QImage overlay;
1411 overlay_sb_->setNearOverlayImage(overlay);
1412 overlay_sb_->setMarkedPacketImage(overlay);
1413 create_near_overlay_ = true;
1414 create_far_overlay_ = true;
1415}
1416
1417void PacketList::writeRecent(FILE *rf) {
1418 int width, col_fmt;
1419 char xalign;
1420
1421 fprintf (rf, "%s:\n", RECENT_KEY_COL_WIDTH"column.width");
1422 for (unsigned col = 0; col < prefs.num_cols; col++) {
1423 if (col > 0) {
1424 fprintf (rf, ",\n");
1425 }
1426 col_fmt = get_column_format(col);
1427 if (col_fmt == COL_CUSTOM) {
1428 fprintf (rf, " \"%%Cus:%s\",", get_column_custom_fields(col));
1429 } else {
1430 fprintf (rf, " %s,", col_format_to_string(col_fmt));
1431 }
1432 width = recent_get_column_width (col);
1433 xalign = recent_get_column_xalign (col);
1434 fprintf (rf, " %d", width);
1435 if (xalign != COLUMN_XALIGN_DEFAULT0) {
1436 fprintf (rf, ":%c", xalign);
1437 }
1438 }
1439 fprintf (rf, "\n");
1440}
1441
1442bool PacketList::contextMenuActive()
1443{
1444 return ctx_column_ >= 0 ? true : false;
1445}
1446
1447QString PacketList::getFilterFromRowAndColumn(QModelIndex idx)
1448{
1449 frame_data *fdata;
1450 QString filter;
1451
1452 if (! idx.isValid())
1453 return filter;
1454
1455 int row = idx.row();
1456 int column = idx.column();
1457
1458 if (!cap_file_ || !packet_list_model_ || column < 0 || (unsigned)column >= cap_file_->cinfo.num_cols)
1459 return filter;
1460
1461 fdata = packet_list_model_->getRowFdata(row);
1462
1463 if (fdata != NULL__null) {
1464 epan_dissect_t edt;
1465 wtap_rec rec; /* Record information */
1466
1467 wtap_rec_init(&rec, DEFAULT_INIT_BUFFER_SIZE_2048(2 * 1024));
1468 if (!cf_read_record(cap_file_, fdata, &rec)) {
1469 wtap_rec_cleanup(&rec);
1470 return filter; /* error reading the record */
1471 }
1472 /* proto tree, visible. We need a proto tree if there's custom columns */
1473 epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), false);
1474 col_custom_prime_edt(&edt, &cap_file_->cinfo);
1475
1476 epan_dissect_run(&edt, cap_file_->cd_t, &rec, fdata, &cap_file_->cinfo);
1477
1478 if (cap_file_->cinfo.columns[column].col_fmt == COL_CUSTOM) {
1479 filter.append(gchar_free_to_qstring(col_custom_get_filter(&edt, &cap_file_->cinfo, (unsigned)column)));
1480 } else {
1481 /* We don't need to fill in the custom columns, as we get their
1482 * filters above.
1483 */
1484 col_fill_in(&edt.pi, true, true);
1485 if (strlen(cap_file_->cinfo.col_expr.col_expr[column]) != 0 &&
1486 strlen(cap_file_->cinfo.col_expr.col_expr_val[column]) != 0) {
1487 bool is_string_value = false;
1488 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.col_expr.col_expr[column]);
1489 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
)
) {
1490 /* Could be an address type such as usb.src which must be quoted. */
1491 is_string_value = true;
1492 }
1493
1494 if (filter.isEmpty()) {
1495 if (is_string_value) {
1496 filter.append(QStringLiteral("%1 == \"%2\"")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 == \"%2\"")))
1497 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1498 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1499 } else {
1500 filter.append(QStringLiteral("%1 == %2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 == %2")))
1501 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1502 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1503 }
1504 }
1505 }
1506 }
1507
1508 epan_dissect_cleanup(&edt);
1509 wtap_rec_cleanup(&rec);
1510 }
1511
1512 return filter;
1513}
1514
1515void PacketList::resetColorized()
1516{
1517 packet_list_model_->resetColorized();
1518 update();
1519}
1520
1521QString PacketList::getPacketComment(unsigned c_number)
1522{
1523 int row = currentIndex().row();
1524 const frame_data *fdata;
1525 char *pkt_comment;
1526 wtap_opttype_return_val result;
1527 QString ret_val = NULL__null;
1528
1529 if (!cap_file_ || !packet_list_model_) return NULL__null;
1530
1531 fdata = packet_list_model_->getRowFdata(row);
1532
1533 if (!fdata) return NULL__null;
1534
1535 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1536 result = wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT1, c_number, &pkt_comment);
1537 if (result == WTAP_OPTTYPE_SUCCESS) {
1538 ret_val = QString(pkt_comment);
1539 }
1540 wtap_block_unref(pkt_block);
1541 return ret_val;
1542}
1543
1544void PacketList::addPacketComment(QString new_comment)
1545{
1546 if (!cap_file_ || !packet_list_model_) return;
1547 if (new_comment.isEmpty()) return;
1548
1549 QByteArray ba = new_comment.toUtf8();
1550
1551 /*
1552 * Make sure this would fit in a pcapng option.
1553 *
1554 * XXX - 65535 is the maximum size for an option in pcapng;
1555 * what if another capture file format supports larger
1556 * comments?
1557 */
1558 if (ba.size() > 65535) {
1559 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK0x01,
1560 "That comment is too large to save in a capture file.");
1561 return;
1562 }
1563
1564 if (selectionModel() && selectionModel()->hasSelection()) {
1565 packet_list_model_->addFrameComment(selectionModel()->selectedRows(), ba);
1566 drawCurrentPacket();
1567 }
1568}
1569
1570void PacketList::setPacketComment(unsigned c_number, QString new_comment)
1571{
1572 QModelIndex curIndex = currentIndex();
1573
1574 if (!cap_file_ || !packet_list_model_) return;
1575
1576 QByteArray ba = new_comment.toUtf8();
1577 /*
1578 * Make sure this would fit in a pcapng option.
1579 *
1580 * XXX - 65535 is the maximum size for an option in pcapng;
1581 * what if another capture file format supports larger
1582 * comments?
1583 */
1584 if (ba.size() > 65535) {
1585 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK0x01,
1586 "That comment is too large to save in a capture file.");
1587 return;
1588 }
1589
1590 packet_list_model_->setFrameComment(curIndex, ba, c_number);
1591 drawCurrentPacket();
1592}
1593
1594QString PacketList::allPacketComments()
1595{
1596 uint32_t framenum;
1597 frame_data *fdata;
1598 QString buf_str;
1599
1600 if (!cap_file_) return buf_str;
1601
1602 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1603 fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum);
1604
1605 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1606
1607 if (pkt_block) {
1608 unsigned n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT1);
1609 for (unsigned i = 0; i < n_comments; i++) {
1610 char *comment_text;
1611 if (WTAP_OPTTYPE_SUCCESS == wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT1, i, &comment_text)) {
1612 buf_str.append(tr("Frame %1: %2\n\n").arg(framenum).arg(comment_text));
1613 if (buf_str.length() > max_comments_to_fetch_) {
1614 buf_str.append(tr("[ Comment text exceeds %1. Stopping. ]")
1615 .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))
));
1616 return buf_str;
1617 }
1618 }
1619 }
1620 }
1621 }
1622 return buf_str;
1623}
1624
1625void PacketList::deleteCommentsFromPackets()
1626{
1627 if (!cap_file_ || !packet_list_model_) return;
1628
1629 if (selectionModel() && selectionModel()->hasSelection()) {
1630 packet_list_model_->deleteFrameComments(selectionModel()->selectedRows());
1631 drawCurrentPacket();
1632 }
1633}
1634
1635void PacketList::deleteAllPacketComments()
1636{
1637 if (!cap_file_ || !packet_list_model_) return;
1638
1639 packet_list_model_->deleteAllFrameComments();
1640 drawCurrentPacket();
1641}
1642
1643
1644// Slots
1645
1646void PacketList::setCaptureFile(capture_file *cf)
1647{
1648 cap_file_ = cf;
1649 packet_list_model_->setCaptureFile(cf);
1650 if (cap_file_ && columns_changed_) {
1651 columnsChanged();
1652 }
1653 create_near_overlay_ = true;
1654 changing_profile_ = false;
1655 sortByColumn(-1, Qt::AscendingOrder);
1656}
1657
1658void PacketList::setMonospaceFont(const QFont &mono_font)
1659{
1660 setFont(mono_font);
1661}
1662
1663void PacketList::setRegularFont(const QFont &regular_font)
1664{
1665 header()->setFont(regular_font);
1666 header()->viewport()->setFont(regular_font);
1667}
1668
1669void PacketList::goNextPacket(void)
1670{
1671 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1672 // Alt+toolbar
1673 goNextHistoryPacket();
1674 return;
1675 }
1676
1677 if (selectionModel()->hasSelection()) {
1678 selectionModel()->setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1679 } else {
1680 // First visible packet.
1681 selectionModel()->setCurrentIndex(indexAt(viewport()->rect().topLeft()), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1682 }
1683
1684 scrollViewChanged(false);
1685}
1686
1687void PacketList::goPreviousPacket(void)
1688{
1689 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1690 // Alt+toolbar
1691 goPreviousHistoryPacket();
1692 return;
1693 }
1694
1695 if (selectionModel()->hasSelection()) {
1696 selectionModel()->setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1697 } else {
1698 // Last visible packet.
1699 QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
1700 if (last_idx.isValid()) {
1701 selectionModel()->setCurrentIndex(last_idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1702 } else {
1703 goLastPacket();
1704 }
1705 }
1706
1707 scrollViewChanged(false);
1708}
1709
1710void PacketList::goFirstPacket(void) {
1711 if (packet_list_model_->rowCount() < 1) return;
1712 selectionModel()->setCurrentIndex(packet_list_model_->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1713 scrollTo(currentIndex());
1714
1715 scrollViewChanged(false);
1716}
1717
1718void PacketList::goLastPacket(void) {
1719 if (packet_list_model_->rowCount() < 1) return;
1720 selectionModel()->setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1721 scrollTo(currentIndex());
1722
1723 scrollViewChanged(false);
1724}
1725
1726void PacketList::goToPacket(int packet, int hf_id)
1727{
1728 if (!cf_goto_frame(cap_file_, packet, false))
1729 return;
1730
1731 // cf_goto_frame only returns true if packet_list_select_row_from_data
1732 // succeeds, the latter has already selected and scrolled to the frame.
1733 if (hf_id > 0) {
1734 proto_tree_->goToHfid(hf_id);
1735 }
1736
1737 scrollViewChanged(false);
1738}
1739
1740void PacketList::goNextHistoryPacket()
1741{
1742 if (haveNextHistory(true)) {
1743 in_history_ = true;
1744 goToPacket(selection_history_.at(cur_history_));
1745 in_history_ = false;
1746 }
1747}
1748
1749void PacketList::goPreviousHistoryPacket()
1750{
1751 if (havePreviousHistory(true)) {
1752 in_history_ = true;
1753 goToPacket(selection_history_.at(cur_history_));
1754 in_history_ = false;
1755 }
1756}
1757
1758void PacketList::markFrame()
1759{
1760 if (!cap_file_ || !packet_list_model_) return;
1761
1762 QModelIndexList frames;
1763
1764 if (selectionModel() && selectionModel()->hasSelection())
1765 {
1766 QModelIndexList selRows = selectionModel()->selectedRows(0);
1767 foreach (QModelIndex idx, selRows)for (auto _container_1767 = QtPrivate::qMakeForeachContainer(
selRows); _container_1767.i != _container_1767.e; ++_container_1767
.i) if (QModelIndex idx = *_container_1767.i; false) {} else
1768 {
1769 if (idx.isValid())
1770 {
1771 frames << idx;
1772 }
1773 }
1774 }
1775 else
1776 frames << currentIndex();
1777
1778 packet_list_model_->toggleFrameMark(frames);
1779
1780 // Make sure the packet list's frame.marked related field text is updated.
1781 redrawVisiblePackets();
1782
1783 create_far_overlay_ = true;
1784 packets_bar_update();
1785}
1786
1787void PacketList::markAllDisplayedFrames(bool set)
1788{
1789 if (!cap_file_ || !packet_list_model_) return;
1790
1791 packet_list_model_->setDisplayedFrameMark(set);
1792
1793 // Make sure the packet list's frame.marked related field text is updated.
1794 redrawVisiblePackets();
1795
1796 create_far_overlay_ = true;
1797 packets_bar_update();
1798}
1799
1800void PacketList::ignoreFrame()
1801{
1802 if (!cap_file_ || !packet_list_model_) return;
1803
1804 QModelIndexList frames;
1805
1806 if (selectionModel() && selectionModel()->hasSelection())
1807 {
1808 foreach (QModelIndex idx, selectionModel()->selectedRows(0))for (auto _container_1808 = QtPrivate::qMakeForeachContainer(
selectionModel()->selectedRows(0)); _container_1808.i != _container_1808
.e; ++_container_1808.i) if (QModelIndex idx = *_container_1808
.i; false) {} else
1809 {
1810 if (idx.isValid())
1811 {
1812 frames << idx;
1813 }
1814 }
1815 }
1816 else
1817 frames << currentIndex();
1818
1819
1820 packet_list_model_->toggleFrameIgnore(frames);
1821 create_far_overlay_ = true;
1822 int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1823 setUpdatesEnabled(false);
1824 emit packetDissectionChanged();
1825 setUpdatesEnabled(true);
1826 verticalScrollBar()->setValue(sb_val);
1827}
1828
1829void PacketList::ignoreAllDisplayedFrames(bool set)
1830{
1831 if (!cap_file_ || !packet_list_model_) return;
1832
1833 packet_list_model_->setDisplayedFrameIgnore(set);
1834 create_far_overlay_ = true;
1835 emit packetDissectionChanged();
1836}
1837
1838void PacketList::setTimeReference()
1839{
1840 if (!cap_file_ || !packet_list_model_) return;
1841 packet_list_model_->toggleFrameRefTime(currentIndex());
1842 create_far_overlay_ = true;
1843}
1844
1845void PacketList::unsetAllTimeReferences()
1846{
1847 if (!cap_file_ || !packet_list_model_) return;
1848 packet_list_model_->unsetAllFrameRefTime();
1849 create_far_overlay_ = true;
1850}
1851
1852void PacketList::applyTimeShift()
1853{
1854 packet_list_model_->resetColumns();
1855 redrawVisiblePackets();
1856 emit packetDissectionChanged();
1857}
1858
1859void PacketList::updatePackets(bool redraw)
1860{
1861 if (redraw) {
1862 packet_list_model_->resetColumns();
1863 redrawVisiblePackets();
1864 } else {
1865 update();
1866 }
1867}
1868
1869void PacketList::columnVisibilityTriggered()
1870{
1871 QAction *ha = qobject_cast<QAction*>(sender());
1872 if (!ha) return;
1873
1874 int col = ha->data().toInt();
1875 set_column_visible(col, ha->isChecked());
1876 setColumnVisibility();
1877 if (ha->isChecked()) {
1878 setRecentColumnWidth(col);
1879 }
1880 prefs_main_write();
1881}
1882
1883void PacketList::sectionResized(int col, int, int new_width)
1884{
1885 if (isVisible() && !columns_changed_ && !set_column_visibility_ && !set_style_sheet_ && new_width > 0) {
1886 // Column 1 gets an invalid value (32 on macOS) when we're not yet
1887 // visible.
1888 //
1889 // Don't set column width when columns changed or setting column
1890 // visibility because we may get a sectionResized() from QTreeView
1891 // with values from a old columns layout.
1892 //
1893 // Don't set column width when hiding a column.
1894
1895 recent_set_column_width(col, new_width);
1896 }
1897}
1898
1899// The user moved a column. Make sure prefs.col_list, the column format
1900// array, and the header's visual and logical indices all agree.
1901// gtk/packet_list.c:column_dnd_changed_cb
1902void PacketList::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
1903{
1904 GList *new_col_list = NULL__null;
1905 GList *new_recent_col_list = NULL__null;
1906 QList<int> saved_sizes;
1907 int sort_idx;
1908
1909 // Since we undo the move below, these should always stay in sync.
1910 // Otherwise the order of columns can be unexpected after drag and drop.
1911 if (logicalIndex != oldVisualIndex) {
1912 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"
, 1913, __func__, "Column moved from an unexpected state (%d, %d, %d)"
, logicalIndex, oldVisualIndex, newVisualIndex); } } while (0
)
1913 logicalIndex, oldVisualIndex, newVisualIndex)do { if (true) { ws_log_full("", LOG_LEVEL_WARNING, "ui/qt/packet_list.cpp"
, 1913, __func__, "Column moved from an unexpected state (%d, %d, %d)"
, logicalIndex, oldVisualIndex, newVisualIndex); } } while (0
)
;
1914 }
1915
1916 // Remember which column should be sorted. Use the visual index since this
1917 // points to the current GUI state rather than the outdated column order
1918 // (indicated by the logical index).
1919 sort_idx = header()->sortIndicatorSection();
1920 if (sort_idx != -1) {
1921 sort_idx = header()->visualIndex(sort_idx);
1922 }
1923
1924 // Build a new column list based on the header's logical order.
1925 for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
1926 int log_idx = header()->logicalIndex(vis_idx);
1927 saved_sizes << header()->sectionSize(log_idx);
1928
1929 void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
1930 if (pref_data) {
1931 new_col_list = g_list_append(new_col_list, pref_data);
1932 }
1933
1934 pref_data = g_list_nth_data(recent.col_width_list, log_idx);
1935 if (pref_data) {
1936 new_recent_col_list = g_list_append(new_recent_col_list, pref_data);
1937 }
1938 }
1939
1940 // Undo move to ensure that the logical indices map to the visual indices,
1941 // otherwise the column order is changed twice (once via the modified
1942 // col_list, once because of the visual/logical index mismatch).
1943 disconnect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
1944 header()->moveSection(newVisualIndex, oldVisualIndex);
1945 connect(header(), &QHeaderView::sectionMoved, this, &PacketList::sectionMoved);
1946
1947 // Clear and rebuild our (and the header's) model. There doesn't appear
1948 // to be another way to reset the logical index.
1949 freeze();
1950
1951 g_list_free(prefs.col_list);
1952 prefs.col_list = new_col_list;
1953 g_list_free(recent.col_width_list);
1954 recent.col_width_list = new_recent_col_list;
1955
1956 thaw(true);
1957
1958 for (int i = 0; i < saved_sizes.length(); i++) {
1959 if (saved_sizes[i] < 1) continue;
1960 header()->resizeSection(i, saved_sizes[i]);
1961 }
1962
1963 prefs_main_write();
1964
1965 mainApp->emitAppSignal(MainApplication::ColumnsChanged);
1966
1967 // If the column with the sort indicator got shifted, mark the new column
1968 // after updating the columns contents (via ColumnsChanged) to ensure that
1969 // the columns are sorted using the intended column contents.
1970 int left_col = MIN(oldVisualIndex, newVisualIndex)(((oldVisualIndex) < (newVisualIndex)) ? (oldVisualIndex) :
(newVisualIndex))
;
1971 int right_col = MAX(oldVisualIndex, newVisualIndex)(((oldVisualIndex) > (newVisualIndex)) ? (oldVisualIndex) :
(newVisualIndex))
;
1972 if (left_col <= sort_idx && sort_idx <= right_col) {
1973 header()->setSortIndicator(sort_idx, header()->sortIndicatorOrder());
1974 }
1975}
1976
1977QString PacketList::createSummaryText(QModelIndex idx, SummaryCopyType type)
1978{
1979 if (! idx.isValid())
1980 return "";
1981
1982 QStringList col_parts;
1983 int row = idx.row();
1984 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1985 if (get_column_visible(col)) {
1986 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
1987 }
1988 }
1989 return joinSummaryRow(col_parts, row, type);
1990}
1991
1992QString PacketList::createHeaderSummaryText(SummaryCopyType type)
1993{
1994 QStringList col_parts;
1995 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
1996 if (get_column_visible(col)) {
1997 col_parts << packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
1998 }
1999 }
2000 return joinSummaryRow(col_parts, 0, type);
2001}
2002
2003QStringList PacketList::createHeaderPartsForAligned()
2004{
2005 QStringList hdr_parts;
2006 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
2007 if (get_column_visible(col)) {
2008 hdr_parts << packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
2009 }
2010 }
2011 return hdr_parts;
2012}
2013
2014QList<int> PacketList::createAlignmentPartsForAligned()
2015{
2016 QList<int> align_parts;
2017 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2018 if (get_column_visible(col)) {
2019 align_parts << packet_list_model_->data(packet_list_model_->index(0, col), Qt::TextAlignmentRole).toInt();
2020 }
2021 }
2022 return align_parts;
2023}
2024
2025QList<int> PacketList::createSizePartsForAligned(bool useHeader, QStringList hdr_parts, QList<int> rows)
2026{
2027 QList<int> size_parts;
2028
2029 for (int i = 0; i < hdr_parts.size(); ++i) {
2030 if (useHeader)
2031 size_parts << static_cast<int>(hdr_parts.at(i).size());
2032 else
2033 size_parts << 0;
2034 }
2035
2036 foreach(int row, rows)for (auto _container_2036 = QtPrivate::qMakeForeachContainer(
rows); _container_2036.i != _container_2036.e; ++_container_2036
.i) if (int row = *_container_2036.i; false) {} else
2037 {
2038 QModelIndex idx = model()->index(row, 0);
2039 if (! idx.isValid())
2040 continue;
2041
2042 QStringList col_parts;
2043 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2044 if (get_column_visible(col)) {
2045 col_parts << (packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString());
2046 }
2047 }
2048
2049 for (int i = 0; i < col_parts.size(); ++i) {
2050 if (col_parts.at(i).size() > size_parts.at(i)) {
2051 size_parts[i] = static_cast<int>(col_parts.at(i).size());
2052 }
2053 }
2054 col_parts.clear();
2055 }
2056
2057 return size_parts;
2058}
2059
2060QString PacketList::createHeaderSummaryForAligned(QStringList hdr_parts, QList<int> align_parts, QList<int> size_parts)
2061{
2062 QString hdr_text;
2063 for (int i = 0; i < hdr_parts.size(); ++i) {
2064 if (align_parts.at(i) == Qt::AlignLeft) {
2065 hdr_text += hdr_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
2066 }
2067 else if (align_parts.at(i) == Qt::AlignRight) {
2068 hdr_text += hdr_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
2069 }
2070 }
2071 return QStringLiteral("-%1")(QString(QtPrivate::qMakeStringPrivate(u"" "-%1"))).arg(hdr_text).trimmed().mid(1);
2072}
2073
2074QString PacketList::createSummaryForAligned(QModelIndex idx, QList<int> align_parts, QList<int> size_parts)
2075{
2076 if (! idx.isValid())
2077 return "";
2078
2079 QStringList col_parts;
2080 int row = idx.row();
2081 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2082 if (get_column_visible(col)) {
2083 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
2084 }
2085 }
2086
2087 QString col_text;
2088 for (int i = 0; i < col_parts.size(); ++i) {
2089 if (align_parts.at(i) == Qt::AlignLeft) {
2090 col_text += col_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
2091 }
2092 else if (align_parts.at(i) == Qt::AlignRight) {
2093 col_text += col_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
2094 }
2095 }
2096
2097 return QStringLiteral("-%1")(QString(QtPrivate::qMakeStringPrivate(u"" "-%1"))).arg(col_text).trimmed().mid(1);
2098}
2099
2100QString PacketList::createDefaultStyleForHtml()
2101{
2102 QString fontFamily = QString(prefs.gui_font_name).split(",")[0];
2103 QString fontSize = QString(prefs.gui_font_name).split(",")[1];
2104
2105 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>")))
2106 "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>")))
2107 "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>")))
2108 "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>")))
2109 "</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);
2110}
2111
2112QString PacketList::createOpeningTagForHtml()
2113{
2114 return "<table>";
2115}
2116
2117QString PacketList::createHeaderSummaryForHtml()
2118{
2119 QString hdr_text;
2120 hdr_text += "<tr>";
2121 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
2122 if (get_column_visible(col)) {
2123 hdr_text += "<th>";
2124 hdr_text += packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
2125 hdr_text += "</th>";
2126 }
2127 }
2128 hdr_text += "</tr>";
2129 return hdr_text;
2130}
2131
2132QString PacketList::createSummaryForHtml(QModelIndex idx)
2133{
2134 if (! idx.isValid())
2135 return "";
2136
2137 int row = idx.row();
2138 QString col_text;
2139
2140 QString bg_color = packet_list_model_->data(packet_list_model_->index(row, 0), Qt::BackgroundRole).toString();
2141 QString fg_color = packet_list_model_->data(packet_list_model_->index(row, 0), Qt::ForegroundRole).toString();
2142 col_text += "<tr style=\"background-color:" + bg_color + ";color:" + fg_color + ";\">";
2143
2144 QString alignment[] = {"left", "right", "center", "justify"};
2145
2146 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2147 if (get_column_visible(col)) {
2148 col_text += "<td style=\"text-align:" + alignment[packet_list_model_->data(packet_list_model_->index(row, col), Qt::TextAlignmentRole).toInt() / 2] + ";\">";
2149 col_text += packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
2150 col_text += "</td>";
2151 }
2152 }
2153
2154 col_text += "</tr>";
2155 return col_text;
2156}
2157
2158QString PacketList::createClosingTagForHtml()
2159{
2160 return "</table>";
2161}
2162
2163void PacketList::copySummary()
2164{
2165 if (!currentIndex().isValid()) return;
2166
2167 QAction *ca = qobject_cast<QAction*>(sender());
2168 if (!ca) return;
2169
2170 QVariant type = ca->data();
2171 if (! type.canConvert<SummaryCopyType>())
2172 return;
2173 SummaryCopyType copy_type = type.value<SummaryCopyType>();
2174
2175 QString copy_text;
2176 if (type == CopyAsText || type == CopyAsHTML) {
2177 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
2178 QList<int> rows;
2179 rows << currentIndex().row();
2180 QStringList hdr_parts;
2181 QList<int> align_parts, size_parts;
2182 hdr_parts = createHeaderPartsForAligned();
2183 align_parts = createAlignmentPartsForAligned();
2184 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
2185 copy_text = createSummaryForAligned(currentIndex(), align_parts, size_parts);
2186 }
2187 else {
2188 copy_text = createSummaryText(currentIndex(), CopyAsText);
2189 }
2190 copy_text += "\n";
2191 if (type == CopyAsHTML) {
2192 QStringList htmlContent;
2193 htmlContent << createDefaultStyleForHtml();
2194 htmlContent << createOpeningTagForHtml();
2195 htmlContent << createSummaryForHtml(currentIndex());
2196 htmlContent << createClosingTagForHtml();
2197 // htmlContent will never be empty as they will always have
2198 // style and table tags
2199 QMimeData *mimeData = new QMimeData;
2200 mimeData->setHtml(htmlContent.join('\n'));
2201 mimeData->setText(copy_text);
2202 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
2203 }
2204 else {
2205 mainApp->clipboard()->setText(copy_text);
2206 }
2207 }
2208 else {
2209 copy_text = createSummaryText(currentIndex(), copy_type);
2210 if (type != CopyAsYAML)
2211 copy_text += "\n";
2212 mainApp->clipboard()->setText(copy_text);
2213 }
2214}
2215
2216// We need to tell when the user has scrolled the packet list, either to
2217// the end or anywhere other than the end.
2218void PacketList::vScrollBarActionTriggered(int)
2219{
2220 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
2221 // past the end.
2222 tail_at_end_ = (overlay_sb_->sliderPosition() >= overlay_sb_->maximum());
2223
2224 scrollViewChanged(tail_at_end_);
2225}
2226
2227void PacketList::scrollViewChanged(bool at_end)
2228{
2229 if (capture_in_progress_) {
2230 // We want to start auto scrolling when the user scrolls to (or past)
2231 // the end only if recent.capture_auto_scroll is set.
2232 // We want to stop autoscrolling if the user scrolls up or uses
2233 // Go to Packet regardless of the preference setting.
2234 if (recent.capture_auto_scroll || !at_end) {
2235 emit packetListScrolled(at_end);
2236 }
2237 }
2238}
2239
2240// Goal: Overlay the packet list scroll bar with the colors of all of the
2241// packets.
2242// Try 1: Average packet colors in each scroll bar raster line. This has
2243// two problems: It's easy to wash out colors and we dissect every packet.
2244// Try 2: Color across a 5000 or 10000 packet window. We still end up washing
2245// out colors.
2246// Try 3: One packet per vertical scroll bar pixel. This seems to work best
2247// but has the smallest window.
2248// Try 4: Use a multiple of the scroll bar height and scale the image down
2249// using Qt::SmoothTransformation. This gives us more packets per raster
2250// line.
2251
2252// Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
2253// of 9 washed out colors a little too much.
2254//const int height_multiplier_ = 7;
2255void PacketList::drawNearOverlay()
2256{
2257 if (create_near_overlay_) {
2258 create_near_overlay_ = false;
2259 }
2260
2261 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
2262
2263 if (!prefs.gui_packet_list_show_minimap) return;
2264
2265 qreal dp_ratio = overlay_sb_->devicePixelRatio();
2266 int o_height = overlay_sb_->height() * dp_ratio;
2267 int o_rows = qMin(packet_list_model_->rowCount(), o_height);
2268 QFontMetricsF fmf(mainApp->font());
2269 int o_width = ((static_cast<int>(fmf.height())) * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
2270
2271 if (recent.packet_list_colorize && o_rows > 0) {
2272 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
2273
2274 QPainter painter(&overlay);
2275
2276 overlay.fill(Qt::transparent);
2277
2278 int cur_line = 0;
2279 int start = 0;
2280
2281 if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
2282 start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows);
2283 }
2284 int end = start + o_rows;
2285 for (int row = start; row < end; row++) {
2286 packet_list_model_->ensureRowColorized(row);
2287
2288 frame_data *fdata = packet_list_model_->getRowFdata(row);
2289 int next_line = (row - start + 1) * o_height / o_rows;
2290 int row_height = next_line - cur_line;
2291
2292 // Multi-color support in minimap (enabled for all non-Off modes)
2293 if (prefs.gui_packet_list_multi_color_mode != PACKET_LIST_MULTI_COLOR_MODE_OFF) {
2294 QModelIndex idx = packet_list_model_->index(row, 0);
2295 PacketListRecord *record = static_cast<PacketListRecord*>(idx.internalPointer());
2296 // Conversation color filters take full precedence — skip multi-color stripe rendering
2297 bool is_conversation_color = fdata->color_filter &&
2298 strncmp(((const color_filter_t *)fdata->color_filter)->filter_name,
2299 CONVERSATION_COLOR_PREFIX"___conversation_color_filter___", strlen(CONVERSATION_COLOR_PREFIX"___conversation_color_filter___")) == 0;
2300 if (record && record->hasMultipleColors() && !is_conversation_color) {
2301 // Draw vertical stripes for multiple colors (skip paused filters)
2302 const GSList *filters = record->matchingColorFilters();
2303
2304 // Count non-paused filters
2305 int num_active_colors = 0;
2306 for (const GSList *item = filters; item != NULL__null; item = g_slist_next(item)((item) ? (((GSList *)(item))->next) : __null)) {
2307 const color_filter_t *colorf = (const color_filter_t *)item->data;
2308 if (!color_filter_is_session_disabled(colorf->filter_name)) {
2309 num_active_colors++;
2310 }
2311 }
2312
2313 if (num_active_colors > 0) {
2314 int stripe_width = o_width / num_active_colors;
2315 int x_pos = 0;
2316 int stripe_idx = 0;
2317
2318 for (const GSList *item = filters; item != NULL__null; item = g_slist_next(item)((item) ? (((GSList *)(item))->next) : __null)) {
2319 const color_filter_t *colorf = (const color_filter_t *)item->data;
2320 // Skip paused filters
2321 if (!color_filter_is_session_disabled(colorf->filter_name)) {
2322 QColor color(ColorUtils::fromColorT(&colorf->bg_color));
2323 int width = (stripe_idx == num_active_colors - 1) ? (o_width - x_pos) : stripe_width;
2324 painter.fillRect(x_pos, cur_line, width, row_height, color);
2325 x_pos += stripe_width;
2326 stripe_idx++;
2327 }
2328 }
2329 } else {
2330 // All filters paused, draw no color
2331 painter.fillRect(0, cur_line, o_width, row_height, QColor(Qt::white));
2332 }
2333 } else if (fdata->color_filter) {
2334 // Single color fallback
2335 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
2336 QColor color(ColorUtils::fromColorT(&color_filter->bg_color));
2337 painter.fillRect(0, cur_line, o_width, row_height, color);
2338 }
2339 } else {
2340 // Original single-color behavior
2341 const color_t *bgcolor = NULL__null;
2342 if (fdata->color_filter) {
2343 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
2344 bgcolor = &color_filter->bg_color;
2345 }
2346
2347 if (bgcolor) {
2348 QColor color(ColorUtils::fromColorT(bgcolor));
2349 painter.fillRect(0, cur_line, o_width, row_height, color);
2350 }
2351 }
2352
2353 cur_line = next_line;
2354 }
2355
2356 // If the selected packet is in the overlay set selected_pos
2357 // accordingly. Otherwise, pin it to either the top or bottom.
2358 QList<int> positions;
2359 if (selectionModel()->hasSelection()) {
2360
2361 QModelIndexList selRows = selectionModel()->selectedRows(0);
2362 int last_row = -1;
2363 int last_pos = -1;
2364 foreach (QModelIndex idx, selRows)for (auto _container_2364 = QtPrivate::qMakeForeachContainer(
selRows); _container_2364.i != _container_2364.e; ++_container_2364
.i) if (QModelIndex idx = *_container_2364.i; false) {} else
2365 {
2366 int selected_pos = -1;
2367 int sel_row = idx.row();
2368 if (sel_row < start) {
2369 selected_pos = 0;
2370 } else if (sel_row >= end) {
2371 selected_pos = overlay.height() - 1;
2372 } else {
2373 selected_pos = (sel_row - start) * o_height / o_rows;
2374 }
2375
2376 /* Due to the difference in the display height, we sometimes get empty positions
2377 * inbetween consecutive valid rows. If those are detected, they are signaled as
2378 * being selected as well */
2379 if (last_pos >= 0 && selected_pos > (last_pos + 1) && (last_row + 1) == sel_row)
2380 {
2381 for (int pos = (last_pos + 1); pos < selected_pos; pos++)
2382 {
2383 if (! positions.contains(pos))
2384 positions << pos;
2385 }
2386 }
2387 else if (selected_pos != -1 && ! positions.contains(selected_pos))
2388 positions << selected_pos;
2389
2390 last_row = sel_row;
2391 last_pos = selected_pos;
2392 }
2393 }
2394
2395 overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, positions, (o_height / o_rows));
2396 } else {
2397 QImage overlay;
2398 overlay_sb_->setNearOverlayImage(overlay);
2399 }
2400}
2401
2402void PacketList::drawFarOverlay()
2403{
2404 if (create_far_overlay_) {
2405 create_far_overlay_ = false;
2406 }
2407
2408 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
2409
2410 if (!prefs.gui_packet_list_show_minimap) return;
2411
2412 QSize groove_size = overlay_sb_->grooveRect().size();
2413 qreal dp_ratio = overlay_sb_->devicePixelRatio();
2414 groove_size *= dp_ratio;
2415 int o_width = groove_size.width();
2416 int o_height = groove_size.height();
2417 int pl_rows = packet_list_model_->rowCount();
2418 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
2419 bool have_marked_image = false;
2420
2421 // If only there were references from popular culture about getting into
2422 // some sort of groove.
2423 if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
2424
2425 QPainter painter(&overlay);
2426
2427 // Draw text-colored tick marks on a transparent background.
2428 // Hopefully no themes use the text color for the groove color.
2429 overlay.fill(Qt::transparent);
2430
2431 QColor tick_color = palette().text().color();
2432 tick_color.setAlphaF(0.3f);
2433 painter.setPen(tick_color);
2434
2435 for (int row = 0; row < pl_rows; row++) {
2436
2437 frame_data *fdata = packet_list_model_->getRowFdata(row);
2438 if (fdata->marked || fdata->ref_time || fdata->ignored) {
2439 int new_line = row * o_height / pl_rows;
2440 int tick_width = o_width / 3;
2441 // Marked or ignored: left side, time refs: right side.
2442 // XXX Draw ignored ticks in the middle?
2443 int x1 = fdata->ref_time ? o_width - tick_width : 1;
2444 int x2 = fdata->ref_time ? o_width - 1 : tick_width;
2445
2446 painter.drawLine(x1, new_line, x2, new_line);
2447 have_marked_image = true;
2448 }
2449 }
2450
2451 if (have_marked_image) {
2452 overlay_sb_->setMarkedPacketImage(overlay);
2453 return;
2454 }
2455 }
2456
2457 if (!have_marked_image) {
2458 QImage null_overlay;
2459 overlay_sb_->setMarkedPacketImage(null_overlay);
2460 }
2461}
2462
2463// Auto scroll if:
2464// - We are capturing
2465// - actionGoAutoScroll in the main UI is checked.
2466
2467// actionGoAutoScroll in the main UI:
2468// - Is set to the value of recent.capture_auto_scroll when beginning a capture
2469// - Can be triggered manually by the user
2470// - Is turned on if the last user-set vertical scrollbar position is at the
2471// end and recent.capture_auto_scroll is enabled
2472// - Is turned off if the last user-set vertical scrollbar is not at the end,
2473// or if one of the Go to Packet actions is used (XXX: Should keyboard
2474// navigation in keyPressEvent turn it off for similar reasons?)
2475void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
2476{
2477 QTreeView::rowsInserted(parent, start, end);
2478 const QModelIndex& cIndex = currentIndex();
2479 bool aggregation_mode = recent.aggregation_view && prefs.aggregation_fields_num > 0;
2480 if (aggregation_mode && cIndex.isValid() && cIndex.row() >= 0) {
2481 int row = cIndex.row();
2482 frame_data* fdata = getFDataForRow(row);
2483 if (fdata && cap_file_->current_frame->num != fdata->num) {
2484 cf_select_packet(cap_file_, fdata);
2485 emit framesSelected(QList<int>() << row);
2486 }
2487 }
2488 if (capture_in_progress_ && tail_at_end_) {
2489 scrollToBottom();
2490 }
2491}
2492
2493void PacketList::resizeAllColumns(bool onlyTimeFormatted)
2494{
2495 if (!cap_file_ || cap_file_->state == FILE_CLOSED || cap_file_->state == FILE_READ_PENDING)
2496 return;
2497
2498 for (unsigned col = 0; col < cap_file_->cinfo.num_cols; col++) {
2499 if (! onlyTimeFormatted || col_has_time_fmt(&cap_file_->cinfo, col)) {
2500 resizeColumnToContents(col);
2501 }
2502 }
2503}