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