diff --git a/docbook/wsug_graphics/ws-stats-conversations.png b/docbook/wsug_graphics/ws-stats-conversations.png index 3655e50657..763188a368 100644 Binary files a/docbook/wsug_graphics/ws-stats-conversations.png and b/docbook/wsug_graphics/ws-stats-conversations.png differ diff --git a/docbook/wsug_graphics/ws-stats-hierarchy.png b/docbook/wsug_graphics/ws-stats-hierarchy.png index 3dbb93f8e7..1fb0860ed9 100644 Binary files a/docbook/wsug_graphics/ws-stats-hierarchy.png and b/docbook/wsug_graphics/ws-stats-hierarchy.png differ diff --git a/docbook/wsug_src/WSUG_chapter_statistics.asciidoc b/docbook/wsug_src/WSUG_chapter_statistics.asciidoc index 3755159d90..5255ef7ad0 100644 --- a/docbook/wsug_src/WSUG_chapter_statistics.asciidoc +++ b/docbook/wsug_src/WSUG_chapter_statistics.asciidoc @@ -35,7 +35,6 @@ These statistics range from general information about the loaded capture file - Various other protocol specific statistics. - [NOTE] ==== The protocol specific statistics require detailed knowledge about the specific @@ -45,11 +44,11 @@ be pretty hard to understand. [[ChStatSummary]] -=== The _Summary_ window +=== The ``Summary'' window General statistics about the current capture file. -.The "Summary" window +.The ``Summary'' window image::wsug_graphics/ws-stats-summary.png[] * __File__: general information about the capture file. @@ -73,12 +72,12 @@ image::wsug_graphics/ws-stats-summary.png[] [[ChStatHierarchy]] -=== The "Protocol Hierarchy" window +=== The ``Protocol Hierarchy'' window The protocol hierarchy of the captured packets. -.The "Protocol Hierarchy" window -image::wsug_graphics/ws-stats-hierarchy.png[] +.The ``Protocol Hierarchy'' window +image::wsug_graphics/ws-stats-hierarchy.png[scaledwidth="100%"] This is a tree of all the protocols in the capture. Each row contains the statistical values of one protocol. Two of the columns (_Percent Packets_ and @@ -143,10 +142,11 @@ The conversations window is similar to the endpoint Window. See <> for a description of their common features. Along with addresses, packet counters, and byte counters the conversation window adds four columns: the time in seconds between the start of the capture and the start of -the conversation ("Rel Start"), the duration of the conversation in seconds, and -the average bits (not bytes) per second in each direction. +the conversation (``Rel Start''), the duration of the conversation in seconds, and +the average bits (not bytes) per second in each direction. A timeline graph is +also drawn across the ``Rel Start'' and ``Duration'' columns. -.The "Conversations" window +.The ``Conversations'' window image::wsug_graphics/ws-stats-conversations.png[scaledwidth="100%"] Each row in the list shows the statistical values for exactly one conversation. @@ -241,7 +241,7 @@ will be received by some or all of the listed unicast endpoints. [[ChStatEndpointsWindow]] -==== The "Endpoints" window +==== The ``Endpoints'' window This window shows statistics about the endpoints captured. @@ -281,13 +281,13 @@ it before (or while) you are doing a live capture. [[ChStatIOGraphs]] -=== The "IO Graphs" window +=== The ``IO Graphs'' window User configurable graph of the captured network packets. You can define up to five differently colored graphs. -.The "IO Graphs" window +.The ``IO Graphs'' window image::wsug_graphics/ws-stats-iographs.png[] The user can configure the following things: diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index 6b2d9f1fbf..5d7d06a223 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -146,6 +146,7 @@ set(WIRESHARK_QT_HEADERS tap_parameter_dialog.h tcp_stream_dialog.h time_shift_dialog.h + timeline_delegate.h traffic_table_dialog.h uat_dialog.h voip_calls_dialog.h @@ -301,6 +302,7 @@ set(WIRESHARK_QT_SRC tap_parameter_dialog.cpp tcp_stream_dialog.cpp time_shift_dialog.cpp + timeline_delegate.cpp traffic_table_dialog.cpp uat_dialog.cpp voip_calls_dialog.cpp diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am index 8385f35c6e..f8971397d9 100644 --- a/ui/qt/Makefile.am +++ b/ui/qt/Makefile.am @@ -278,6 +278,7 @@ MOC_HDRS = \ tap_parameter_dialog.h \ tcp_stream_dialog.h \ time_shift_dialog.h \ + timeline_delegate.h \ traffic_table_dialog.h \ uat_dialog.h \ voip_calls_dialog.h \ @@ -545,6 +546,7 @@ WIRESHARK_QT_SRC = \ tap_parameter_dialog.cpp \ tcp_stream_dialog.cpp \ time_shift_dialog.cpp \ + timeline_delegate.cpp \ traffic_table_dialog.cpp \ uat_dialog.cpp \ voip_calls_dialog.cpp \ diff --git a/ui/qt/conversation_dialog.cpp b/ui/qt/conversation_dialog.cpp index bf6dce50eb..4ace205e19 100644 --- a/ui/qt/conversation_dialog.cpp +++ b/ui/qt/conversation_dialog.cpp @@ -31,6 +31,7 @@ #include "wsutil/str_util.h" #include "qt_ui_utils.h" +#include "timeline_delegate.h" #include "wireshark_application.h" #include @@ -153,6 +154,9 @@ bool ConversationDialog::addTrafficTable(register_ct_t* table) trafficTableTabWidget()->addTab(conv_tree, table_name); + conv_tree->setItemDelegateForColumn(CONV_COLUMN_START, new TimelineDelegate(conv_tree)); + conv_tree->setItemDelegateForColumn(CONV_COLUMN_DURATION, new TimelineDelegate(conv_tree)); + connect(conv_tree, SIGNAL(titleChanged(QWidget*,QString)), this, SLOT(setTabText(QWidget*,QString))); connect(conv_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)), @@ -325,14 +329,20 @@ public: conv_array_(conv_array), conv_idx_(conv_idx), resolve_names_ptr_(resolve_names_ptr) - {} + { + QString timeline_tt = QObject::tr("Bars show the relative timeline for each conversation."); + setToolTip(CONV_COLUMN_START, timeline_tt); + setToolTip(CONV_COLUMN_DURATION, timeline_tt); + } conv_item_t *convItem() { return &g_array_index(conv_array_, conv_item_t, conv_idx_); } virtual QVariant data(int column, int role) const { - if (role == Qt::DisplayRole) { + switch (role) { + case Qt::DisplayRole: + { // Column text cooked representation. conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_); @@ -371,6 +381,37 @@ public: default: return colData(column, resolve_names).toString(); } + break; + } + case Qt::UserRole: + { + if (column != CONV_COLUMN_START && column != CONV_COLUMN_DURATION) break; + + ConversationTreeWidget *ctw = qobject_cast(treeWidget()); + if (!ctw) break; + + conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_); + double start_time = nstime_to_sec(&conv_item->start_time); + double stop_time = nstime_to_sec(&conv_item->stop_time); + + double span_s = ctw->maxRelStopTime() - ctw->minRelStartTime(); + if (span_s <= 0) break; + int start_px = ctw->columnWidth(CONV_COLUMN_START); + int column_px = start_px + ctw->columnWidth(CONV_COLUMN_DURATION); + + struct timeline_span span_px; + span_px.start = ((start_time - ctw->minRelStartTime()) * column_px) / span_s; + span_px.width = ((stop_time - start_time) * column_px) / span_s; + + if (column == CONV_COLUMN_DURATION) { + span_px.start -= start_px; + } + return qVariantFromValue(span_px); + + break; + } + default: + break; } return QTreeWidgetItem::data(column, role); } @@ -498,7 +539,9 @@ private: // TrafficTableTreeWidget / QTreeWidget subclass that allows tapping ConversationTreeWidget::ConversationTreeWidget(QWidget *parent, register_ct_t* table) : - TrafficTableTreeWidget(parent, table) + TrafficTableTreeWidget(parent, table), + min_rel_start_time_(0), + max_rel_stop_time_(0) { setColumnCount(CONV_NUM_COLUMNS); setUniformRowHeights(true); @@ -609,6 +652,8 @@ void ConversationTreeWidget::tapReset(void *conv_hash_ptr) conv_tree->clear(); reset_conversation_table_data(&conv_tree->hash_); + conv_tree->min_rel_start_time_ = 0; + conv_tree->max_rel_stop_time_ = 0; } void ConversationTreeWidget::tapDraw(void *conv_hash_ptr) @@ -659,6 +704,11 @@ void ConversationTreeWidget::updateItems() { ConversationTreeWidgetItem *ctwi = new ConversationTreeWidgetItem(hash_.conv_array, i, &resolve_names_); new_items << ctwi; + if (i == 0) { + min_rel_start_time_ = nstime_to_sec(&(ctwi->convItem()->start_time)); + max_rel_stop_time_ = nstime_to_sec(&(ctwi->convItem()->stop_time)); + } + for (int col = 0; col < columnCount(); col++) { switch (col) { case CONV_COLUMN_SRC_ADDR: @@ -672,6 +722,20 @@ void ConversationTreeWidget::updateItems() { } addTopLevelItems(new_items); + for (int i = 0; i < topLevelItemCount(); i++) { + ConversationTreeWidgetItem *ctwi = dynamic_cast(topLevelItem(i)); + + double item_rel_start = nstime_to_sec(&(ctwi->convItem()->start_time)); + if (item_rel_start < min_rel_start_time_) { + min_rel_start_time_ = item_rel_start; + } + + double item_rel_stop = nstime_to_sec(&(ctwi->convItem()->stop_time)); + if (item_rel_stop > max_rel_stop_time_) { + max_rel_stop_time_ = item_rel_stop; + } + } + setSortingEnabled(true); if (resize) { diff --git a/ui/qt/conversation_dialog.h b/ui/qt/conversation_dialog.h index 6e7abf5ca6..d962684ac3 100644 --- a/ui/qt/conversation_dialog.h +++ b/ui/qt/conversation_dialog.h @@ -35,10 +35,14 @@ public: static void tapReset(void *conv_hash_ptr); static void tapDraw(void *conv_hash_ptr); + double minRelStartTime() { return min_rel_start_time_; } + double maxRelStopTime() { return max_rel_stop_time_; } private: void initDirectionMap(); void updateItems(); + double min_rel_start_time_; // seconds + double max_rel_stop_time_; // seconds private slots: void filterActionTriggered(); diff --git a/ui/qt/percent_bar_delegate.cpp b/ui/qt/percent_bar_delegate.cpp index 76517de85e..31555aec38 100644 --- a/ui/qt/percent_bar_delegate.cpp +++ b/ui/qt/percent_bar_delegate.cpp @@ -35,7 +35,9 @@ void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op QStyleOptionViewItem option_vi = option; QStyledItemDelegate::initStyleOption(&option_vi, index); - QStyledItemDelegate::paint(painter, option, index); + // Paint our rect with no text using the current style, then draw our + // bar and text over it. + QStyledItemDelegate::paint(painter, option, QModelIndex()); bool ok = false; double value = index.data(Qt::UserRole).toDouble(&ok); @@ -45,8 +47,6 @@ void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op return; } - painter->save(); - if (QApplication::style()->objectName().contains("vista")) { // QWindowsVistaStyle::drawControl does this internally. Unfortunately there // doesn't appear to be a more general way to do this. @@ -54,29 +54,35 @@ void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op option_vi.palette.color(QPalette::Active, QPalette::Text)); } - QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(), - option_vi.palette.window(), bar_blend_); QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + QColor text_color = option_vi.palette.color(cg, QPalette::Text); + QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(), + option_vi.palette.window(), bar_blend_); + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) cg = QPalette::Inactive; if (option_vi.state & QStyle::State_Selected) { - painter->setPen(option_vi.palette.color(cg, QPalette::HighlightedText)); + text_color = option_vi.palette.color(cg, QPalette::HighlightedText); bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window), option_vi.palette.color(cg, QPalette::Highlight), bar_blend_); - } else { - painter->setPen(option_vi.palette.color(cg, QPalette::Text)); } + painter->save(); + int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos. QRect pct_rect = option.rect; pct_rect.adjust(1, 1, -1, -1); pct_rect.setWidth(((pct_rect.width() * value) / 100.0) + 0.5); - painter->fillRect(pct_rect, bar_color); + painter->setPen(Qt::NoPen); + painter->setBrush(bar_color); + painter->drawRoundedRect(pct_rect, border_radius, border_radius); + painter->restore(); + painter->save(); QString pct_str = QString::number(value, 'f', 1); + painter->setPen(text_color); painter->drawText(option.rect, Qt::AlignCenter, pct_str); - painter->restore(); } diff --git a/ui/qt/timeline_delegate.cpp b/ui/qt/timeline_delegate.cpp new file mode 100644 index 0000000000..0cfdd73147 --- /dev/null +++ b/ui/qt/timeline_delegate.cpp @@ -0,0 +1,103 @@ +/* timeline_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "timeline_delegate.h" + +#include "color_utils.h" + +#include +#include + +// XXX We might want to move this to conversation_dialog.cpp. + +// PercentBarDelegate uses a stronger blend value, but its bars are also +// more of a prominent feature. Make the blend weaker here so that we don't +// obscure our text. +static const double bar_blend_ = 0.08; + +TimelineDelegate::TimelineDelegate(QWidget *parent) : + QStyledItemDelegate(parent) +{} + +void TimelineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + + + struct timeline_span span_px = index.data(Qt::UserRole).value(); + + // Paint our rect with no text using the current style, then draw our + // bar and text over it. + QStyledItemDelegate::paint(painter, option, QModelIndex()); + + if (QApplication::style()->objectName().contains("vista")) { + // QWindowsVistaStyle::drawControl does this internally. Unfortunately there + // doesn't appear to be a more general way to do this. + option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, + option_vi.palette.color(QPalette::Active, QPalette::Text)); + } + + QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + QColor text_color = option_vi.palette.color(cg, QPalette::Text); + QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(), + option_vi.palette.window(), bar_blend_); + + if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option_vi.state & QStyle::State_Selected) { + text_color = option_vi.palette.color(cg, QPalette::HighlightedText); + bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window), + option_vi.palette.color(cg, QPalette::Highlight), + bar_blend_); + } + + painter->save(); + int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos. + QRect timeline_rect = option.rect; + timeline_rect.adjust(span_px.start, 1, 0, -1); + timeline_rect.setWidth(span_px.width); + painter->setClipRect(option.rect); + painter->setPen(Qt::NoPen); + painter->setBrush(bar_color); + painter->drawRoundedRect(timeline_rect, border_radius, border_radius); + painter->restore(); + + painter->save(); + painter->setPen(text_color); + painter->drawText(option.rect, Qt::AlignCenter, index.data(Qt::DisplayRole).toString()); + painter->restore(); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/timeline_delegate.h b/ui/qt/timeline_delegate.h new file mode 100644 index 0000000000..face6da320 --- /dev/null +++ b/ui/qt/timeline_delegate.h @@ -0,0 +1,80 @@ +/* timeline_delegate.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef TIMELINE_DELEGATE_H +#define TIMELINE_DELEGATE_H + +/* + * @file Timeline delegate. + * + * QStyledItemDelegate subclass that will draw a timeline indicator for + * the specified value. + * + * This is intended to be used in QTreeWidgets to show timelines, e.g. for + * conversations. + * To use it, first call setItemDelegate: + * + * myTreeWidget()->setItemDelegateForColumn(col_time_start_, new TimelineDelegate()); + * + * Then, for each QTreeWidgetItem, set or return a timeline_span for the start and end + * of the timeline in pixels relative to the column width. + * + * setData(col_start_, Qt::UserRole, start_span); + * setData(col_end_, Qt::UserRole, end_span); + * + */ + +#include + +// Pixels are relative to item rect and will be clipped. +struct timeline_span { + int start; + int width; +}; + +Q_DECLARE_METATYPE(timeline_span) + +class TimelineDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + TimelineDelegate(QWidget *parent = 0); +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +private: +}; + +#endif // TIMELINE_DELEGATE_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */