1 /* packet_list.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include <ui/qt/packet_list.h>
11
12 #include "config.h"
13
14 #include <glib.h>
15
16 #include "file.h"
17
18 #include <epan/epan.h>
19 #include <epan/epan_dissect.h>
20
21 #include <epan/column-info.h>
22 #include <epan/column.h>
23 #include <epan/expert.h>
24 #include <epan/ipproto.h>
25 #include <epan/packet.h>
26 #include <epan/prefs.h>
27 #include <epan/proto.h>
28
29 #include "ui/main_statusbar.h"
30 #include "ui/packet_list_utils.h"
31 #include "ui/preference_utils.h"
32 #include "ui/recent.h"
33 #include "ui/recent_utils.h"
34 #include "ui/ws_ui_util.h"
35 #include <wsutil/utf8_entities.h>
36 #include "ui/util.h"
37
38 #include "wiretap/wtap_opttypes.h"
39 #include "wsutil/str_util.h"
40 #include <wsutil/wslog.h>
41
42 #include <epan/color_filters.h>
43 #include "frame_tvbuff.h"
44
45 #include <ui/qt/utils/color_utils.h>
46 #include <ui/qt/widgets/overlay_scroll_bar.h>
47 #include "proto_tree.h"
48 #include <ui/qt/utils/qt_ui_utils.h>
49 #include "wireshark_application.h"
50 #include <ui/qt/utils/data_printer.h>
51 #include <ui/qt/utils/frame_information.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/decode_as_dialog.h>
59
60 #include <QAction>
61 #include <QActionGroup>
62 #include <QClipboard>
63 #include <QContextMenuEvent>
64 #include <QtCore/qmath.h>
65 #include <QElapsedTimer>
66 #include <QFontMetrics>
67 #include <QHeaderView>
68 #include <QMessageBox>
69 #include <QPainter>
70 #include <QScreen>
71 #include <QScrollBar>
72 #include <QTabWidget>
73 #include <QTextEdit>
74 #include <QTimerEvent>
75 #include <QTreeWidget>
76 #include <QWindow>
77 #include <QJsonObject>
78 #include <QJsonDocument>
79
80 #ifdef Q_OS_WIN
81 #include "wsutil/file_util.h"
82 #include <QSysInfo>
83 #include <Uxtheme.h>
84 #endif
85
86 // To do:
87 // - Fix "apply as filter" behavior.
88 // - Add colorize conversation.
89 // - Use a timer to trigger automatic scrolling.
90
91 // If we ever add the ability to open multiple capture files we might be
92 // able to use something like QMap<capture_file *, PacketList *> to match
93 // capture files against packet lists and models.
94 static PacketList *gbl_cur_packet_list = NULL;
95
96 const int max_comments_to_fetch_ = 20000000; // Arbitrary
97 const int tail_update_interval_ = 100; // Milliseconds.
98 const int overlay_update_interval_ = 100; // 250; // Milliseconds.
99
100
101 // Copied from ui/gtk/packet_list.c
packet_list_resize_column(gint col)102 void packet_list_resize_column(gint col)
103 {
104 if (!gbl_cur_packet_list) return;
105 gbl_cur_packet_list->resizeColumnToContents(col);
106 }
107
108 void
packet_list_select_first_row(void)109 packet_list_select_first_row(void)
110 {
111 if (!gbl_cur_packet_list)
112 return;
113 gbl_cur_packet_list->goFirstPacket(false);
114 }
115
116 /*
117 * Given a frame_data structure, scroll to and select the row in the
118 * packet list corresponding to that frame. If there is no such
119 * row, return FALSE, otherwise return TRUE.
120 */
121 gboolean
packet_list_select_row_from_data(frame_data * fdata_needle)122 packet_list_select_row_from_data(frame_data *fdata_needle)
123 {
124 if (! gbl_cur_packet_list || ! gbl_cur_packet_list->model())
125 return FALSE;
126
127 PacketListModel * model = qobject_cast<PacketListModel *>(gbl_cur_packet_list->model());
128
129 if (! model)
130 return FALSE;
131
132 model->flushVisibleRows();
133 int row = model->visibleIndexOf(fdata_needle);
134 if (row >= 0) {
135 gbl_cur_packet_list->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
136 return TRUE;
137 }
138
139 return FALSE;
140 }
141
142 void
packet_list_clear(void)143 packet_list_clear(void)
144 {
145 if (gbl_cur_packet_list) {
146 gbl_cur_packet_list->clear();
147 }
148 }
149
150 void
packet_list_recolor_packets(void)151 packet_list_recolor_packets(void)
152 {
153 if (gbl_cur_packet_list) {
154 gbl_cur_packet_list->recolorPackets();
155 }
156 }
157
158 void
packet_list_freeze(void)159 packet_list_freeze(void)
160 {
161 if (gbl_cur_packet_list) {
162 gbl_cur_packet_list->freeze();
163 }
164 }
165
166 void
packet_list_thaw(void)167 packet_list_thaw(void)
168 {
169 if (gbl_cur_packet_list) {
170 gbl_cur_packet_list->thaw();
171 }
172
173 packets_bar_update();
174 }
175
176 frame_data *
packet_list_get_row_data(gint row)177 packet_list_get_row_data(gint row)
178 {
179 if (gbl_cur_packet_list) {
180 return gbl_cur_packet_list->getFDataForRow(row);
181 }
182 return NULL;
183 }
184
185 // Called from cf_continue_tail and cf_finish_tail when auto_scroll_live
186 // is enabled.
187 void
packet_list_moveto_end(void)188 packet_list_moveto_end(void)
189 {
190 // gbl_cur_packet_list->scrollToBottom();
191 }
192
193 /* Redraw the packet list *and* currently-selected detail */
194 void
packet_list_queue_draw(void)195 packet_list_queue_draw(void)
196 {
197 if (gbl_cur_packet_list)
198 gbl_cur_packet_list->redrawVisiblePackets();
199 }
200
201 void
packet_list_recent_write_all(FILE * rf)202 packet_list_recent_write_all(FILE *rf) {
203 if (!gbl_cur_packet_list)
204 return;
205
206 gbl_cur_packet_list->writeRecent(rf);
207 }
208
209 gboolean
packet_list_multi_select_active(void)210 packet_list_multi_select_active(void)
211 {
212 if (gbl_cur_packet_list) {
213 return gbl_cur_packet_list->multiSelectActive();
214 }
215 return FALSE;
216 }
217
218 #define MIN_COL_WIDTH_STR "MMMMMM"
219
PacketList(QWidget * parent)220 PacketList::PacketList(QWidget *parent) :
221 QTreeView(parent),
222 proto_tree_(NULL),
223 cap_file_(NULL),
224 ctx_column_(-1),
225 overlay_timer_id_(0),
226 create_near_overlay_(true),
227 create_far_overlay_(true),
228 mouse_pressed_at_(QModelIndex()),
229 capture_in_progress_(false),
230 tail_timer_id_(0),
231 tail_at_end_(0),
232 rows_inserted_(false),
233 columns_changed_(false),
234 set_column_visibility_(false),
235 frozen_rows_(QModelIndexList()),
236 cur_history_(-1),
237 in_history_(false)
238 {
239 setItemsExpandable(false);
240 setRootIsDecorated(false);
241 setSortingEnabled(true);
242 setUniformRowHeights(true);
243 setAccessibleName("Packet list");
244
245 proto_prefs_menus_.setTitle(tr("Protocol Preferences"));
246
247 packet_list_header_ = new PacketListHeader(header()->orientation(), cap_file_);
248 connect(packet_list_header_, &PacketListHeader::resetColumnWidth, this, &PacketList::setRecentColumnWidth);
249 connect(packet_list_header_, &PacketListHeader::updatePackets, this, &PacketList::updatePackets);
250 connect(packet_list_header_, &PacketListHeader::showColumnPreferences, this, &PacketList::showProtocolPreferences);
251 connect(packet_list_header_, &PacketListHeader::editColumn, this, &PacketList::editColumn);
252 connect(packet_list_header_, &PacketListHeader::columnsChanged, this, &PacketList::columnsChanged);
253 setHeader(packet_list_header_);
254
255 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
256 header()->setFirstSectionMovable(true);
257 #endif
258
259 setSelectionMode(QAbstractItemView::ExtendedSelection);
260
261 // Shrink down to a small but nonzero size in the main splitter.
262 int one_em = fontMetrics().height();
263 setMinimumSize(one_em, one_em);
264
265 overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
266 setVerticalScrollBar(overlay_sb_);
267
268 header()->setSortIndicator(-1, Qt::AscendingOrder);
269
270 packet_list_model_ = new PacketListModel(this, cap_file_);
271 setModel(packet_list_model_);
272
273 Q_ASSERT(gbl_cur_packet_list == Q_NULLPTR);
274 gbl_cur_packet_list = this;
275
276 connect(packet_list_model_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
277 connect(packet_list_model_, SIGNAL(itemHeightChanged(const QModelIndex&)), this, SLOT(updateRowHeights(const QModelIndex&)));
278 connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
279 connect(wsApp, SIGNAL(columnDataChanged()), this, SLOT(redrawVisiblePacketsDontSelectCurrent()));
280
281 connect(header(), SIGNAL(sectionResized(int,int,int)),
282 this, SLOT(sectionResized(int,int,int)));
283 connect(header(), SIGNAL(sectionMoved(int,int,int)),
284 this, SLOT(sectionMoved(int,int,int)));
285
286 connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
287 }
288
colorsChanged()289 void PacketList::colorsChanged()
290 {
291 const QString c_active = "active";
292 const QString c_inactive = "!active";
293
294 QString flat_style_format =
295 "QTreeView::item:selected:%1 {"
296 " color: %2;"
297 " background-color: %3;"
298 "}";
299
300 QString gradient_style_format =
301 "QTreeView::item:selected:%1 {"
302 " color: %2;"
303 " background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1 stop: 0 %4, stop: 0.5 %3, stop: 1 %4);"
304 "}";
305
306 QString hover_style;
307 #if !defined(Q_OS_WIN)
308 #if defined(Q_OS_MAC)
309 QPalette default_pal = QApplication::palette();
310 default_pal.setCurrentColorGroup(QPalette::Active);
311 QColor hover_color = default_pal.highlight().color();
312 #else
313 QColor hover_color = ColorUtils::alphaBlend(palette().window(), palette().highlight(), 0.5);
314 #endif
315
316 hover_style = QString(
317 "QTreeView:item:hover {"
318 " background-color: %1;"
319 " color: palette(text);"
320 "}").arg(hover_color.name(QColor::HexArgb));
321 #endif
322
323 QString active_style = QString();
324 QString inactive_style = QString();
325
326 if (prefs.gui_active_style == COLOR_STYLE_DEFAULT) {
327 // ACTIVE = Default
328 } else if (prefs.gui_active_style == COLOR_STYLE_FLAT) {
329 // ACTIVE = Flat
330 QColor foreground = ColorUtils::fromColorT(prefs.gui_active_fg);
331 QColor background = ColorUtils::fromColorT(prefs.gui_active_bg);
332
333 active_style = flat_style_format.arg(
334 c_active,
335 foreground.name(),
336 background.name());
337 } else if (prefs.gui_active_style == COLOR_STYLE_GRADIENT) {
338 // ACTIVE = Gradient
339 QColor foreground = ColorUtils::fromColorT(prefs.gui_active_fg);
340 QColor background1 = ColorUtils::fromColorT(prefs.gui_active_bg);
341 QColor background2 = QColor::fromRgb(ColorUtils::alphaBlend(foreground, background1, COLOR_STYLE_ALPHA));
342
343 active_style = gradient_style_format.arg(
344 c_active,
345 foreground.name(),
346 background1.name(),
347 background2.name());
348 }
349
350 // INACTIVE style sheet settings
351 if (prefs.gui_inactive_style == COLOR_STYLE_DEFAULT) {
352 // INACTIVE = Default
353 } else if (prefs.gui_inactive_style == COLOR_STYLE_FLAT) {
354 // INACTIVE = Flat
355 QColor foreground = ColorUtils::fromColorT(prefs.gui_inactive_fg);
356 QColor background = ColorUtils::fromColorT(prefs.gui_inactive_bg);
357
358 inactive_style = flat_style_format.arg(
359 c_inactive,
360 foreground.name(),
361 background.name());
362 } else if (prefs.gui_inactive_style == COLOR_STYLE_GRADIENT) {
363 // INACTIVE = Gradient
364 QColor foreground = ColorUtils::fromColorT(prefs.gui_inactive_fg);
365 QColor background1 = ColorUtils::fromColorT(prefs.gui_inactive_bg);
366 QColor background2 = QColor::fromRgb(ColorUtils::alphaBlend(foreground, background1, COLOR_STYLE_ALPHA));
367
368 inactive_style = gradient_style_format.arg(
369 c_inactive,
370 foreground.name(),
371 background1.name(),
372 background2.name());
373 }
374
375 // Set the style sheet
376 if(prefs.gui_qt_packet_list_hover_style) {
377 setStyleSheet(active_style + inactive_style + hover_style);
378 } else {
379 setStyleSheet(active_style + inactive_style);
380 }
381 }
382
joinSummaryRow(QStringList col_parts,int row,SummaryCopyType type)383 QString PacketList::joinSummaryRow(QStringList col_parts, int row, SummaryCopyType type)
384 {
385 QString copy_text;
386 switch (type) {
387 case CopyAsCSV:
388 copy_text = "\"";
389 copy_text += col_parts.join("\",\"");
390 copy_text += "\"";
391 break;
392 case CopyAsYAML:
393 copy_text = "----\n";
394 copy_text += QString("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename);
395 copy_text += "- ";
396 copy_text += col_parts.join("\n- ");
397 copy_text += "\n";
398 break;
399 case CopyAsText:
400 default:
401 copy_text = col_parts.join("\t");
402 }
403
404 return copy_text;
405 }
406
drawRow(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const407 void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
408 {
409 QTreeView::drawRow(painter, option, index);
410
411 if (prefs.gui_qt_packet_list_separator) {
412 QRect rect = visualRect(index);
413
414 painter->setPen(QColor(Qt::white));
415 painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
416 }
417 }
418
setProtoTree(ProtoTree * proto_tree)419 void PacketList::setProtoTree (ProtoTree *proto_tree) {
420 proto_tree_ = proto_tree;
421
422 connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
423 connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)),
424 &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t)));
425 }
426
multiSelectActive()427 bool PacketList::multiSelectActive()
428 {
429 return selectionModel()->selectedRows(0).count() > 1 ? true : false;
430 }
431
selectedRows(bool useFrameNum)432 QList<int> PacketList::selectedRows(bool useFrameNum)
433 {
434 QList<int> rows;
435 if (selectionModel() && selectionModel()->hasSelection())
436 {
437 foreach (QModelIndex idx, selectionModel()->selectedRows(0))
438 {
439 if (idx.isValid())
440 {
441 if (! useFrameNum)
442 rows << idx.row();
443 else if (useFrameNum)
444 {
445 frame_data * frame = getFDataForRow(idx.row());
446 if (frame)
447 rows << frame->num;
448 }
449 }
450 }
451 }
452 else if (currentIndex().isValid())
453 {
454 //
455 // XXX - will we ever have a current index but not a selection
456 // model?
457 //
458 if (! useFrameNum)
459 rows << currentIndex().row();
460 else
461 {
462 frame_data *frame = getFDataForRow(currentIndex().row());
463 if (frame)
464 rows << frame->num;
465 }
466 }
467
468 return rows;
469 }
470
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)471 void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected)
472 {
473 QTreeView::selectionChanged(selected, deselected);
474
475 if (!cap_file_) return;
476
477 int row = -1;
478 static bool multiSelect = false;
479
480 if (selectionModel())
481 {
482 QModelIndexList selRows = selectionModel()->selectedRows(0);
483 if (selRows.count() > 1)
484 {
485 QList<int> rows;
486 foreach (QModelIndex idx, selRows)
487 {
488 if (idx.isValid())
489 rows << idx.row();
490 }
491
492 emit framesSelected(rows);
493 emit fieldSelected(0);
494 cf_unselect_packet(cap_file_);
495
496 /* We have to repaint the content while changing state, as some delegates react to multi-select */
497 if (! multiSelect)
498 {
499 related_packet_delegate_.clear();
500 viewport()->update();
501 }
502
503 multiSelect = true;
504
505 return;
506 }
507 else if (selRows.count() > 0 && selRows.at(0).isValid())
508 {
509 multiSelect = false;
510 row = selRows.at(0).row();
511 }
512
513 /* Handling empty selection */
514 if (selRows.count() <= 0)
515 {
516 /* Nothing selected, but multiSelect is still active */
517 if (multiSelect)
518 {
519 multiSelect = false;
520 if (currentIndex().isValid())
521 {
522 selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
523 return;
524 }
525 }
526 /* Nothing selected, so in WS <= 3.0 nothing was indicated as well */
527 else if (currentIndex().isValid())
528 {
529 setCurrentIndex(QModelIndex());
530 }
531 }
532 }
533
534 if (row < 0)
535 cf_unselect_packet(cap_file_);
536 else
537 cf_select_packet(cap_file_, row);
538
539 if (!in_history_ && cap_file_->current_frame) {
540 cur_history_++;
541 selection_history_.resize(cur_history_);
542 selection_history_.append(cap_file_->current_frame->num);
543 }
544 in_history_ = false;
545
546 related_packet_delegate_.clear();
547
548 // The previous dissection state has been invalidated by cf_select_packet
549 // above, receivers must clear the previous state and apply the updated one.
550 emit framesSelected(QList<int>() << row);
551
552 if (!cap_file_->edt) {
553 viewport()->update();
554 emit fieldSelected(0);
555 return;
556 }
557
558 if (cap_file_->edt->tree) {
559 packet_info *pi = &cap_file_->edt->pi;
560 related_packet_delegate_.setCurrentFrame(pi->num);
561 conversation_t *conv = find_conversation_pinfo(pi, 0);
562 if (conv) {
563 related_packet_delegate_.setConversation(conv);
564 }
565 viewport()->update();
566 }
567
568 if (cap_file_->search_in_progress) {
569 match_data mdata;
570 field_info *fi = NULL;
571
572 if (cap_file_->string && cap_file_->decode_data) {
573 // The tree where the target string matched one of the labels was discarded in
574 // match_protocol_tree() so we have to search again in the latest tree.
575 if (cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree, &mdata)) {
576 fi = mdata.finfo;
577 }
578 } else if (cap_file_->search_pos != 0) {
579 // Find the finfo that corresponds to our byte.
580 fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos,
581 cap_file_->edt->tvb);
582 }
583
584 if (fi) {
585 FieldInformation finfo(fi, this);
586 emit fieldSelected(&finfo);
587 } else {
588 emit fieldSelected(0);
589 }
590 } else if (proto_tree_) {
591 proto_tree_->restoreSelectedField();
592 }
593 }
594
contextMenuEvent(QContextMenuEvent * event)595 void PacketList::contextMenuEvent(QContextMenuEvent *event)
596 {
597 const char *module_name = NULL;
598
599 proto_prefs_menus_.clear();
600
601 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
602 GPtrArray *finfo_array = proto_all_finfos(cap_file_->edt->tree);
603 QList<QString> added_proto_prefs;
604
605 for (guint i = 0; i < finfo_array->len; i++) {
606 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
607 header_field_info *hfinfo = fi->hfinfo;
608
609 if (prefs_is_registered_protocol(hfinfo->abbrev)) {
610 if (hfinfo->parent == -1) {
611 module_name = hfinfo->abbrev;
612 } else {
613 module_name = proto_registrar_get_abbrev(hfinfo->parent);
614 }
615
616 if (added_proto_prefs.contains(module_name)) {
617 continue;
618 }
619
620 ProtocolPreferencesMenu *proto_prefs_menu = new ProtocolPreferencesMenu(hfinfo->name, module_name, &proto_prefs_menus_);
621
622 connect(proto_prefs_menu, SIGNAL(showProtocolPreferences(QString)),
623 this, SIGNAL(showProtocolPreferences(QString)));
624 connect(proto_prefs_menu, SIGNAL(editProtocolPreference(preference*,pref_module*)),
625 this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
626
627 proto_prefs_menus_.addMenu(proto_prefs_menu);
628 added_proto_prefs << module_name;
629 }
630 }
631 g_ptr_array_free(finfo_array, TRUE);
632 }
633
634 QModelIndex ctxIndex = indexAt(event->pos());
635
636 if (selectionModel() && selectionModel()->selectedRows(0).count() > 1)
637 selectionModel()->select(ctxIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
638
639 // frameData will be owned by one of the submenus, see below.
640 FrameInformation * frameData =
641 new FrameInformation(new CaptureFile(this, cap_file_), packet_list_model_->getRowFdata(ctxIndex.row()));
642
643 QMenu * ctx_menu = new QMenu(this);
644 // XXX We might want to reimplement setParent() and fill in the context
645 // menu there.
646 ctx_menu->addAction(window()->findChild<QAction *>("actionEditMarkPacket"));
647 ctx_menu->addAction(window()->findChild<QAction *>("actionEditIgnorePacket"));
648 ctx_menu->addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
649 ctx_menu->addAction(window()->findChild<QAction *>("actionEditTimeShift"));
650 ctx_menu->addMenu(window()->findChild<QMenu *>("menuPacketComment"));
651
652 ctx_menu->addSeparator();
653
654 ctx_menu->addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
655 ctx_menu->addSeparator();
656
657 QString selectedfilter = getFilterFromRowAndColumn(currentIndex());
658
659 if (! hasFocus() && cap_file_ && cap_file_->finfo_selected) {
660 char *tmp_field = proto_construct_match_selected_string(cap_file_->finfo_selected, cap_file_->edt);
661 selectedfilter = QString(tmp_field);
662 wmem_free(NULL, tmp_field);
663 }
664
665 bool have_filter_expr = !selectedfilter.isEmpty();
666 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, have_filter_expr, ctx_menu));
667 ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, have_filter_expr, ctx_menu));
668
669 const char *conv_menu_name = "menuConversationFilter";
670 QMenu * main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
671 conv_menu_.setTitle(main_menu_item->title());
672 conv_menu_.setObjectName(conv_menu_name);
673 ctx_menu->addMenu(&conv_menu_);
674
675 const char *colorize_menu_name = "menuColorizeConversation";
676 main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
677 colorize_menu_.setTitle(main_menu_item->title());
678 colorize_menu_.setObjectName(colorize_menu_name);
679 ctx_menu->addMenu(&colorize_menu_);
680
681 main_menu_item = window()->findChild<QMenu *>("menuSCTP");
682 QMenu * 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 main_menu_item = window()->findChild<QMenu *>("menuFollow");
689 submenu = new QMenu(main_menu_item->title(), ctx_menu);
690 ctx_menu->addMenu(submenu);
691 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
692 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
693 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowDCCPStream"));
694 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTLSStream"));
695 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
696 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTP2Stream"));
697 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowQUICStream"));
698 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSIPCall"));
699
700 ctx_menu->addSeparator();
701
702 main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
703 submenu = new QMenu(main_menu_item->title(), ctx_menu);
704 ctx_menu->addMenu(submenu);
705
706 QAction * action = submenu->addAction(tr("Summary as Text"));
707 action->setData(CopyAsText);
708 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
709 action = submenu->addAction(tr("…as CSV"));
710 action->setData(CopyAsCSV);
711 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
712 action = submenu->addAction(tr("…as YAML"));
713 action->setData(CopyAsYAML);
714 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
715 submenu->addSeparator();
716
717 submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
718 submenu->addSeparator();
719
720 QActionGroup * copyEntries = DataPrinter::copyActions(this, frameData);
721 submenu->addActions(copyEntries->actions());
722 copyEntries->setParent(submenu);
723 frameData->setParent(submenu);
724
725 ctx_menu->addSeparator();
726 ctx_menu->addMenu(&proto_prefs_menus_);
727 action = ctx_menu->addAction(tr("Decode As…"));
728 action->setProperty("create_new", QVariant(true));
729 connect(action, &QAction::triggered, this, &PacketList::ctxDecodeAsDialog);
730 // "Print" not ported intentionally
731 action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
732 ctx_menu->addAction(action);
733
734
735 // Set menu sensitivity for the current column and set action data.
736 if (frameData)
737 emit framesSelected(QList<int>() << frameData->frameNum());
738 else
739 emit framesSelected(QList<int>());
740
741 ctx_menu->exec(event->globalPos());
742 }
743
ctxDecodeAsDialog()744 void PacketList::ctxDecodeAsDialog()
745 {
746 QAction *da_action = qobject_cast<QAction*>(sender());
747 if (! da_action)
748 return;
749 bool create_new = da_action->property("create_new").toBool();
750
751 DecodeAsDialog *da_dialog = new DecodeAsDialog(this, cap_file_, create_new);
752 connect(da_dialog, SIGNAL(destroyed(QObject*)), wsApp, SLOT(flushAppSignals()));
753 da_dialog->setWindowModality(Qt::ApplicationModal);
754 da_dialog->setAttribute(Qt::WA_DeleteOnClose);
755 da_dialog->show();
756 }
757
758 // Auto scroll if:
759 // - We're not at the end
760 // - We are capturing
761 // - actionGoAutoScroll in the main UI is checked.
762 // - It's been more than tail_update_interval_ ms since we last scrolled
763 // - The last user-set vertical scrollbar position was at the end.
764
765 // Using a timer assumes that we can save CPU overhead by updating
766 // periodically. If that's not the case we can dispense with it and call
767 // scrollToBottom() from rowsInserted().
timerEvent(QTimerEvent * event)768 void PacketList::timerEvent(QTimerEvent *event)
769 {
770 if (event->timerId() == tail_timer_id_) {
771 if (rows_inserted_ && capture_in_progress_ && tail_at_end_) {
772 scrollToBottom();
773 rows_inserted_ = false;
774 }
775 } else if (event->timerId() == overlay_timer_id_) {
776 if (!capture_in_progress_) {
777 if (create_near_overlay_) drawNearOverlay();
778 if (create_far_overlay_) drawFarOverlay();
779 }
780 } else {
781 QTreeView::timerEvent(event);
782 }
783 }
784
paintEvent(QPaintEvent * event)785 void PacketList::paintEvent(QPaintEvent *event)
786 {
787 // XXX This is overkill, but there are quite a few events that
788 // require a new overlay, e.g. page up/down, scrolling, column
789 // resizing, etc.
790 create_near_overlay_ = true;
791 QTreeView::paintEvent(event);
792 }
793
mousePressEvent(QMouseEvent * event)794 void PacketList::mousePressEvent (QMouseEvent *event)
795 {
796 setAutoScroll(false);
797 QTreeView::mousePressEvent(event);
798 setAutoScroll(true);
799
800 QModelIndex curIndex = indexAt(event->pos());
801 mouse_pressed_at_ = curIndex;
802
803 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
804 bool midButton = (event->buttons() & Qt::MiddleButton) == Qt::MiddleButton;
805 #else
806 bool midButton = (event->buttons() & Qt::MidButton) == Qt::MidButton;
807 #endif
808 if (midButton && cap_file_ && packet_list_model_)
809 {
810 packet_list_model_->toggleFrameMark(QModelIndexList() << curIndex);
811
812 // Make sure the packet list's frame.marked related field text is updated.
813 redrawVisiblePackets();
814
815 create_far_overlay_ = true;
816 packets_bar_update();
817 }
818 }
819
mouseReleaseEvent(QMouseEvent * event)820 void PacketList::mouseReleaseEvent(QMouseEvent *event) {
821 QTreeView::mouseReleaseEvent(event);
822
823 mouse_pressed_at_ = QModelIndex();
824 }
825
mouseMoveEvent(QMouseEvent * event)826 void PacketList::mouseMoveEvent (QMouseEvent *event)
827 {
828 QModelIndex curIndex = indexAt(event->pos());
829 if (event->buttons() & Qt::LeftButton && curIndex.isValid() && curIndex == mouse_pressed_at_)
830 {
831 ctx_column_ = curIndex.column();
832 QMimeData * mimeData = new QMimeData();
833 QWidget * content = nullptr;
834
835 QString filter = getFilterFromRowAndColumn(curIndex);
836 QList<int> rows = selectedRows();
837 if (rows.count() > 1)
838 {
839 QStringList content;
840 foreach (int row, rows)
841 {
842 QModelIndex idx = model()->index(row, 0);
843 if (! idx.isValid())
844 continue;
845
846 QString entry = createSummaryText(idx, CopyAsText);
847 content << entry;
848 }
849
850 if (content.count() > 0)
851 mimeData->setText(content.join("\n"));
852 }
853 else if (! filter.isEmpty())
854 {
855 QString abbrev;
856 QString name = model()->headerData(curIndex.column(), header()->orientation()).toString();
857
858 if (! filter.isEmpty())
859 {
860 abbrev = filter.left(filter.indexOf(' '));
861 }
862 else
863 {
864 filter = model()->data(curIndex).toString().toLower();
865 abbrev = filter;
866 }
867
868 mimeData->setText(filter);
869
870 QJsonObject filterData;
871 filterData["filter"] = filter;
872 filterData["name"] = abbrev;
873 filterData["description"] = name;
874
875 mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
876 content = new DragLabel(QString("%1\n%2").arg(name, abbrev), this);
877 }
878 else
879 {
880 QString text = model()->data(curIndex).toString();
881 if (! text.isEmpty())
882 mimeData->setText(text);
883 }
884
885 if (mimeData->hasText() || mimeData->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
886 {
887 QDrag * drag = new QDrag(this);
888 drag->setMimeData(mimeData);
889 if (content)
890 {
891 qreal dpr = window()->windowHandle()->devicePixelRatio();
892 QPixmap pixmap= QPixmap(content->size() * dpr);
893 pixmap.setDevicePixelRatio(dpr);
894 content->render(&pixmap);
895 drag->setPixmap(pixmap);
896 }
897
898 drag->exec(Qt::CopyAction);
899 }
900 else
901 {
902 delete mimeData;
903 }
904 }
905 }
906
keyPressEvent(QKeyEvent * event)907 void PacketList::keyPressEvent(QKeyEvent *event)
908 {
909 QTreeView::keyPressEvent(event);
910 if (event->matches(QKeySequence::Copy))
911 {
912 QStringList content;
913 if (model() && selectionModel() && selectionModel()->hasSelection())
914 {
915 QList<int> rows;
916 QModelIndexList selRows = selectionModel()->selectedRows(0);
917 foreach(QModelIndex row, selRows)
918 rows.append(row.row());
919
920 foreach(int row, rows)
921 {
922 QModelIndex idx = model()->index(row, 0);
923 if (! idx.isValid())
924 continue;
925
926 QString entry = createSummaryText(idx, CopyAsText);
927 content << entry;
928 }
929 }
930
931 if (content.count() > 0)
932 wsApp->clipboard()->setText(content.join('\n'), QClipboard::Clipboard);
933 }
934 }
935
resizeEvent(QResizeEvent * event)936 void PacketList::resizeEvent(QResizeEvent *event)
937 {
938 create_near_overlay_ = true;
939 create_far_overlay_ = true;
940 QTreeView::resizeEvent(event);
941 }
942
setColumnVisibility()943 void PacketList::setColumnVisibility()
944 {
945 set_column_visibility_ = true;
946 for (int i = 0; i < prefs.num_cols; i++) {
947 setColumnHidden(i, get_column_visible(i) ? false : true);
948 }
949 set_column_visibility_ = false;
950 }
951
sizeHintForColumn(int column) const952 int PacketList::sizeHintForColumn(int column) const
953 {
954 int size_hint = 0;
955
956 // This is a bit hacky but Qt does a fine job of column sizing and
957 // reimplementing QTreeView::sizeHintForColumn seems like a worse idea.
958 if (itemDelegateForColumn(column)) {
959 // In my (gcc) testing this results in correct behavior on Windows but adds extra space
960 // on macOS and Linux. We might want to add Q_OS_... #ifdefs accordingly.
961 size_hint = itemDelegateForColumn(column)->sizeHint(viewOptions(), QModelIndex()).width();
962 }
963 size_hint += QTreeView::sizeHintForColumn(column); // Decoration padding
964 return size_hint;
965 }
966
setRecentColumnWidth(int col)967 void PacketList::setRecentColumnWidth(int col)
968 {
969 int col_width = recent_get_column_width(col);
970
971 if (col_width < 1) {
972 int fmt = get_column_format(col);
973 const char *long_str = get_column_width_string(fmt, col);
974
975 QFontMetrics fm = QFontMetrics(wsApp->monospaceFont());
976 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
977 if (long_str) {
978 col_width = fm.horizontalAdvance(long_str);
979 } else {
980 col_width = fm.horizontalAdvance(MIN_COL_WIDTH_STR);
981 }
982 #else
983 if (long_str) {
984 col_width = fm.width(long_str);
985 } else {
986 col_width = fm.width(MIN_COL_WIDTH_STR);
987 }
988 #endif
989 // Custom delegate padding
990 if (itemDelegateForColumn(col)) {
991 col_width += itemDelegateForColumn(col)->sizeHint(viewOptions(), QModelIndex()).width();
992 }
993 }
994
995 setColumnWidth(col, col_width);
996 }
997
drawCurrentPacket()998 void PacketList::drawCurrentPacket()
999 {
1000 QModelIndex current_index = currentIndex();
1001 if (selectionModel() && current_index.isValid()) {
1002 selectionModel()->clearCurrentIndex();
1003 selectionModel()->setCurrentIndex(current_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
1004 }
1005 }
1006
1007 // Redraw the packet list and detail. Re-selects the current packet (causes
1008 // the UI to scroll to that packet).
1009 // Called from many places.
redrawVisiblePackets()1010 void PacketList::redrawVisiblePackets() {
1011 redrawVisiblePacketsDontSelectCurrent();
1012 drawCurrentPacket();
1013 }
1014
1015 // Redraw the packet list and detail.
1016 // Does not scroll back to the selected packet.
redrawVisiblePacketsDontSelectCurrent()1017 void PacketList::redrawVisiblePacketsDontSelectCurrent() {
1018 packet_list_model_->invalidateAllColumnStrings();
1019 }
1020
resetColumns()1021 void PacketList::resetColumns()
1022 {
1023 packet_list_model_->resetColumns();
1024 }
1025
1026 // Return true if we have a visible packet further along in the history.
haveNextHistory(bool update_cur)1027 bool PacketList::haveNextHistory(bool update_cur)
1028 {
1029 if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
1030 return false;
1031 }
1032
1033 for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
1034 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1035 if (update_cur) {
1036 cur_history_ = i;
1037 }
1038 return true;
1039 }
1040 }
1041 return false;
1042 }
1043
1044 // Return true if we have a visible packet back in the history.
havePreviousHistory(bool update_cur)1045 bool PacketList::havePreviousHistory(bool update_cur)
1046 {
1047 if (selection_history_.size() < 1 || cur_history_ < 1) {
1048 return false;
1049 }
1050
1051 for (int i = cur_history_ - 1; i >= 0; i--) {
1052 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
1053 if (update_cur) {
1054 cur_history_ = i;
1055 }
1056 return true;
1057 }
1058 }
1059 return false;
1060 }
1061
getFDataForRow(int row) const1062 frame_data *PacketList::getFDataForRow(int row) const
1063 {
1064 return packet_list_model_->getRowFdata(row);
1065 }
1066
1067 // prefs.col_list has changed.
columnsChanged()1068 void PacketList::columnsChanged()
1069 {
1070 columns_changed_ = true;
1071 if (!cap_file_) {
1072 // Keep columns_changed_ = true until we load a capture file.
1073 return;
1074 }
1075
1076 prefs.num_cols = g_list_length(prefs.col_list);
1077 col_cleanup(&cap_file_->cinfo);
1078 build_column_format_array(&cap_file_->cinfo, prefs.num_cols, FALSE);
1079 create_far_overlay_ = true;
1080 resetColumns();
1081 applyRecentColumnWidths();
1082 setColumnVisibility();
1083 columns_changed_ = false;
1084 }
1085
1086 // Fields have changed, update custom columns
fieldsChanged(capture_file * cf)1087 void PacketList::fieldsChanged(capture_file *cf)
1088 {
1089 prefs.num_cols = g_list_length(prefs.col_list);
1090 col_cleanup(&cf->cinfo);
1091 build_column_format_array(&cf->cinfo, prefs.num_cols, FALSE);
1092 resetColumns();
1093 }
1094
1095 // Column widths should
1096 // - Load from recent when we load a new profile (including at starting up).
1097 // - Reapply when changing columns.
1098 // - Persist across freezes and thaws.
1099 // - Persist across file closing and opening.
1100 // - Save to recent when we save our profile (including shutting down).
1101 // - Not be affected by the behavior of stretchLastSection.
applyRecentColumnWidths()1102 void PacketList::applyRecentColumnWidths()
1103 {
1104 // Either we've just started up or a profile has changed. Read
1105 // the recent settings, apply them, and save the header state.
1106
1107 int column_width = 0;
1108
1109 for (int col = 0; col < prefs.num_cols; col++) {
1110 // The column must be shown before setting column width.
1111 // Visibility will be updated in setColumnVisibility().
1112 setColumnHidden(col, false);
1113 setRecentColumnWidth(col);
1114 column_width += columnWidth(col);
1115 }
1116
1117 if (column_width > width()) {
1118 resize(column_width, height());
1119 }
1120
1121 column_state_ = header()->saveState();
1122 }
1123
preferencesChanged()1124 void PacketList::preferencesChanged()
1125 {
1126 // Update color style changes
1127 colorsChanged();
1128
1129 // Related packet delegate
1130 if (prefs.gui_packet_list_show_related) {
1131 setItemDelegateForColumn(0, &related_packet_delegate_);
1132 } else {
1133 setItemDelegateForColumn(0, 0);
1134 }
1135
1136 // Intelligent scroll bar (minimap)
1137 if (prefs.gui_packet_list_show_minimap) {
1138 if (overlay_timer_id_ == 0) {
1139 overlay_timer_id_ = startTimer(overlay_update_interval_);
1140 }
1141 } else {
1142 if (overlay_timer_id_ != 0) {
1143 killTimer(overlay_timer_id_);
1144 overlay_timer_id_ = 0;
1145 }
1146 }
1147
1148 // Elide mode.
1149 // This sets the mode for the entire view. If we want to make this setting
1150 // per-column we'll either have to generalize RelatedPacketDelegate so that
1151 // we can set it for entire rows or create another delegate.
1152 Qt::TextElideMode elide_mode = Qt::ElideRight;
1153 switch (prefs.gui_packet_list_elide_mode) {
1154 case ELIDE_LEFT:
1155 elide_mode = Qt::ElideLeft;
1156 break;
1157 case ELIDE_MIDDLE:
1158 elide_mode = Qt::ElideMiddle;
1159 break;
1160 case ELIDE_NONE:
1161 elide_mode = Qt::ElideNone;
1162 break;
1163 default:
1164 break;
1165 }
1166 setTextElideMode(elide_mode);
1167 }
1168
recolorPackets()1169 void PacketList::recolorPackets()
1170 {
1171 packet_list_model_->resetColorized();
1172 redrawVisiblePackets();
1173 }
1174
1175 /* Enable autoscroll timer. Note: must be called after the capture is started,
1176 * otherwise the timer will not be executed. */
setVerticalAutoScroll(bool enabled)1177 void PacketList::setVerticalAutoScroll(bool enabled)
1178 {
1179 tail_at_end_ = enabled;
1180 if (enabled && capture_in_progress_) {
1181 scrollToBottom();
1182 if (tail_timer_id_ == 0) tail_timer_id_ = startTimer(tail_update_interval_);
1183 } else if (tail_timer_id_ != 0) {
1184 killTimer(tail_timer_id_);
1185 tail_timer_id_ = 0;
1186 }
1187 }
1188
1189 // Called when we finish reading, reloading, rescanning, and retapping
1190 // packets.
captureFileReadFinished()1191 void PacketList::captureFileReadFinished()
1192 {
1193 packet_list_model_->flushVisibleRows();
1194 packet_list_model_->dissectIdle(true);
1195 // Invalidating the column strings picks up and request/response
1196 // tracking changes. We might just want to call it from flushVisibleRows.
1197 packet_list_model_->invalidateAllColumnStrings();
1198 }
1199
freeze()1200 void PacketList::freeze()
1201 {
1202 column_state_ = header()->saveState();
1203 setHeaderHidden(true);
1204 frozen_rows_ = selectedIndexes();
1205 selectionModel()->clear();
1206 setModel(Q_NULLPTR);
1207 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
1208 // call selectionChanged.
1209 related_packet_delegate_.clear();
1210
1211 /* Clears packet list as well as byteview */
1212 emit framesSelected(QList<int>());
1213 }
1214
thaw(bool restore_selection)1215 void PacketList::thaw(bool restore_selection)
1216 {
1217 setHeaderHidden(false);
1218 setModel(packet_list_model_);
1219
1220 // Resetting the model resets our column widths so we restore them here.
1221 // We don't reapply the recent settings because the user could have
1222 // resized the columns manually since they were initially loaded.
1223 header()->restoreState(column_state_);
1224
1225 if (restore_selection && frozen_rows_.length() > 0 && selectionModel()) {
1226 /* This updates our selection, which redissects the current packet,
1227 * which is needed when we're called from MainWindow::layoutPanes.
1228 * Also, this resets all ProtoTree and ByteView data */
1229 clearSelection();
1230 foreach (QModelIndex idx, frozen_rows_) {
1231 selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1232 }
1233 }
1234 frozen_rows_ = QModelIndexList();
1235 }
1236
clear()1237 void PacketList::clear() {
1238 related_packet_delegate_.clear();
1239 selectionModel()->clear();
1240 packet_list_model_->clear();
1241 proto_tree_->clear();
1242 selection_history_.clear();
1243 cur_history_ = -1;
1244 in_history_ = false;
1245
1246 QImage overlay;
1247 overlay_sb_->setNearOverlayImage(overlay);
1248 overlay_sb_->setMarkedPacketImage(overlay);
1249 create_near_overlay_ = true;
1250 create_far_overlay_ = true;
1251 }
1252
writeRecent(FILE * rf)1253 void PacketList::writeRecent(FILE *rf) {
1254 gint col, width, col_fmt;
1255 gchar xalign;
1256
1257 fprintf (rf, "%s:\n", RECENT_KEY_COL_WIDTH);
1258 for (col = 0; col < prefs.num_cols; col++) {
1259 if (col > 0) {
1260 fprintf (rf, ",\n");
1261 }
1262 col_fmt = get_column_format(col);
1263 if (col_fmt == COL_CUSTOM) {
1264 fprintf (rf, " \"%%Cus:%s\",", get_column_custom_fields(col));
1265 } else {
1266 fprintf (rf, " %s,", col_format_to_string(col_fmt));
1267 }
1268 width = recent_get_column_width (col);
1269 xalign = recent_get_column_xalign (col);
1270 fprintf (rf, " %d", width);
1271 if (xalign != COLUMN_XALIGN_DEFAULT) {
1272 fprintf (rf, ":%c", xalign);
1273 }
1274 }
1275 fprintf (rf, "\n");
1276 }
1277
contextMenuActive()1278 bool PacketList::contextMenuActive()
1279 {
1280 return ctx_column_ >= 0 ? true : false;
1281 }
1282
getFilterFromRowAndColumn(QModelIndex idx)1283 QString PacketList::getFilterFromRowAndColumn(QModelIndex idx)
1284 {
1285 frame_data *fdata;
1286 QString filter;
1287
1288 if (! idx.isValid())
1289 return filter;
1290
1291 int row = idx.row();
1292 int column = idx.column();
1293
1294 if (!cap_file_ || !packet_list_model_ || column < 0 || column >= cap_file_->cinfo.num_cols)
1295 return filter;
1296
1297 fdata = packet_list_model_->getRowFdata(row);
1298
1299 if (fdata != NULL) {
1300 epan_dissect_t edt;
1301 wtap_rec rec; /* Record metadata */
1302 Buffer buf; /* Record data */
1303
1304 wtap_rec_init(&rec);
1305 ws_buffer_init(&buf, 1514);
1306 if (!cf_read_record(cap_file_, fdata, &rec, &buf)) {
1307 wtap_rec_cleanup(&rec);
1308 ws_buffer_free(&buf);
1309 return filter; /* error reading the record */
1310 }
1311 /* proto tree, visible. We need a proto tree if there's custom columns */
1312 epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), FALSE);
1313 col_custom_prime_edt(&edt, &cap_file_->cinfo);
1314
1315 epan_dissect_run(&edt, cap_file_->cd_t, &rec,
1316 frame_tvbuff_new_buffer(&cap_file_->provider, fdata, &buf),
1317 fdata, &cap_file_->cinfo);
1318 epan_dissect_fill_in_columns(&edt, TRUE, TRUE);
1319
1320 if ((cap_file_->cinfo.columns[column].col_custom_occurrence) ||
1321 (strchr (cap_file_->cinfo.col_expr.col_expr_val[column], ',') == NULL))
1322 {
1323 /* Only construct the filter when a single occurrence is displayed
1324 * otherwise we might end up with a filter like "ip.proto==1,6".
1325 *
1326 * Or do we want to be able to filter on multiple occurrences so that
1327 * the filter might be calculated as "ip.proto==1 && ip.proto==6"
1328 * instead?
1329 */
1330 if (strlen(cap_file_->cinfo.col_expr.col_expr[column]) != 0 &&
1331 strlen(cap_file_->cinfo.col_expr.col_expr_val[column]) != 0) {
1332 gboolean is_string_value = FALSE;
1333 if (cap_file_->cinfo.columns[column].col_fmt == COL_CUSTOM) {
1334 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.columns[column].col_custom_fields);
1335 if (hfi && hfi->parent == -1) {
1336 /* Protocol only */
1337 filter.append(cap_file_->cinfo.col_expr.col_expr[column]);
1338 } else if (hfi && hfi->type == FT_STRING) {
1339 /* Custom string, add quotes */
1340 is_string_value = TRUE;
1341 }
1342 } else {
1343 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.col_expr.col_expr[column]);
1344 if (hfi && hfi->type == FT_STRING) {
1345 /* Could be an address type such as usb.src which must be quoted. */
1346 is_string_value = TRUE;
1347 }
1348 }
1349
1350 if (filter.isEmpty()) {
1351 if (is_string_value) {
1352 filter.append(QString("%1 == \"%2\"")
1353 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1354 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1355 } else {
1356 filter.append(QString("%1 == %2")
1357 .arg(cap_file_->cinfo.col_expr.col_expr[column])
1358 .arg(cap_file_->cinfo.col_expr.col_expr_val[column]));
1359 }
1360 }
1361 }
1362 }
1363
1364 epan_dissect_cleanup(&edt);
1365 wtap_rec_cleanup(&rec);
1366 ws_buffer_free(&buf);
1367 }
1368
1369 return filter;
1370 }
1371
resetColorized()1372 void PacketList::resetColorized()
1373 {
1374 packet_list_model_->resetColorized();
1375 update();
1376 }
1377
getPacketComment(guint c_number)1378 QString PacketList::getPacketComment(guint c_number)
1379 {
1380 int row = currentIndex().row();
1381 const frame_data *fdata;
1382 char *pkt_comment;
1383 wtap_opttype_return_val result;
1384 QString ret_val = NULL;
1385
1386 if (!cap_file_ || !packet_list_model_) return NULL;
1387
1388 fdata = packet_list_model_->getRowFdata(row);
1389
1390 if (!fdata) return NULL;
1391
1392 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1393 result = wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, &pkt_comment);
1394 if (result == WTAP_OPTTYPE_SUCCESS) {
1395 ret_val = QString(pkt_comment);
1396 }
1397 wtap_block_unref(pkt_block);
1398 return ret_val;
1399 }
1400
addPacketComment(QString new_comment)1401 void PacketList::addPacketComment(QString new_comment)
1402 {
1403 frame_data *fdata;
1404
1405 if (!cap_file_ || !packet_list_model_) return;
1406 if (new_comment.isEmpty()) return;
1407
1408 QByteArray ba = new_comment.toLocal8Bit();
1409
1410 for (int i = 0; i < selectedRows().size(); i++) {
1411 int row = selectedRows().at(i);
1412
1413 fdata = packet_list_model_->getRowFdata(row);
1414
1415 if (!fdata) continue;
1416
1417 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1418
1419 wtap_block_add_string_option(pkt_block, OPT_COMMENT, ba.data(), ba.size());
1420
1421 cf_set_modified_block(cap_file_, fdata, pkt_block);
1422 }
1423
1424 redrawVisiblePackets();
1425 }
1426
setPacketComment(guint c_number,QString new_comment)1427 void PacketList::setPacketComment(guint c_number, QString new_comment)
1428 {
1429 int row = currentIndex().row();
1430 frame_data *fdata;
1431
1432 if (!cap_file_ || !packet_list_model_) return;
1433
1434 fdata = packet_list_model_->getRowFdata(row);
1435
1436 if (!fdata) return;
1437
1438 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1439
1440 /* Check if we are clearing the comment */
1441 if (new_comment.isEmpty()) {
1442 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, c_number);
1443 } else {
1444 QByteArray ba = new_comment.toLocal8Bit();
1445 wtap_block_set_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, ba.data(), ba.size());
1446 }
1447
1448 cf_set_modified_block(cap_file_, fdata, pkt_block);
1449
1450 redrawVisiblePackets();
1451 }
1452
allPacketComments()1453 QString PacketList::allPacketComments()
1454 {
1455 guint32 framenum;
1456 frame_data *fdata;
1457 QString buf_str;
1458
1459 if (!cap_file_) return buf_str;
1460
1461 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1462 fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum);
1463
1464 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1465
1466 if (pkt_block) {
1467 guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
1468 for (guint i = 0; i < n_comments; i++) {
1469 char *comment_text;
1470 if (WTAP_OPTTYPE_SUCCESS == wtap_block_get_nth_string_option_value(pkt_block, OPT_COMMENT, i, &comment_text)) {
1471 buf_str.append(QString(tr("Frame %1: %2\n\n")).arg(framenum).arg(comment_text));
1472 if (buf_str.length() > max_comments_to_fetch_) {
1473 buf_str.append(QString(tr("[ Comment text exceeds %1. Stopping. ]"))
1474 .arg(format_size(max_comments_to_fetch_, format_size_unit_bytes|format_size_prefix_si)));
1475 return buf_str;
1476 }
1477 }
1478 }
1479 }
1480 }
1481 return buf_str;
1482 }
1483
deleteCommentsFromPackets()1484 void PacketList::deleteCommentsFromPackets()
1485 {
1486 frame_data *fdata;
1487
1488 if (!cap_file_ || !packet_list_model_) return;
1489
1490 for (int i = 0; i < selectedRows().size(); i++) {
1491 int row = selectedRows().at(i);
1492
1493 fdata = packet_list_model_->getRowFdata(row);
1494
1495 if (!fdata) continue;
1496
1497 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1498 guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
1499
1500 for (guint j = 0; j < n_comments; j++) {
1501 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
1502 }
1503
1504 cf_set_modified_block(cap_file_, fdata, pkt_block);
1505 }
1506
1507 redrawVisiblePackets();
1508 }
1509
deleteAllPacketComments()1510 void PacketList::deleteAllPacketComments()
1511 {
1512 guint32 framenum;
1513 frame_data *fdata;
1514 QString buf_str;
1515 guint i;
1516
1517 if (!cap_file_)
1518 return;
1519
1520 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1521 fdata = frame_data_sequence_find(cap_file_->provider.frames, framenum);
1522 wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
1523 guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
1524
1525 for (i = 0; i < n_comments; i++) {
1526 wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
1527 }
1528 cf_set_modified_block(cap_file_, fdata, pkt_block);
1529 }
1530
1531 cap_file_->packet_comment_count = 0;
1532 expert_update_comment_count(cap_file_->packet_comment_count);
1533 redrawVisiblePackets();
1534 }
1535
1536
1537 // Slots
1538
setCaptureFile(capture_file * cf)1539 void PacketList::setCaptureFile(capture_file *cf)
1540 {
1541 cap_file_ = cf;
1542 packet_list_model_->setCaptureFile(cf);
1543 packet_list_header_->setCaptureFile(cf);
1544 if (cf) {
1545 if (columns_changed_) {
1546 columnsChanged();
1547 } else {
1548 // Restore columns widths and visibility.
1549 header()->restoreState(column_state_);
1550 setColumnVisibility();
1551 }
1552 }
1553 create_near_overlay_ = true;
1554 sortByColumn(-1, Qt::AscendingOrder);
1555 }
1556
setMonospaceFont(const QFont & mono_font)1557 void PacketList::setMonospaceFont(const QFont &mono_font)
1558 {
1559 setFont(mono_font);
1560 header()->setFont(wsApp->font());
1561 }
1562
goNextPacket(void)1563 void PacketList::goNextPacket(void)
1564 {
1565 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1566 // Alt+toolbar
1567 goNextHistoryPacket();
1568 return;
1569 }
1570
1571 if (selectionModel()->hasSelection()) {
1572 selectionModel()->setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1573 } else {
1574 // First visible packet.
1575 selectionModel()->setCurrentIndex(indexAt(viewport()->rect().topLeft()), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1576 }
1577
1578 scrollViewChanged(false);
1579 }
1580
goPreviousPacket(void)1581 void PacketList::goPreviousPacket(void)
1582 {
1583 if (QApplication::keyboardModifiers() & Qt::AltModifier) {
1584 // Alt+toolbar
1585 goPreviousHistoryPacket();
1586 return;
1587 }
1588
1589 if (selectionModel()->hasSelection()) {
1590 selectionModel()->setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1591 } else {
1592 // Last visible packet.
1593 QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
1594 if (last_idx.isValid()) {
1595 selectionModel()->setCurrentIndex(last_idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1596 } else {
1597 goLastPacket();
1598 }
1599 }
1600
1601 scrollViewChanged(false);
1602 }
1603
goFirstPacket(bool user_selected)1604 void PacketList::goFirstPacket(bool user_selected) {
1605 if (packet_list_model_->rowCount() < 1) return;
1606 selectionModel()->setCurrentIndex(packet_list_model_->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1607 scrollTo(currentIndex());
1608
1609 if (user_selected) {
1610 scrollViewChanged(false);
1611 }
1612 }
1613
goLastPacket(void)1614 void PacketList::goLastPacket(void) {
1615 if (packet_list_model_->rowCount() < 1) return;
1616 selectionModel()->setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1617 scrollTo(currentIndex());
1618
1619 scrollViewChanged(false);
1620 }
1621
1622 // XXX We can jump to the wrong packet if a display filter is applied
goToPacket(int packet,int hf_id)1623 void PacketList::goToPacket(int packet, int hf_id)
1624 {
1625 if (!cf_goto_frame(cap_file_, packet))
1626 return;
1627
1628 int row = packet_list_model_->packetNumberToRow(packet);
1629 if (row >= 0) {
1630 selectionModel()->setCurrentIndex(packet_list_model_->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1631 proto_tree_->goToHfid(hf_id);
1632 }
1633
1634 scrollViewChanged(false);
1635 }
1636
goNextHistoryPacket()1637 void PacketList::goNextHistoryPacket()
1638 {
1639 if (haveNextHistory(true)) {
1640 in_history_ = true;
1641 goToPacket(selection_history_.at(cur_history_));
1642 in_history_ = false;
1643 }
1644 }
1645
goPreviousHistoryPacket()1646 void PacketList::goPreviousHistoryPacket()
1647 {
1648 if (havePreviousHistory(true)) {
1649 in_history_ = true;
1650 goToPacket(selection_history_.at(cur_history_));
1651 in_history_ = false;
1652 }
1653 }
1654
markFrame()1655 void PacketList::markFrame()
1656 {
1657 if (!cap_file_ || !packet_list_model_) return;
1658
1659 QModelIndexList frames;
1660
1661 if (selectionModel() && selectionModel()->hasSelection())
1662 {
1663 QModelIndexList selRows = selectionModel()->selectedRows(0);
1664 foreach (QModelIndex idx, selRows)
1665 {
1666 if (idx.isValid())
1667 {
1668 frames << idx;
1669 }
1670 }
1671 }
1672 else
1673 frames << currentIndex();
1674
1675 packet_list_model_->toggleFrameMark(frames);
1676
1677 // Make sure the packet list's frame.marked related field text is updated.
1678 redrawVisiblePackets();
1679
1680 create_far_overlay_ = true;
1681 packets_bar_update();
1682 }
1683
markAllDisplayedFrames(bool set)1684 void PacketList::markAllDisplayedFrames(bool set)
1685 {
1686 if (!cap_file_ || !packet_list_model_) return;
1687
1688 packet_list_model_->setDisplayedFrameMark(set);
1689
1690 // Make sure the packet list's frame.marked related field text is updated.
1691 redrawVisiblePackets();
1692
1693 create_far_overlay_ = true;
1694 packets_bar_update();
1695 }
1696
ignoreFrame()1697 void PacketList::ignoreFrame()
1698 {
1699 if (!cap_file_ || !packet_list_model_) return;
1700
1701 QModelIndexList frames;
1702
1703 if (selectionModel() && selectionModel()->hasSelection())
1704 {
1705 foreach (QModelIndex idx, selectionModel()->selectedRows(0))
1706 {
1707 if (idx.isValid())
1708 {
1709 frames << idx;
1710 }
1711 }
1712 }
1713 else
1714 frames << currentIndex();
1715
1716
1717 packet_list_model_->toggleFrameIgnore(frames);
1718 create_far_overlay_ = true;
1719 int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1720 setUpdatesEnabled(false);
1721 emit packetDissectionChanged();
1722 setUpdatesEnabled(true);
1723 verticalScrollBar()->setValue(sb_val);
1724 }
1725
ignoreAllDisplayedFrames(bool set)1726 void PacketList::ignoreAllDisplayedFrames(bool set)
1727 {
1728 if (!cap_file_ || !packet_list_model_) return;
1729
1730 packet_list_model_->setDisplayedFrameIgnore(set);
1731 create_far_overlay_ = true;
1732 emit packetDissectionChanged();
1733 }
1734
setTimeReference()1735 void PacketList::setTimeReference()
1736 {
1737 if (!cap_file_ || !packet_list_model_) return;
1738 packet_list_model_->toggleFrameRefTime(currentIndex());
1739 create_far_overlay_ = true;
1740 }
1741
unsetAllTimeReferences()1742 void PacketList::unsetAllTimeReferences()
1743 {
1744 if (!cap_file_ || !packet_list_model_) return;
1745 packet_list_model_->unsetAllFrameRefTime();
1746 create_far_overlay_ = true;
1747 }
1748
applyTimeShift()1749 void PacketList::applyTimeShift()
1750 {
1751 packet_list_model_->resetColumns();
1752 redrawVisiblePackets();
1753 // XXX emit packetDissectionChanged(); ?
1754 }
1755
updatePackets(bool redraw)1756 void PacketList::updatePackets(bool redraw)
1757 {
1758 if (redraw) {
1759 redrawVisiblePackets();
1760 } else {
1761 update();
1762 }
1763 }
1764
columnVisibilityTriggered()1765 void PacketList::columnVisibilityTriggered()
1766 {
1767 QAction *ha = qobject_cast<QAction*>(sender());
1768 if (!ha) return;
1769
1770 int col = ha->data().toInt();
1771 set_column_visible(col, ha->isChecked());
1772 setColumnVisibility();
1773 if (ha->isChecked()) {
1774 setRecentColumnWidth(col);
1775 }
1776 prefs_main_write();
1777 }
1778
sectionResized(int col,int,int new_width)1779 void PacketList::sectionResized(int col, int, int new_width)
1780 {
1781 if (isVisible() && !columns_changed_ && !set_column_visibility_ && new_width > 0) {
1782 // Column 1 gets an invalid value (32 on macOS) when we're not yet
1783 // visible.
1784 //
1785 // Don't set column width when columns changed or setting column
1786 // visibility because we may get a sectionReized() from QTreeView
1787 // with values from a old columns layout.
1788 //
1789 // Don't set column width when hiding a column.
1790
1791 recent_set_column_width(col, new_width);
1792 }
1793 }
1794
1795 // The user moved a column. Make sure prefs.col_list, the column format
1796 // array, and the header's visual and logical indices all agree.
1797 // gtk/packet_list.c:column_dnd_changed_cb
sectionMoved(int logicalIndex,int oldVisualIndex,int newVisualIndex)1798 void PacketList::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
1799 {
1800 GList *new_col_list = NULL;
1801 QList<int> saved_sizes;
1802 int sort_idx;
1803
1804 // Since we undo the move below, these should always stay in sync.
1805 // Otherwise the order of columns can be unexpected after drag and drop.
1806 if (logicalIndex != oldVisualIndex) {
1807 ws_warning("Column moved from an unexpected state (%d, %d, %d)",
1808 logicalIndex, oldVisualIndex, newVisualIndex);
1809 }
1810
1811 // Remember which column should be sorted. Use the visual index since this
1812 // points to the current GUI state rather than the outdated column order
1813 // (indicated by the logical index).
1814 sort_idx = header()->sortIndicatorSection();
1815 if (sort_idx != -1) {
1816 sort_idx = header()->visualIndex(sort_idx);
1817 }
1818
1819 // Build a new column list based on the header's logical order.
1820 for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
1821 int log_idx = header()->logicalIndex(vis_idx);
1822 saved_sizes << header()->sectionSize(log_idx);
1823
1824 void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
1825 if (!pref_data) continue;
1826
1827 new_col_list = g_list_append(new_col_list, pref_data);
1828 }
1829
1830 // Undo move to ensure that the logical indices map to the visual indices,
1831 // otherwise the column order is changed twice (once via the modified
1832 // col_list, once because of the visual/logical index mismatch).
1833 disconnect(header(), SIGNAL(sectionMoved(int,int,int)),
1834 this, SLOT(sectionMoved(int,int,int)));
1835 header()->moveSection(newVisualIndex, oldVisualIndex);
1836 connect(header(), SIGNAL(sectionMoved(int,int,int)),
1837 this, SLOT(sectionMoved(int,int,int)));
1838
1839 // Clear and rebuild our (and the header's) model. There doesn't appear
1840 // to be another way to reset the logical index.
1841 freeze();
1842
1843 g_list_free(prefs.col_list);
1844 prefs.col_list = new_col_list;
1845
1846 thaw(true);
1847
1848 for (int i = 0; i < saved_sizes.length(); i++) {
1849 if (saved_sizes[i] < 1) continue;
1850 header()->resizeSection(i, saved_sizes[i]);
1851 }
1852
1853 prefs_main_write();
1854
1855 wsApp->emitAppSignal(WiresharkApplication::ColumnsChanged);
1856
1857 // If the column with the sort indicator got shifted, mark the new column
1858 // after updating the columns contents (via ColumnsChanged) to ensure that
1859 // the columns are sorted using the intended column contents.
1860 int left_col = MIN(oldVisualIndex, newVisualIndex);
1861 int right_col = MAX(oldVisualIndex, newVisualIndex);
1862 if (left_col <= sort_idx && sort_idx <= right_col) {
1863 header()->setSortIndicator(sort_idx, header()->sortIndicatorOrder());
1864 }
1865 }
1866
updateRowHeights(const QModelIndex & ih_index)1867 void PacketList::updateRowHeights(const QModelIndex &ih_index)
1868 {
1869 QStyleOptionViewItem option = viewOptions();
1870 int max_height = 0;
1871
1872 // One of our columns increased the maximum row height. Find out which one.
1873 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1874 QSize size_hint = itemDelegate()->sizeHint(option, packet_list_model_->index(ih_index.row(), col));
1875 max_height = qMax(max_height, size_hint.height());
1876 }
1877
1878 if (max_height > 0) {
1879 packet_list_model_->setMaximumRowHeight(max_height);
1880 }
1881 }
1882
createSummaryText(QModelIndex idx,SummaryCopyType type)1883 QString PacketList::createSummaryText(QModelIndex idx, SummaryCopyType type)
1884 {
1885 if (! idx.isValid())
1886 return "";
1887
1888 QStringList col_parts;
1889 int row = idx.row();
1890 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1891 if (get_column_visible(col)) {
1892 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
1893 }
1894 }
1895 return joinSummaryRow(col_parts, row, type);
1896 }
1897
createHeaderSummaryText(SummaryCopyType type)1898 QString PacketList::createHeaderSummaryText(SummaryCopyType type)
1899 {
1900 QStringList col_parts;
1901 for (int col = 0; col < packet_list_model_->columnCount(); ++col)
1902 {
1903 if (get_column_visible(col)) {
1904 col_parts << packet_list_model_->headerData(col, Qt::Orientation::Horizontal, Qt::DisplayRole).toString();
1905 }
1906 }
1907 return joinSummaryRow(col_parts, 0, type);
1908 }
1909
copySummary()1910 void PacketList::copySummary()
1911 {
1912 if (!currentIndex().isValid()) return;
1913
1914 QAction *ca = qobject_cast<QAction*>(sender());
1915 if (!ca) return;
1916
1917 QVariant type = ca->data();
1918 if (! type.canConvert<SummaryCopyType>())
1919 return;
1920 SummaryCopyType copy_type = type.value<SummaryCopyType>();
1921
1922 QString copy_text = createSummaryText(currentIndex(), copy_type);
1923
1924 wsApp->clipboard()->setText(copy_text);
1925 }
1926
1927 // We need to tell when the user has scrolled the packet list, either to
1928 // the end or anywhere other than the end.
vScrollBarActionTriggered(int)1929 void PacketList::vScrollBarActionTriggered(int)
1930 {
1931 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
1932 // past the end.
1933 tail_at_end_ = (overlay_sb_->sliderPosition() >= overlay_sb_->maximum());
1934
1935 scrollViewChanged(tail_at_end_);
1936 }
1937
scrollViewChanged(bool at_end)1938 void PacketList::scrollViewChanged(bool at_end)
1939 {
1940 if (capture_in_progress_ && prefs.capture_auto_scroll) {
1941 emit packetListScrolled(at_end);
1942 }
1943 }
1944
1945 // Goal: Overlay the packet list scroll bar with the colors of all of the
1946 // packets.
1947 // Try 1: Average packet colors in each scroll bar raster line. This has
1948 // two problems: It's easy to wash out colors and we dissect every packet.
1949 // Try 2: Color across a 5000 or 10000 packet window. We still end up washing
1950 // out colors.
1951 // Try 3: One packet per vertical scroll bar pixel. This seems to work best
1952 // but has the smallest window.
1953 // Try 4: Use a multiple of the scroll bar heigh and scale the image down
1954 // using Qt::SmoothTransformation. This gives us more packets per raster
1955 // line.
1956
1957 // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
1958 // of 9 washed out colors a little too much.
1959 //const int height_multiplier_ = 7;
drawNearOverlay()1960 void PacketList::drawNearOverlay()
1961 {
1962 if (create_near_overlay_) {
1963 create_near_overlay_ = false;
1964 }
1965
1966 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
1967
1968 if (!prefs.gui_packet_list_show_minimap) return;
1969
1970 qreal dp_ratio = overlay_sb_->devicePixelRatio();
1971 int o_height = overlay_sb_->height() * dp_ratio;
1972 int o_rows = qMin(packet_list_model_->rowCount(), o_height);
1973 int o_width = (wsApp->fontMetrics().height() * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
1974
1975 if (recent.packet_list_colorize && o_rows > 0) {
1976 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
1977
1978 QPainter painter(&overlay);
1979
1980 overlay.fill(Qt::transparent);
1981
1982 int cur_line = 0;
1983 int start = 0;
1984
1985 if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
1986 start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows);
1987 }
1988 int end = start + o_rows;
1989 for (int row = start; row < end; row++) {
1990 packet_list_model_->ensureRowColorized(row);
1991
1992 frame_data *fdata = packet_list_model_->getRowFdata(row);
1993 const color_t *bgcolor = NULL;
1994 if (fdata->color_filter) {
1995 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
1996 bgcolor = &color_filter->bg_color;
1997 }
1998
1999 int next_line = (row - start) * o_height / o_rows;
2000 if (bgcolor) {
2001 QColor color(ColorUtils::fromColorT(bgcolor));
2002 painter.fillRect(0, cur_line, o_width, next_line - cur_line, color);
2003 }
2004 cur_line = next_line;
2005 }
2006
2007 // If the selected packet is in the overlay set selected_pos
2008 // accordingly. Otherwise, pin it to either the top or bottom.
2009 QList<int> positions;
2010 if (selectionModel()->hasSelection()) {
2011
2012 QModelIndexList selRows = selectionModel()->selectedRows(0);
2013 int last_row = -1;
2014 int last_pos = -1;
2015 foreach (QModelIndex idx, selRows)
2016 {
2017 int selected_pos = -1;
2018 int sel_row = idx.row();
2019 if (sel_row < start) {
2020 selected_pos = 0;
2021 } else if (sel_row >= end) {
2022 selected_pos = overlay.height() - 1;
2023 } else {
2024 selected_pos = (sel_row - start) * o_height / o_rows;
2025 }
2026
2027 /* Due to the difference in the display height, we sometimes get empty positions
2028 * inbetween consecutive valid rows. If those are detected, they are signaled as
2029 * being selected as well */
2030 if (last_pos >= 0 && selected_pos > (last_pos + 1) && (last_row + 1) == sel_row)
2031 {
2032 for (int pos = (last_pos + 1); pos < selected_pos; pos++)
2033 {
2034 if (! positions.contains(pos))
2035 positions << pos;
2036 }
2037 }
2038 else if (selected_pos != -1 && ! positions.contains(selected_pos))
2039 positions << selected_pos;
2040
2041 last_row = sel_row;
2042 last_pos = selected_pos;
2043 }
2044 }
2045
2046 overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, positions);
2047 } else {
2048 QImage overlay;
2049 overlay_sb_->setNearOverlayImage(overlay);
2050 }
2051 }
2052
drawFarOverlay()2053 void PacketList::drawFarOverlay()
2054 {
2055 if (create_far_overlay_) {
2056 create_far_overlay_ = false;
2057 }
2058
2059 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
2060
2061 if (!prefs.gui_packet_list_show_minimap) return;
2062
2063 QSize groove_size = overlay_sb_->grooveRect().size();
2064 qreal dp_ratio = overlay_sb_->devicePixelRatio();
2065 groove_size *= dp_ratio;
2066 int o_width = groove_size.width();
2067 int o_height = groove_size.height();
2068 int pl_rows = packet_list_model_->rowCount();
2069 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
2070 bool have_marked_image = false;
2071
2072 // If only there were references from popular culture about getting into
2073 // some sort of groove.
2074 if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
2075
2076 QPainter painter(&overlay);
2077
2078 // Draw text-colored tick marks on a transparent background.
2079 // Hopefully no themes use the text color for the groove color.
2080 overlay.fill(Qt::transparent);
2081
2082 QColor tick_color = palette().text().color();
2083 tick_color.setAlphaF(0.3);
2084 painter.setPen(tick_color);
2085
2086 for (int row = 0; row < pl_rows; row++) {
2087
2088 frame_data *fdata = packet_list_model_->getRowFdata(row);
2089 if (fdata->marked || fdata->ref_time || fdata->ignored) {
2090 int new_line = row * o_height / pl_rows;
2091 int tick_width = o_width / 3;
2092 // Marked or ignored: left side, time refs: right side.
2093 // XXX Draw ignored ticks in the middle?
2094 int x1 = fdata->ref_time ? o_width - tick_width : 1;
2095 int x2 = fdata->ref_time ? o_width - 1 : tick_width;
2096
2097 painter.drawLine(x1, new_line, x2, new_line);
2098 have_marked_image = true;
2099 }
2100 }
2101
2102 if (have_marked_image) {
2103 overlay_sb_->setMarkedPacketImage(overlay);
2104 return;
2105 }
2106 }
2107
2108 if (!have_marked_image) {
2109 QImage null_overlay;
2110 overlay_sb_->setMarkedPacketImage(null_overlay);
2111 }
2112 }
2113
rowsInserted(const QModelIndex & parent,int start,int end)2114 void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
2115 {
2116 QTreeView::rowsInserted(parent, start, end);
2117 rows_inserted_ = true;
2118 }
2119