3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
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.
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.
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.
22 #include "packet_list.h"
30 #include <epan/epan.h>
31 #include <epan/epan_dissect.h>
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>
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>
49 #include "wsutil/str_util.h"
51 #include <epan/color_filters.h>
52 #include "frame_tvbuff.h"
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"
61 #include <QActionGroup>
63 #include <QContextMenuEvent>
64 #include <QtCore/qmath.h>
65 #include <QElapsedTimer>
66 #include <QFontMetrics>
67 #include <QHeaderView>
68 #include <QMessageBox>
74 #include <QTimerEvent>
75 #include <QTreeWidget>
78 #include "wsutil/file_util.h"
83 // - Fix "apply as filter" behavior.
84 // - Add colorize conversation.
85 // - Use a timer to trigger automatic scrolling.
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;
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.
97 packet_list_append(column_info *, frame_data *fdata)
99 if (!gbl_cur_packet_list)
102 /* fdata should be filled with the stuff we need
103 * strings are built at display time.
107 visible_pos = gbl_cur_packet_list->packetListModel()->appendPacket(fdata);
111 // Copied from ui/gtk/packet_list.c
112 void packet_list_resize_column(gint col)
114 if (!gbl_cur_packet_list) return;
115 gbl_cur_packet_list->resizeColumnToContents(col);
119 packet_list_select_first_row(void)
121 if (!gbl_cur_packet_list)
123 gbl_cur_packet_list->goFirstPacket();
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.
132 packet_list_select_row_from_data(frame_data *fdata_needle)
134 gbl_cur_packet_list->packetListModel()->flushVisibleRows();
135 int row = gbl_cur_packet_list->packetListModel()->visibleIndexOf(fdata_needle);
137 gbl_cur_packet_list->setCurrentIndex(gbl_cur_packet_list->packetListModel()->index(row,0));
145 packet_list_check_end(void)
147 return FALSE; // GTK+ only.
151 packet_list_clear(void)
153 if (gbl_cur_packet_list) {
154 gbl_cur_packet_list->clear();
159 packet_list_enable_color(gboolean)
161 if (gbl_cur_packet_list) {
162 gbl_cur_packet_list->recolorPackets();
167 packet_list_freeze(void)
169 if (gbl_cur_packet_list) {
170 gbl_cur_packet_list->freeze();
175 packet_list_thaw(void)
177 if (gbl_cur_packet_list) {
178 gbl_cur_packet_list->thaw();
181 packets_bar_update();
185 packet_list_recreate_visible_rows(void)
187 if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) {
188 gbl_cur_packet_list->packetListModel()->recreateVisibleRows();
193 packet_list_get_row_data(gint row)
195 if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) {
196 return gbl_cur_packet_list->packetListModel()->getRowFdata(row);
201 // Called from cf_continue_tail and cf_finish_tail when auto_scroll_live
204 packet_list_moveto_end(void)
206 // gbl_cur_packet_list->scrollToBottom();
209 /* Redraw the packet list *and* currently-selected detail */
211 packet_list_queue_draw(void)
213 if (gbl_cur_packet_list)
214 gbl_cur_packet_list->redrawVisiblePackets();
218 packet_list_recent_write_all(FILE *rf) {
219 if (!gbl_cur_packet_list)
222 gbl_cur_packet_list->writeRecent(rf);
225 #define MIN_COL_WIDTH_STR "MMMMMM"
227 Q_DECLARE_METATYPE(PacketList::ColumnActions)
229 enum copy_summary_type {
235 PacketList::PacketList(QWidget *parent) :
238 byte_view_tab_(NULL),
242 overlay_timer_id_(0),
243 create_near_overlay_(true),
244 create_far_overlay_(true),
245 capture_in_progress_(false),
247 rows_inserted_(false),
248 columns_changed_(false),
249 set_column_visibility_(false),
254 QMenu *main_menu_item, *submenu;
257 setItemsExpandable(false);
258 setRootIsDecorated(false);
259 setSortingEnabled(true);
260 setUniformRowHeights(true);
261 setAccessibleName("Packet list");
263 overlay_sb_ = new OverlayScrollBar(Qt::Vertical, this);
264 setVerticalScrollBar(overlay_sb_);
266 packet_list_model_ = new PacketListModel(this, cap_file_);
267 setModel(packet_list_model_);
268 sortByColumn(-1, Qt::AscendingOrder);
270 // XXX We might want to reimplement setParent() and fill in the context
272 ctx_menu_.addAction(window()->findChild<QAction *>("actionEditMarkPacket"));
273 ctx_menu_.addAction(window()->findChild<QAction *>("actionEditIgnorePacket"));
274 ctx_menu_.addAction(window()->findChild<QAction *>("actionEditSetTimeReference"));
275 ctx_menu_.addAction(window()->findChild<QAction *>("actionEditTimeShift"));
276 ctx_menu_.addAction(window()->findChild<QAction *>("actionEditPacketComment"));
278 ctx_menu_.addSeparator();
280 ctx_menu_.addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
281 ctx_menu_.addSeparator();
283 main_menu_item = window()->findChild<QMenu *>("menuApplyAsFilter");
284 submenu = new QMenu(main_menu_item->title());
285 ctx_menu_.addMenu(submenu);
286 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFSelected"));
287 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFNotSelected"));
288 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFAndSelected"));
289 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFOrSelected"));
290 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFAndNotSelected"));
291 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeAAFOrNotSelected"));
293 main_menu_item = window()->findChild<QMenu *>("menuPrepareAFilter");
294 submenu = new QMenu(main_menu_item->title());
295 ctx_menu_.addMenu(submenu);
296 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFSelected"));
297 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFNotSelected"));
298 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFAndSelected"));
299 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFOrSelected"));
300 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFAndNotSelected"));
301 submenu->addAction(window()->findChild<QAction *>("actionAnalyzePAFOrNotSelected"));
303 const char *conv_menu_name = "menuConversationFilter";
304 main_menu_item = window()->findChild<QMenu *>(conv_menu_name);
305 conv_menu_.setTitle(main_menu_item->title());
306 conv_menu_.setObjectName(conv_menu_name);
307 ctx_menu_.addMenu(&conv_menu_);
309 const char *colorize_menu_name = "menuColorizeConversation";
310 main_menu_item = window()->findChild<QMenu *>(colorize_menu_name);
311 colorize_menu_.setTitle(main_menu_item->title());
312 colorize_menu_.setObjectName(colorize_menu_name);
313 ctx_menu_.addMenu(&colorize_menu_);
315 main_menu_item = window()->findChild<QMenu *>("menuSCTP");
316 submenu = new QMenu(main_menu_item->title());
317 ctx_menu_.addMenu(submenu);
318 submenu->addAction(window()->findChild<QAction *>("actionSCTPAnalyseThisAssociation"));
319 submenu->addAction(window()->findChild<QAction *>("actionSCTPShowAllAssociations"));
320 submenu->addAction(window()->findChild<QAction *>("actionSCTPFilterThisAssociation"));
322 main_menu_item = window()->findChild<QMenu *>("menuFollow");
323 submenu = new QMenu(main_menu_item->title());
324 ctx_menu_.addMenu(submenu);
325 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
326 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
327 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSSLStream"));
328 submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
330 ctx_menu_.addSeparator();
332 main_menu_item = window()->findChild<QMenu *>("menuEditCopy");
333 submenu = new QMenu(main_menu_item->title());
334 ctx_menu_.addMenu(submenu);
336 action = submenu->addAction(tr("Summary as Text"));
337 action->setData(copy_summary_text_);
338 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
339 action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as CSV"));
340 action->setData(copy_summary_csv_);
341 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
342 action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as YAML"));
343 action->setData(copy_summary_yaml_);
344 connect(action, SIGNAL(triggered()), this, SLOT(copySummary()));
345 submenu->addSeparator();
347 submenu->addAction(window()->findChild<QAction *>("actionEditCopyAsFilter"));
348 submenu->addSeparator();
350 action = window()->findChild<QAction *>("actionContextCopyBytesHexTextDump");
351 submenu->addAction(action);
352 copy_actions_ << action;
353 action = window()->findChild<QAction *>("actionContextCopyBytesHexDump");
354 submenu->addAction(action);
355 copy_actions_ << action;
356 action = window()->findChild<QAction *>("actionContextCopyBytesPrintableText");
357 submenu->addAction(action);
358 copy_actions_ << action;
359 action = window()->findChild<QAction *>("actionContextCopyBytesHexStream");
360 submenu->addAction(action);
361 copy_actions_ << action;
362 action = window()->findChild<QAction *>("actionContextCopyBytesBinary");
363 submenu->addAction(action);
364 copy_actions_ << action;
365 action = window()->findChild<QAction *>("actionContextCopyBytesEscapedString");
366 submenu->addAction(action);
367 copy_actions_ << action;
369 ctx_menu_.addSeparator();
370 ctx_menu_.addMenu(&proto_prefs_menu_);
371 decode_as_ = window()->findChild<QAction *>("actionAnalyzeDecodeAs");
372 ctx_menu_.addAction(decode_as_);
373 // "Print" not ported intentionally
374 action = window()->findChild<QAction *>("actionViewShowPacketInNewWindow");
375 ctx_menu_.addAction(action);
377 initHeaderContextMenu();
379 g_assert(gbl_cur_packet_list == NULL);
380 gbl_cur_packet_list = this;
382 bool style_inactive_selected = true;
384 #ifdef Q_OS_WIN // && Qt version >= 4.8.6
385 if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) {
386 // See if we're running Vista or 7 and we have a theme applied.
387 HMODULE uxtheme_lib = (HMODULE) ws_load_library("uxtheme.dll");
390 typedef BOOL (WINAPI *IsAppThemedHandler)(void);
391 typedef BOOL (WINAPI *IsThemeActiveHandler)(void);
393 IsAppThemedHandler PIsAppThemed = (IsAppThemedHandler) GetProcAddress(uxtheme_lib, "IsAppThemed");
394 IsThemeActiveHandler PIsThemeActive = (IsThemeActiveHandler) GetProcAddress(uxtheme_lib, "IsThemeActive");
395 if (PIsAppThemed && PIsAppThemed() && PIsThemeActive && PIsThemeActive()) {
396 style_inactive_selected = false;
402 if (style_inactive_selected) {
403 // XXX Style the protocol tree as well?
404 QPalette inactive_pal = palette();
405 inactive_pal.setCurrentColorGroup(QPalette::Inactive);
406 QColor border = QColor::fromRgb(ColorUtils::alphaBlend(
407 inactive_pal.highlightedText(),
408 inactive_pal.highlight(),
410 QColor shadow = QColor::fromRgb(ColorUtils::alphaBlend(
411 inactive_pal.highlightedText(),
412 inactive_pal.highlight(),
414 setStyleSheet(QString(
415 "QTreeView::item:selected:first:!active {"
416 " border-left: 1px solid %1;"
418 "QTreeView::item:selected:last:!active {"
419 " border-right: 1px solid %1;"
421 "QTreeView::item:selected:!active {"
422 " border-top: 1px solid %1;"
423 " border-bottom: 1px solid %1;"
425 // Try to approximate a subtle box shadow.
426 " background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1"
427 " stop: 0 %4, stop: 0.2 %3, stop: 0.8 %3, stop: 1 %4);"
430 .arg(inactive_pal.highlightedText().color().name())
431 .arg(inactive_pal.highlight().color().name())
436 connect(packet_list_model_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
437 connect(packet_list_model_, SIGNAL(itemHeightChanged(const QModelIndex&)), this, SLOT(updateRowHeights(const QModelIndex&)));
438 connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(redrawVisiblePackets()));
440 header()->setContextMenuPolicy(Qt::CustomContextMenu);
441 connect(header(), SIGNAL(customContextMenuRequested(QPoint)),
442 this, SLOT(showHeaderMenu(QPoint)));
443 connect(header(), SIGNAL(sectionResized(int,int,int)),
444 this, SLOT(sectionResized(int,int,int)));
445 connect(header(), SIGNAL(sectionMoved(int,int,int)),
446 this, SLOT(sectionMoved(int,int,int)));
448 connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
450 connect(&proto_prefs_menu_, SIGNAL(showProtocolPreferences(QString)),
451 this, SIGNAL(showProtocolPreferences(QString)));
452 connect(&proto_prefs_menu_, SIGNAL(editProtocolPreference(preference*,pref_module*)),
453 this, SIGNAL(editProtocolPreference(preference*,pref_module*)));
456 void PacketList::drawRow (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
458 QTreeView::drawRow(painter, option, index);
460 if (prefs.gui_qt_packet_list_separator) {
461 QRect rect = visualRect(index);
463 painter->setPen(QColor(Qt::white));
464 painter->drawLine(0, rect.y() + rect.height() - 1, width(), rect.y() + rect.height() - 1);
468 void PacketList::setProtoTree (ProtoTree *proto_tree) {
469 proto_tree_ = proto_tree;
471 connect(proto_tree_, SIGNAL(goToPacket(int)), this, SLOT(goToPacket(int)));
472 connect(proto_tree_, SIGNAL(relatedFrame(int,ft_framenum_type_t)),
473 &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t)));
476 void PacketList::setByteViewTab (ByteViewTab *byte_view_tab) {
477 byte_view_tab_ = byte_view_tab;
479 connect(proto_tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
480 byte_view_tab_, SLOT(protoTreeItemChanged(QTreeWidgetItem*)));
483 PacketListModel *PacketList::packetListModel() const {
484 return packet_list_model_;
487 void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected) {
488 QTreeView::selectionChanged(selected, deselected);
490 if (!cap_file_) return;
492 if (selected.isEmpty()) {
493 cf_unselect_packet(cap_file_);
495 int row = selected.first().top();
496 cf_select_packet(cap_file_, row);
499 if (!in_history_ && cap_file_->current_frame) {
501 selection_history_.resize(cur_history_);
502 selection_history_.append(cap_file_->current_frame->num);
506 related_packet_delegate_.clear();
507 if (proto_tree_) proto_tree_->clear();
508 if (byte_view_tab_) byte_view_tab_->clear();
510 emit packetSelectionChanged();
512 if (!cap_file_->edt) {
513 viewport()->update();
517 if (proto_tree_ && cap_file_->edt->tree) {
518 packet_info *pi = &cap_file_->edt->pi;
519 related_packet_delegate_.setCurrentFrame(pi->num);
520 proto_tree_->fillProtocolTree(cap_file_->edt->tree);
521 conversation_t *conv = find_conversation(pi->num, &pi->src, &pi->dst, pi->ptype,
522 pi->srcport, pi->destport, 0);
524 related_packet_delegate_.setConversation(conv);
526 viewport()->update();
529 if (byte_view_tab_) {
531 struct data_source *source;
534 for (src_le = cap_file_->edt->pi.data_src; src_le != NULL; src_le = src_le->next) {
535 source = (struct data_source *)src_le->data;
536 source_name = get_data_source_name(source);
537 byte_view_tab_->addTab(source_name, get_data_source_tvb(source), cap_file_->edt->tree, proto_tree_, (packet_char_enc)cap_file_->current_frame->flags.encoding);
538 wmem_free(NULL, source_name);
540 byte_view_tab_->setCurrentIndex(0);
543 if (cap_file_->search_in_progress &&
544 (cap_file_->search_pos != 0 || (cap_file_->string && cap_file_->decode_data)))
547 field_info *fi = NULL;
549 if (cap_file_->string && cap_file_->decode_data) {
550 // The tree where the target string matched one of the labels was discarded in
551 // match_protocol_tree() so we have to search again in the latest tree.
552 if (cf_find_string_protocol_tree(cap_file_, cap_file_->edt->tree, &mdata)) {
556 // Find the finfo that corresponds to our byte.
557 fi = proto_find_field_from_offset(cap_file_->edt->tree, cap_file_->search_pos,
558 cap_file_->edt->tvb);
561 if (fi && proto_tree_) {
562 proto_tree_->selectField(fi);
564 } else if (!cap_file_->search_in_progress && proto_tree_) {
565 proto_tree_->restoreSelectedField();
569 void PacketList::contextMenuEvent(QContextMenuEvent *event)
571 const char *module_name = NULL;
572 if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
573 GPtrArray *finfo_array = proto_all_finfos(cap_file_->edt->tree);
575 for (guint i = finfo_array->len - 1; i > 0 ; i --) {
576 field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
577 header_field_info *hfinfo = fi->hfinfo;
579 if (!g_str_has_prefix(hfinfo->abbrev, "text") &&
580 !g_str_has_prefix(hfinfo->abbrev, "_ws.expert") &&
581 !g_str_has_prefix(hfinfo->abbrev, "_ws.malformed")) {
583 if (hfinfo->parent == -1) {
584 module_name = hfinfo->abbrev;
586 module_name = proto_registrar_get_abbrev(hfinfo->parent);
592 proto_prefs_menu_.setModule(module_name);
594 foreach (QAction *action, copy_actions_) {
595 action->setData(QVariant());
598 decode_as_->setData(qVariantFromValue(true));
599 ctx_column_ = columnAt(event->x());
601 // Set menu sensitivity for the current column and set action data.
602 emit packetSelectionChanged();
604 ctx_menu_.exec(event->globalPos());
606 decode_as_->setData(QVariant());
611 // - We're not at the end
612 // - We are capturing
613 // - actionGoAutoScroll in the main UI is checked.
614 // - It's been more than tail_update_interval_ ms since we last scrolled
615 // - The last user-set vertical scrollbar position was at the end.
617 // Using a timer assumes that we can save CPU overhead by updating
618 // periodically. If that's not the case we can dispense with it and call
619 // scrollToBottom() from rowsInserted().
620 void PacketList::timerEvent(QTimerEvent *event)
622 if (event->timerId() == tail_timer_id_) {
623 if (rows_inserted_ && capture_in_progress_ && tail_at_end_) {
625 rows_inserted_ = false;
627 } else if (event->timerId() == overlay_timer_id_) {
628 if (!capture_in_progress_) {
629 if (create_near_overlay_) drawNearOverlay();
630 if (create_far_overlay_) drawFarOverlay();
633 QTreeView::timerEvent(event);
637 void PacketList::paintEvent(QPaintEvent *event)
639 // XXX This is overkill, but there are quite a few events that
640 // require a new overlay, e.g. page up/down, scrolling, column
642 create_near_overlay_ = true;
643 QTreeView::paintEvent(event);
646 void PacketList::mousePressEvent (QMouseEvent *event)
648 setAutoScroll(false);
649 QTreeView::mousePressEvent(event);
653 void PacketList::resizeEvent(QResizeEvent *event)
655 create_near_overlay_ = true;
656 create_far_overlay_ = true;
657 QTreeView::resizeEvent(event);
660 void PacketList::setColumnVisibility()
662 set_column_visibility_ = true;
663 for (int i = 0; i < prefs.num_cols; i++) {
664 setColumnHidden(i, get_column_visible(i) ? false : true);
666 set_column_visibility_ = false;
669 int PacketList::sizeHintForColumn(int column) const
673 // This is a bit hacky but Qt does a fine job of column sizing and
674 // reimplementing QTreeView::sizeHintForColumn seems like a worse idea.
675 if (itemDelegateForColumn(column)) {
676 // In my (gcc) testing this results in correct behavior on Windows but adds extra space
677 // on OS X and Linux. We might want to add Q_OS_... #ifdefs accordingly.
678 size_hint = itemDelegateForColumn(column)->sizeHint(viewOptions(), QModelIndex()).width();
680 size_hint += QTreeView::sizeHintForColumn(column); // Decoration padding
684 void PacketList::setRecentColumnWidth(int col)
686 int col_width = recent_get_column_width(col);
689 int fmt = get_column_format(col);
690 const char *long_str = get_column_width_string(fmt, col);
692 QFontMetrics fm = QFontMetrics(wsApp->monospaceFont());
694 col_width = fm.width(long_str);
696 col_width = fm.width(MIN_COL_WIDTH_STR);
699 // Custom delegate padding
700 if (itemDelegateForColumn(col)) {
701 col_width += itemDelegateForColumn(col)->sizeHint(viewOptions(), QModelIndex()).width();
705 setColumnWidth(col, col_width);
708 void PacketList::initHeaderContextMenu()
710 header_ctx_menu_.clear();
711 header_actions_.clear();
713 // Leave these out for now since Qt doesn't have a "no sort" option
714 // and the user can sort by left-clicking on the header.
715 // header_actions_[] = header_ctx_menu_.addAction(tr("Sort Ascending"));
716 // header_actions_[] = header_ctx_menu_.addAction(tr("Sort Descending"));
717 // header_actions_[] = header_ctx_menu_.addAction(tr("Do Not Sort"));
718 // header_ctx_menu_.addSeparator();
719 header_actions_[caAlignLeft] = header_ctx_menu_.addAction(tr("Align Left"));
720 header_actions_[caAlignCenter] = header_ctx_menu_.addAction(tr("Align Center"));
721 header_actions_[caAlignRight] = header_ctx_menu_.addAction(tr("Align Right"));
722 header_ctx_menu_.addSeparator();
723 header_actions_[caColumnPreferences] = header_ctx_menu_.addAction(tr("Column Preferences" UTF8_HORIZONTAL_ELLIPSIS));
724 header_actions_[caEditColumn] = header_ctx_menu_.addAction(tr("Edit Column")); // XXX Create frame instead of dialog
725 header_actions_[caResizeToContents] = header_ctx_menu_.addAction(tr("Resize To Contents"));
726 header_actions_[caResolveNames] = header_ctx_menu_.addAction(tr("Resolve Names"));
727 header_ctx_menu_.addSeparator();
728 // header_actions_[caDisplayedColumns] = header_ctx_menu_.addAction(tr("Displayed Columns"));
729 show_hide_separator_ = header_ctx_menu_.addSeparator();
730 // header_actions_[caHideColumn] = header_ctx_menu_.addAction(tr("Hide This Column"));
731 header_actions_[caRemoveColumn] = header_ctx_menu_.addAction(tr("Remove This Column"));
733 foreach (ColumnActions ca, header_actions_.keys()) {
734 header_actions_[ca]->setData(qVariantFromValue(ca));
735 connect(header_actions_[ca], SIGNAL(triggered()), this, SLOT(headerMenuTriggered()));
738 checkable_actions_ = QList<ColumnActions>() << caAlignLeft << caAlignCenter << caAlignRight << caResolveNames;
739 foreach (ColumnActions ca, checkable_actions_) {
740 header_actions_[ca]->setCheckable(true);
744 void PacketList::drawCurrentPacket()
746 QModelIndex current_index = currentIndex();
747 setCurrentIndex(QModelIndex());
748 if (current_index.isValid()) {
749 setCurrentIndex(current_index);
753 // Redraw the packet list and detail. Called from many places.
754 // XXX We previously re-selected the packet here, but that seems to cause
755 // automatic scrolling problems.
756 void PacketList::redrawVisiblePackets() {
762 void PacketList::resetColumns()
764 packet_list_model_->resetColumns();
767 // Return true if we have a visible packet further along in the history.
768 bool PacketList::haveNextHistory(bool update_cur)
770 if (selection_history_.size() < 1 || cur_history_ >= selection_history_.size() - 1) {
774 for (int i = cur_history_ + 1; i < selection_history_.size(); i++) {
775 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
785 // Return true if we have a visible packet back in the history.
786 bool PacketList::havePreviousHistory(bool update_cur)
788 if (selection_history_.size() < 1 || cur_history_ < 1) {
792 for (int i = cur_history_ - 1; i >= 0; i--) {
793 if (packet_list_model_->packetNumberToRow(selection_history_.at(i)) >= 0) {
803 // prefs.col_list has changed.
804 void PacketList::columnsChanged()
806 columns_changed_ = true;
808 // Keep columns_changed_ = true until we load a capture file.
812 prefs.num_cols = g_list_length(prefs.col_list);
813 col_cleanup(&cap_file_->cinfo);
814 build_column_format_array(&cap_file_->cinfo, prefs.num_cols, FALSE);
815 create_far_overlay_ = true;
817 applyRecentColumnWidths();
818 setColumnVisibility();
819 columns_changed_ = false;
822 // Fields have changed, update custom columns
823 void PacketList::fieldsChanged(capture_file *cf)
825 prefs.num_cols = g_list_length(prefs.col_list);
826 col_cleanup(&cf->cinfo);
827 build_column_format_array(&cf->cinfo, prefs.num_cols, FALSE);
828 // call packet_list_model_->resetColumns() ?
831 // Column widths should
832 // - Load from recent when we load a new profile (including at starting up).
833 // - Reapply when changing columns.
834 // - Persist across freezes and thaws.
835 // - Persist across file closing and opening.
836 // - Save to recent when we save our profile (including shutting down).
837 // - Not be affected by the behavior of stretchLastSection.
838 void PacketList::applyRecentColumnWidths()
840 // Either we've just started up or a profile has changed. Read
841 // the recent settings, apply them, and save the header state.
843 int column_width = 0;
845 for (int col = 0; col < prefs.num_cols; col++) {
846 setRecentColumnWidth(col);
847 column_width += columnWidth(col);
850 if (column_width > width()) {
851 resize(column_width, height());
854 column_state_ = header()->saveState();
857 void PacketList::preferencesChanged()
859 // Related packet delegate
860 if (prefs.gui_packet_list_show_related) {
861 setItemDelegateForColumn(0, &related_packet_delegate_);
863 setItemDelegateForColumn(0, 0);
866 // Intelligent scroll bar (minimap)
867 if (prefs.gui_packet_list_show_minimap) {
868 if (overlay_timer_id_ == 0) {
869 overlay_timer_id_ = startTimer(overlay_update_interval_);
872 if (overlay_timer_id_ != 0) {
873 killTimer(overlay_timer_id_);
874 overlay_timer_id_ = 0;
879 // This sets the mode for the entire view. If we want to make this setting
880 // per-column we'll either have to generalize RelatedPacketDelegate so that
881 // we can set it for entire rows or create another delegate.
882 Qt::TextElideMode elide_mode = Qt::ElideRight;
883 switch (prefs.gui_packet_list_elide_mode) {
885 elide_mode = Qt::ElideLeft;
888 elide_mode = Qt::ElideMiddle;
891 elide_mode = Qt::ElideNone;
896 setTextElideMode(elide_mode);
899 void PacketList::recolorPackets()
901 packet_list_model_->resetColorized();
902 redrawVisiblePackets();
905 /* Enable autoscroll timer. Note: must be called after the capture is started,
906 * otherwise the timer will not be executed. */
907 void PacketList::setVerticalAutoScroll(bool enabled)
909 tail_at_end_ = enabled;
910 if (enabled && capture_in_progress_) {
912 if (tail_timer_id_ == 0) tail_timer_id_ = startTimer(tail_update_interval_);
913 } else if (tail_timer_id_ != 0) {
914 killTimer(tail_timer_id_);
919 // Called when we finish reading, reloading, rescanning, and retapping
921 void PacketList::captureFileReadFinished()
923 packet_list_model_->flushVisibleRows();
924 packet_list_model_->dissectIdle(true);
927 void PacketList::freeze()
929 setUpdatesEnabled(false);
930 column_state_ = header()->saveState();
931 if (currentIndex().isValid()) {
932 frozen_row_ = currentIndex().row();
936 selectionModel()->clear();
938 // It looks like GTK+ sends a cursor-changed signal at this point but Qt doesn't
939 // call selectionChanged.
940 related_packet_delegate_.clear();
941 proto_tree_->clear();
942 byte_view_tab_->clear();
945 void PacketList::thaw(bool restore_selection)
947 setUpdatesEnabled(true);
948 setModel(packet_list_model_);
950 // Resetting the model resets our column widths so we restore them here.
951 // We don't reapply the recent settings because the user could have
952 // resized the columns manually since they were initially loaded.
953 header()->restoreState(column_state_);
955 if (restore_selection && frozen_row_ > -1) {
956 // This updates our selection, which redissects the current packet,
957 // which is needed when we're called from MainWindow::layoutPanes.
958 setCurrentIndex(packet_list_model_->index(frozen_row_, 0));
963 void PacketList::clear() {
964 related_packet_delegate_.clear();
965 selectionModel()->clear();
966 packet_list_model_->clear();
967 proto_tree_->clear();
968 byte_view_tab_->clear();
969 selection_history_.clear();
974 overlay_sb_->setNearOverlayImage(overlay);
975 overlay_sb_->setMarkedPacketImage(overlay);
976 create_near_overlay_ = true;
977 create_far_overlay_ = true;
980 void PacketList::writeRecent(FILE *rf) {
981 gint col, width, col_fmt;
984 fprintf (rf, "%s:", RECENT_KEY_COL_WIDTH);
985 for (col = 0; col < prefs.num_cols; col++) {
989 col_fmt = get_column_format(col);
990 if (col_fmt == COL_CUSTOM) {
991 fprintf (rf, " %%Cus:%s,", get_column_custom_fields(col));
993 fprintf (rf, " %s,", col_format_to_string(col_fmt));
995 width = recent_get_column_width (col);
996 xalign = recent_get_column_xalign (col);
997 fprintf (rf, " %d", width);
998 if (xalign != COLUMN_XALIGN_DEFAULT) {
999 fprintf (rf, ":%c", xalign);
1006 bool PacketList::contextMenuActive()
1008 return ctx_column_ >= 0 ? true : false;
1011 QString PacketList::getFilterFromRowAndColumn()
1015 int row = currentIndex().row();
1017 if (!cap_file_ || !packet_list_model_ || ctx_column_ < 0 || ctx_column_ >= cap_file_->cinfo.num_cols) return filter;
1019 fdata = packet_list_model_->getRowFdata(row);
1021 if (fdata != NULL) {
1024 if (!cf_read_record(cap_file_, fdata))
1025 return filter; /* error reading the record */
1026 /* proto tree, visible. We need a proto tree if there's custom columns */
1027 epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), FALSE);
1028 col_custom_prime_edt(&edt, &cap_file_->cinfo);
1030 epan_dissect_run(&edt, cap_file_->cd_t, &cap_file_->phdr, frame_tvbuff_new_buffer(fdata, &cap_file_->buf), fdata, &cap_file_->cinfo);
1031 epan_dissect_fill_in_columns(&edt, TRUE, TRUE);
1033 if ((cap_file_->cinfo.columns[ctx_column_].col_custom_occurrence) ||
1034 (strchr (cap_file_->cinfo.col_expr.col_expr_val[ctx_column_], ',') == NULL))
1036 /* Only construct the filter when a single occurrence is displayed
1037 * otherwise we might end up with a filter like "ip.proto==1,6".
1039 * Or do we want to be able to filter on multiple occurrences so that
1040 * the filter might be calculated as "ip.proto==1 && ip.proto==6"
1043 if (strlen(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) != 0 &&
1044 strlen(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]) != 0) {
1045 if (cap_file_->cinfo.columns[ctx_column_].col_fmt == COL_CUSTOM) {
1046 header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.columns[ctx_column_].col_custom_fields);
1047 if (hfi && hfi->parent == -1) {
1049 filter.append(cap_file_->cinfo.col_expr.col_expr[ctx_column_]);
1050 } else if (hfi && hfi->type == FT_STRING) {
1051 /* Custom string, add quotes */
1052 filter.append(QString("%1 == \"%2\"")
1053 .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_])
1054 .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]));
1057 if (filter.isEmpty()) {
1058 filter.append(QString("%1 == %2")
1059 .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_])
1060 .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]));
1065 epan_dissect_cleanup(&edt);
1071 void PacketList::resetColorized()
1073 packet_list_model_->resetColorized();
1077 QString PacketList::packetComment()
1079 int row = currentIndex().row();
1080 const frame_data *fdata;
1083 if (!cap_file_ || !packet_list_model_) return NULL;
1085 fdata = packet_list_model_->getRowFdata(row);
1087 if (!fdata) return NULL;
1089 pkt_comment = cf_get_comment(cap_file_, fdata);
1091 return QString(pkt_comment);
1093 /* XXX, g_free(pkt_comment) */
1096 void PacketList::setPacketComment(QString new_comment)
1098 int row = currentIndex().row();
1100 gchar *new_packet_comment;
1102 if (!cap_file_ || !packet_list_model_) return;
1104 fdata = packet_list_model_->getRowFdata(row);
1108 /* Check if we are clearing the comment */
1109 if(new_comment.isEmpty()) {
1110 new_packet_comment = NULL;
1112 new_packet_comment = qstring_strdup(new_comment);
1115 cf_set_user_packet_comment(cap_file_, fdata, new_packet_comment);
1116 g_free(new_packet_comment);
1118 redrawVisiblePackets();
1121 QString PacketList::allPacketComments()
1127 if (!cap_file_) return buf_str;
1129 for (framenum = 1; framenum <= cap_file_->count ; framenum++) {
1130 fdata = frame_data_sequence_find(cap_file_->frames, framenum);
1132 char *pkt_comment = cf_get_comment(cap_file_, fdata);
1135 buf_str.append(QString(tr("Frame %1: %2\n\n")).arg(framenum).arg(pkt_comment));
1136 g_free(pkt_comment);
1138 if (buf_str.length() > max_comments_to_fetch_) {
1139 buf_str.append(QString(tr("[ Comment text exceeds %1. Stopping. ]"))
1140 .arg(format_size(max_comments_to_fetch_, format_size_unit_bytes|format_size_prefix_si)));
1149 void PacketList::setCaptureFile(capture_file *cf)
1152 // We're opening. Restore our column widths.
1153 header()->restoreState(column_state_);
1156 if (cap_file_ && columns_changed_) {
1159 packet_list_model_->setCaptureFile(cf);
1160 create_near_overlay_ = true;
1161 sortByColumn(-1, Qt::AscendingOrder);
1164 void PacketList::setMonospaceFont(const QFont &mono_font)
1167 header()->setFont(wsApp->font());
1170 void PacketList::goNextPacket(void) {
1171 if (QApplication::keyboardModifiers() | Qt::MetaModifier) {
1173 goNextHistoryPacket();
1177 if (selectionModel()->hasSelection()) {
1178 setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier));
1180 // First visible packet.
1181 setCurrentIndex(indexAt(viewport()->rect().topLeft()));
1185 void PacketList::goPreviousPacket(void) {
1186 if (QApplication::keyboardModifiers() | Qt::MetaModifier) {
1188 goPreviousHistoryPacket();
1192 if (selectionModel()->hasSelection()) {
1193 setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier));
1195 // Last visible packet.
1196 QModelIndex last_idx = indexAt(viewport()->rect().bottomLeft());
1197 if (last_idx.isValid()) {
1198 setCurrentIndex(last_idx);
1205 void PacketList::goFirstPacket(void) {
1206 if (packet_list_model_->rowCount() < 1) return;
1207 setCurrentIndex(packet_list_model_->index(0, 0));
1208 scrollTo(currentIndex());
1211 void PacketList::goLastPacket(void) {
1212 if (packet_list_model_->rowCount() < 1) return;
1213 setCurrentIndex(packet_list_model_->index(packet_list_model_->rowCount() - 1, 0));
1214 scrollTo(currentIndex());
1217 // XXX We can jump to the wrong packet if a display filter is applied
1218 void PacketList::goToPacket(int packet) {
1219 if (!cf_goto_frame(cap_file_, packet)) return;
1220 int row = packet_list_model_->packetNumberToRow(packet);
1222 setCurrentIndex(packet_list_model_->index(row, 0));
1226 void PacketList::goToPacket(int packet, int hf_id)
1229 proto_tree_->goToField(hf_id);
1232 void PacketList::goNextHistoryPacket()
1234 if (haveNextHistory(true)) {
1236 goToPacket(selection_history_.at(cur_history_));
1237 in_history_ = false;
1241 void PacketList::goPreviousHistoryPacket()
1243 if (havePreviousHistory(true)) {
1245 goToPacket(selection_history_.at(cur_history_));
1246 in_history_ = false;
1250 void PacketList::markFrame()
1252 if (!cap_file_ || !packet_list_model_) return;
1254 packet_list_model_->toggleFrameMark(currentIndex());
1255 create_far_overlay_ = true;
1256 packets_bar_update();
1259 void PacketList::markAllDisplayedFrames(bool set)
1261 if (!cap_file_ || !packet_list_model_) return;
1263 packet_list_model_->setDisplayedFrameMark(set);
1264 create_far_overlay_ = true;
1265 packets_bar_update();
1268 void PacketList::ignoreFrame()
1270 if (!cap_file_ || !packet_list_model_) return;
1272 packet_list_model_->toggleFrameIgnore(currentIndex());
1273 create_far_overlay_ = true;
1274 int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position?
1275 setUpdatesEnabled(false);
1276 emit packetDissectionChanged();
1277 setUpdatesEnabled(true);
1278 verticalScrollBar()->setValue(sb_val);
1281 void PacketList::ignoreAllDisplayedFrames(bool set)
1283 if (!cap_file_ || !packet_list_model_) return;
1285 packet_list_model_->setDisplayedFrameIgnore(set);
1286 create_far_overlay_ = true;
1287 emit packetDissectionChanged();
1290 void PacketList::setTimeReference()
1292 if (!cap_file_ || !packet_list_model_) return;
1293 packet_list_model_->toggleFrameRefTime(currentIndex());
1294 create_far_overlay_ = true;
1297 void PacketList::unsetAllTimeReferences()
1299 if (!cap_file_ || !packet_list_model_) return;
1300 packet_list_model_->unsetAllFrameRefTime();
1301 create_far_overlay_ = true;
1304 void PacketList::applyTimeShift()
1306 packet_list_model_->applyTimeShift();
1307 redrawVisiblePackets();
1308 // XXX emit packetDissectionChanged(); ?
1311 void PacketList::showHeaderMenu(QPoint pos)
1313 header_ctx_column_ = header()->logicalIndexAt(pos);
1314 foreach (ColumnActions ca, checkable_actions_) {
1315 header_actions_[ca]->setChecked(false);
1318 switch (recent_get_column_xalign(header_ctx_column_)) {
1319 case COLUMN_XALIGN_LEFT:
1320 header_actions_[caAlignLeft]->setChecked(true);
1322 case COLUMN_XALIGN_CENTER:
1323 header_actions_[caAlignCenter]->setChecked(true);
1325 case COLUMN_XALIGN_RIGHT:
1326 header_actions_[caAlignRight]->setChecked(true);
1332 bool can_resolve = resolve_column(header_ctx_column_, cap_file_);
1333 header_actions_[caResolveNames]->setChecked(can_resolve && get_column_resolved(header_ctx_column_));
1334 header_actions_[caResolveNames]->setEnabled(can_resolve);
1336 header_actions_[caRemoveColumn]->setEnabled(header_ctx_column_ >= 0 && header()->count() > 2);
1338 foreach (QAction *action, show_hide_actions_) {
1339 header_ctx_menu_.removeAction(action);
1342 show_hide_actions_.clear();
1343 for (int i = 0; i < prefs.num_cols; i++) {
1344 QAction *action = new QAction(get_column_title(i), &header_ctx_menu_);
1345 action->setCheckable(true);
1346 action->setChecked(get_column_visible(i));
1347 action->setData(qVariantFromValue(i));
1348 connect(action, SIGNAL(triggered()), this, SLOT(columnVisibilityTriggered()));
1349 header_ctx_menu_.insertAction(show_hide_separator_, action);
1350 show_hide_actions_ << action;
1353 header_ctx_menu_.popup(header()->viewport()->mapToGlobal(pos));
1356 void PacketList::headerMenuTriggered()
1358 QAction *ha = qobject_cast<QAction*>(sender());
1361 bool checked = ha->isChecked();
1362 bool redraw = false;
1364 switch(ha->data().value<ColumnActions>()) {
1366 recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_LEFT : COLUMN_XALIGN_DEFAULT);
1369 recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_CENTER : COLUMN_XALIGN_DEFAULT);
1372 recent_set_column_xalign(header_ctx_column_, checked ? COLUMN_XALIGN_RIGHT : COLUMN_XALIGN_DEFAULT);
1374 case caColumnPreferences:
1375 emit showColumnPreferences(PreferencesDialog::ppColumn);
1378 emit editColumn(header_ctx_column_);
1380 case caResolveNames:
1381 set_column_resolved(header_ctx_column_, checked);
1382 packet_list_model_->resetColumns();
1383 if (!prefs.gui_use_pref_save) {
1388 case caResizeToContents:
1389 resizeColumnToContents(header_ctx_column_);
1391 case caDisplayedColumns:
1395 set_column_visible(header_ctx_column_, FALSE);
1396 hideColumn(header_ctx_column_);
1397 if (!prefs.gui_use_pref_save) {
1401 case caRemoveColumn:
1403 if (header()->count() > 2) {
1404 column_prefs_remove_nth(header_ctx_column_);
1406 if (!prefs.gui_use_pref_save) {
1417 redrawVisiblePackets();
1423 void PacketList::columnVisibilityTriggered()
1425 QAction *ha = qobject_cast<QAction*>(sender());
1428 int col = ha->data().toInt();
1429 set_column_visible(col, ha->isChecked());
1430 setColumnVisibility();
1431 if (ha->isChecked()) {
1432 setRecentColumnWidth(col);
1434 if (!prefs.gui_use_pref_save) {
1439 void PacketList::sectionResized(int col, int, int new_width)
1441 if (isVisible() && !columns_changed_ && !set_column_visibility_ && new_width > 0) {
1442 // Column 1 gets an invalid value (32 on OS X) when we're not yet
1445 // Don't set column width when columns changed or setting column
1446 // visibility because we may get a sectionReized() from QTreeView
1447 // with values from a old columns layout.
1449 // Don't set column width when hiding a column.
1451 recent_set_column_width(col, new_width);
1455 // The user moved a column. Make sure prefs.col_list, the column format
1456 // array, and the header's visual and logical indices all agree.
1457 // gtk/packet_list.c:column_dnd_changed_cb
1458 void PacketList::sectionMoved(int, int, int)
1460 GList *new_col_list = NULL;
1461 QList<int> saved_sizes;
1463 // Build a new column list based on the header's logical order.
1464 for (int vis_idx = 0; vis_idx < header()->count(); vis_idx++) {
1465 int log_idx = header()->logicalIndex(vis_idx);
1466 saved_sizes << header()->sectionSize(log_idx);
1468 void *pref_data = g_list_nth_data(prefs.col_list, log_idx);
1469 if (!pref_data) continue;
1471 new_col_list = g_list_append(new_col_list, pref_data);
1474 // Clear and rebuild our (and the header's) model. There doesn't appear
1475 // to be another way to reset the logical index.
1478 g_list_free(prefs.col_list);
1479 prefs.col_list = new_col_list;
1483 for (int i = 0; i < saved_sizes.length(); i++) {
1484 if (saved_sizes[i] < 1) continue;
1485 header()->resizeSection(i, saved_sizes[i]);
1488 if (!prefs.gui_use_pref_save) {
1492 wsApp->emitAppSignal(WiresharkApplication::ColumnsChanged);
1495 void PacketList::updateRowHeights(const QModelIndex &ih_index)
1497 QStyleOptionViewItem option = viewOptions();
1500 // One of our columns increased the maximum row height. Find out which one.
1501 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1502 QSize size_hint = itemDelegate()->sizeHint(option, packet_list_model_->index(ih_index.row(), col));
1503 max_height = qMax(max_height, size_hint.height());
1506 if (max_height > 0) {
1507 packet_list_model_->setMaximiumRowHeight(max_height);
1511 void PacketList::copySummary()
1513 if (!currentIndex().isValid()) return;
1515 QAction *ca = qobject_cast<QAction*>(sender());
1519 int copy_type = ca->data().toInt(&ok);
1522 QStringList col_parts;
1523 int row = currentIndex().row();
1524 for (int col = 0; col < packet_list_model_->columnCount(); col++) {
1525 if (get_column_visible(col)) {
1526 col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString();
1531 switch (copy_type) {
1532 case copy_summary_csv_:
1534 copy_text += col_parts.join("\",\"");
1537 case copy_summary_yaml_:
1538 copy_text = "----\n";
1539 copy_text += QString("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename);
1541 copy_text += col_parts.join("\n- ");
1544 case copy_summary_text_:
1546 copy_text = col_parts.join("\t");
1548 wsApp->clipboard()->setText(copy_text);
1551 // We need to tell when the user has scrolled the packet list, either to
1552 // the end or anywhere other than the end.
1553 void PacketList::vScrollBarActionTriggered(int)
1555 // If we're scrolling with a mouse wheel or trackpad sliderPosition can end up
1557 tail_at_end_ = (verticalScrollBar()->sliderPosition() >= verticalScrollBar()->maximum());
1559 if (capture_in_progress_ && prefs.capture_auto_scroll) {
1560 emit packetListScrolled(tail_at_end_);
1564 // Goal: Overlay the packet list scroll bar with the colors of all of the
1566 // Try 1: Average packet colors in each scroll bar raster line. This has
1567 // two problems: It's easy to wash out colors and we dissect every packet.
1568 // Try 2: Color across a 5000 or 10000 packet window. We still end up washing
1570 // Try 3: One packet per vertical scroll bar pixel. This seems to work best
1571 // but has the smallest window.
1572 // Try 4: Use a multiple of the scroll bar heigh and scale the image down
1573 // using Qt::SmoothTransformation. This gives us more packets per raster
1576 // Odd (prime?) numbers resulted in fewer scaling artifacts. A multiplier
1577 // of 9 washed out colors a little too much.
1578 //const int height_multiplier_ = 7;
1579 void PacketList::drawNearOverlay()
1581 if (create_near_overlay_) {
1582 create_near_overlay_ = false;
1585 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
1587 if (!prefs.gui_packet_list_show_minimap) return;
1589 qreal dp_ratio = 1.0;
1590 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
1591 dp_ratio = overlay_sb_->devicePixelRatio();
1593 int o_height = overlay_sb_->height() * dp_ratio;
1594 int o_rows = qMin(packet_list_model_->rowCount(), o_height);
1595 int o_width = (wsApp->fontMetrics().height() * 2 * dp_ratio) + 2; // 2ems + 1-pixel border on either side.
1596 int selected_pos = -1;
1598 if (recent.packet_list_colorize && o_rows > 0) {
1599 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
1601 QPainter painter(&overlay);
1603 overlay.fill(Qt::transparent);
1608 if (packet_list_model_->rowCount() > o_height && overlay_sb_->maximum() > 0) {
1609 start += ((double) overlay_sb_->value() / overlay_sb_->maximum()) * (packet_list_model_->rowCount() - o_rows);
1611 int end = start + o_rows;
1612 for (int row = start; row < end; row++) {
1613 packet_list_model_->ensureRowColorized(row);
1615 frame_data *fdata = packet_list_model_->getRowFdata(row);
1616 const color_t *bgcolor = NULL;
1617 if (fdata->color_filter) {
1618 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
1619 bgcolor = &color_filter->bg_color;
1622 int next_line = (row - start) * o_height / o_rows;
1624 QColor color(ColorUtils::fromColorT(bgcolor));
1625 painter.fillRect(0, cur_line, o_width, next_line - cur_line, color);
1627 cur_line = next_line;
1630 // If the selected packet is in the overlay set selected_pos
1631 // accordingly. Otherwise, pin it to either the top or bottom.
1632 if (selectionModel()->hasSelection()) {
1633 int sel_row = selectionModel()->currentIndex().row();
1634 if (sel_row < start) {
1636 } else if (sel_row >= end) {
1637 selected_pos = overlay.height() - 1;
1639 selected_pos = (sel_row - start) * o_height / o_rows;
1643 overlay_sb_->setNearOverlayImage(overlay, packet_list_model_->rowCount(), start, end, selected_pos);
1646 overlay_sb_->setNearOverlayImage(overlay);
1650 void PacketList::drawFarOverlay()
1652 if (create_far_overlay_) {
1653 create_far_overlay_ = false;
1656 if (!cap_file_ || cap_file_->state != FILE_READ_DONE) return;
1658 if (!prefs.gui_packet_list_show_minimap) return;
1660 QSize groove_size = overlay_sb_->grooveRect().size();
1661 #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
1662 qreal dp_ratio = 1.0;
1663 dp_ratio = overlay_sb_->devicePixelRatio();
1664 groove_size *= dp_ratio;
1666 int o_width = groove_size.width();
1667 int o_height = groove_size.height();
1668 int pl_rows = packet_list_model_->rowCount();
1669 QImage overlay(o_width, o_height, QImage::Format_ARGB32_Premultiplied);
1670 bool have_marked_image = false;
1672 // If only there were references from popular culture about getting into
1673 // some sort of groove.
1674 if (!overlay.isNull() && recent.packet_list_colorize && pl_rows > 0) {
1676 QPainter painter(&overlay);
1678 // Draw text-colored tick marks on a transparent background.
1679 // Hopefully no themes use the text color for the groove color.
1680 overlay.fill(Qt::transparent);
1682 QColor tick_color = palette().text().color();
1683 tick_color.setAlphaF(0.3);
1684 painter.setPen(tick_color);
1686 for (int row = 0; row < pl_rows; row++) {
1688 frame_data *fdata = packet_list_model_->getRowFdata(row);
1689 if (fdata->flags.marked || fdata->flags.ref_time || fdata->flags.ignored) {
1690 int new_line = row * o_height / pl_rows;
1691 int tick_width = o_width / 3;
1692 // Marked or ignored: left side, time refs: right side.
1693 // XXX Draw ignored ticks in the middle?
1694 int x1 = fdata->flags.ref_time ? o_width - tick_width : 1;
1695 int x2 = fdata->flags.ref_time ? o_width - 1 : tick_width;
1697 painter.drawLine(x1, new_line, x2, new_line);
1698 have_marked_image = true;
1702 if (have_marked_image) {
1703 overlay_sb_->setMarkedPacketImage(overlay);
1708 if (!have_marked_image) {
1709 QImage null_overlay;
1710 overlay_sb_->setMarkedPacketImage(null_overlay);
1714 void PacketList::rowsInserted(const QModelIndex &parent, int start, int end)
1716 QTreeView::rowsInserted(parent, start, end);
1717 rows_inserted_ = true;
1726 * indent-tabs-mode: nil
1729 * ex: set shiftwidth=4 tabstop=8 expandtab:
1730 * :indentSize=4:tabSize=8:noTabs=true: