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