Qt: Add selection history navigation.
[metze/wireshark/wip.git] / ui / qt / packet_list.cpp
1 /* packet_list.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "packet_list.h"
23
24 #include "config.h"
25
26 #include <glib.h>
27
28 #include "file.h"
29
30 #include <epan/epan.h>
31 #include <epan/epan_dissect.h>
32
33 #include <epan/column-info.h>
34 #include <epan/column.h>
35 #include <epan/ipproto.h>
36 #include <epan/packet.h>
37 #include <epan/prefs.h>
38 #include <epan/proto.h>
39
40 #include "ui/main_statusbar.h"
41 #include "ui/packet_list_utils.h"
42 #include "ui/preference_utils.h"
43 #include "ui/recent.h"
44 #include "ui/recent_utils.h"
45 #include "ui/ui_util.h"
46 #include <wsutil/utf8_entities.h>
47 #include "ui/util.h"
48
49 #include "wsutil/str_util.h"
50
51 #include <epan/color_filters.h>
52 #include "frame_tvbuff.h"
53
54 #include "color_utils.h"
55 #include "overlay_scroll_bar.h"
56 #include "proto_tree.h"
57 #include "qt_ui_utils.h"
58 #include "wireshark_application.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
77 #ifdef Q_OS_WIN
78 #include "wsutil/file_util.h"
79 #include <QSysInfo>
80 #endif
81
82 // To do:
83 // - Fix "apply as filter" behavior.
84 // - Add colorize conversation.
85 // - Use a timer to trigger automatic scrolling.
86
87 // If we ever add the ability to open multiple capture files we might be
88 // able to use something like QMap<capture_file *, PacketList *> to match
89 // capture files against packet lists and models.
90 static PacketList *gbl_cur_packet_list = NULL;
91
92 const int max_comments_to_fetch_ = 20000000; // Arbitrary
93 const int tail_update_interval_ = 100; // Milliseconds.
94 const int overlay_update_interval_ = 100; // 250; // Milliseconds.
95
96 guint
97 packet_list_append(column_info *, frame_data *fdata)
98 {
99     if (!gbl_cur_packet_list)
100         return 0;
101
102     /* fdata should be filled with the stuff we need
103      * strings are built at display time.
104      */
105     guint visible_pos;
106
107     visible_pos = gbl_cur_packet_list->packetListModel()->appendPacket(fdata);
108     return visible_pos;
109 }
110
111 // Copied from ui/gtk/packet_list.c
112 void packet_list_resize_column(gint col)
113 {
114     if (!gbl_cur_packet_list) return;
115     gbl_cur_packet_list->resizeColumnToContents(col);
116 }
117
118 void
119 packet_list_select_first_row(void)
120 {
121     if (!gbl_cur_packet_list)
122         return;
123     gbl_cur_packet_list->goFirstPacket();
124 }
125
126 /*
127  * Given a frame_data structure, scroll to and select the row in the
128  * packet list corresponding to that frame.  If there is no such
129  * row, return FALSE, otherwise return TRUE.
130  */
131 gboolean
132 packet_list_select_row_from_data(frame_data *fdata_needle)
133 {
134     gbl_cur_packet_list->packetListModel()->flushVisibleRows();
135     int row = gbl_cur_packet_list->packetListModel()->visibleIndexOf(fdata_needle);
136     if (row >= 0) {
137         gbl_cur_packet_list->setCurrentIndex(gbl_cur_packet_list->packetListModel()->index(row,0));
138         return TRUE;
139     }
140
141     return FALSE;
142 }
143
144 gboolean
145 packet_list_check_end(void)
146 {
147     return FALSE; // GTK+ only.
148 }
149
150 void
151 packet_list_clear(void)
152 {
153     if (gbl_cur_packet_list) {
154         gbl_cur_packet_list->clear();
155     }
156 }
157
158 void
159 packet_list_enable_color(gboolean)
160 {
161     if (gbl_cur_packet_list) {
162         gbl_cur_packet_list->recolorPackets();
163     }
164 }
165
166 void
167 packet_list_freeze(void)
168 {
169     if (gbl_cur_packet_list) {
170         gbl_cur_packet_list->freeze();
171     }
172 }
173
174 void
175 packet_list_thaw(void)
176 {
177     if (gbl_cur_packet_list) {
178         gbl_cur_packet_list->thaw();
179     }
180
181     packets_bar_update();
182 }
183
184 void
185 packet_list_recreate_visible_rows(void)
186 {
187     if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) {
188         gbl_cur_packet_list->packetListModel()->recreateVisibleRows();
189     }
190 }
191
192 frame_data *
193 packet_list_get_row_data(gint row)
194 {
195     if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) {
196         return gbl_cur_packet_list->packetListModel()->getRowFdata(row);
197     }
198     return NULL;
199 }
200
201 // Called from cf_continue_tail and cf_finish_tail when auto_scroll_live
202 // is enabled.
203 void
204 packet_list_moveto_end(void)
205 {
206     // gbl_cur_packet_list->scrollToBottom();
207 }
208
209 /* Redraw the packet list *and* currently-selected detail */
210 void
211 packet_list_queue_draw(void)
212 {
213     if (gbl_cur_packet_list)
214         gbl_cur_packet_list->redrawVisiblePackets();
215 }
216
217 void
218 packet_list_recent_write_all(FILE *rf) {
219     if (!gbl_cur_packet_list)
220         return;
221
222     gbl_cur_packet_list->writeRecent(rf);
223 }
224
225 #define MIN_COL_WIDTH_STR "MMMMMM"
226
227 Q_DECLARE_METATYPE(PacketList::ColumnActions)
228
229 enum copy_summary_type {
230     copy_summary_text_,
231     copy_summary_csv_,
232     copy_summary_yaml_
233 };
234
235 PacketList::PacketList(QWidget *parent) :
236     QTreeView(parent),
237     proto_tree_(NULL),
238     byte_view_tab_(NULL),
239     cap_file_(NULL),
240     decode_as_(NULL),
241     ctx_column_(-1),
242     overlay_timer_id_(0),
243     create_near_overlay_(true),
244     create_far_overlay_(true),
245     capture_in_progress_(false),
246     tail_timer_id_(0),
247     rows_inserted_(false),
248     columns_changed_(false),
249     set_column_visibility_(false),
250     frozen_row_(-1),
251     cur_history_(-1),
252     in_history_(false)
253 {
254     QMenu *main_menu_item, *submenu;
255     QAction *action;
256
257     setItemsExpandable(false);
258     setRootIsDecorated(false);
259     setSortingEnabled(true);
260     setUniformRowHeights(true);
261     setAccessibleName("Packet list");
262
263     overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
264     setVerticalScrollBar(overlay_sb_);
265
266     packet_list_model_ = new PacketListModel(this, cap_file_);
267     setModel(packet_list_model_);
268     sortByColumn(-1, Qt::AscendingOrder);
269
270     // XXX We might want to reimplement setParent() and fill in the context
271     // menu there.
272     ctx_menu_.addAction(window()->findChild<QAction *>("actionEditMarkPacket"));
273     ctx_menu_.addAction(window()->findChild<QAction *>("actionEditIgnorePacket"));
274     ctx_menu_.addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
275     ctx_menu_.addAction(window()->findChild<QAction *>("actionEditTimeShift"));
276     ctx_menu_.addAction(window()->findChild<QAction *>("actionEditPacketComment"));
277
278     ctx_menu_.addSeparator();
279
280     ctx_menu_.addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
281     ctx_menu_.addSeparator();
282
283     main_menu_item = window()->findChild<QMenu *>("menuApplyAsFilter");
284     submenu = new QMenu(main_menu_item->title());
285     ctx_menu_.addMenu(submenu);
286     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFSelected"));
287     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFNotSelected"));
288     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFAndSelected"));
289     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFOrSelected"));
290     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFAndNotSelected"));
291     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFOrNotSelected"));
292
293     main_menu_item = window()->findChild<QMenu *>("menuPrepareAFilter");
294     submenu = new QMenu(main_menu_item->title());
295     ctx_menu_.addMenu(submenu);
296     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFSelected"));
297     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFNotSelected"));
298     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFAndSelected"));
299     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFOrSelected"));
300     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFAndNotSelected"));
301     submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFOrNotSelected"));
302
303     const char *conv_menu_name = "menuConversationFilter";
304     main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
305     conv_menu_.setTitle(main_menu_item->title());
306     conv_menu_.setObjectName(conv_menu_name);
307     ctx_menu_.addMenu(&conv_menu_);
308
309     const char *colorize_menu_name = "menuColorizeConversation";
310     main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
311     colorize_menu_.setTitle(main_menu_item->title());
312     colorize_menu_.setObjectName(colorize_menu_name);
313     ctx_menu_.addMenu(&colorize_menu_);
314
315     main_menu_item = window()->findChild<QMenu *>("menuSCTP");
316     submenu = new QMenu(main_menu_item->title());
317     ctx_menu_.addMenu(submenu);
318     submenu->addAction(window()->findChild<QAction *>("actionSCTPAnalyseThisAssociation"));
319     submenu->addAction(window()->findChild<QAction *>("actionSCTPShowAllAssociations"));
320     submenu->addAction(window()->findChild<QAction *>("actionSCTPFilterThisAssociation"));
321
322     main_menu_item = window()->findChild<QMenu *>("menuFollow");
323     submenu = new QMenu(main_menu_item->title());
324     ctx_menu_.addMenu(submenu);
325     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
326     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
327     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSSLStream"));
328     submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
329
330     ctx_menu_.addSeparator();
331
332     main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
333     submenu = new QMenu(main_menu_item->title());
334     ctx_menu_.addMenu(submenu);
335
336     action = submenu->addAction(tr("Summary as Text"));
337     action->setData(copy_summary_text_);
338     connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
339     action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as CSV"));
340     action->setData(copy_summary_csv_);
341     connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
342     action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as YAML"));
343     action->setData(copy_summary_yaml_);
344     connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
345     submenu->addSeparator();
346
347     submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
348     submenu->addSeparator();
349
350     action = window()->findChild<QAction *>("actionContextCopyBytesHexTextDump");
351     submenu->addAction(action);
352     copy_actions_ << action;
353     action = window()->findChild<QAction *>("actionContextCopyBytesHexDump");
354     submenu->addAction(action);
355     copy_actions_ << action;
356     action = window()->findChild<QAction *>("actionContextCopyBytesPrintableText");
357     submenu->addAction(action);
358     copy_actions_ << action;
359     action = window()->findChild<QAction *>("actionContextCopyBytesHexStream");
360     submenu->addAction(action);
361     copy_actions_ << action;
362     action = window()->findChild<QAction *>("actionContextCopyBytesBinary");
363     submenu->addAction(action);
364     copy_actions_ << action;
365     action = window()->findChild<QAction *>("actionContextCopyBytesEscapedString");
366     submenu->addAction(action);
367     copy_actions_ << action;
368
369     ctx_menu_.addSeparator();
370     ctx_menu_.addMenu(&proto_prefs_menu_);
371     decode_as_ = window()->findChild<QAction *>("actionAnalyzeDecodeAs");
372     ctx_menu_.addAction(decode_as_);
373     // "Print" not ported intentionally
374     action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
375     ctx_menu_.addAction(action);
376
377     initHeaderContextMenu();
378
379     g_assert(gbl_cur_packet_list == NULL);
380     gbl_cur_packet_list = this;
381
382     bool style_inactive_selected = true;
383
384 #ifdef Q_OS_WIN // && Qt version >= 4.8.6
385     if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) {
386         // See if we're running Vista or 7 and we have a theme applied.
387         HMODULE uxtheme_lib = (HMODULE) ws_load_library("uxtheme.dll");
388
389         if (uxtheme_lib) {
390             typedef BOOL (WINAPI *IsAppThemedHandler)(void);
391             typedef BOOL (WINAPI *IsThemeActiveHandler)(void);
392
393             IsAppThemedHandler PIsAppThemed = (IsAppThemedHandler) GetProcAddress(uxtheme_lib, "IsAppThemed");
394             IsThemeActiveHandler PIsThemeActive = (IsThemeActiveHandler) GetProcAddress(uxtheme_lib, "IsThemeActive");
395             if (PIsAppThemed && PIsAppThemed() && PIsThemeActive && PIsThemeActive()) {
396                 style_inactive_selected = false;
397             }
398         }
399     }
400 #endif
401
402     if (style_inactive_selected) {
403         // XXX Style the protocol tree as well?
404         QPalette inactive_pal = palette();
405         inactive_pal.setCurrentColorGroup(QPalette::Inactive);
406         QColor border = QColor::fromRgb(ColorUtils::alphaBlend(
407                                                 inactive_pal.highlightedText(),
408                                                 inactive_pal.highlight(),
409                                                 0.25));
410         QColor shadow = QColor::fromRgb(ColorUtils::alphaBlend(
411                                                 inactive_pal.highlightedText(),
412                                                 inactive_pal.highlight(),
413                                                 0.07));
414         setStyleSheet(QString(
415                           "QTreeView::item:selected:first:!active {"
416                           "  border-left: 1px solid %1;"
417                           "}"
418                           "QTreeView::item:selected:last:!active {"
419                           "  border-right: 1px solid %1;"
420                           "}"
421                           "QTreeView::item:selected:!active {"
422                           "  border-top: 1px solid %1;"
423                           "  border-bottom: 1px solid %1;"
424                           "  color: %2;"
425                           // Try to approximate a subtle box shadow.
426                           "  background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1"
427                           "    stop: 0 %4, stop: 0.2 %3, stop: 0.8 %3, stop: 1 %4);"
428                           "}")
429                       .arg(border.name())
430                       .arg(inactive_pal.highlightedText().color().name())
431                       .arg(inactive_pal.highlight().color().name())
432                       .arg(shadow.name())
433                       );
434     }
435
436     connect(packet_list_model_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
437     connect(packet_list_model_, SIGNAL(itemHeightChanged(const QModelIndex&)), this, SLOT(updateRowHeights(const QModelIndex&)));
438     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePackets()));
439
440     header()->setContextMenuPolicy(Qt::CustomContextMenu);
441     connect(header(), SIGNAL(customContextMenuRequested(QPoint)),
442             this, SLOT(showHeaderMenu(QPoint)));
443     connect(header(), SIGNAL(sectionResized(int,int,int)),
444             this, SLOT(sectionResized(int,int,int)));
445     connect(header(), SIGNAL(sectionMoved(int,int,int)),
446             this, SLOT(sectionMoved(int,int,int)));
447
448     connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
449
450     connect(&proto_prefs_menu_, SIGNAL(showProtocolPreferences(QString)),
451             this, SIGNAL(showProtocolPreferences(QString)));
452     connect(&proto_prefs_menu_, SIGNAL(editProtocolPreference(preference*,pref_module*)),
453             this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
454 }
455
456 void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
457 {
458     QTreeView::drawRow(painter, option, index);
459
460     if (prefs.gui_qt_packet_list_separator) {
461         QRect rect = visualRect(index);
462
463         painter->setPen(QColor(Qt::white));
464         painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
465     }
466 }
467
468 void PacketList::setProtoTree (ProtoTree *proto_tree) {
469     proto_tree_ = proto_tree;
470
471     connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
472     connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)),
473             &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t)));
474 }
475
476 void PacketList::setByteViewTab (ByteViewTab *byte_view_tab) {
477     byte_view_tab_ = byte_view_tab;
478
479     connect(proto_tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
480             byte_view_tab_, SLOT(protoTreeItemChanged(QTreeWidgetItem*)));
481 }
482
483 PacketListModel *PacketList::packetListModel() const {
484     return packet_list_model_;
485 }
486
487 void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected) {
488     QTreeView::selectionChanged(selected, deselected);
489
490     if (!cap_file_) return;
491
492     if (selected.isEmpty()) {
493         cf_unselect_packet(cap_file_);
494     } else {
495         int row = selected.first().top();
496         cf_select_packet(cap_file_, row);
497     }
498
499     if (!in_history_ && cap_file_->current_frame) {
500         cur_history_++;
501         selection_history_.resize(cur_history_);
502         selection_history_.append(cap_file_->current_frame->num);
503     }
504     in_history_ = false;
505
506     related_packet_delegate_.clear();
507     if (proto_tree_) proto_tree_->clear();
508     if (byte_view_tab_) byte_view_tab_->clear();
509
510     emit packetSelectionChanged();
511
512     if (!cap_file_->edt) {
513         viewport()->update();
514         return;
515     }
516
517     if (proto_tree_ && cap_file_->edt->tree) {
518         packet_info *pi = &cap_file_->edt->pi;
519         related_packet_delegate_.setCurrentFrame(pi->num);
520         proto_tree_->fillProtocolTree(cap_file_->edt->tree);
521         conversation_t *conv = find_conversation(pi->num, &pi->src, &pi->dst, pi->ptype,
522                                                 pi->srcport, pi->destport, 0);
523         if (conv) {
524             related_packet_delegate_.setConversation(conv);
525         }
526         viewport()->update();
527     }
528
529     if (byte_view_tab_) {
530         GSList *src_le;
531         struct data_source *source;
532         char* source_name;
533
534         for (src_le = cap_file_->edt->pi.data_src; src_le != NULL; src_le = src_le->next) {
535             source = (struct data_source *)src_le->data;
536             source_name = get_data_source_name(source);
537             byte_view_tab_->addTab(source_name, get_data_source_tvb(source), cap_file_->edt->tree, proto_tree_, (packet_char_enc)cap_file_->current_frame->flags.encoding);
538             wmem_free(NULL, source_name);
539         }
540         byte_view_tab_->setCurrentIndex(0);
541     }
542
543     if (cap_file_->search_in_progress &&
544         (cap_file_->search_pos != 0 || (cap_file_->string && cap_file_->decode_data)))
545     {
546         match_data  mdata;
547         field_info *fi = NULL;
548
549         if (cap_file_->string && cap_file_->decode_data) {
550             // The tree where the target string matched one of the labels was discarded in
551             // match_protocol_tree() so we have to search again in the latest tree.
552             if (cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree, &mdata)) {
553                 fi = mdata.finfo;
554             }
555         } else {
556             // Find the finfo that corresponds to our byte.
557             fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos,
558                                               cap_file_->edt->tvb);
559         }
560
561         if (fi && proto_tree_) {
562             proto_tree_->selectField(fi);
563         }
564     } else if (!cap_file_->search_in_progress && proto_tree_) {
565         proto_tree_->restoreSelectedField();
566     }
567 }
568
569 void PacketList::contextMenuEvent(QContextMenuEvent *event)
570 {
571     const char *module_name = NULL;
572     if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
573         GPtrArray          *finfo_array = proto_all_finfos(cap_file_->edt->tree);
574
575         for (guint i = finfo_array->len - 1; i > 0 ; i --) {
576             field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
577             header_field_info *hfinfo =  fi->hfinfo;
578
579             if (!g_str_has_prefix(hfinfo->abbrev, "text") &&
580                 !g_str_has_prefix(hfinfo->abbrev, "_ws.expert") &&
581                 !g_str_has_prefix(hfinfo->abbrev, "_ws.malformed")) {
582
583                 if (hfinfo->parent == -1) {
584                     module_name = hfinfo->abbrev;
585                 } else {
586                     module_name = proto_registrar_get_abbrev(hfinfo->parent);
587                 }
588                 break;
589             }
590         }
591     }
592     proto_prefs_menu_.setModule(module_name);
593
594     foreach (QAction *action, copy_actions_) {
595         action->setData(QVariant());
596     }
597
598     decode_as_->setData(qVariantFromValue(true));
599     ctx_column_ = columnAt(event->x());
600
601     // Set menu sensitivity for the current column and set action data.
602     emit packetSelectionChanged();
603
604     ctx_menu_.exec(event->globalPos());
605     ctx_column_ = -1;
606     decode_as_->setData(QVariant());
607
608 }
609
610 // Auto scroll if:
611 // - We're not at the end
612 // - We are capturing
613 // - actionGoAutoScroll in the main UI is checked.
614 // - It's been more than tail_update_interval_ ms since we last scrolled
615 // - The last user-set vertical scrollbar position was at the end.
616
617 // Using a timer assumes that we can save CPU overhead by updating
618 // periodically. If that's not the case we can dispense with it and call
619 // scrollToBottom() from rowsInserted().
620 void PacketList::timerEvent(QTimerEvent *event)
621 {
622     if (event->timerId() == tail_timer_id_) {
623         if (rows_inserted_ && capture_in_progress_ && tail_at_end_) {
624             scrollToBottom();
625             rows_inserted_ = false;
626         }
627     } else if (event->timerId() == overlay_timer_id_) {
628         if (!capture_in_progress_) {
629             if (create_near_overlay_) drawNearOverlay();
630             if (create_far_overlay_) drawFarOverlay();
631         }
632     } else {
633         QTreeView::timerEvent(event);
634     }
635 }
636
637 void PacketList::paintEvent(QPaintEvent *event)
638 {
639     // XXX This is overkill, but there are quite a few events that
640     // require a new overlay, e.g. page up/down, scrolling, column
641     // resizing, etc.
642     create_near_overlay_ = true;
643     QTreeView::paintEvent(event);
644 }
645
646 void PacketList::mousePressEvent (QMouseEvent *event)
647 {
648     setAutoScroll(false);
649     QTreeView::mousePressEvent(event);
650     setAutoScroll(true);
651 }
652
653 void PacketList::resizeEvent(QResizeEvent *event)
654 {
655     create_near_overlay_ = true;
656     create_far_overlay_ = true;
657     QTreeView::resizeEvent(event);
658 }
659
660 void PacketList::setColumnVisibility()
661 {
662     set_column_visibility_ = true;
663     for (int i = 0; i < prefs.num_cols; i++) {
664         setColumnHidden(i, get_column_visible(i) ? false : true);
665     }
666     set_column_visibility_ = false;
667 }
668
669 int PacketList::sizeHintForColumn(int column) const
670 {
671     int size_hint = 0;
672
673     // This is a bit hacky but Qt does a fine job of column sizing and
674     // reimplementing QTreeView::sizeHintForColumn seems like a worse idea.
675     if (itemDelegateForColumn(column)) {
676         // In my (gcc) testing this results in correct behavior on Windows but adds extra space
677         // on OS X and Linux. We might want to add Q_OS_... #ifdefs accordingly.
678         size_hint = itemDelegateForColumn(column)->sizeHint(viewOptions(), QModelIndex()).width();
679     }
680     size_hint += QTreeView::sizeHintForColumn(column); // Decoration padding
681     return size_hint;
682 }
683
684 void PacketList::setRecentColumnWidth(int col)
685 {
686     int col_width = recent_get_column_width(col);
687
688     if (col_width < 1) {
689         int fmt = get_column_format(col);
690         const char *long_str = get_column_width_string(fmt, col);
691
692         QFontMetrics fm = QFontMetrics(wsApp->monospaceFont());
693         if (long_str) {
694             col_width = fm.width(long_str);
695         } else {
696             col_width = fm.width(MIN_COL_WIDTH_STR);
697         }
698
699         // Custom delegate padding
700         if (itemDelegateForColumn(col)) {
701             col_width += itemDelegateForColumn(col)->sizeHint(viewOptions(), QModelIndex()).width();
702         }
703     }
704
705     setColumnWidth(col, col_width);
706 }
707
708 void PacketList::initHeaderContextMenu()
709 {
710     header_ctx_menu_.clear();
711     header_actions_.clear();
712
713     // Leave these out for now since Qt doesn't have a "no sort" option
714     // and the user can sort by left-clicking on the header.
715 //    header_actions_[] = header_ctx_menu_.addAction(tr("Sort Ascending"));
716 //    header_actions_[] = header_ctx_menu_.addAction(tr("Sort Descending"));
717 //    header_actions_[] = header_ctx_menu_.addAction(tr("Do Not Sort"));
718 //    header_ctx_menu_.addSeparator();
719     header_actions_[caAlignLeft] = header_ctx_menu_.addAction(tr("Align Left"));
720     header_actions_[caAlignCenter] = header_ctx_menu_.addAction(tr("Align Center"));
721     header_actions_[caAlignRight] = header_ctx_menu_.addAction(tr("Align Right"));
722     header_ctx_menu_.addSeparator();
723     header_actions_[caColumnPreferences] = header_ctx_menu_.addAction(tr("Column Preferences" UTF8_HORIZONTAL_ELLIPSIS));
724     header_actions_[caEditColumn] = header_ctx_menu_.addAction(tr("Edit Column")); // XXX Create frame instead of dialog
725     header_actions_[caResizeToContents] = header_ctx_menu_.addAction(tr("Resize To Contents"));
726     header_actions_[caResolveNames] = header_ctx_menu_.addAction(tr("Resolve Names"));
727     header_ctx_menu_.addSeparator();
728 //    header_actions_[caDisplayedColumns] = header_ctx_menu_.addAction(tr("Displayed Columns"));
729     show_hide_separator_ = header_ctx_menu_.addSeparator();
730 //    header_actions_[caHideColumn] = header_ctx_menu_.addAction(tr("Hide This Column"));
731     header_actions_[caRemoveColumn] = header_ctx_menu_.addAction(tr("Remove This Column"));
732
733     foreach (ColumnActions ca, header_actions_.keys()) {
734         header_actions_[ca]->setData(qVariantFromValue(ca));
735         connect(header_actions_[ca], SIGNAL(triggered()), this, SLOT(headerMenuTriggered()));
736     }
737
738     checkable_actions_ = QList<ColumnActions>() << caAlignLeft << caAlignCenter << caAlignRight << caResolveNames;
739     foreach (ColumnActions ca, checkable_actions_) {
740         header_actions_[ca]->setCheckable(true);
741     }
742 }
743
744 void PacketList::drawCurrentPacket()
745 {
746     QModelIndex current_index = currentIndex();
747     setCurrentIndex(QModelIndex());
748     if (current_index.isValid()) {
749         setCurrentIndex(current_index);
750     }
751 }
752
753 // Redraw the packet list and detail. Called from many places.
754 // XXX We previously re-selected the packet here, but that seems to cause
755 // automatic scrolling problems.
756 void PacketList::redrawVisiblePackets() {
757     update();
758     header()->update();
759     drawCurrentPacket();
760 }
761
762 void PacketList::resetColumns()
763 {
764     packet_list_model_->resetColumns();
765 }
766
767 // Return true if we have a visible packet further along in the history.
768 bool PacketList::haveNextHistory(bool update_cur)
769 {
770     if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
771         return false;
772     }
773
774     for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
775         if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
776             if (update_cur) {
777                 cur_history_ = i;
778             }
779             return true;
780         }
781     }
782     return false;
783 }
784
785 // Return true if we have a visible packet back in the history.
786 bool PacketList::havePreviousHistory(bool update_cur)
787 {
788     if (selection_history_.size() < 1 || cur_history_ < 1) {
789         return false;
790     }
791
792     for (int i = cur_history_ - 1; i >= 0; i--) {
793         if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
794             if (update_cur) {
795                 cur_history_ = i;
796             }
797             return true;
798         }
799     }
800     return false;
801 }
802
803 // prefs.col_list has changed.
804 void PacketList::columnsChanged()
805 {
806     columns_changed_ = true;
807     if (!cap_file_) {
808         // Keep columns_changed_ = true until we load a capture file.
809         return;
810     }
811
812     prefs.num_cols = g_list_length(prefs.col_list);
813     col_cleanup(&cap_file_->cinfo);
814     build_column_format_array(&cap_file_->cinfo, prefs.num_cols, FALSE);
815     create_far_overlay_ = true;
816     resetColumns();
817     applyRecentColumnWidths();
818     setColumnVisibility();
819     columns_changed_ = false;
820 }
821
822 // Fields have changed, update custom columns
823 void PacketList::fieldsChanged(capture_file *cf)
824 {
825     prefs.num_cols = g_list_length(prefs.col_list);
826     col_cleanup(&cf->cinfo);
827     build_column_format_array(&cf->cinfo, prefs.num_cols, FALSE);
828     // call packet_list_model_->resetColumns() ?
829 }
830
831 // Column widths should
832 // - Load from recent when we load a new profile (including at starting up).
833 // - Reapply when changing columns.
834 // - Persist across freezes and thaws.
835 // - Persist across file closing and opening.
836 // - Save to recent when we save our profile (including shutting down).
837 // - Not be affected by the behavior of stretchLastSection.
838 void PacketList::applyRecentColumnWidths()
839 {
840     // Either we've just started up or a profile has changed. Read
841     // the recent settings, apply them, and save the header state.
842
843     int column_width = 0;
844
845     for (int col = 0; col < prefs.num_cols; col++) {
846         setRecentColumnWidth(col);
847         column_width += columnWidth(col);
848     }
849
850     if (column_width > width()) {
851         resize(column_width, height());
852     }
853
854     column_state_ = header()->saveState();
855 }
856
857 void PacketList::preferencesChanged()
858 {
859     // Related packet delegate
860     if (prefs.gui_packet_list_show_related) {
861         setItemDelegateForColumn(0, &related_packet_delegate_);
862     } else {
863         setItemDelegateForColumn(0, 0);
864     }
865
866     // Intelligent scroll bar (minimap)
867     if (prefs.gui_packet_list_show_minimap) {
868         if (overlay_timer_id_ == 0) {
869             overlay_timer_id_ = startTimer(overlay_update_interval_);
870         }
871     } else {
872         if (overlay_timer_id_ != 0) {
873             killTimer(overlay_timer_id_);
874             overlay_timer_id_ = 0;
875         }
876     }
877
878     // Elide mode.
879     // This sets the mode for the entire view. If we want to make this setting
880     // per-column we'll either have to generalize RelatedPacketDelegate so that
881     // we can set it for entire rows or create another delegate.
882     Qt::TextElideMode elide_mode = Qt::ElideRight;
883     switch (prefs.gui_packet_list_elide_mode) {
884     case ELIDE_LEFT:
885         elide_mode = Qt::ElideLeft;
886         break;
887     case ELIDE_MIDDLE:
888         elide_mode = Qt::ElideMiddle;
889         break;
890     case ELIDE_NONE:
891         elide_mode = Qt::ElideNone;
892         break;
893     default:
894         break;
895     }
896     setTextElideMode(elide_mode);
897 }
898
899 void PacketList::recolorPackets()
900 {
901     packet_list_model_->resetColorized();
902     redrawVisiblePackets();
903 }
904
905 /* Enable autoscroll timer. Note: must be called after the capture is started,
906  * otherwise the timer will not be executed. */
907 void PacketList::setVerticalAutoScroll(bool enabled)
908 {
909     tail_at_end_ = enabled;
910     if (enabled && capture_in_progress_) {
911         scrollToBottom();
912         if (tail_timer_id_ == 0) tail_timer_id_ = startTimer(tail_update_interval_);
913     } else if (tail_timer_id_ != 0) {
914         killTimer(tail_timer_id_);
915         tail_timer_id_ = 0;
916     }
917 }
918
919 // Called when we finish reading, reloading, rescanning, and retapping
920 // packets.
921 void PacketList::captureFileReadFinished()
922 {
923     packet_list_model_->flushVisibleRows();
924     packet_list_model_->dissectIdle(true);
925 }
926
927 void PacketList::freeze()
928 {
929     setUpdatesEnabled(false);
930     column_state_ = header()->saveState();
931     if (currentIndex().isValid()) {
932         frozen_row_ = currentIndex().row();
933     } else {
934         frozen_row_ = -1;
935     }
936     selectionModel()->clear();
937     setModel(NULL);
938     // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
939     // call selectionChanged.
940     related_packet_delegate_.clear();
941     proto_tree_->clear();
942     byte_view_tab_->clear();
943 }
944
945 void PacketList::thaw(bool restore_selection)
946 {
947     setUpdatesEnabled(true);
948     setModel(packet_list_model_);
949
950     // Resetting the model resets our column widths so we restore them here.
951     // We don't reapply the recent settings because the user could have
952     // resized the columns manually since they were initially loaded.
953     header()->restoreState(column_state_);
954
955     if (restore_selection && frozen_row_ > -1) {
956         // This updates our selection, which redissects the current packet,
957         // which is needed when we're called from MainWindow::layoutPanes.
958         setCurrentIndex(packet_list_model_->index(frozen_row_, 0));
959     }
960     frozen_row_ = -1;
961 }
962
963 void PacketList::clear() {
964     related_packet_delegate_.clear();
965     selectionModel()->clear();
966     packet_list_model_->clear();
967     proto_tree_->clear();
968     byte_view_tab_->clear();
969     selection_history_.clear();
970     cur_history_ = -1;
971     in_history_ = false;
972
973     QImage overlay;
974     overlay_sb_->setNearOverlayImage(overlay);
975     overlay_sb_->setMarkedPacketImage(overlay);
976     create_near_overlay_ = true;
977     create_far_overlay_ = true;
978 }
979
980 void PacketList::writeRecent(FILE *rf) {
981     gint col, width, col_fmt;
982     gchar xalign;
983
984     fprintf (rf, "%s:", RECENT_KEY_COL_WIDTH);
985     for (col = 0; col < prefs.num_cols; col++) {
986         if (col > 0) {
987             fprintf (rf, ",");
988         }
989         col_fmt = get_column_format(col);
990         if (col_fmt == COL_CUSTOM) {
991             fprintf (rf, " %%Cus:%s,", get_column_custom_fields(col));
992         } else {
993             fprintf (rf, " %s,", col_format_to_string(col_fmt));
994         }
995         width = recent_get_column_width (col);
996         xalign = recent_get_column_xalign (col);
997         fprintf (rf, " %d", width);
998         if (xalign != COLUMN_XALIGN_DEFAULT) {
999             fprintf (rf, ":%c", xalign);
1000         }
1001     }
1002     fprintf (rf, "\n");
1003
1004 }
1005
1006 bool PacketList::contextMenuActive()
1007 {
1008     return ctx_column_ >= 0 ? true : false;
1009 }
1010
1011 QString PacketList::getFilterFromRowAndColumn()
1012 {
1013     frame_data *fdata;
1014     QString filter;
1015     int row = currentIndex().row();
1016
1017     if (!cap_file_ || !packet_list_model_ || ctx_column_ < 0 || ctx_column_ >= cap_file_->cinfo.num_cols) return filter;
1018
1019     fdata = packet_list_model_->getRowFdata(row);
1020
1021     if (fdata != NULL) {
1022         epan_dissect_t edt;
1023
1024         if (!cf_read_record(cap_file_, fdata))
1025             return filter; /* error reading the record */
1026         /* proto tree, visible. We need a proto tree if there's custom columns */
1027         epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), FALSE);
1028         col_custom_prime_edt(&edt, &cap_file_->cinfo);
1029
1030         epan_dissect_run(&edt, cap_file_->cd_t, &cap_file_->phdr, frame_tvbuff_new_buffer(fdata, &cap_file_->buf), fdata, &cap_file_->cinfo);
1031         epan_dissect_fill_in_columns(&edt, TRUE, TRUE);
1032
1033         if ((cap_file_->cinfo.columns[ctx_column_].col_custom_occurrence) ||
1034             (strchr (cap_file_->cinfo.col_expr.col_expr_val[ctx_column_], ',') == NULL))
1035         {
1036             /* Only construct the filter when a single occurrence is displayed
1037              * otherwise we might end up with a filter like "ip.proto==1,6".
1038              *
1039              * Or do we want to be able to filter on multiple occurrences so that
1040              * the filter might be calculated as "ip.proto==1 && ip.proto==6"
1041              * instead?
1042              */
1043             if (strlen(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) != 0 &&
1044                 strlen(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]) != 0) {
1045                 if (cap_file_->cinfo.columns[ctx_column_].col_fmt == COL_CUSTOM) {
1046                     header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.columns[ctx_column_].col_custom_fields);
1047                     if (hfi && hfi->parent == -1) {
1048                         /* Protocol only */
1049                         filter.append(cap_file_->cinfo.col_expr.col_expr[ctx_column_]);
1050                     } else if (hfi && hfi->type == FT_STRING) {
1051                         /* Custom string, add quotes */
1052                         filter.append(QString("%1 == \"%2\"")
1053                                       .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_])
1054                                       .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]));
1055                     }
1056                 }
1057                 if (filter.isEmpty()) {
1058                     filter.append(QString("%1 == %2")
1059                                   .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_])
1060                                   .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]));
1061                 }
1062             }
1063         }
1064
1065         epan_dissect_cleanup(&edt);
1066     }
1067
1068     return filter;
1069 }
1070
1071 void PacketList::resetColorized()
1072 {
1073     packet_list_model_->resetColorized();
1074     update();
1075 }
1076
1077 QString PacketList::packetComment()
1078 {
1079     int row = currentIndex().row();
1080     const frame_data *fdata;
1081     char *pkt_comment;
1082
1083     if (!cap_file_ || !packet_list_model_) return NULL;
1084
1085     fdata = packet_list_model_->getRowFdata(row);
1086
1087     if (!fdata) return NULL;
1088
1089     pkt_comment = cf_get_comment(cap_file_, fdata);
1090
1091     return QString(pkt_comment);
1092
1093     /* XXX, g_free(pkt_comment) */
1094 }
1095
1096 void PacketList::setPacketComment(QString new_comment)
1097 {
1098     int row = currentIndex().row();
1099     frame_data *fdata;
1100     gchar *new_packet_comment;
1101
1102     if (!cap_file_ || !packet_list_model_) return;
1103
1104     fdata = packet_list_model_->getRowFdata(row);
1105
1106     if (!fdata) return;
1107
1108     /* Check if we are clearing the comment */
1109     if(new_comment.isEmpty()) {
1110         new_packet_comment = NULL;
1111     } else {
1112         new_packet_comment = qstring_strdup(new_comment);
1113     }
1114
1115     cf_set_user_packet_comment(cap_file_, fdata, new_packet_comment);
1116     g_free(new_packet_comment);
1117
1118     redrawVisiblePackets();
1119 }
1120
1121 QString PacketList::allPacketComments()
1122 {
1123     guint32 framenum;
1124     frame_data *fdata;
1125     QString buf_str;
1126
1127     if (!cap_file_) return buf_str;
1128
1129     for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1130         fdata = frame_data_sequence_find(cap_file_->frames, framenum);
1131
1132         char *pkt_comment = cf_get_comment(cap_file_, fdata);
1133
1134         if (pkt_comment) {
1135             buf_str.append(QString(tr("Frame %1: %2\n\n")).arg(framenum).arg(pkt_comment));
1136             g_free(pkt_comment);
1137         }
1138         if (buf_str.length() > max_comments_to_fetch_) {
1139             buf_str.append(QString(tr("[ Comment text exceeds %1. Stopping. ]"))
1140                            .arg(format_size(max_comments_to_fetch_, format_size_unit_bytes|format_size_prefix_si)));
1141             return buf_str;
1142         }
1143     }
1144     return buf_str;
1145 }
1146
1147 // Slots
1148
1149 void PacketList::setCaptureFile(capture_file *cf)
1150 {
1151     if (cf) {
1152         // We're opening. Restore our column widths.
1153         header()->restoreState(column_state_);
1154     }
1155     cap_file_ = cf;
1156     if (cap_file_ && columns_changed_) {
1157         columnsChanged();
1158     }
1159     packet_list_model_->setCaptureFile(cf);
1160     create_near_overlay_ = true;
1161     sortByColumn(-1, Qt::AscendingOrder);
1162 }
1163
1164 void PacketList::setMonospaceFont(const QFont &mono_font)
1165 {
1166     setFont(mono_font);
1167     header()->setFont(wsApp->font());
1168 }
1169
1170 void PacketList::goNextPacket(void) {
1171     if (QApplication::keyboardModifiers() | Qt::MetaModifier) {
1172         // Alt+toolbar
1173         goNextHistoryPacket();
1174         return;
1175     }
1176
1177     if (selectionModel()->hasSelection()) {
1178         setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier));
1179     } else {
1180         // First visible packet.
1181         setCurrentIndex(indexAt(viewport()->rect().topLeft()));
1182     }
1183 }
1184
1185 void PacketList::goPreviousPacket(void) {
1186     if (QApplication::keyboardModifiers() | Qt::MetaModifier) {
1187         // Alt+toolbar
1188         goPreviousHistoryPacket();
1189         return;
1190     }
1191
1192     if (selectionModel()->hasSelection()) {
1193         setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier));
1194     } else {
1195         // Last visible packet.
1196         QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
1197         if (last_idx.isValid()) {
1198             setCurrentIndex(last_idx);
1199         } else {
1200             goLastPacket();
1201         }
1202     }
1203 }
1204
1205 void PacketList::goFirstPacket(void) {
1206     if (packet_list_model_->rowCount() < 1) return;
1207     setCurrentIndex(packet_list_model_->index(0, 0));
1208     scrollTo(currentIndex());
1209 }
1210
1211 void PacketList::goLastPacket(void) {
1212     if (packet_list_model_->rowCount() < 1) return;
1213     setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0));
1214     scrollTo(currentIndex());
1215 }
1216
1217 // XXX We can jump to the wrong packet if a display filter is applied
1218 void PacketList::goToPacket(int packet) {
1219     if (!cf_goto_frame(cap_file_, packet)) return;
1220     int row = packet_list_model_->packetNumberToRow(packet);
1221     if (row >= 0) {
1222         setCurrentIndex(packet_list_model_->index(row, 0));
1223     }
1224 }
1225
1226 void PacketList::goToPacket(int packet, int hf_id)
1227 {
1228     goToPacket(packet);
1229     proto_tree_->goToField(hf_id);
1230 }
1231
1232 void PacketList::goNextHistoryPacket()
1233 {
1234     if (haveNextHistory(true)) {
1235         in_history_ = true;
1236         goToPacket(selection_history_.at(cur_history_));
1237         in_history_ = false;
1238     }
1239 }
1240
1241 void PacketList::goPreviousHistoryPacket()
1242 {
1243     if (havePreviousHistory(true)) {
1244         in_history_ = true;
1245         goToPacket(selection_history_.at(cur_history_));
1246         in_history_ = false;
1247     }
1248 }
1249
1250 void PacketList::markFrame()
1251 {
1252     if (!cap_file_ || !packet_list_model_) return;
1253
1254     packet_list_model_->toggleFrameMark(currentIndex());
1255     create_far_overlay_ = true;
1256     packets_bar_update();
1257 }
1258
1259 void PacketList::markAllDisplayedFrames(bool set)
1260 {
1261     if (!cap_file_ || !packet_list_model_) return;
1262
1263     packet_list_model_->setDisplayedFrameMark(set);
1264     create_far_overlay_ = true;
1265     packets_bar_update();
1266 }
1267
1268 void PacketList::ignoreFrame()
1269 {
1270     if (!cap_file_ || !packet_list_model_) return;
1271
1272     packet_list_model_->toggleFrameIgnore(currentIndex());
1273     create_far_overlay_ = true;
1274     int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1275     setUpdatesEnabled(false);
1276     emit packetDissectionChanged();
1277     setUpdatesEnabled(true);
1278     verticalScrollBar()->setValue(sb_val);
1279 }
1280
1281 void PacketList::ignoreAllDisplayedFrames(bool set)
1282 {
1283     if (!cap_file_ || !packet_list_model_) return;
1284
1285     packet_list_model_->setDisplayedFrameIgnore(set);
1286     create_far_overlay_ = true;
1287     emit packetDissectionChanged();
1288 }
1289
1290 void PacketList::setTimeReference()
1291 {
1292     if (!cap_file_ || !packet_list_model_) return;
1293     packet_list_model_->toggleFrameRefTime(currentIndex());
1294     create_far_overlay_ = true;
1295 }
1296
1297 void PacketList::unsetAllTimeReferences()
1298 {
1299     if (!cap_file_ || !packet_list_model_) return;
1300     packet_list_model_->unsetAllFrameRefTime();
1301     create_far_overlay_ = true;
1302 }
1303
1304 void PacketList::applyTimeShift()
1305 {
1306     packet_list_model_->applyTimeShift();
1307     redrawVisiblePackets();
1308     // XXX emit packetDissectionChanged(); ?
1309 }
1310
1311 void PacketList::showHeaderMenu(QPoint pos)
1312 {
1313     header_ctx_column_ = header()->logicalIndexAt(pos);
1314     foreach (ColumnActions ca, checkable_actions_) {
1315         header_actions_[ca]->setChecked(false);
1316     }
1317
1318     switch (recent_get_column_xalign(header_ctx_column_)) {
1319     case COLUMN_XALIGN_LEFT:
1320         header_actions_[caAlignLeft]->setChecked(true);
1321         break;
1322     case COLUMN_XALIGN_CENTER:
1323         header_actions_[caAlignCenter]->setChecked(true);
1324         break;
1325     case COLUMN_XALIGN_RIGHT:
1326         header_actions_[caAlignRight]->setChecked(true);
1327         break;
1328     default:
1329         break;
1330     }
1331
1332     bool can_resolve = resolve_column(header_ctx_column_, cap_file_);
1333     header_actions_[caResolveNames]->setChecked(can_resolve && get_column_resolved(header_ctx_column_));
1334     header_actions_[caResolveNames]->setEnabled(can_resolve);
1335
1336     header_actions_[caRemoveColumn]->setEnabled(header_ctx_column_ >= 0 && header()->count() > 2);
1337
1338     foreach (QAction *action, show_hide_actions_) {
1339         header_ctx_menu_.removeAction(action);
1340         delete action;
1341     }
1342     show_hide_actions_.clear();
1343     for (int i = 0; i < prefs.num_cols; i++) {
1344         QAction *action = new QAction(get_column_title(i), &header_ctx_menu_);
1345         action->setCheckable(true);
1346         action->setChecked(get_column_visible(i));
1347         action->setData(qVariantFromValue(i));
1348         connect(action, SIGNAL(triggered()), this, SLOT(columnVisibilityTriggered()));
1349         header_ctx_menu_.insertAction(show_hide_separator_, action);
1350         show_hide_actions_ << action;
1351     }
1352
1353     header_ctx_menu_.popup(header()->viewport()->mapToGlobal(pos));
1354 }
1355
1356 void PacketList::headerMenuTriggered()
1357 {
1358     QAction *ha = qobject_cast<QAction*>(sender());
1359     if (!ha) return;
1360
1361     bool checked = ha->isChecked();
1362     bool redraw = false;
1363
1364     switch(ha->data().value<ColumnActions>()) {
1365     case caAlignLeft:
1366         recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_LEFT : COLUMN_XALIGN_DEFAULT);
1367         break;
1368     case caAlignCenter:
1369         recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_CENTER : COLUMN_XALIGN_DEFAULT);
1370         break;
1371     case caAlignRight:
1372         recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_RIGHT : COLUMN_XALIGN_DEFAULT);
1373         break;
1374     case caColumnPreferences:
1375         emit showColumnPreferences(PreferencesDialog::ppColumn);
1376         break;
1377     case caEditColumn:
1378         emit editColumn(header_ctx_column_);
1379         break;
1380     case caResolveNames:
1381         set_column_resolved(header_ctx_column_, checked);
1382         packet_list_model_->resetColumns();
1383         if (!prefs.gui_use_pref_save) {
1384             prefs_main_write();
1385         }
1386         redraw = true;
1387         break;
1388     case caResizeToContents:
1389         resizeColumnToContents(header_ctx_column_);
1390         break;
1391     case caDisplayedColumns:
1392         // No-op
1393         break;
1394     case caHideColumn:
1395         set_column_visible(header_ctx_column_, FALSE);
1396         hideColumn(header_ctx_column_);
1397         if (!prefs.gui_use_pref_save) {
1398             prefs_main_write();
1399         }
1400         break;
1401     case caRemoveColumn:
1402     {
1403         if (header()->count() > 2) {
1404             column_prefs_remove_nth(header_ctx_column_);
1405             columnsChanged();
1406             if (!prefs.gui_use_pref_save) {
1407                 prefs_main_write();
1408             }
1409         }
1410         break;
1411     }
1412     default:
1413         break;
1414     }
1415
1416     if (redraw) {
1417         redrawVisiblePackets();
1418     } else {
1419         update();
1420     }
1421 }
1422
1423 void PacketList::columnVisibilityTriggered()
1424 {
1425     QAction *ha = qobject_cast<QAction*>(sender());
1426     if (!ha) return;
1427
1428     int col = ha->data().toInt();
1429     set_column_visible(col, ha->isChecked());
1430     setColumnVisibility();
1431     if (ha->isChecked()) {
1432         setRecentColumnWidth(col);
1433     }
1434     if (!prefs.gui_use_pref_save) {
1435         prefs_main_write();
1436     }
1437 }
1438
1439 void PacketList::sectionResized(int col, int, int new_width)
1440 {
1441     if (isVisible() && !columns_changed_ && !set_column_visibility_ && new_width > 0) {
1442         // Column 1 gets an invalid value (32 on OS X) when we're not yet
1443         // visible.
1444         //
1445         // Don't set column width when columns changed or setting column
1446         // visibility because we may get a sectionReized() from QTreeView
1447         // with values from a old columns layout.
1448         //
1449         // Don't set column width when hiding a column.
1450
1451         recent_set_column_width(col, new_width);
1452     }
1453 }
1454
1455 // The user moved a column. Make sure prefs.col_list, the column format
1456 // array, and the header's visual and logical indices all agree.
1457 // gtk/packet_list.c:column_dnd_changed_cb
1458 void PacketList::sectionMoved(int, int, int)
1459 {
1460     GList *new_col_list = NULL;
1461     QList<int> saved_sizes;
1462
1463     // Build a new column list based on the header's logical order.
1464     for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
1465         int log_idx = header()->logicalIndex(vis_idx);
1466         saved_sizes << header()->sectionSize(log_idx);
1467
1468         void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
1469         if (!pref_data) continue;
1470
1471         new_col_list = g_list_append(new_col_list, pref_data);
1472     }
1473
1474     // Clear and rebuild our (and the header's) model. There doesn't appear
1475     // to be another way to reset the logical index.
1476     freeze();
1477
1478     g_list_free(prefs.col_list);
1479     prefs.col_list = new_col_list;
1480
1481     thaw(true);
1482
1483     for (int i = 0; i < saved_sizes.length(); i++) {
1484         if (saved_sizes[i] < 1) continue;
1485         header()->resizeSection(i, saved_sizes[i]);
1486     }
1487
1488     if (!prefs.gui_use_pref_save) {
1489         prefs_main_write();
1490     }
1491
1492     wsApp->emitAppSignal(WiresharkApplication::ColumnsChanged);
1493 }
1494
1495 void PacketList::updateRowHeights(const QModelIndex &ih_index)
1496 {
1497     QStyleOptionViewItem option = viewOptions();
1498     int max_height = 0;
1499
1500     // One of our columns increased the maximum row height. Find out which one.
1501     for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1502         QSize size_hint = itemDelegate()->sizeHint(option, packet_list_model_->index(ih_index.row(), col));
1503         max_height = qMax(max_height, size_hint.height());
1504     }
1505
1506     if (max_height > 0) {
1507         packet_list_model_->setMaximiumRowHeight(max_height);
1508     }
1509 }
1510
1511 void PacketList::copySummary()
1512 {
1513     if (!currentIndex().isValid()) return;
1514
1515     QAction *ca = qobject_cast<QAction*>(sender());
1516     if (!ca) return;
1517
1518     bool ok = false;
1519     int copy_type = ca->data().toInt(&ok);
1520     if (!ok) return;
1521
1522     QStringList col_parts;
1523     int row = currentIndex().row();
1524     for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1525         if (get_column_visible(col)) {
1526             col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
1527         }
1528     }
1529
1530     QString copy_text;
1531     switch (copy_type) {
1532     case copy_summary_csv_:
1533         copy_text = "\"";
1534         copy_text += col_parts.join("\",\"");
1535         copy_text += "\"";
1536         break;
1537     case copy_summary_yaml_:
1538         copy_text = "----\n";
1539         copy_text += QString("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename);
1540         copy_text += "- ";
1541         copy_text += col_parts.join("\n- ");
1542         copy_text += "\n";
1543         break;
1544     case copy_summary_text_:
1545     default:
1546         copy_text = col_parts.join("\t");
1547     }
1548     wsApp->clipboard()->setText(copy_text);
1549 }
1550
1551 // We need to tell when the user has scrolled the packet list, either to
1552 // the end or anywhere other than the end.
1553 void PacketList::vScrollBarActionTriggered(int)
1554 {
1555     // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
1556     // past the end.
1557     tail_at_end_ = (verticalScrollBar()->sliderPosition() >= verticalScrollBar()->maximum());
1558
1559     if (capture_in_progress_ && prefs.capture_auto_scroll) {
1560         emit packetListScrolled(tail_at_end_);
1561     }
1562 }
1563
1564 // Goal: Overlay the packet list scroll bar with the colors of all of the
1565 // packets.
1566 // Try 1: Average packet colors in each scroll bar raster line. This has
1567 // two problems: It's easy to wash out colors and we dissect every packet.
1568 // Try 2: Color across a 5000 or 10000 packet window. We still end up washing
1569 // out colors.
1570 // Try 3: One packet per vertical scroll bar pixel. This seems to work best
1571 // but has the smallest window.
1572 // Try 4: Use a multiple of the scroll bar heigh and scale the image down
1573 // using Qt::SmoothTransformation. This gives us more packets per raster
1574 // line.
1575
1576 // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
1577 // of 9 washed out colors a little too much.
1578 //const int height_multiplier_ = 7;
1579 void PacketList::drawNearOverlay()
1580 {
1581     if (create_near_overlay_) {
1582         create_near_overlay_ = false;
1583     }
1584
1585     if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
1586
1587     if (!prefs.gui_packet_list_show_minimap) return;
1588
1589     qreal dp_ratio = 1.0;
1590 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
1591     dp_ratio = overlay_sb_->devicePixelRatio();
1592 #endif
1593     int o_height = overlay_sb_->height() * dp_ratio;
1594     int o_rows = qMin(packet_list_model_->rowCount(), o_height);
1595     int o_width = (wsApp->fontMetrics().height() * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
1596     int selected_pos = -1;
1597
1598     if (recent.packet_list_colorize && o_rows > 0) {
1599         QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
1600
1601         QPainter painter(&overlay);
1602
1603         overlay.fill(Qt::transparent);
1604
1605         int cur_line = 0;
1606         int start = 0;
1607
1608         if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
1609             start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows);
1610         }
1611         int end = start + o_rows;
1612         for (int row = start; row < end; row++) {
1613             packet_list_model_->ensureRowColorized(row);
1614
1615             frame_data *fdata = packet_list_model_->getRowFdata(row);
1616             const color_t *bgcolor = NULL;
1617             if (fdata->color_filter) {
1618                 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
1619                 bgcolor = &color_filter->bg_color;
1620             }
1621
1622             int next_line = (row - start) * o_height / o_rows;
1623             if (bgcolor) {
1624                 QColor color(ColorUtils::fromColorT(bgcolor));
1625                 painter.fillRect(0, cur_line, o_width, next_line - cur_line, color);
1626             }
1627             cur_line = next_line;
1628         }
1629
1630         // If the selected packet is in the overlay set selected_pos
1631         // accordingly. Otherwise, pin it to either the top or bottom.
1632         if (selectionModel()->hasSelection()) {
1633             int sel_row = selectionModel()->currentIndex().row();
1634             if (sel_row < start) {
1635                 selected_pos = 0;
1636             } else if (sel_row >= end) {
1637                 selected_pos = overlay.height() - 1;
1638             } else {
1639                 selected_pos = (sel_row - start) * o_height / o_rows;
1640             }
1641         }
1642
1643         overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, selected_pos);
1644     } else {
1645         QImage overlay;
1646         overlay_sb_->setNearOverlayImage(overlay);
1647     }
1648 }
1649
1650 void PacketList::drawFarOverlay()
1651 {
1652     if (create_far_overlay_) {
1653         create_far_overlay_ = false;
1654     }
1655
1656     if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
1657
1658     if (!prefs.gui_packet_list_show_minimap) return;
1659
1660     QSize groove_size = overlay_sb_->grooveRect().size();
1661 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
1662     qreal dp_ratio = 1.0;
1663     dp_ratio = overlay_sb_->devicePixelRatio();
1664     groove_size *= dp_ratio;
1665 #endif
1666     int o_width = groove_size.width();
1667     int o_height = groove_size.height();
1668     int pl_rows = packet_list_model_->rowCount();
1669     QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
1670     bool have_marked_image = false;
1671
1672     // If only there were references from popular culture about getting into
1673     // some sort of groove.
1674     if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
1675
1676         QPainter painter(&overlay);
1677
1678         // Draw text-colored tick marks on a transparent background.
1679         // Hopefully no themes use the text color for the groove color.
1680         overlay.fill(Qt::transparent);
1681
1682         QColor tick_color = palette().text().color();
1683         tick_color.setAlphaF(0.3);
1684         painter.setPen(tick_color);
1685
1686         for (int row = 0; row < pl_rows; row++) {
1687
1688             frame_data *fdata = packet_list_model_->getRowFdata(row);
1689             if (fdata->flags.marked || fdata->flags.ref_time || fdata->flags.ignored) {
1690                 int new_line = row * o_height / pl_rows;
1691                 int tick_width = o_width / 3;
1692                 // Marked or ignored: left side, time refs: right side.
1693                 // XXX Draw ignored ticks in the middle?
1694                 int x1 = fdata->flags.ref_time ? o_width - tick_width : 1;
1695                 int x2 = fdata->flags.ref_time ? o_width - 1 : tick_width;
1696
1697                 painter.drawLine(x1, new_line, x2, new_line);
1698                 have_marked_image = true;
1699             }
1700         }
1701
1702         if (have_marked_image) {
1703             overlay_sb_->setMarkedPacketImage(overlay);
1704             return;
1705         }
1706     }
1707
1708     if (!have_marked_image) {
1709         QImage null_overlay;
1710         overlay_sb_->setMarkedPacketImage(null_overlay);
1711     }
1712 }
1713
1714 void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
1715 {
1716     QTreeView::rowsInserted(parent, start, end);
1717     rows_inserted_ = true;
1718 }
1719
1720 /*
1721  * Editor modelines
1722  *
1723  * Local Variables:
1724  * c-basic-offset: 4
1725  * tab-width: 8
1726  * indent-tabs-mode: nil
1727  * End:
1728  *
1729  * ex: set shiftwidth=4 tabstop=8 expandtab:
1730  * :indentSize=4:tabSize=8:noTabs=true:
1731  */