Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / packet_list.cpp
blob14304e6a1e5109dfcd03d2b269165a12226ab4fa
1 /* packet_list.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
10 #include <ui/qt/packet_list.h>
12 #include "config.h"
14 #include "file.h"
16 #include <epan/epan.h>
17 #include <epan/epan_dissect.h>
19 #include <epan/column.h>
20 #include <epan/expert.h>
21 #include <epan/ipproto.h>
22 #include <epan/packet.h>
23 #include <epan/prefs.h>
24 #include <epan/proto.h>
26 #include "ui/main_statusbar.h"
27 #include "ui/packet_list_utils.h"
28 #include "ui/preference_utils.h"
29 #include "ui/recent.h"
30 #include "ui/recent_utils.h"
31 #include "ui/ws_ui_util.h"
32 #include "ui/simple_dialog.h"
33 #include <wsutil/utf8_entities.h>
34 #include "ui/util.h"
36 #include "wiretap/wtap_opttypes.h"
37 #include "wsutil/filesystem.h"
38 #include "wsutil/str_util.h"
39 #include <wsutil/wslog.h>
41 #include <epan/color_filters.h>
42 #include "frame_tvbuff.h"
44 #include <ui/qt/utils/color_utils.h>
45 #include <ui/qt/widgets/overlay_scroll_bar.h>
46 #include "proto_tree.h"
47 #include <ui/qt/utils/qt_ui_utils.h>
48 #include "main_application.h"
49 #include <ui/qt/utils/data_printer.h>
50 #include <ui/qt/utils/frame_information.h>
51 #include <ui/qt/utils/profile_switcher.h>
52 #include <ui/qt/utils/variant_pointer.h>
53 #include <ui/qt/models/pref_models.h>
54 #include <ui/qt/widgets/packet_list_header.h>
55 #include <ui/qt/utils/wireshark_mime_data.h>
56 #include <ui/qt/widgets/drag_label.h>
57 #include <ui/qt/filter_action.h>
58 #include <ui/qt/follow_stream_action.h>
59 #include <ui/qt/decode_as_dialog.h>
60 #include <ui/qt/wireshark_main_window.h>
62 #include <QAction>
63 #include <QActionGroup>
64 #include <QClipboard>
65 #include <QContextMenuEvent>
66 #include <QtCore/qmath.h>
67 #include <QElapsedTimer>
68 #include <QFontMetrics>
69 #include <QHeaderView>
70 #include <QMessageBox>
71 #include <QPainter>
72 #include <QScreen>
73 #include <QScrollBar>
74 #include <QTabWidget>
75 #include <QTextEdit>
76 #include <QTimerEvent>
77 #include <QTreeWidget>
78 #include <QWindow>
79 #include <QJsonObject>
80 #include <QJsonDocument>
82 #ifdef Q_OS_WIN
83 #include "wsutil/file_util.h"
84 #include <QSysInfo>
85 #include <uxtheme.h>
86 #endif
88 // To do:
89 // - Fix "apply as filter" behavior.
90 // - Add colorize conversation.
91 // - Use a timer to trigger automatic scrolling.
93 // If we ever add the ability to open multiple capture files we might be
94 // able to use something like QMap<capture_file *, PacketList *> to match
95 // capture files against packet lists and models.
96 static PacketList *gbl_cur_packet_list;
98 const int max_comments_to_fetch_ = 20000000; // Arbitrary
99 const int overlay_update_interval_ = 100; // 250; // Milliseconds.
103 * Given a frame_data structure, scroll to and select the row in the
104 * packet list corresponding to that frame. If there is no such
105 * row, return false, otherwise return true.
107 bool
108 packet_list_select_row_from_data(frame_data *fdata_needle)
110 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
111 return false;
113 PacketListModel * model = qobject_cast<PacketListModel *>(gbl_cur_packet_list->model());
115 if (! model)
116 return false;
118 model->flushVisibleRows();
119 int row = -1;
120 if (!fdata_needle)
121 row = 0;
122 else
123 row = model->visibleIndexOf(fdata_needle);
125 if (row >= 0) {
126 /* Calling ClearAndSelect with setCurrentIndex clears the "current"
127 * item, but doesn't clear the "selected" item. We want to clear
128 * the "selected" item as well so that selectionChanged() will be
129 * emitted in order to force an update of the packet details and
130 * packet bytes after a search.
132 gbl_cur_packet_list->selectionModel()->clearSelection();
133 gbl_cur_packet_list->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
134 gbl_cur_packet_list->scrollTo(gbl_cur_packet_list->currentIndex(), PacketList::PositionAtCenter);
135 return true;
138 return false;
142 * Given a field_info, select the field (which will scroll to it in
143 * the main ProtoTree, etc.) This is kind of an odd place for it,
144 * but we call this when performing Find Packet in lieu of changing the
145 * selected frame (the function above), because we found a match in the
146 * same frame as the currently selected one.
148 bool
149 packet_list_select_finfo(field_info *fi)
151 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
152 return false;
154 if (fi) {
155 FieldInformation finfo(fi, gbl_cur_packet_list);
156 emit gbl_cur_packet_list->fieldSelected(&finfo);
157 } else {
158 emit gbl_cur_packet_list->fieldSelected(0);
160 return true;
163 void
164 packet_list_clear(void)
166 if (gbl_cur_packet_list) {
167 gbl_cur_packet_list->clear();
171 void
172 packet_list_freeze(void)
174 if (gbl_cur_packet_list) {
175 gbl_cur_packet_list->freeze();
179 void
180 packet_list_thaw(void)
182 if (gbl_cur_packet_list) {
183 gbl_cur_packet_list->thaw();
186 packets_bar_update();
189 /* Redraw the packet list *and* currently-selected detail */
190 void
191 packet_list_queue_draw(void)
193 if (gbl_cur_packet_list)
194 gbl_cur_packet_list->redrawVisiblePackets();
197 void
198 packet_list_recent_write_all(FILE *rf) {
199 if (!gbl_cur_packet_list)
200 return;
202 gbl_cur_packet_list->writeRecent(rf);
205 bool
206 packet_list_multi_select_active(void)
208 if (gbl_cur_packet_list) {
209 return gbl_cur_packet_list->multiSelectActive();
211 return false;
214 #define MIN_COL_WIDTH_STR "MMMMMM"
216 PacketList::PacketList(QWidget *parent) :
217 QTreeView(parent),
218 proto_tree_(nullptr),
219 cap_file_(nullptr),
220 ctx_column_(-1),
221 overlay_timer_id_(0),
222 create_near_overlay_(true),
223 create_far_overlay_(true),
224 mouse_pressed_at_(QModelIndex()),
225 capture_in_progress_(false),
226 tail_at_end_(0),
227 columns_changed_(false),
228 set_column_visibility_(false),
229 set_style_sheet_(false),
230 frozen_current_row_(QModelIndex()),
231 frozen_selected_rows_(QModelIndexList()),
232 cur_history_(-1),
233 in_history_(false),
234 finfo_array(nullptr),
235 profile_switcher_(nullptr)
237 setItemsExpandable(false);
238 setRootIsDecorated(false);
239 setSortingEnabled(prefs.gui_packet_list_sortable);
240 setUniformRowHeights(true);
241 setAccessibleName("Packet list");
243 packet_list_header_ = new PacketListHeader(header()->orientation());
244 connect(packet_list_header_, &PacketListHeader::resetColumnWidth, this, &PacketList::setRecentColumnWidth);
245 connect(packet_list_header_, &PacketListHeader::updatePackets, this, &PacketList::updatePackets);
246 connect(packet_list_header_, &PacketListHeader::showColumnPreferences, this, &PacketList::showProtocolPreferences);
247 connect(packet_list_header_, &PacketListHeader::editColumn, this, &PacketList::editColumn);
248 connect(packet_list_header_, &PacketListHeader::columnsChanged, this, &PacketList::columnsChanged);
249 setHeader(packet_list_header_);
251 header()->setFirstSectionMovable(true);
253 setSelectionMode(QAbstractItemView::ExtendedSelection);
255 // Shrink down to a small but nonzero size in the main splitter.
256 int one_em = fontMetrics().height();
257 setMinimumSize(one_em, one_em);
259 overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
260 setVerticalScrollBar(overlay_sb_);
262 header()->setSortIndicator(-1, Qt::AscendingOrder);
264 packet_list_model_ = new PacketListModel(this, cap_file_);
265 setModel(packet_list_model_);
267 Q_ASSERT(gbl_cur_packet_list == Q_NULLPTR);
268 gbl_cur_packet_list = this;
270 connect(packet_list_model_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
271 connect(packet_list_model_, SIGNAL(itemHeightChanged(const QModelIndex&)), this, SLOT(updateRowHeights(const QModelIndex&)));
272 connect(mainApp, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
273 connect(mainApp, SIGNAL(columnDataChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
274 connect(mainApp, &MainApplication::preferencesChanged, this, [=]() {
275 /* The pref is a uint but QCache maxCost is a signed int (/
276 * qsizetype in Qt 6). Note that QAbstractItemModel row numbers
277 * are ints, not unsigned ints, so we're limited to INT_MAX
278 * rows anyway.
280 PacketListRecord::setMaxCache(prefs.gui_packet_list_cached_rows_max > INT_MAX ? INT_MAX : prefs.gui_packet_list_cached_rows_max);
281 if ((bool) (prefs.gui_packet_list_sortable) != isSortingEnabled()) {
282 setSortingEnabled(prefs.gui_packet_list_sortable);
286 connect(header(), SIGNAL(sectionResized(int,int,int)),
287 this, SLOT(sectionResized(int,int,int)));
288 connect(header(), SIGNAL(sectionMoved(int,int,int)),
289 this, SLOT(sectionMoved(int,int,int)));
291 connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
294 PacketList::~PacketList()
296 if (finfo_array)
298 g_ptr_array_free(finfo_array, true);
302 void PacketList::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
304 /* QAbstractItemView doesn't have a way to indicate "auto scroll, but
305 * only vertically." So just restore the horizontal scroll value whenever
306 * it scrolls.
308 setUpdatesEnabled(false);
309 int horizVal = horizontalScrollBar()->value();
310 QTreeView::scrollTo(index, hint);
311 horizontalScrollBar()->setValue(horizVal);
312 setUpdatesEnabled(true);
315 void PacketList::colorsChanged()
317 const QString c_active = "active";
318 const QString c_inactive = "!active";
320 QString flat_style_format =
321 "QTreeView::item:selected:%1 {"
322 " color: %2;"
323 " background-color: %3;"
324 "}";
326 QString gradient_style_format =
327 "QTreeView::item:selected:%1 {"
328 " color: %2;"
329 " background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1 stop: 0 %4, stop: 0.5 %3, stop: 1 %4);"
330 "}";
332 QString hover_style;
333 #if !defined(Q_OS_WIN)
334 hover_style = QString(
335 "QTreeView:item:hover {"
336 " background-color: %1;"
337 " color: palette(text);"
338 "}").arg(ColorUtils::hoverBackground().name(QColor::HexArgb));
339 #endif
341 QString active_style = QString();
342 QString inactive_style = QString();
344 if (prefs.gui_active_style == COLOR_STYLE_DEFAULT) {
345 // ACTIVE = Default
346 } else if (prefs.gui_active_style == COLOR_STYLE_FLAT) {
347 // ACTIVE = Flat
348 QColor foreground = ColorUtils::fromColorT(prefs.gui_active_fg);
349 QColor background = ColorUtils::fromColorT(prefs.gui_active_bg);
351 active_style = flat_style_format.arg(
352 c_active,
353 foreground.name(),
354 background.name());
355 } else if (prefs.gui_active_style == COLOR_STYLE_GRADIENT) {
356 // ACTIVE = Gradient
357 QColor foreground = ColorUtils::fromColorT(prefs.gui_active_fg);
358 QColor background1 = ColorUtils::fromColorT(prefs.gui_active_bg);
359 QColor background2 = QColor::fromRgb(ColorUtils::alphaBlend(foreground, background1, COLOR_STYLE_ALPHA));
361 active_style = gradient_style_format.arg(
362 c_active,
363 foreground.name(),
364 background1.name(),
365 background2.name());
368 // INACTIVE style sheet settings
369 if (prefs.gui_inactive_style == COLOR_STYLE_DEFAULT) {
370 // INACTIVE = Default
371 } else if (prefs.gui_inactive_style == COLOR_STYLE_FLAT) {
372 // INACTIVE = Flat
373 QColor foreground = ColorUtils::fromColorT(prefs.gui_inactive_fg);
374 QColor background = ColorUtils::fromColorT(prefs.gui_inactive_bg);
376 inactive_style = flat_style_format.arg(
377 c_inactive,
378 foreground.name(),
379 background.name());
380 } else if (prefs.gui_inactive_style == COLOR_STYLE_GRADIENT) {
381 // INACTIVE = Gradient
382 QColor foreground = ColorUtils::fromColorT(prefs.gui_inactive_fg);
383 QColor background1 = ColorUtils::fromColorT(prefs.gui_inactive_bg);
384 QColor background2 = QColor::fromRgb(ColorUtils::alphaBlend(foreground, background1, COLOR_STYLE_ALPHA));
386 inactive_style = gradient_style_format.arg(
387 c_inactive,
388 foreground.name(),
389 background1.name(),
390 background2.name());
393 // Set the style sheet
394 set_style_sheet_ = true;
395 if(prefs.gui_packet_list_hover_style) {
396 setStyleSheet(active_style + inactive_style + hover_style);
397 } else {
398 setStyleSheet(active_style + inactive_style);
400 set_style_sheet_ = false;
401 #if \
403 (QT_VERSION >= QT_VERSION_CHECK(6, 5, 4) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) \
404 || (QT_VERSION >= QT_VERSION_CHECK(6, 6, 1)) \
406 // https://bugreports.qt.io/browse/QTBUG-122109
407 // Affects Qt 6.5.4 and later, 6.6.1 and later.
408 // When setting the style sheet, all visible sections are set
409 // to the new minimum DefaultSectionSize (even if it hasn't
410 // changed.) So make sure the new widths aren't saved to recent
411 // and then restore from recent.
412 applyRecentColumnWidths();
413 setColumnVisibility();
414 #endif
417 QString PacketList::joinSummaryRow(QStringList col_parts, int row, SummaryCopyType type)
419 QString copy_text;
420 switch (type) {
421 case CopyAsCSV:
422 copy_text = "\"";
423 copy_text += col_parts.join("\",\"");
424 copy_text += "\"";
425 break;
426 case CopyAsYAML:
427 copy_text = "----\n";
428 copy_text += QStringLiteral("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename);
429 copy_text += "- ";
430 copy_text += col_parts.join("\n- ");
431 copy_text += "\n";
432 break;
433 case CopyAsText:
434 default:
435 copy_text = col_parts.join("\t");
438 return copy_text;
441 void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
443 QTreeView::drawRow(painter, option, index);
445 if (prefs.gui_packet_list_separator) {
446 QRect rect = visualRect(index);
448 painter->setPen(QColor(Qt::white));
449 painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
453 void PacketList::setProtoTree (ProtoTree *proto_tree) {
454 proto_tree_ = proto_tree;
456 connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
457 connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)),
458 &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t)));
461 bool PacketList::uniqueSelectActive()
463 return selectionModel()->selectedRows(0).count() == 1 ? true : false;
466 bool PacketList::multiSelectActive()
468 return selectionModel()->selectedRows(0).count() > 1 ? true : false;
471 QList<int> PacketList::selectedRows(bool useFrameNum)
473 QList<int> rows;
474 if (selectionModel() && selectionModel()->hasSelection())
476 foreach (QModelIndex idx, selectionModel()->selectedRows(0))
478 if (idx.isValid())
480 if (! useFrameNum)
481 rows << idx.row();
482 else if (useFrameNum)
484 frame_data * frame = getFDataForRow(idx.row());
485 if (frame)
486 rows << frame->num;
491 else if (currentIndex().isValid())
494 // XXX - will we ever have a current index but not a selection
495 // model?
497 if (! useFrameNum)
498 rows << currentIndex().row();
499 else
501 frame_data *frame = getFDataForRow(currentIndex().row());
502 if (frame)
503 rows << frame->num;
507 return rows;
510 void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected)
512 QTreeView::selectionChanged(selected, deselected);
514 if (!cap_file_) return;
516 int row = -1;
517 static bool multiSelect = false;
519 if (selectionModel())
521 QModelIndexList selRows = selectionModel()->selectedRows(0);
522 if (selRows.count() > 1)
524 QList<int> rows;
525 foreach (QModelIndex idx, selRows)
527 if (idx.isValid())
528 rows << idx.row();
531 emit framesSelected(rows);
532 emit fieldSelected(0);
533 cf_unselect_packet(cap_file_);
535 /* We have to repaint the content while changing state, as some delegates react to multi-select */
536 if (! multiSelect)
538 related_packet_delegate_.clear();
539 viewport()->update();
542 multiSelect = true;
544 return;
546 else if (selRows.count() > 0 && selRows.at(0).isValid())
548 multiSelect = false;
549 row = selRows.at(0).row();
552 /* Handling empty selection */
553 if (selRows.count() <= 0)
555 /* Nothing selected, but multiSelect is still active */
556 if (multiSelect)
558 multiSelect = false;
559 if (currentIndex().isValid())
561 selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
562 return;
565 /* Nothing selected, so in WS <= 3.0 nothing was indicated as well */
566 else if (currentIndex().isValid())
568 setCurrentIndex(QModelIndex());
573 if (row < 0 || !packet_list_model_)
574 cf_unselect_packet(cap_file_);
575 else {
576 frame_data * fdata = packet_list_model_->getRowFdata(row);
577 cf_select_packet(cap_file_, fdata);
580 if (!in_history_ && cap_file_->current_frame) {
581 cur_history_++;
582 selection_history_.resize(cur_history_);
583 selection_history_.append(cap_file_->current_frame->num);
586 related_packet_delegate_.clear();
588 // The previous dissection state has been invalidated by cf_select_packet
589 // above, receivers must clear the previous state and apply the updated one.
590 emit framesSelected(QList<int>() << row);
592 if (!cap_file_->edt) {
593 viewport()->update();
594 emit fieldSelected(0);
595 return;
598 if (cap_file_->edt->tree) {
599 packet_info *pi = &cap_file_->edt->pi;
600 related_packet_delegate_.setCurrentFrame(pi->num);
601 conversation_t *conv = find_conversation_pinfo_ro(pi, 0);
602 if (conv) {
603 related_packet_delegate_.setConversation(conv);
605 viewport()->update();
608 if (cap_file_->search_in_progress) {
609 field_info *fi = NULL;
611 if (cap_file_->string && cap_file_->decode_data) {
612 // The tree where the target string matched one of the labels was discarded in
613 // match_protocol_tree() so we have to search again in the latest tree.
614 fi = cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree);
615 } else if (cap_file_->search_len != 0) {
616 // Find the finfo that corresponds to our byte.
617 // The match can span multiple fields (and a single byte can
618 // match more than one field.) Our behavior is to find the last
619 // field in the tree (so hopefully spanning fewer bytes) that
620 // matches the last byte in the search match.
621 // (regex search can find a zero length match not at the
622 // start of the frame if lookbehind is used, but
623 // proto_find_field_from_offset doesn't match such a field
624 // and it's not clear which field we would want to match.)
625 fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos + cap_file_->search_len - 1,
626 cap_file_->edt->tvb);
629 if (fi) {
630 FieldInformation finfo(fi, this);
631 emit fieldSelected(&finfo);
632 } else {
633 emit fieldSelected(0);
635 } else if (proto_tree_) {
636 proto_tree_->restoreSelectedField();
640 void PacketList::contextMenuEvent(QContextMenuEvent *event)
642 const char *module_name = NULL;
644 if (finfo_array)
646 g_ptr_array_free(finfo_array, true);
647 finfo_array = NULL;
650 QModelIndex ctxIndex = indexAt(event->pos());
652 if (selectionModel() && selectionModel()->selectedRows(0).count() > 1)
653 selectionModel()->select(ctxIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
655 // frameData will be owned by one of the submenus, see below.
656 FrameInformation * frameData =
657 new FrameInformation(new CaptureFile(this, cap_file_), packet_list_model_->getRowFdata(ctxIndex.row()));
659 QMenu * ctx_menu = new QMenu(this);
660 ctx_menu->setAttribute(Qt::WA_DeleteOnClose);
661 // XXX We might want to reimplement setParent() and fill in the context
662 // menu there.
663 ctx_menu->addAction(window()->findChild<QAction *>("actionEditMarkSelected"));
664 ctx_menu->addAction(window()->findChild<QAction *>("actionEditIgnoreSelected"));
665 ctx_menu->addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
666 ctx_menu->addAction(window()->findChild<QAction *>("actionEditTimeShift"));
667 ctx_menu->addMenu(window()->findChild<QMenu *>("menuPacketComment"));
669 ctx_menu->addSeparator();
671 // Code for custom context menus from Lua's register_packet_menu()
672 MainWindow * mainWindow = qobject_cast<MainWindow *>(mainApp->mainWindow());
673 // N.B., will only call for a single frame selection,
674 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
675 finfo_array = proto_all_finfos(cap_file_->edt->tree);
676 if (mainWindow) {
677 bool insertedPacketMenu = mainWindow->addPacketMenus(ctx_menu, finfo_array);
678 if (insertedPacketMenu) {
679 ctx_menu->addSeparator();
684 ctx_menu->addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
685 ctx_menu->addSeparator();
687 QString selectedfilter = getFilterFromRowAndColumn(currentIndex());
689 if (! hasFocus() && cap_file_ && cap_file_->finfo_selected) {
690 char *tmp_field = proto_construct_match_selected_string(cap_file_->finfo_selected, cap_file_->edt);
691 selectedfilter = QString(tmp_field);
692 wmem_free(NULL, tmp_field);
695 bool have_filter_expr = !selectedfilter.isEmpty();
696 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, have_filter_expr, ctx_menu));
697 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, have_filter_expr, ctx_menu));
699 const char *conv_menu_name = "menuConversationFilter";
700 QMenu * main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
701 conv_menu_.setTitle(main_menu_item->title());
702 conv_menu_.setObjectName(conv_menu_name);
703 ctx_menu->addMenu(&conv_menu_);
705 const char *colorize_menu_name = "menuColorizeConversation";
706 main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
707 colorize_menu_.setTitle(main_menu_item->title());
708 colorize_menu_.setObjectName(colorize_menu_name);
709 ctx_menu->addMenu(&colorize_menu_);
711 QMenu * submenu;
712 main_menu_item = window()->findChild<QMenu *>("menuSCTP");
713 if (main_menu_item) {
714 submenu = new QMenu(main_menu_item->title(), ctx_menu);
715 ctx_menu->addMenu(submenu);
716 submenu->addAction(window()->findChild<QAction *>("actionSCTPAnalyseThisAssociation"));
717 submenu->addAction(window()->findChild<QAction *>("actionSCTPShowAllAssociations"));
718 submenu->addAction(window()->findChild<QAction *>("actionSCTPFilterThisAssociation"));
721 main_menu_item = window()->findChild<QMenu *>("menuFollow");
722 if (main_menu_item) {
723 submenu = new QMenu(main_menu_item->title(), ctx_menu);
724 ctx_menu->addMenu(submenu);
725 foreach (FollowStreamAction *follow_action, main_menu_item->findChildren<FollowStreamAction *>()) {
726 /* XXX: We could, like the prefs above, walk the protocols/layers
727 * and add the follow actions in the order they appear in the packet.
729 if (follow_action->isEnabled()) {
730 submenu->addAction(follow_action);
735 ctx_menu->addSeparator();
737 main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
738 submenu = new QMenu(main_menu_item->title(), ctx_menu);
739 submenu->setToolTipsVisible(true);
740 ctx_menu->addMenu(submenu);
742 QAction * action = submenu->addAction(tr("Summary as Text"));
743 action->setData(CopyAsText);
744 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
745 action = submenu->addAction(tr("…as CSV"));
746 action->setData(CopyAsCSV);
747 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
748 action = submenu->addAction(tr("…as YAML"));
749 action->setData(CopyAsYAML);
750 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
751 action = submenu->addAction(tr("…as HTML"));
752 action->setData(CopyAsHTML);
753 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
754 submenu->addSeparator();
756 submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
757 submenu->addSeparator();
759 QActionGroup * copyEntries = DataPrinter::copyActions(this, frameData);
760 submenu->addActions(copyEntries->actions());
761 copyEntries->setParent(submenu);
762 frameData->setParent(submenu);
764 if (is_packet_configuration_namespace()) {
765 /* i.e., Wireshark only */
766 ctx_menu->addSeparator();
767 QMenu *proto_prefs_menus = new QMenu(ProtocolPreferencesMenu::tr("Protocol Preferences"), ctx_menu);
769 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
770 QList<QString> added_proto_prefs;
771 // N.B. finfo_array will be assigned above
772 for (unsigned i = 0; i < finfo_array->len; i++) {
773 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
774 const header_field_info *hfinfo = fi->hfinfo;
776 if (prefs_is_registered_protocol(hfinfo->abbrev)) {
777 if (hfinfo->parent == -1) {
778 module_name = hfinfo->abbrev;
779 } else {
780 module_name = proto_registrar_get_abbrev(hfinfo->parent);
783 if (added_proto_prefs.contains(module_name)) {
784 continue;
787 ProtocolPreferencesMenu *proto_prefs_menu = new ProtocolPreferencesMenu(hfinfo->name, module_name, proto_prefs_menus);
789 connect(proto_prefs_menu, SIGNAL(showProtocolPreferences(QString)),
790 this, SIGNAL(showProtocolPreferences(QString)));
791 connect(proto_prefs_menu, SIGNAL(editProtocolPreference(preference*,pref_module*)),
792 this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
794 proto_prefs_menus->addMenu(proto_prefs_menu);
795 added_proto_prefs << module_name;
799 ctx_menu->addMenu(proto_prefs_menus);
800 action = ctx_menu->addAction(tr("Decode As…"));
801 action->setProperty("create_new", QVariant(true));
802 connect(action, &QAction::triggered, this, &PacketList::ctxDecodeAsDialog);
803 // "Print" not ported intentionally
804 action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
805 ctx_menu->addAction(action);
808 // Set menu sensitivity for the current column and set action data.
809 if (frameData)
810 emit framesSelected(QList<int>() << frameData->frameNum());
811 else
812 emit framesSelected(QList<int>());
814 ctx_menu->popup(event->globalPos());
817 void PacketList::ctxDecodeAsDialog()
819 QAction *da_action = qobject_cast<QAction*>(sender());
820 if (! da_action)
821 return;
822 bool create_new = da_action->property("create_new").toBool();
824 DecodeAsDialog *da_dialog = new DecodeAsDialog(this, cap_file_, create_new);
825 connect(da_dialog, SIGNAL(destroyed(QObject*)), mainApp, SLOT(flushAppSignals()));
826 da_dialog->setWindowModality(Qt::ApplicationModal);
827 da_dialog->setAttribute(Qt::WA_DeleteOnClose);
828 da_dialog->show();
831 void PacketList::timerEvent(QTimerEvent *event)
833 if (event->timerId() == overlay_timer_id_) {
834 if (!capture_in_progress_) {
835 if (create_near_overlay_) drawNearOverlay();
836 if (create_far_overlay_) drawFarOverlay();
838 } else {
839 QTreeView::timerEvent(event);
843 void PacketList::paintEvent(QPaintEvent *event)
845 // XXX This is overkill, but there are quite a few events that
846 // require a new overlay, e.g. page up/down, scrolling, column
847 // resizing, etc.
848 create_near_overlay_ = true;
849 QTreeView::paintEvent(event);
852 void PacketList::mousePressEvent (QMouseEvent *event)
854 QTreeView::mousePressEvent(event);
856 QModelIndex curIndex = indexAt(event->pos());
857 mouse_pressed_at_ = curIndex;
859 bool midButton = (event->buttons() & Qt::MiddleButton) == Qt::MiddleButton;
860 if (midButton && cap_file_ && packet_list_model_)
862 packet_list_model_->toggleFrameMark(QModelIndexList() << curIndex);
864 // Make sure the packet list's frame.marked related field text is updated.
865 redrawVisiblePackets();
867 create_far_overlay_ = true;
868 packets_bar_update();
872 void PacketList::mouseReleaseEvent(QMouseEvent *event) {
873 QTreeView::mouseReleaseEvent(event);
875 mouse_pressed_at_ = QModelIndex();
878 void PacketList::mouseMoveEvent (QMouseEvent *event)
880 QModelIndex curIndex = indexAt(event->pos());
881 if (event->buttons() & Qt::LeftButton && curIndex.isValid() && curIndex == mouse_pressed_at_)
883 ctx_column_ = curIndex.column();
884 QMimeData * mimeData = new QMimeData();
885 QWidget * content = nullptr;
887 QString filter = getFilterFromRowAndColumn(curIndex);
888 QList<int> rows = selectedRows();
889 if (rows.count() > 1)
891 QStringList content;
892 foreach (int row, rows)
894 QModelIndex idx = model()->index(row, 0);
895 if (! idx.isValid())
896 continue;
898 QString entry = createSummaryText(idx, CopyAsText);
899 content << entry;
902 if (content.count() > 0)
903 mimeData->setText(content.join("\n"));
905 else if (! filter.isEmpty())
907 QString abbrev;
908 QString name = model()->headerData(curIndex.column(), header()->orientation()).toString();
910 if (! filter.isEmpty())
912 abbrev = filter.left(filter.indexOf(' '));
914 else
916 filter = model()->data(curIndex).toString().toLower();
917 abbrev = filter;
920 mimeData->setText(filter);
922 QJsonObject filterData;
923 filterData["filter"] = filter;
924 filterData["name"] = abbrev;
925 filterData["description"] = name;
927 mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
928 content = new DragLabel(QStringLiteral("%1\n%2").arg(name, abbrev), this);
930 else
932 QString text = model()->data(curIndex).toString();
933 if (! text.isEmpty())
934 mimeData->setText(text);
937 if (mimeData->hasText() || mimeData->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
939 QDrag * drag = new QDrag(this);
940 drag->setMimeData(mimeData);
941 if (content)
943 qreal dpr = window()->windowHandle()->devicePixelRatio();
944 QPixmap pixmap= QPixmap(content->size() * dpr);
945 pixmap.setDevicePixelRatio(dpr);
946 content->render(&pixmap);
947 drag->setPixmap(pixmap);
950 drag->exec(Qt::CopyAction);
952 else
954 delete mimeData;
959 void PacketList::keyPressEvent(QKeyEvent *event)
961 QTreeView::keyPressEvent(event);
963 if (event->matches(QKeySequence::Copy))
965 QStringList content, htmlContent;
966 if (model() && selectionModel() && selectionModel()->hasSelection())
968 QList<int> rows;
969 QModelIndexList selRows = selectionModel()->selectedRows(0);
970 foreach(QModelIndex row, selRows)
971 rows.append(row.row());
973 QStringList hdr_parts;
974 QList<int> align_parts, size_parts;
976 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
977 case COPY_FORMAT_TEXT:
978 case COPY_FORMAT_HTML:
979 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
980 hdr_parts = createHeaderPartsForAligned();
981 align_parts = createAlignmentPartsForAligned();
982 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
984 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
985 htmlContent << createDefaultStyleForHtml();
986 htmlContent << createOpeningTagForHtml();
988 break;
989 case COPY_FORMAT_CSV:
990 case COPY_FORMAT_YAML:
991 break;
994 QList<QStringList> entries;
995 foreach(int row, rows)
997 QModelIndex idx = model()->index(row, 0);
998 if (! idx.isValid())
999 continue;
1001 switch (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut) {
1002 case COPY_FORMAT_TEXT:
1003 case COPY_FORMAT_HTML:
1004 if (prefs.gui_packet_list_copy_text_with_aligned_columns)
1005 content << createSummaryForAligned(idx, align_parts, size_parts);
1006 else
1007 content << createSummaryText(idx, CopyAsText);
1008 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML)
1009 htmlContent << createSummaryForHtml(idx);
1010 break;
1011 case COPY_FORMAT_CSV:
1012 content << createSummaryText(idx, CopyAsCSV);
1013 break;
1014 case COPY_FORMAT_YAML:
1015 content << createSummaryText(idx, CopyAsYAML);
1016 break;
1021 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_HTML) {
1022 // htmlContent will never be empty as they will always have style and table tags
1023 QMimeData *mimeData = new QMimeData;
1024 htmlContent << createClosingTagForHtml();
1025 mimeData->setHtml(htmlContent.join('\n'));
1026 mimeData->setText(content.join('\n').append("\n"));
1027 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1029 else {
1030 if (content.count() > 0) {
1031 QString copy_text;
1032 if (prefs.gui_packet_list_copy_format_options_for_keyboard_shortcut == COPY_FORMAT_YAML) {
1033 copy_text = content.join("");
1035 else {
1036 copy_text = content.join("\n");
1037 copy_text += "\n";
1039 mainApp->clipboard()->setText(copy_text);
1045 void PacketList::resizeEvent(QResizeEvent *event)
1047 create_near_overlay_ = true;
1048 create_far_overlay_ = true;
1049 QTreeView::resizeEvent(event);
1052 void PacketList::setColumnVisibility()
1054 set_column_visibility_ = true;
1055 for (int i = 0; i < prefs.num_cols; i++) {
1056 setColumnHidden(i, get_column_visible(i) ? false : true);
1058 set_column_visibility_ = false;
1061 int PacketList::sizeHintForColumn(int column) const
1063 int size_hint = 0;
1065 // This is a bit hacky but Qt does a fine job of column sizing and
1066 // reimplementing QTreeView::sizeHintForColumn seems like a worse idea.
1067 if (itemDelegateForColumn(column)) {
1068 QStyleOptionViewItem option;
1069 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1070 initViewItemOption(&option);
1071 #else
1072 option = viewOptions();
1073 #endif
1074 // In my (gcc) testing this results in correct behavior on Windows but adds extra space
1075 // on macOS and Linux. We might want to add Q_OS_... #ifdefs accordingly.
1076 size_hint = itemDelegateForColumn(column)->sizeHint(option, QModelIndex()).width();
1078 size_hint += QTreeView::sizeHintForColumn(column); // Decoration padding
1079 return size_hint;
1082 void PacketList::setRecentColumnWidth(int col)
1084 int col_width = recent_get_column_width(col);
1086 if (col_width < 1) {
1087 int fmt = get_column_format(col);
1088 const char *long_str = get_column_width_string(fmt, col);
1090 QFontMetrics fm = QFontMetrics(mainApp->monospaceFont());
1091 if (long_str) {
1092 col_width = fm.horizontalAdvance(long_str);
1093 } else {
1094 col_width = fm.horizontalAdvance(MIN_COL_WIDTH_STR);
1096 // Custom delegate padding
1097 if (itemDelegateForColumn(col)) {
1098 QStyleOptionViewItem option;
1099 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1100 initViewItemOption(&option);
1101 #else
1102 option = viewOptions();
1103 #endif
1104 col_width += itemDelegateForColumn(col)->sizeHint(option, QModelIndex()).width();
1108 setColumnWidth(col, col_width);
1111 void PacketList::drawCurrentPacket()
1113 // XXX - Update for multi-select? If more than one packet is Selected,
1114 // this changes it so that only the Current packet is Selected.
1115 QModelIndex current_index = currentIndex();
1116 if (selectionModel() && current_index.isValid()) {
1117 selectionModel()->clearSelection();
1118 selectionModel()->setCurrentIndex(current_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
1122 // Redraw the packet list and detail. Re-selects the current packet (causes
1123 // the UI to scroll to that packet).
1124 // Called from many places.
1125 void PacketList::redrawVisiblePackets() {
1126 redrawVisiblePacketsDontSelectCurrent();
1127 drawCurrentPacket();
1130 // Redraw the packet list and detail.
1131 // Does not scroll back to the selected packet.
1132 void PacketList::redrawVisiblePacketsDontSelectCurrent() {
1133 packet_list_model_->invalidateAllColumnStrings();
1136 void PacketList::resetColumns()
1138 packet_list_model_->resetColumns();
1141 // Return true if we have a visible packet further along in the history.
1142 bool PacketList::haveNextHistory(bool update_cur)
1144 if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
1145 return false;
1148 for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
1149 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1150 if (update_cur) {
1151 cur_history_ = i;
1153 return true;
1156 return false;
1159 // Return true if we have a visible packet back in the history.
1160 bool PacketList::havePreviousHistory(bool update_cur)
1162 if (selection_history_.size() < 1 || cur_history_ < 1) {
1163 return false;
1166 for (int i = cur_history_ - 1; i >= 0; i--) {
1167 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1168 if (update_cur) {
1169 cur_history_ = i;
1171 return true;
1174 return false;
1177 void PacketList::setProfileSwitcher(ProfileSwitcher *profile_switcher)
1179 profile_switcher_ = profile_switcher;
1180 if (profile_switcher) {
1181 connect(packet_list_model_, &PacketListModel::packetAppended, profile_switcher_, &ProfileSwitcher::checkPacket);
1185 frame_data *PacketList::getFDataForRow(int row) const
1187 return packet_list_model_->getRowFdata(row);
1190 // prefs.col_list has changed.
1191 void PacketList::columnsChanged()
1193 columns_changed_ = true;
1194 column_register_fields();
1195 mainApp->emitAppSignal(MainApplication::FieldsChanged);
1196 if (!cap_file_) {
1197 // Keep columns_changed_ = true until we load a capture file.
1198 return;
1201 prefs.num_cols = g_list_length(prefs.col_list);
1202 col_cleanup(&cap_file_->cinfo);
1203 build_column_format_array(&cap_file_->cinfo, prefs.num_cols, false);
1204 create_far_overlay_ = true;
1205 resetColumns();
1206 applyRecentColumnWidths();
1207 setColumnVisibility();
1208 columns_changed_ = false;
1211 // Fields have changed, update custom columns
1212 void PacketList::fieldsChanged(capture_file *cf)
1214 prefs.num_cols = g_list_length(prefs.col_list);
1215 col_cleanup(&cf->cinfo);
1216 build_column_format_array(&cf->cinfo, prefs.num_cols, false);
1217 resetColumns();
1220 // Column widths should
1221 // - Load from recent when we load a new profile (including at starting up).
1222 // - Reapply when changing columns.
1223 // - Persist across freezes and thaws.
1224 // - Persist across file closing and opening.
1225 // - Save to recent when we save our profile (including shutting down).
1226 // - Not be affected by the behavior of stretchLastSection. (XXX: We
1227 // still save the stretched value to recent, sectionResized doesn't
1228 // distinguish between a resize from being stretched and a manual change.)
1229 void PacketList::applyRecentColumnWidths()
1231 // Either we've just started up or a profile has changed. Read
1232 // the recent settings, apply them, and save the header state.
1234 for (int col = 0; col < prefs.num_cols; col++) {
1235 // The column must be shown before setting column width.
1236 // Visibility will be updated in setColumnVisibility().
1237 setColumnHidden(col, false);
1238 setRecentColumnWidth(col);
1241 column_state_ = header()->saveState();
1244 void PacketList::preferencesChanged()
1246 // Related packet delegate
1247 if (prefs.gui_packet_list_show_related) {
1248 setItemDelegateForColumn(0, &related_packet_delegate_);
1249 } else {
1250 setItemDelegateForColumn(0, 0);
1253 // Intelligent scroll bar (minimap)
1254 if (prefs.gui_packet_list_show_minimap) {
1255 if (overlay_timer_id_ == 0) {
1256 overlay_timer_id_ = startTimer(overlay_update_interval_);
1258 } else {
1259 if (overlay_timer_id_ != 0) {
1260 killTimer(overlay_timer_id_);
1261 overlay_timer_id_ = 0;
1265 // Elide mode.
1266 // This sets the mode for the entire view. If we want to make this setting
1267 // per-column we'll either have to generalize RelatedPacketDelegate so that
1268 // we can set it for entire rows or create another delegate.
1269 Qt::TextElideMode elide_mode = Qt::ElideRight;
1270 switch (prefs.gui_packet_list_elide_mode) {
1271 case ELIDE_LEFT:
1272 elide_mode = Qt::ElideLeft;
1273 break;
1274 case ELIDE_MIDDLE:
1275 elide_mode = Qt::ElideMiddle;
1276 break;
1277 case ELIDE_NONE:
1278 elide_mode = Qt::ElideNone;
1279 break;
1280 default:
1281 break;
1283 setTextElideMode(elide_mode);
1286 void PacketList::freezePacketList(bool changing_profile)
1288 changing_profile_ = changing_profile;
1289 freeze(true);
1292 void PacketList::recolorPackets()
1294 packet_list_model_->resetColorized();
1295 redrawVisiblePackets();
1298 // Enable autoscroll.
1299 void PacketList::setVerticalAutoScroll(bool enabled)
1301 tail_at_end_ = enabled;
1302 if (enabled && capture_in_progress_) {
1303 scrollToBottom();
1307 // Called when we finish reading, reloading, rescanning, and retapping
1308 // packets.
1309 void PacketList::captureFileReadFinished()
1311 packet_list_model_->flushVisibleRows();
1312 packet_list_model_->dissectIdle(true);
1313 // Invalidating the column strings picks up and request/response
1314 // tracking changes. We might just want to call it from flushVisibleRows.
1315 packet_list_model_->invalidateAllColumnStrings();
1316 // Sort *after* invalidating the column strings
1317 if (isSortingEnabled()) {
1318 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
1322 bool PacketList::freeze(bool keep_current_frame)
1324 if (!cap_file_ || model() == Q_NULLPTR) {
1325 // No capture file or already frozen
1326 return false;
1329 frame_data *current_frame = cap_file_->current_frame;
1330 column_state_ = header()->saveState();
1331 setHeaderHidden(true);
1332 frozen_current_row_ = currentIndex();
1333 frozen_selected_rows_ = selectionModel()->selectedRows();
1334 selectionModel()->clear();
1335 setModel(Q_NULLPTR);
1336 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
1337 // call selectionChanged.
1338 related_packet_delegate_.clear();
1340 if (keep_current_frame) {
1341 cap_file_->current_frame = current_frame;
1344 /* Clears packet list as well as byteview */
1345 emit framesSelected(QList<int>());
1347 return true;
1350 bool PacketList::thaw(bool restore_selection)
1352 if (!cap_file_ || model() != Q_NULLPTR) {
1353 // No capture file or not frozen
1354 return false;
1357 setHeaderHidden(false);
1358 // Note that if we have a current sort status set in the header,
1359 // this will automatically try to sort the model (we don't want
1360 // that to happen if we're in the middle of reading the file).
1361 setModel(packet_list_model_);
1363 if (changing_profile_) {
1364 // When changing profile the new recent settings must be applied to the columns.
1365 applyRecentColumnWidths();
1366 setColumnVisibility();
1367 changing_profile_ = false;
1368 } else {
1369 // Resetting the model resets our column widths so we restore them here.
1370 // We don't reapply the recent settings because the user could have
1371 // resized the columns manually since they were initially loaded.
1372 header()->restoreState(column_state_);
1375 if (restore_selection && frozen_selected_rows_.length() > 0 && selectionModel()) {
1376 /* This updates our selection, which redissects the current packet,
1377 * which is needed when we're called from MainWindow::layoutPanes.
1378 * Also, this resets all ProtoTree and ByteView data */
1379 clearSelection();
1380 setCurrentIndex(frozen_current_row_);
1381 foreach (QModelIndex idx, frozen_selected_rows_) {
1382 selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1384 scrollTo(currentIndex(), PositionAtCenter);
1386 frozen_current_row_ = QModelIndex();
1387 frozen_selected_rows_ = QModelIndexList();
1389 return true;
1392 void PacketList::clear() {
1393 related_packet_delegate_.clear();
1394 selectionModel()->clear();
1395 packet_list_model_->clear();
1396 proto_tree_->clear();
1397 selection_history_.clear();
1398 cur_history_ = -1;
1399 in_history_ = false;
1401 QImage overlay;
1402 overlay_sb_->setNearOverlayImage(overlay);
1403 overlay_sb_->setMarkedPacketImage(overlay);
1404 create_near_overlay_ = true;
1405 create_far_overlay_ = true;
1408 void PacketList::writeRecent(FILE *rf) {
1409 int col, width, col_fmt;
1410 char xalign;
1412 fprintf (rf, "%s:\n", RECENT_KEY_COL_WIDTH);
1413 for (col = 0; col < prefs.num_cols; col++) {
1414 if (col > 0) {
1415 fprintf (rf, ",\n");
1417 col_fmt = get_column_format(col);
1418 if (col_fmt == COL_CUSTOM) {
1419 fprintf (rf, " \"%%Cus:%s\",", get_column_custom_fields(col));
1420 } else {
1421 fprintf (rf, " %s,", col_format_to_string(col_fmt));
1423 width = recent_get_column_width (col);
1424 xalign = recent_get_column_xalign (col);
1425 fprintf (rf, " %d", width);
1426 if (xalign != COLUMN_XALIGN_DEFAULT) {
1427 fprintf (rf, ":%c", xalign);
1430 fprintf (rf, "\n");
1433 bool PacketList::contextMenuActive()
1435 return ctx_column_ >= 0 ? true : false;
1438 QString PacketList::getFilterFromRowAndColumn(QModelIndex idx)
1440 frame_data *fdata;
1441 QString filter;
1443 if (! idx.isValid())
1444 return filter;
1446 int row = idx.row();
1447 int column = idx.column();
1449 if (!cap_file_ || !packet_list_model_ || column < 0 || column >= cap_file_->cinfo.num_cols)
1450 return filter;
1452 fdata = packet_list_model_->getRowFdata(row);
1454 if (fdata != NULL) {
1455 epan_dissect_t edt;
1456 wtap_rec rec; /* Record metadata */
1457 Buffer buf; /* Record data */
1459 wtap_rec_init(&rec);
1460 ws_buffer_init(&buf, 1514);
1461 if (!cf_read_record(cap_file_, fdata, &rec, &buf)) {
1462 wtap_rec_cleanup(&rec);
1463 ws_buffer_free(&buf);
1464 return filter; /* error reading the record */
1466 /* proto tree, visible. We need a proto tree if there's custom columns */
1467 epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), false);
1468 col_custom_prime_edt(&edt, &cap_file_->cinfo);
1470 epan_dissect_run(&edt, cap_file_->cd_t, &rec,
1471 frame_tvbuff_new_buffer(&cap_file_->provider, fdata, &buf),
1472 fdata, &cap_file_->cinfo);
1474 if (cap_file_->cinfo.columns[column].col_fmt == COL_CUSTOM) {
1475 filter.append(gchar_free_to_qstring(col_custom_get_filter(&edt, &cap_file_->cinfo, column)));
1476 } else {
1477 /* We don't need to fill in the custom columns, as we get their
1478 * filters above.
1480 col_fill_in(&edt.pi, true, true);
1481 if (strlen(cap_file_->cinfo.col_expr.col_expr[column]) != 0 &&
1482 strlen(cap_file_->cinfo.col_expr.col_expr_val[column]) != 0) {
1483 bool is_string_value = false;
1484 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.col_expr.col_expr[column]);
1485 if (hfi && FT_IS_STRING(hfi->type)) {
1486 /* Could be an address type such as usb.src which must be quoted. */
1487 is_string_value = true;
1490 if (filter.isEmpty()) {
1491 if (is_string_value) {
1492 filter.append(QStringLiteral("%1 == \"%2\"")
1493 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1494 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1495 } else {
1496 filter.append(QStringLiteral("%1 == %2")
1497 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1498 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1504 epan_dissect_cleanup(&edt);
1505 wtap_rec_cleanup(&rec);
1506 ws_buffer_free(&buf);
1509 return filter;
1512 void PacketList::resetColorized()
1514 packet_list_model_->resetColorized();
1515 update();
1518 QString PacketList::getPacketComment(unsigned c_number)
1520 int row = currentIndex().row();
1521 const frame_data *fdata;
1522 char *pkt_comment;
1523 wtap_opttype_return_val result;
1524 QString ret_val = NULL;
1526 if (!cap_file_ || !packet_list_model_) return NULL;
1528 fdata = packet_list_model_->getRowFdata(row);
1530 if (!fdata) return NULL;
1532 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1533 result = wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, &pkt_comment);
1534 if (result == WTAP_OPTTYPE_SUCCESS) {
1535 ret_val = QString(pkt_comment);
1537 wtap_block_unref(pkt_block);
1538 return ret_val;
1541 void PacketList::addPacketComment(QString new_comment)
1543 if (!cap_file_ || !packet_list_model_) return;
1544 if (new_comment.isEmpty()) return;
1546 QByteArray ba = new_comment.toUtf8();
1549 * Make sure this would fit in a pcapng option.
1551 * XXX - 65535 is the maximum size for an option in pcapng;
1552 * what if another capture file format supports larger
1553 * comments?
1555 if (ba.size() > 65535) {
1556 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
1557 "That comment is too large to save in a capture file.");
1558 return;
1561 if (selectionModel() && selectionModel()->hasSelection()) {
1562 packet_list_model_->addFrameComment(selectionModel()->selectedRows(), ba);
1563 drawCurrentPacket();
1567 void PacketList::setPacketComment(unsigned c_number, QString new_comment)
1569 QModelIndex curIndex = currentIndex();
1571 if (!cap_file_ || !packet_list_model_) return;
1573 QByteArray ba = new_comment.toUtf8();
1575 * Make sure this would fit in a pcapng option.
1577 * XXX - 65535 is the maximum size for an option in pcapng;
1578 * what if another capture file format supports larger
1579 * comments?
1581 if (ba.size() > 65535) {
1582 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
1583 "That comment is too large to save in a capture file.");
1584 return;
1587 packet_list_model_->setFrameComment(curIndex, ba, c_number);
1588 drawCurrentPacket();
1591 QString PacketList::allPacketComments()
1593 uint32_t framenum;
1594 frame_data *fdata;
1595 QString buf_str;
1597 if (!cap_file_) return buf_str;
1599 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1600 fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum);
1602 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1604 if (pkt_block) {
1605 unsigned n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
1606 for (unsigned i = 0; i < n_comments; i++) {
1607 char *comment_text;
1608 if (WTAP_OPTTYPE_SUCCESS == wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT, i, &comment_text)) {
1609 buf_str.append(tr("Frame %1: %2\n\n").arg(framenum).arg(comment_text));
1610 if (buf_str.length() > max_comments_to_fetch_) {
1611 buf_str.append(tr("[ Comment text exceeds %1. Stopping. ]")
1612 .arg(format_size(max_comments_to_fetch_, FORMAT_SIZE_UNIT_BYTES, FORMAT_SIZE_PREFIX_SI)));
1613 return buf_str;
1619 return buf_str;
1622 void PacketList::deleteCommentsFromPackets()
1624 if (!cap_file_ || !packet_list_model_) return;
1626 if (selectionModel() && selectionModel()->hasSelection()) {
1627 packet_list_model_->deleteFrameComments(selectionModel()->selectedRows());
1628 drawCurrentPacket();
1632 void PacketList::deleteAllPacketComments()
1634 if (!cap_file_ || !packet_list_model_) return;
1636 packet_list_model_->deleteAllFrameComments();
1637 drawCurrentPacket();
1641 // Slots
1643 void PacketList::setCaptureFile(capture_file *cf)
1645 cap_file_ = cf;
1646 packet_list_model_->setCaptureFile(cf);
1647 if (cf) {
1648 if (columns_changed_) {
1649 columnsChanged();
1650 } else {
1651 // Restore columns widths and visibility.
1652 header()->restoreState(column_state_);
1653 setColumnVisibility();
1656 create_near_overlay_ = true;
1657 changing_profile_ = false;
1658 sortByColumn(-1, Qt::AscendingOrder);
1661 void PacketList::setMonospaceFont(const QFont &mono_font)
1663 setFont(mono_font);
1666 void PacketList::setRegularFont(const QFont &regular_font)
1668 header()->setFont(regular_font);
1669 header()->viewport()->setFont(regular_font);
1672 void PacketList::goNextPacket(void)
1674 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1675 // Alt+toolbar
1676 goNextHistoryPacket();
1677 return;
1680 if (selectionModel()->hasSelection()) {
1681 selectionModel()->setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1682 } else {
1683 // First visible packet.
1684 selectionModel()->setCurrentIndex(indexAt(viewport()->rect().topLeft()), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1687 scrollViewChanged(false);
1690 void PacketList::goPreviousPacket(void)
1692 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1693 // Alt+toolbar
1694 goPreviousHistoryPacket();
1695 return;
1698 if (selectionModel()->hasSelection()) {
1699 selectionModel()->setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1700 } else {
1701 // Last visible packet.
1702 QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
1703 if (last_idx.isValid()) {
1704 selectionModel()->setCurrentIndex(last_idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1705 } else {
1706 goLastPacket();
1710 scrollViewChanged(false);
1713 void PacketList::goFirstPacket(void) {
1714 if (packet_list_model_->rowCount() < 1) return;
1715 selectionModel()->setCurrentIndex(packet_list_model_->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1716 scrollTo(currentIndex());
1718 scrollViewChanged(false);
1721 void PacketList::goLastPacket(void) {
1722 if (packet_list_model_->rowCount() < 1) return;
1723 selectionModel()->setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1724 scrollTo(currentIndex());
1726 scrollViewChanged(false);
1729 void PacketList::goToPacket(int packet, int hf_id)
1731 if (!cf_goto_frame(cap_file_, packet, false))
1732 return;
1734 // cf_goto_frame only returns true if packet_list_select_row_from_data
1735 // succeeds, the latter has already selected and scrolled to the frame.
1736 if (hf_id > 0) {
1737 proto_tree_->goToHfid(hf_id);
1740 scrollViewChanged(false);
1743 void PacketList::goNextHistoryPacket()
1745 if (haveNextHistory(true)) {
1746 in_history_ = true;
1747 goToPacket(selection_history_.at(cur_history_));
1748 in_history_ = false;
1752 void PacketList::goPreviousHistoryPacket()
1754 if (havePreviousHistory(true)) {
1755 in_history_ = true;
1756 goToPacket(selection_history_.at(cur_history_));
1757 in_history_ = false;
1761 void PacketList::markFrame()
1763 if (!cap_file_ || !packet_list_model_) return;
1765 QModelIndexList frames;
1767 if (selectionModel() && selectionModel()->hasSelection())
1769 QModelIndexList selRows = selectionModel()->selectedRows(0);
1770 foreach (QModelIndex idx, selRows)
1772 if (idx.isValid())
1774 frames << idx;
1778 else
1779 frames << currentIndex();
1781 packet_list_model_->toggleFrameMark(frames);
1783 // Make sure the packet list's frame.marked related field text is updated.
1784 redrawVisiblePackets();
1786 create_far_overlay_ = true;
1787 packets_bar_update();
1790 void PacketList::markAllDisplayedFrames(bool set)
1792 if (!cap_file_ || !packet_list_model_) return;
1794 packet_list_model_->setDisplayedFrameMark(set);
1796 // Make sure the packet list's frame.marked related field text is updated.
1797 redrawVisiblePackets();
1799 create_far_overlay_ = true;
1800 packets_bar_update();
1803 void PacketList::ignoreFrame()
1805 if (!cap_file_ || !packet_list_model_) return;
1807 QModelIndexList frames;
1809 if (selectionModel() && selectionModel()->hasSelection())
1811 foreach (QModelIndex idx, selectionModel()->selectedRows(0))
1813 if (idx.isValid())
1815 frames << idx;
1819 else
1820 frames << currentIndex();
1823 packet_list_model_->toggleFrameIgnore(frames);
1824 create_far_overlay_ = true;
1825 int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1826 setUpdatesEnabled(false);
1827 emit packetDissectionChanged();
1828 setUpdatesEnabled(true);
1829 verticalScrollBar()->setValue(sb_val);
1832 void PacketList::ignoreAllDisplayedFrames(bool set)
1834 if (!cap_file_ || !packet_list_model_) return;
1836 packet_list_model_->setDisplayedFrameIgnore(set);
1837 create_far_overlay_ = true;
1838 emit packetDissectionChanged();
1841 void PacketList::setTimeReference()
1843 if (!cap_file_ || !packet_list_model_) return;
1844 packet_list_model_->toggleFrameRefTime(currentIndex());
1845 create_far_overlay_ = true;
1848 void PacketList::unsetAllTimeReferences()
1850 if (!cap_file_ || !packet_list_model_) return;
1851 packet_list_model_->unsetAllFrameRefTime();
1852 create_far_overlay_ = true;
1855 void PacketList::applyTimeShift()
1857 packet_list_model_->resetColumns();
1858 redrawVisiblePackets();
1859 emit packetDissectionChanged();
1862 void PacketList::updatePackets(bool redraw)
1864 if (redraw) {
1865 packet_list_model_->resetColumns();
1866 redrawVisiblePackets();
1867 } else {
1868 update();
1872 void PacketList::columnVisibilityTriggered()
1874 QAction *ha = qobject_cast<QAction*>(sender());
1875 if (!ha) return;
1877 int col = ha->data().toInt();
1878 set_column_visible(col, ha->isChecked());
1879 setColumnVisibility();
1880 if (ha->isChecked()) {
1881 setRecentColumnWidth(col);
1883 prefs_main_write();
1886 void PacketList::sectionResized(int col, int, int new_width)
1888 if (isVisible() && !columns_changed_ && !set_column_visibility_ && !set_style_sheet_ && new_width > 0) {
1889 // Column 1 gets an invalid value (32 on macOS) when we're not yet
1890 // visible.
1892 // Don't set column width when columns changed or setting column
1893 // visibility because we may get a sectionResized() from QTreeView
1894 // with values from a old columns layout.
1896 // Don't set column width when hiding a column.
1898 recent_set_column_width(col, new_width);
1902 // The user moved a column. Make sure prefs.col_list, the column format
1903 // array, and the header's visual and logical indices all agree.
1904 // gtk/packet_list.c:column_dnd_changed_cb
1905 void PacketList::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
1907 GList *new_col_list = NULL;
1908 GList *new_recent_col_list = NULL;
1909 QList<int> saved_sizes;
1910 int sort_idx;
1912 // Since we undo the move below, these should always stay in sync.
1913 // Otherwise the order of columns can be unexpected after drag and drop.
1914 if (logicalIndex != oldVisualIndex) {
1915 ws_warning("Column moved from an unexpected state (%d, %d, %d)",
1916 logicalIndex, oldVisualIndex, newVisualIndex);
1919 // Remember which column should be sorted. Use the visual index since this
1920 // points to the current GUI state rather than the outdated column order
1921 // (indicated by the logical index).
1922 sort_idx = header()->sortIndicatorSection();
1923 if (sort_idx != -1) {
1924 sort_idx = header()->visualIndex(sort_idx);
1927 // Build a new column list based on the header's logical order.
1928 for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
1929 int log_idx = header()->logicalIndex(vis_idx);
1930 saved_sizes << header()->sectionSize(log_idx);
1932 void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
1933 if (pref_data) {
1934 new_col_list = g_list_append(new_col_list, pref_data);
1937 pref_data = g_list_nth_data(recent.col_width_list, log_idx);
1938 if (pref_data) {
1939 new_recent_col_list = g_list_append(new_recent_col_list, pref_data);
1943 // Undo move to ensure that the logical indices map to the visual indices,
1944 // otherwise the column order is changed twice (once via the modified
1945 // col_list, once because of the visual/logical index mismatch).
1946 disconnect(header(), SIGNAL(sectionMoved(int,int,int)),
1947 this, SLOT(sectionMoved(int,int,int)));
1948 header()->moveSection(newVisualIndex, oldVisualIndex);
1949 connect(header(), SIGNAL(sectionMoved(int,int,int)),
1950 this, SLOT(sectionMoved(int,int,int)));
1952 // Clear and rebuild our (and the header's) model. There doesn't appear
1953 // to be another way to reset the logical index.
1954 freeze();
1956 g_list_free(prefs.col_list);
1957 prefs.col_list = new_col_list;
1958 g_list_free(recent.col_width_list);
1959 recent.col_width_list = new_recent_col_list;
1961 thaw(true);
1963 for (int i = 0; i < saved_sizes.length(); i++) {
1964 if (saved_sizes[i] < 1) continue;
1965 header()->resizeSection(i, saved_sizes[i]);
1968 prefs_main_write();
1970 mainApp->emitAppSignal(MainApplication::ColumnsChanged);
1972 // If the column with the sort indicator got shifted, mark the new column
1973 // after updating the columns contents (via ColumnsChanged) to ensure that
1974 // the columns are sorted using the intended column contents.
1975 int left_col = MIN(oldVisualIndex, newVisualIndex);
1976 int right_col = MAX(oldVisualIndex, newVisualIndex);
1977 if (left_col <= sort_idx && sort_idx <= right_col) {
1978 header()->setSortIndicator(sort_idx, header()->sortIndicatorOrder());
1982 void PacketList::updateRowHeights(const QModelIndex &ih_index)
1984 QStyleOptionViewItem option;
1985 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1986 initViewItemOption(&option);
1987 #else
1988 option = viewOptions();
1989 #endif
1990 int max_height = 0;
1992 // One of our columns increased the maximum row height. Find out which one.
1993 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1994 QSize size_hint = itemDelegate()->sizeHint(option, packet_list_model_->index(ih_index.row(), col));
1995 max_height = qMax(max_height, size_hint.height());
1998 if (max_height > 0) {
1999 packet_list_model_->setMaximumRowHeight(max_height);
2003 QString PacketList::createSummaryText(QModelIndex idx, SummaryCopyType type)
2005 if (! idx.isValid())
2006 return "";
2008 QStringList col_parts;
2009 int row = idx.row();
2010 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2011 if (get_column_visible(col)) {
2012 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
2015 return joinSummaryRow(col_parts, row, type);
2018 QString PacketList::createHeaderSummaryText(SummaryCopyType type)
2020 QStringList col_parts;
2021 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
2022 if (get_column_visible(col)) {
2023 col_parts << packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
2026 return joinSummaryRow(col_parts, 0, type);
2029 QStringList PacketList::createHeaderPartsForAligned()
2031 QStringList hdr_parts;
2032 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
2033 if (get_column_visible(col)) {
2034 hdr_parts << packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
2037 return hdr_parts;
2040 QList<int> PacketList::createAlignmentPartsForAligned()
2042 QList<int> align_parts;
2043 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2044 if (get_column_visible(col)) {
2045 align_parts << packet_list_model_->data(packet_list_model_->index(0, col), Qt::TextAlignmentRole).toInt();
2048 return align_parts;
2051 QList<int> PacketList::createSizePartsForAligned(bool useHeader, QStringList hdr_parts, QList<int> rows)
2053 QList<int> size_parts;
2055 for (int i = 0; i < hdr_parts.size(); ++i) {
2056 if (useHeader)
2057 size_parts << static_cast<int>(hdr_parts.at(i).size());
2058 else
2059 size_parts << 0;
2062 foreach(int row, rows)
2064 QModelIndex idx = model()->index(row, 0);
2065 if (! idx.isValid())
2066 continue;
2068 QStringList col_parts;
2069 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2070 if (get_column_visible(col)) {
2071 col_parts << (packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString());
2075 for (int i = 0; i < col_parts.size(); ++i) {
2076 if (col_parts.at(i).size() > size_parts.at(i)) {
2077 size_parts[i] = static_cast<int>(col_parts.at(i).size());
2080 col_parts.clear();
2083 return size_parts;
2086 QString PacketList::createHeaderSummaryForAligned(QStringList hdr_parts, QList<int> align_parts, QList<int> size_parts)
2088 QString hdr_text;
2089 for (int i = 0; i < hdr_parts.size(); ++i) {
2090 if (align_parts.at(i) == Qt::AlignLeft) {
2091 hdr_text += hdr_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
2093 else if (align_parts.at(i) == Qt::AlignRight) {
2094 hdr_text += hdr_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
2097 return QStringLiteral("-%1").arg(hdr_text).trimmed().mid(1);
2100 QString PacketList::createSummaryForAligned(QModelIndex idx, QList<int> align_parts, QList<int> size_parts)
2102 if (! idx.isValid())
2103 return "";
2105 QStringList col_parts;
2106 int row = idx.row();
2107 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2108 if (get_column_visible(col)) {
2109 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
2113 QString col_text;
2114 for (int i = 0; i < col_parts.size(); ++i) {
2115 if (align_parts.at(i) == Qt::AlignLeft) {
2116 col_text += col_parts[i].leftJustified(size_parts.at(i), ' ') + " ";
2118 else if (align_parts.at(i) == Qt::AlignRight) {
2119 col_text += col_parts[i].rightJustified(size_parts.at(i), ' ') + " ";
2123 return QStringLiteral("-%1").arg(col_text).trimmed().mid(1);
2126 QString PacketList::createDefaultStyleForHtml()
2128 QString style_text;
2129 style_text = "<style>";
2130 QString fontFamily = QString(prefs.gui_font_name).split(",")[0];
2131 QString fontSize = QString(prefs.gui_font_name).split(",")[1];
2132 style_text += "table{font-family:" + fontFamily + ";font-size:" + fontSize + "pt;}";
2133 style_text += "th{background-color:#000000;color:#ffffff;text-align:left;}";
2134 style_text += "th,td{padding:" + QString::number(fontSize.toInt() / 2) + "pt}";
2135 style_text += "</style>";
2136 return style_text;
2139 QString PacketList::createOpeningTagForHtml()
2141 return "<table>";
2144 QString PacketList::createHeaderSummaryForHtml()
2146 QString hdr_text;
2147 hdr_text += "<tr>";
2148 for (int col = 0; col < packet_list_model_->columnCount(); ++col) {
2149 if (get_column_visible(col)) {
2150 hdr_text += "<th>";
2151 hdr_text += packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
2152 hdr_text += "</th>";
2155 hdr_text += "</tr>";
2156 return hdr_text;
2159 QString PacketList::createSummaryForHtml(QModelIndex idx)
2161 if (! idx.isValid())
2162 return "";
2164 int row = idx.row();
2165 QString col_text;
2167 QString bg_color = packet_list_model_->data(packet_list_model_->index(row, 0), Qt::BackgroundRole).toString();
2168 QString fg_color = packet_list_model_->data(packet_list_model_->index(row, 0), Qt::ForegroundRole).toString();
2169 col_text += "<tr style=\"background-color:" + bg_color + ";color:" + fg_color + ";\">";
2171 QString alignment[] = {"left", "right", "center", "justify"};
2173 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
2174 if (get_column_visible(col)) {
2175 col_text += "<td style=\"text-align:" + alignment[packet_list_model_->data(packet_list_model_->index(row, col), Qt::TextAlignmentRole).toInt() / 2] + ";\">";
2176 col_text += packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
2177 col_text += "</td>";
2181 col_text += "</tr>";
2182 return col_text;
2185 QString PacketList::createClosingTagForHtml()
2187 return "</table>";
2190 void PacketList::copySummary()
2192 if (!currentIndex().isValid()) return;
2194 QAction *ca = qobject_cast<QAction*>(sender());
2195 if (!ca) return;
2197 QVariant type = ca->data();
2198 if (! type.canConvert<SummaryCopyType>())
2199 return;
2200 SummaryCopyType copy_type = type.value<SummaryCopyType>();
2202 QString copy_text;
2203 if (type == CopyAsText || type == CopyAsHTML) {
2204 if (prefs.gui_packet_list_copy_text_with_aligned_columns) {
2205 QList<int> rows;
2206 rows << currentIndex().row();
2207 QStringList hdr_parts;
2208 QList<int> align_parts, size_parts;
2209 hdr_parts = createHeaderPartsForAligned();
2210 align_parts = createAlignmentPartsForAligned();
2211 size_parts = createSizePartsForAligned(false, hdr_parts, rows);
2212 copy_text = createSummaryForAligned(currentIndex(), align_parts, size_parts);
2214 else {
2215 copy_text = createSummaryText(currentIndex(), CopyAsText);
2217 copy_text += "\n";
2218 if (type == CopyAsHTML) {
2219 QStringList htmlContent;
2220 htmlContent << createDefaultStyleForHtml();
2221 htmlContent << createOpeningTagForHtml();
2222 htmlContent << createSummaryForHtml(currentIndex());
2223 htmlContent << createClosingTagForHtml();
2224 // htmlContent will never be empty as they will always have
2225 // style and table tags
2226 QMimeData *mimeData = new QMimeData;
2227 mimeData->setHtml(htmlContent.join('\n'));
2228 mimeData->setText(copy_text);
2229 mainApp->clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
2231 else {
2232 mainApp->clipboard()->setText(copy_text);
2235 else {
2236 copy_text = createSummaryText(currentIndex(), copy_type);
2237 if (type != CopyAsYAML)
2238 copy_text += "\n";
2239 mainApp->clipboard()->setText(copy_text);
2243 // We need to tell when the user has scrolled the packet list, either to
2244 // the end or anywhere other than the end.
2245 void PacketList::vScrollBarActionTriggered(int)
2247 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
2248 // past the end.
2249 tail_at_end_ = (overlay_sb_->sliderPosition() >= overlay_sb_->maximum());
2251 scrollViewChanged(tail_at_end_);
2254 void PacketList::scrollViewChanged(bool at_end)
2256 if (capture_in_progress_) {
2257 // We want to start auto scrolling when the user scrolls to (or past)
2258 // the end only if recent.capture_auto_scroll is set.
2259 // We want to stop autoscrolling if the user scrolls up or uses
2260 // Go to Packet regardless of the preference setting.
2261 if (recent.capture_auto_scroll || !at_end) {
2262 emit packetListScrolled(at_end);
2267 // Goal: Overlay the packet list scroll bar with the colors of all of the
2268 // packets.
2269 // Try 1: Average packet colors in each scroll bar raster line. This has
2270 // two problems: It's easy to wash out colors and we dissect every packet.
2271 // Try 2: Color across a 5000 or 10000 packet window. We still end up washing
2272 // out colors.
2273 // Try 3: One packet per vertical scroll bar pixel. This seems to work best
2274 // but has the smallest window.
2275 // Try 4: Use a multiple of the scroll bar height and scale the image down
2276 // using Qt::SmoothTransformation. This gives us more packets per raster
2277 // line.
2279 // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
2280 // of 9 washed out colors a little too much.
2281 //const int height_multiplier_ = 7;
2282 void PacketList::drawNearOverlay()
2284 if (create_near_overlay_) {
2285 create_near_overlay_ = false;
2288 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
2290 if (!prefs.gui_packet_list_show_minimap) return;
2292 qreal dp_ratio = overlay_sb_->devicePixelRatio();
2293 int o_height = overlay_sb_->height() * dp_ratio;
2294 int o_rows = qMin(packet_list_model_->rowCount(), o_height);
2295 QFontMetricsF fmf(mainApp->font());
2296 int o_width = ((static_cast<int>(fmf.height())) * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
2298 if (recent.packet_list_colorize && o_rows > 0) {
2299 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
2301 QPainter painter(&overlay);
2303 overlay.fill(Qt::transparent);
2305 int cur_line = 0;
2306 int start = 0;
2308 if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
2309 start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows);
2311 int end = start + o_rows;
2312 for (int row = start; row < end; row++) {
2313 packet_list_model_->ensureRowColorized(row);
2315 frame_data *fdata = packet_list_model_->getRowFdata(row);
2316 const color_t *bgcolor = NULL;
2317 if (fdata->color_filter) {
2318 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
2319 bgcolor = &color_filter->bg_color;
2322 int next_line = (row - start + 1) * o_height / o_rows;
2323 if (bgcolor) {
2324 QColor color(ColorUtils::fromColorT(bgcolor));
2325 painter.fillRect(0, cur_line, o_width, next_line - cur_line, color);
2327 cur_line = next_line;
2330 // If the selected packet is in the overlay set selected_pos
2331 // accordingly. Otherwise, pin it to either the top or bottom.
2332 QList<int> positions;
2333 if (selectionModel()->hasSelection()) {
2335 QModelIndexList selRows = selectionModel()->selectedRows(0);
2336 int last_row = -1;
2337 int last_pos = -1;
2338 foreach (QModelIndex idx, selRows)
2340 int selected_pos = -1;
2341 int sel_row = idx.row();
2342 if (sel_row < start) {
2343 selected_pos = 0;
2344 } else if (sel_row >= end) {
2345 selected_pos = overlay.height() - 1;
2346 } else {
2347 selected_pos = (sel_row - start) * o_height / o_rows;
2350 /* Due to the difference in the display height, we sometimes get empty positions
2351 * inbetween consecutive valid rows. If those are detected, they are signaled as
2352 * being selected as well */
2353 if (last_pos >= 0 && selected_pos > (last_pos + 1) && (last_row + 1) == sel_row)
2355 for (int pos = (last_pos + 1); pos < selected_pos; pos++)
2357 if (! positions.contains(pos))
2358 positions << pos;
2361 else if (selected_pos != -1 && ! positions.contains(selected_pos))
2362 positions << selected_pos;
2364 last_row = sel_row;
2365 last_pos = selected_pos;
2369 overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, positions, (o_height / o_rows));
2370 } else {
2371 QImage overlay;
2372 overlay_sb_->setNearOverlayImage(overlay);
2376 void PacketList::drawFarOverlay()
2378 if (create_far_overlay_) {
2379 create_far_overlay_ = false;
2382 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
2384 if (!prefs.gui_packet_list_show_minimap) return;
2386 QSize groove_size = overlay_sb_->grooveRect().size();
2387 qreal dp_ratio = overlay_sb_->devicePixelRatio();
2388 groove_size *= dp_ratio;
2389 int o_width = groove_size.width();
2390 int o_height = groove_size.height();
2391 int pl_rows = packet_list_model_->rowCount();
2392 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
2393 bool have_marked_image = false;
2395 // If only there were references from popular culture about getting into
2396 // some sort of groove.
2397 if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
2399 QPainter painter(&overlay);
2401 // Draw text-colored tick marks on a transparent background.
2402 // Hopefully no themes use the text color for the groove color.
2403 overlay.fill(Qt::transparent);
2405 QColor tick_color = palette().text().color();
2406 tick_color.setAlphaF(0.3f);
2407 painter.setPen(tick_color);
2409 for (int row = 0; row < pl_rows; row++) {
2411 frame_data *fdata = packet_list_model_->getRowFdata(row);
2412 if (fdata->marked || fdata->ref_time || fdata->ignored) {
2413 int new_line = row * o_height / pl_rows;
2414 int tick_width = o_width / 3;
2415 // Marked or ignored: left side, time refs: right side.
2416 // XXX Draw ignored ticks in the middle?
2417 int x1 = fdata->ref_time ? o_width - tick_width : 1;
2418 int x2 = fdata->ref_time ? o_width - 1 : tick_width;
2420 painter.drawLine(x1, new_line, x2, new_line);
2421 have_marked_image = true;
2425 if (have_marked_image) {
2426 overlay_sb_->setMarkedPacketImage(overlay);
2427 return;
2431 if (!have_marked_image) {
2432 QImage null_overlay;
2433 overlay_sb_->setMarkedPacketImage(null_overlay);
2437 // Auto scroll if:
2438 // - We are capturing
2439 // - actionGoAutoScroll in the main UI is checked.
2441 // actionGoAutoScroll in the main UI:
2442 // - Is set to the value of recent.capture_auto_scroll when beginning a capture
2443 // - Can be triggered manually by the user
2444 // - Is turned on if the last user-set vertical scrollbar position is at the
2445 // end and recent.capture_auto_scroll is enabled
2446 // - Is turned off if the last user-set vertical scrollbar is not at the end,
2447 // or if one of the Go to Packet actions is used (XXX: Should keyboard
2448 // navigation in keyPressEvent turn it off for similar reasons?)
2449 void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
2451 QTreeView::rowsInserted(parent, start, end);
2452 if (capture_in_progress_ && tail_at_end_) {
2453 scrollToBottom();
2457 void PacketList::resizeAllColumns(bool onlyTimeFormatted)
2459 if (!cap_file_ || cap_file_->state == FILE_CLOSED || cap_file_->state == FILE_READ_PENDING)
2460 return;
2462 for (int col = 0; col < cap_file_->cinfo.num_cols; col++) {
2463 if (! onlyTimeFormatted || col_has_time_fmt(&cap_file_->cinfo, col)) {
2464 resizeColumnToContents(col);