### Bug Summary

File: | builds/wireshark/wireshark/ui/qt/packet_list.cpp
Warning: | line 928, column 20 Potential leak of memory pointed to by 'drag_label'

### Annotated Source Code

Press '?' to see keyboard shortcuts

Show analyzer invocation
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-15-100323-3641-1 -x c++ /builds/wireshark/wireshark/ui/qt/packet_list.cpp

Keyboard shortcuts:

- Use 'j/k' keys for keyboard navigation
- Use 'Shift+S' to show/hide relevant lines
- Use '?' to toggle this window

Close
Show only relevant lines Show control flow arrows

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