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