### Bug Summary

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

### Annotated Source Code

Press '?' to see keyboard shortcuts

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