Bug Summary

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