summaryrefslogtreecommitdiff
path: root/ui/qt
diff options
context:
space:
mode:
authorGerald Combs <gerald@wireshark.org>2014-02-11 16:07:10 -0800
committerGerald Combs <gerald@wireshark.org>2014-04-07 20:56:42 +0000
commita5cb72fe9eadfaf8cb0aefccb106a7eaad9266c9 (patch)
tree34a1965805b7373553d6057e9981de3ae6a01460 /ui/qt
parentcc3c05ed5f9e2d3eb8d72b3acc66bbacd50a26e7 (diff)
downloadwireshark-a5cb72fe9eadfaf8cb0aefccb106a7eaad9266c9.tar.gz
Add a Qt I/O Graph dialog.
For each graph you can set: - Its visibility - A name - A display filter - Color, from a fixed list - Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond - Basic Y Axes (packets/s, bytes/s, bits/s) - Computed Y Axes (SUM, MIN, AVG, MAX) - Smoothing You can pan and zoom using the mouse and keyboard. Clicking on a graph selects the last packet for that interval. If all graphs have the same Y axis a single label is shown, otherwise a legend is shown. The time scale (X axis) can be toggled between relative seconds and the time of day. Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky" via the io_graphs UAT. To do: - Minimize graph drawing delays. - Figure out why smoothing differs from GTK+ - Everything else at the top of io_graph_dialog.cpp - Fix empty resets. A fair amount of code was copied from TCPStreamDialog. We might want to subclass QCustomPlot and place the shared code there. Move common syntax checking to SyntaxLineEdit. Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and use it in both GTK+ and Qt. Make the io_graph_item_t array allocation in io_stat.c static. The behavior should be identical and this gives us additional compile-time checks. Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577 Reviewed-on: https://code.wireshark.org/review/435 Reviewed-by: Gerald Combs <gerald@wireshark.org> Tested-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt')
-rw-r--r--ui/qt/CMakeLists.txt3
-rw-r--r--ui/qt/Makefile.am2
-rw-r--r--ui/qt/Makefile.common4
-rw-r--r--ui/qt/QtShark.pro3
-rw-r--r--ui/qt/column_preferences_frame.cpp20
-rw-r--r--ui/qt/column_preferences_frame.h1
-rw-r--r--ui/qt/display_filter_edit.cpp51
-rw-r--r--ui/qt/display_filter_edit.h1
-rw-r--r--ui/qt/filter_expressions_preferences_frame.cpp21
-rw-r--r--ui/qt/filter_expressions_preferences_frame.h1
-rw-r--r--ui/qt/io_graph_dialog.cpp2156
-rw-r--r--ui/qt/io_graph_dialog.h253
-rw-r--r--ui/qt/io_graph_dialog.ui506
-rw-r--r--ui/qt/main_window.cpp1
-rw-r--r--ui/qt/main_window.h1
-rw-r--r--ui/qt/main_window.ui9
-rw-r--r--ui/qt/main_window_slots.cpp10
-rw-r--r--ui/qt/search_frame.cpp20
-rw-r--r--ui/qt/sequence_dialog.cpp12
-rw-r--r--ui/qt/syntax_line_edit.cpp51
-rw-r--r--ui/qt/syntax_line_edit.h11
-rw-r--r--ui/qt/tcp_stream_dialog.cpp17
22 files changed, 3045 insertions, 109 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt
index 45723eda51..b8b50ba6e3 100644
--- a/ui/qt/CMakeLists.txt
+++ b/ui/qt/CMakeLists.txt
@@ -51,6 +51,7 @@ set(WIRESHARK_QT_HEADERS
font_color_preferences_frame.h
import_text_dialog.h
interface_tree.h
+ io_graph_dialog.h
label_stack.h
layout_preferences_frame.h
main_status_bar.h
@@ -126,6 +127,7 @@ set(WIRESHARK_QT_SRC
font_color_preferences_frame.cpp
import_text_dialog.cpp
interface_tree.cpp
+ io_graph_dialog.cpp
label_stack.cpp
layout_preferences_frame.cpp
main.cpp
@@ -195,6 +197,7 @@ set(WIRESHARK_QT_UI
follow_stream_dialog.ui
font_color_preferences_frame.ui
import_text_dialog.ui
+ io_graph_dialog.ui
layout_preferences_frame.ui
main_welcome.ui
main_window.ui
diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am
index 2bb27aead7..e22775f92e 100644
--- a/ui/qt/Makefile.am
+++ b/ui/qt/Makefile.am
@@ -149,6 +149,8 @@ font_color_preferences_frame.cpp font_color_preferences_frame.h: ui_font_color_p
import_text_dialog.cpp import_text_dialog.h: ui_import_text_dialog.h
+io_graph_dialog.cpp io_graph_dialog.h: ui_io_graph_dialog.h
+
layout_preferences_frame.cpp layout_preferences_frame.h: ui_layout_preferences_frame.h
main_welcome.cpp main_welcome.h: ui_main_welcome.h
diff --git a/ui/qt/Makefile.common b/ui/qt/Makefile.common
index 3581dbe409..eeac2f572f 100644
--- a/ui/qt/Makefile.common
+++ b/ui/qt/Makefile.common
@@ -41,6 +41,7 @@ NODIST_GENERATED_HEADER_FILES = \
ui_follow_stream_dialog.h \
ui_font_color_preferences_frame.h \
ui_import_text_dialog.h \
+ ui_io_graph_dialog.h \
ui_layout_preferences_frame.h \
ui_main_welcome.h \
ui_main_window.h \
@@ -130,6 +131,7 @@ MOC_HDRS = \
font_color_preferences_frame.h \
import_text_dialog.h \
interface_tree.h \
+ io_graph_dialog.h \
label_stack.h \
layout_preferences_frame.h \
main_status_bar.h \
@@ -187,6 +189,7 @@ UI_FILES = \
follow_stream_dialog.ui \
font_color_preferences_frame.ui \
import_text_dialog.ui \
+ io_graph_dialog.ui \
layout_preferences_frame.ui \
main_welcome.ui \
main_window.ui \
@@ -292,6 +295,7 @@ WIRESHARK_QT_SRC = \
font_color_preferences_frame.cpp \
import_text_dialog.cpp \
interface_tree.cpp \
+ io_graph_dialog.cpp \
label_stack.cpp \
layout_preferences_frame.cpp \
main.cpp \
diff --git a/ui/qt/QtShark.pro b/ui/qt/QtShark.pro
index c8bb097905..672b634170 100644
--- a/ui/qt/QtShark.pro
+++ b/ui/qt/QtShark.pro
@@ -231,6 +231,7 @@ FORMS += \
follow_stream_dialog.ui \
font_color_preferences_frame.ui \
import_text_dialog.ui \
+ io_graph_dialog.ui \
layout_preferences_frame.ui \
main_welcome.ui \
main_window.ui \
@@ -529,6 +530,7 @@ HEADERS += \
file_set_dialog.h \
import_text_dialog.h \
interface_tree.h \
+ io_graph_dialog.h \
label_stack.h \
main_status_bar.h \
main_welcome.h \
@@ -583,6 +585,7 @@ SOURCES += \
font_color_preferences_frame.cpp \
import_text_dialog.cpp \
interface_tree.cpp \
+ io_graph_dialog.cpp \
label_stack.cpp \
layout_preferences_frame.cpp \
main.cpp \
diff --git a/ui/qt/column_preferences_frame.cpp b/ui/qt/column_preferences_frame.cpp
index 2d757aa4fa..6fdd8dfe47 100644
--- a/ui/qt/column_preferences_frame.cpp
+++ b/ui/qt/column_preferences_frame.cpp
@@ -259,7 +259,7 @@ void ColumnPreferencesFrame::on_columnTreeWidget_itemActivated(QTreeWidgetItem *
SyntaxLineEdit *syntax_edit = new SyntaxLineEdit();
saved_col_string_ = item->text(custom_field_col_);
connect(syntax_edit, SIGNAL(textChanged(QString)),
- this, SLOT(customFieldTextChanged(QString)));
+ syntax_edit, SLOT(checkFieldName(QString)));
connect(syntax_edit, SIGNAL(editingFinished()), this, SLOT(customFieldEditingFinished()));
editor = cur_line_edit_ = syntax_edit;
@@ -348,24 +348,6 @@ void ColumnPreferencesFrame::columnTypeCurrentIndexChanged(int index)
}
}
-void ColumnPreferencesFrame::customFieldTextChanged(QString)
-{
- SyntaxLineEdit *syntax_edit = qobject_cast<SyntaxLineEdit *>(cur_line_edit_);
- QTreeWidgetItem *item = ui->columnTreeWidget->currentItem();
- if (!syntax_edit || !item) return;
-
- dfilter_t *dfp = NULL;
- const char *field_text = syntax_edit->text().toUtf8().constData();
- if (strlen(field_text) < 1) {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Empty);
- } else if (!dfilter_compile(field_text, &dfp)) {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Invalid);
- } else {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Valid);
- }
- dfilter_free(dfp);
-}
-
void ColumnPreferencesFrame::customFieldEditingFinished()
{
QTreeWidgetItem *item = ui->columnTreeWidget->currentItem();
diff --git a/ui/qt/column_preferences_frame.h b/ui/qt/column_preferences_frame.h
index 7cee9eaaed..ab9c4ba52e 100644
--- a/ui/qt/column_preferences_frame.h
+++ b/ui/qt/column_preferences_frame.h
@@ -62,7 +62,6 @@ private slots:
void comboDestroyed();
void columnTitleEditingFinished();
void columnTypeCurrentIndexChanged(int index);
- void customFieldTextChanged(QString);
void customFieldEditingFinished();
void customOccurrenceTextChanged(QString);
void customOccurrenceEditingFinished();
diff --git a/ui/qt/display_filter_edit.cpp b/ui/qt/display_filter_edit.cpp
index aa6c9d9de2..ea01dcca14 100644
--- a/ui/qt/display_filter_edit.cpp
+++ b/ui/qt/display_filter_edit.cpp
@@ -23,7 +23,6 @@
#include <glib.h>
-#include <epan/proto.h>
#include <epan/dfilter/dfilter.h>
#include "display_filter_edit.h"
@@ -85,7 +84,6 @@ UIMiniCancelButton::UIMiniCancelButton(QWidget *pParent /* = 0 */)
DisplayFilterEdit::DisplayFilterEdit(QWidget *parent, bool plain) :
SyntaxLineEdit(parent),
plain_(plain),
- field_name_only_(false),
apply_button_(NULL)
{
@@ -266,43 +264,34 @@ void DisplayFilterEdit::resizeEvent(QResizeEvent *)
void DisplayFilterEdit::checkFilter(const QString& text)
{
- dfilter_t *dfp;
- guchar c;
-
clear_button_->setVisible(!text.isEmpty());
popFilterSyntaxStatus();
-
- if (field_name_only_ && (c = proto_check_field_name(text.toUtf8().constData()))) {
- setSyntaxState(Invalid);
- emit pushFilterSyntaxStatus(QString().sprintf("Illegal character in field name: '%c'", c));
- } else if (dfilter_compile(text.toUtf8().constData(), &dfp)) {
- GPtrArray *depr = NULL;
- if (dfp != NULL) {
- depr = dfilter_deprecated_tokens(dfp);
- }
- if (text.isEmpty()) {
- setSyntaxState(Empty);
- } else if (depr) {
- /* You keep using that word. I do not think it means what you think it means. */
- setSyntaxState(Deprecated);
- /*
- * We're being lazy and only printing the first "problem" token.
- * Would it be better to print all of them?
- */
- emit pushFilterSyntaxWarning(QString().sprintf("\"%s\" may have unexpected results (see the User's Guide)",
- (const char *) g_ptr_array_index(depr, 0)));
- } else {
- setSyntaxState(Valid);
- }
- dfilter_free(dfp);
- } else {
- setSyntaxState(Invalid);
+ checkDisplayFilter(text);
+
+ switch (syntaxState()) {
+ case Deprecated:
+ {
+ /*
+ * We're being lazy and only printing the first "problem" token.
+ * Would it be better to print all of them?
+ */
+ QString deprecatedMsg(tr("\"%1\" may have unexpected results (see the User's Guide)")
+ .arg(deprecatedToken()));
+ emit pushFilterSyntaxWarning(deprecatedMsg);
+ break;
+ }
+ case Invalid:
+ {
QString invalidMsg(tr("Invalid filter"));
if (dfilter_error_msg) {
invalidMsg.append(QString().sprintf(": %s", dfilter_error_msg));
}
emit pushFilterSyntaxStatus(invalidMsg);
+ break;
+ }
+ default:
+ break;
}
bookmark_button_->setEnabled(syntaxState() == Valid || syntaxState() == Deprecated);
diff --git a/ui/qt/display_filter_edit.h b/ui/qt/display_filter_edit.h
index b760aef597..72cde11aa7 100644
--- a/ui/qt/display_filter_edit.h
+++ b/ui/qt/display_filter_edit.h
@@ -48,7 +48,6 @@ private slots:
private:
bool plain_;
- bool field_name_only_;
QString empty_filter_message_;
QToolButton *bookmark_button_;
QToolButton *clear_button_;
diff --git a/ui/qt/filter_expressions_preferences_frame.cpp b/ui/qt/filter_expressions_preferences_frame.cpp
index 55c7792bcc..46eba30ad9 100644
--- a/ui/qt/filter_expressions_preferences_frame.cpp
+++ b/ui/qt/filter_expressions_preferences_frame.cpp
@@ -196,7 +196,6 @@ void FilterExpressionsPreferencesFrame::on_expressionTreeWidget_itemActivated(QT
case label_col_:
{
cur_line_edit_ = new QLineEdit();
- cur_column_ = column;
saved_col_string_ = item->text(label_col_);
connect(cur_line_edit_, SIGNAL(editingFinished()), this, SLOT(labelEditingFinished()));
editor = cur_line_edit_;
@@ -207,7 +206,7 @@ void FilterExpressionsPreferencesFrame::on_expressionTreeWidget_itemActivated(QT
SyntaxLineEdit *syntax_edit = new SyntaxLineEdit();
saved_col_string_ = item->text(expression_col_);
connect(syntax_edit, SIGNAL(textChanged(QString)),
- this, SLOT(expressionTextChanged(QString)));
+ syntax_edit, SLOT(checkDisplayFilter(QString)));
connect(syntax_edit, SIGNAL(editingFinished()), this, SLOT(expressionEditingFinished()));
editor = cur_line_edit_ = syntax_edit;
break;
@@ -257,24 +256,6 @@ void FilterExpressionsPreferencesFrame::labelEditingFinished()
ui->expressionTreeWidget->removeItemWidget(item, label_col_);
}
-void FilterExpressionsPreferencesFrame::expressionTextChanged(QString)
-{
- SyntaxLineEdit *syntax_edit = qobject_cast<SyntaxLineEdit *>(cur_line_edit_);
- QTreeWidgetItem *item = ui->expressionTreeWidget->currentItem();
- if (!syntax_edit || !item) return;
-
- dfilter_t *dfp = NULL;
- const char *field_text = syntax_edit->text().toUtf8().constData();
- if (strlen(field_text) < 1) {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Empty);
- } else if (proto_check_field_name(field_text) != 0 || !dfilter_compile(field_text, &dfp)) {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Invalid);
- } else {
- syntax_edit->setSyntaxState(SyntaxLineEdit::Valid);
- }
- dfilter_free(dfp);
-}
-
void FilterExpressionsPreferencesFrame::expressionEditingFinished()
{
QTreeWidgetItem *item = ui->expressionTreeWidget->currentItem();
diff --git a/ui/qt/filter_expressions_preferences_frame.h b/ui/qt/filter_expressions_preferences_frame.h
index 6cea5de67f..0c4d587d4c 100644
--- a/ui/qt/filter_expressions_preferences_frame.h
+++ b/ui/qt/filter_expressions_preferences_frame.h
@@ -58,7 +58,6 @@ private slots:
void on_expressionTreeWidget_itemActivated(QTreeWidgetItem *item, int column);
void lineEditDestroyed();
void labelEditingFinished();
- void expressionTextChanged(QString);
void expressionEditingFinished();
void on_newToolButton_clicked();
void on_deleteToolButton_clicked();
diff --git a/ui/qt/io_graph_dialog.cpp b/ui/qt/io_graph_dialog.cpp
new file mode 100644
index 0000000000..c6d776015c
--- /dev/null
+++ b/ui/qt/io_graph_dialog.cpp
@@ -0,0 +1,2156 @@
+/* io_graph_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * 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 "io_graph_dialog.h"
+#include "ui_io_graph_dialog.h"
+
+#include "epan/stats_tree_priv.h"
+#include "epan/uat-int.h"
+
+#include "qt_ui_utils.h"
+#include "tango_colors.h"
+
+#include "wireshark_application.h"
+
+#include <QClipboard>
+#include <QComboBox>
+#include <QFileDialog>
+#include <QFontMetrics>
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QSpacerItem>
+#include <QTreeWidget>
+#include <QVariant>
+
+// Bugs and uncertainties:
+// - Regular (non-stacked) bar graphs are drawn on top of each other on the Z axis.
+// The QCP forum suggests drawing them side by side:
+// http://www.qcustomplot.com/index.php/support/forum/62
+// - You can't manually set a graph color other than manually editing the io_graphs
+// UAT. We should add a "graph color" preference.
+// - We retap and redraw more than we should.
+// - We don't use scroll bars. Should we?
+// - We should automatically scroll during live captures.
+// - Smoothing doesn't seem to match GTK+
+// - We don't register a tap listener ("-z io,stat", bottom of gtk/io_stat.c)
+
+const int name_col_ = 0;
+const int dfilter_col_ = 1;
+const int color_col_ = 2;
+const int style_col_ = 3;
+const int yaxis_col_ = 4;
+const int yfield_col_ = 5;
+const int sma_period_col_ = 6;
+const int num_cols_ = 7;
+
+// Available colors
+// XXX - Add custom
+QList<QRgb> colors_ = QList<QRgb>()
+ << tango_aluminium_6 // Bar outline (use black instead)?
+ << tango_sky_blue_5
+ << tango_butter_6
+ << tango_chameleon_5
+ << tango_scarlet_red_5
+ << tango_plum_5
+ << tango_orange_6
+ << tango_aluminium_3
+ << tango_sky_blue_3
+ << tango_butter_3
+ << tango_chameleon_3
+ << tango_scarlet_red_3
+ << tango_plum_3
+ << tango_orange_3;
+
+const qreal graph_line_width_ = 1.0;
+
+// When we drop support for Qt <5 we can initialize these with
+// datastreams.
+const QMap<io_graph_item_unit_t, QString> value_unit_to_name_ = IOGraph::valueUnitsToNames();
+const QMap<IOGraph::PlotStyles, QString> plot_style_to_name_ = IOGraph::plotStylesToNames();
+const QMap<int, QString> moving_average_to_name_ = IOGraph::movingAveragesToNames();
+
+const int default_moving_average_ = 0;
+
+// Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
+// in zoom mode.
+const int min_zoom_pixels_ = 20;
+
+const int stat_update_interval_ = 200; // ms
+
+// Saved graph settings
+
+static const value_string graph_enabled_vs[] = {
+ { 0, "Disabled" },
+ { 1, "Enabled" },
+ { 0, NULL }
+};
+
+typedef struct _io_graph_settings_t {
+ guint32 enabled;
+ char* name;
+ char* dfilter;
+ char* color;
+ char* style;
+ char* yaxis;
+ char* yfield;
+ int sma_period;
+} io_graph_settings_t;
+
+static io_graph_settings_t *iog_settings_ = NULL;
+static guint num_io_graphs_ = 0;
+static uat_t *iog_uat_ = NULL;
+
+extern "C" {
+
+UAT_VS_DEF(io_graph, enabled, io_graph_settings_t, guint32, 0, "Disabled")
+UAT_CSTRING_CB_DEF(io_graph, name, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, dfilter, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, color, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, style, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, yaxis, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, yfield, io_graph_settings_t)
+UAT_DEC_CB_DEF(io_graph, sma_period, io_graph_settings_t)
+
+static uat_field_t io_graph_fields[] = {
+ UAT_FLD_VS(io_graph, enabled, "Enabled", graph_enabled_vs, "Graph visibility"),
+ UAT_FLD_CSTRING(io_graph, name, "Graph Name", "The name of the graph"),
+ UAT_FLD_CSTRING(io_graph, dfilter, "Display Filter", "Graph packets matching this display filter"),
+ UAT_FLD_CSTRING(io_graph, color, "Color", "Graph color (#RRGGBB)"),
+ UAT_FLD_CSTRING(io_graph, style, "Style", "Graph style (Line, Bars, etc.)"),
+ UAT_FLD_CSTRING(io_graph, yaxis, "Y Axis", "Y Axis units"),
+ UAT_FLD_CSTRING(io_graph, yfield, "Y Field", "Apply calculations to this field"),
+ UAT_FLD_DEC(io_graph, sma_period, "SMA Period", "Simple moving average period"),
+ UAT_END_FIELDS
+};
+
+static void* io_graph_copy_cb(void* dst_ptr, const void* src_ptr, size_t len _U_) {
+ io_graph_settings_t* dst = (io_graph_settings_t *)dst_ptr;
+ const io_graph_settings_t* src = (const io_graph_settings_t *)src_ptr;
+
+ dst->enabled = src->enabled;
+ dst->name = g_strdup(src->name);
+ dst->dfilter = g_strdup(src->dfilter);
+ dst->color = g_strdup(src->color);
+ dst->style = g_strdup(src->style);
+ dst->yaxis = g_strdup(src->yaxis);
+ dst->yfield = g_strdup(src->yfield);
+ dst->sma_period = src->sma_period;
+
+ return dst;
+}
+
+static void io_graph_free_cb(void* p) {
+ io_graph_settings_t *iogs = (io_graph_settings_t *)p;
+ g_free(iogs->name);
+ g_free(iogs->dfilter);
+ g_free(iogs->color);
+ g_free(iogs->yfield);
+}
+
+} // extern "C"
+
+
+Q_DECLARE_METATYPE(IOGraph *);
+
+IOGraphDialog::IOGraphDialog(QWidget *parent, capture_file *cf) :
+ QDialog(parent),
+ ui(new Ui::IOGraphDialog),
+ cap_file_(cf),
+ name_line_edit_(NULL),
+ dfilter_line_edit_(NULL),
+ yfield_line_edit_(NULL),
+ color_combo_box_(NULL),
+ style_combo_box_(NULL),
+ yaxis_combo_box_(NULL),
+ sma_combo_box_(NULL),
+ base_graph_(NULL),
+ tracer_(NULL),
+ start_time_(0.0),
+ mouse_drags_(true),
+ rubber_band_(NULL),
+ stat_timer_(NULL),
+ need_replot_(false),
+ need_retap_(false),
+ auto_axes_(true)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ QCustomPlot *iop = ui->ioPlot;
+
+ QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
+ save_bt->setText(tr("Save As..."));
+
+ stat_timer_ = new QTimer(this);
+ connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
+ stat_timer_->start(stat_update_interval_);
+
+ // Intervals (ms)
+ ui->intervalComboBox->addItem(tr("0.001 sec"), 1);
+ ui->intervalComboBox->addItem(tr("0.01 sec"), 10);
+ ui->intervalComboBox->addItem(tr("0.1 sec"), 100);
+ ui->intervalComboBox->addItem(tr("1 sec"), 1000);
+ ui->intervalComboBox->addItem(tr("10 sec"), 10000);
+ ui->intervalComboBox->addItem(tr("1 min"), 60000);
+ ui->intervalComboBox->addItem(tr("10 min"), 600000);
+ ui->intervalComboBox->setCurrentIndex(3);
+
+ ui->todCheckBox->setChecked(false);
+
+ ui->dragRadioButton->setChecked(mouse_drags_);
+
+ ctx_menu_.addAction(ui->actionZoomIn);
+ ctx_menu_.addAction(ui->actionZoomOut);
+ ctx_menu_.addAction(ui->actionReset);
+ ctx_menu_.addSeparator();
+ ctx_menu_.addAction(ui->actionMoveRight10);
+ ctx_menu_.addAction(ui->actionMoveLeft10);
+ ctx_menu_.addAction(ui->actionMoveUp10);
+ ctx_menu_.addAction(ui->actionMoveDown10);
+ ctx_menu_.addAction(ui->actionMoveRight1);
+ ctx_menu_.addAction(ui->actionMoveLeft1);
+ ctx_menu_.addAction(ui->actionMoveUp1);
+ ctx_menu_.addAction(ui->actionMoveDown1);
+ ctx_menu_.addSeparator();
+ ctx_menu_.addAction(ui->actionGoToPacket);
+ ctx_menu_.addSeparator();
+ ctx_menu_.addAction(ui->actionDragZoom);
+ ctx_menu_.addAction(ui->actionToggleTimeOrigin);
+ ctx_menu_.addAction(ui->actionCrosshairs);
+
+ iop->xAxis->setLabel(tr("Time (s)"));
+
+ iop->setMouseTracking(true);
+ iop->setEnabled(true);
+
+ QString dlg_title = tr("Wireshark IO Graphs: ");
+ if (cap_file_) {
+ dlg_title += cf_get_display_name(cap_file_);
+ } else {
+ dlg_title += tr("No Capture Data");
+ }
+ setWindowTitle(dlg_title);
+ QCPPlotTitle *title = new QCPPlotTitle(iop);
+ iop->plotLayout()->insertRow(0);
+ iop->plotLayout()->addElement(0, 0, title);
+ title->setText(dlg_title);
+
+ tracer_ = new QCPItemTracer(iop);
+ iop->addItem(tracer_);
+
+ loadProfileGraphs();
+ if (num_io_graphs_ > 0) {
+ for (guint i = 0; i < num_io_graphs_; i++) {
+ io_graph_settings_t *iogs = &iog_settings_[i];
+ QRgb pcolor = QColor(iogs->color).rgb();
+ int color_idx;
+ IOGraph::PlotStyles style = plot_style_to_name_.key(iogs->style, IOGraph::psLine);
+ io_graph_item_unit_t value_units = value_unit_to_name_.key(iogs->yaxis, IOG_ITEM_UNIT_PACKETS);
+
+ for (color_idx = 0; color_idx < colors_.size(); color_idx++) {
+ if (pcolor == colors_[color_idx]) break;
+ }
+ if (color_idx >= colors_.size()) {
+ colors_ << pcolor;
+ }
+
+ addGraph(iogs->enabled == 1, iogs->name, iogs->dfilter, color_idx, style, value_units, iogs->yfield, iogs->sma_period);
+ }
+ } else {
+ addDefaultGraph(true, 0);
+ addDefaultGraph(true, 1);
+ }
+
+ on_graphTreeWidget_itemSelectionChanged();
+
+ toggleTracerStyle(true);
+ iop->setFocus();
+
+ iop->rescaleAxes();
+
+ // Shrink columns down, then expand as needed
+ QTreeWidget *gtw = ui->graphTreeWidget;
+ int one_em = fontMetrics().height();
+ gtw->setRootIsDecorated(false);
+ gtw->setColumnWidth(name_col_, one_em * 10);
+ gtw->setColumnWidth(dfilter_col_, one_em * 10);
+ gtw->setColumnWidth(color_col_, one_em * 2.5);
+ gtw->setColumnWidth(style_col_, one_em * 5.5);
+ gtw->setColumnWidth(yaxis_col_, one_em * 6.5);
+ gtw->setColumnWidth(yfield_col_, one_em * 6);
+ gtw->setColumnWidth(sma_period_col_, one_em * 6);
+
+ connect(wsApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(focusChanged(QWidget*,QWidget*)));
+ connect(iop, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
+ connect(iop, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
+ connect(iop, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
+ disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+}
+
+IOGraphDialog::~IOGraphDialog()
+{
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ IOGraph *iog = qvariant_cast<IOGraph *>(ui->graphTreeWidget->topLevelItem(i)->data(name_col_, Qt::UserRole));
+ delete iog;
+ }
+ delete ui;
+}
+
+void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, int color_idx, IOGraph::PlotStyles style, io_graph_item_unit_t value_units, QString yfield, int moving_average)
+{
+ QTreeWidgetItem *ti = new QTreeWidgetItem();
+ ui->graphTreeWidget->addTopLevelItem(ti);
+
+ IOGraph *iog = new IOGraph(ui->ioPlot);
+ ti->setData(name_col_, Qt::UserRole, qVariantFromValue(iog));
+ ti->setCheckState(name_col_, checked ? Qt::Checked : Qt::Unchecked);
+ ti->setText(name_col_, name);
+ ti->setText(dfilter_col_, dfilter);
+ color_idx = color_idx % colors_.size();
+ ti->setData(color_col_, Qt::UserRole, color_idx);
+ ti->setIcon(color_col_, graphColorIcon(color_idx));
+ ti->setText(style_col_, plot_style_to_name_[style]);
+ ti->setData(style_col_, Qt::UserRole, style);
+ ti->setText(yaxis_col_, value_unit_to_name_[value_units]);
+ ti->setData(yaxis_col_, Qt::UserRole, value_units);
+ ti->setText(yfield_col_, yfield);
+ ti->setText(sma_period_col_, moving_average_to_name_[moving_average]);
+ ti->setData(sma_period_col_, Qt::UserRole, moving_average);
+
+ connect(this, SIGNAL(recalcGraphData(capture_file *)), iog, SLOT(recalcGraphData(capture_file *)));
+ connect(iog, SIGNAL(requestRetap()), this, SLOT(scheduleRetap()));
+ connect(iog, SIGNAL(requestRecalc()), this, SLOT(scheduleRecalc()));
+ connect(iog, SIGNAL(requestReplot()), this, SLOT(scheduleReplot()));
+
+ syncGraphSettings(ti);
+ if (iog->visible()) {
+ scheduleRetap();
+ }
+}
+
+void IOGraphDialog::addGraph(bool copy_from_current)
+{
+ QTreeWidgetItem *cur_ti = NULL;
+
+ if (copy_from_current) {
+ cur_ti = ui->graphTreeWidget->currentItem();
+ }
+
+ if (copy_from_current && cur_ti) {
+ addGraph(cur_ti->checkState(name_col_) == Qt::Checked,
+ cur_ti->text(name_col_),
+ cur_ti->text(dfilter_col_),
+ cur_ti->data(color_col_, Qt::UserRole).toInt(),
+ (IOGraph::PlotStyles)cur_ti->data(style_col_, Qt::UserRole).toInt(),
+ (io_graph_item_unit_t)cur_ti->data(yaxis_col_, Qt::UserRole).toInt(),
+ cur_ti->text(yfield_col_),
+ cur_ti->data(sma_period_col_, Qt::UserRole).toInt());
+ } else {
+ addDefaultGraph(false);
+ }
+}
+
+void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
+{
+ switch (idx % 2) {
+ case 0:
+ addGraph(enabled, tr("All packets"), QString(), ui->graphTreeWidget->topLevelItemCount(),
+ IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), default_moving_average_);
+ break;
+ default:
+ addGraph(enabled, tr("TCP errors"), "tcp.analysis.flags", ui->graphTreeWidget->topLevelItemCount(),
+ IOGraph::psBar, IOG_ITEM_UNIT_PACKETS, QString(), default_moving_average_);
+ break;
+ }
+}
+
+// Sync the settings from a graphTreeWidget item to its IOGraph.
+// Disables the graph if any errors are found.
+void IOGraphDialog::syncGraphSettings(QTreeWidgetItem *item)
+{
+ if (!item) return;
+ IOGraph *iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ if (!iog) return;
+
+ bool visible = item->checkState(name_col_) == Qt::Checked;
+ bool retap = !iog->visible() && visible;
+
+ iog->setName(item->text(name_col_));
+
+ iog->setFilter(item->text(dfilter_col_));
+ iog->setColor(colors_[item->data(color_col_, Qt::UserRole).toInt() % colors_.size()]);
+ iog->setPlotStyle(item->data(style_col_, Qt::UserRole).toInt());
+
+ iog->setValueUnits(item->data(yaxis_col_, Qt::UserRole).toInt());
+ iog->setValueUnitField(item->text(yfield_col_));
+
+ iog->moving_avg_period_ = item->data(sma_period_col_, Qt::UserRole).toUInt();
+
+ iog->setInterval(ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt());
+
+ ui->graphTreeWidget->blockSignals(true); // setFlags emits itemChanged
+ if (!iog->configError().isEmpty()) {
+ hint_err_ = iog->configError();
+ visible = false;
+ retap = false;
+ // On OS X the "not user checkable" checkbox isn't obviously disabled.
+ // For now show it as partially checked.
+ item->setCheckState(name_col_, Qt::PartiallyChecked);
+ item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
+ } else {
+ item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+ }
+ ui->graphTreeWidget->blockSignals(false);
+
+ iog->setVisible(visible);
+
+ getGraphInfo();
+ mouseMoved(NULL); // Update hint
+ updateLegend();
+
+ if (visible) {
+ if (retap) {
+ scheduleRetap();
+ } else {
+ scheduleReplot();
+ }
+ }
+}
+
+void IOGraphDialog::setCaptureFile(capture_file *cf)
+{
+ if (!cf) { // We only want to know when the file closes.
+ cap_file_ = NULL;
+ }
+}
+
+void IOGraphDialog::scheduleReplot(bool now)
+{
+ need_replot_ = true;
+ if (now) updateStatistics();
+}
+
+void IOGraphDialog::scheduleRecalc(bool now)
+{
+ need_recalc_ = true;
+ if (now) updateStatistics();
+}
+
+void IOGraphDialog::scheduleRetap(bool now)
+{
+ need_retap_ = true;
+ if (now) updateStatistics();
+}
+
+void IOGraphDialog::keyPressEvent(QKeyEvent *event)
+{
+ int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
+
+ switch(event->key()) {
+ case Qt::Key_Minus:
+ case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
+ case Qt::Key_O: // GTK+
+ case Qt::Key_R:
+ zoomAxes(false);
+ break;
+ case Qt::Key_Plus:
+ case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
+ case Qt::Key_I: // GTK+
+ zoomAxes(true);
+ break;
+
+ case Qt::Key_Right:
+ case Qt::Key_L:
+ panAxes(pan_pixels, 0);
+ break;
+ case Qt::Key_Left:
+ case Qt::Key_H:
+ panAxes(-1 * pan_pixels, 0);
+ break;
+ case Qt::Key_Up:
+ case Qt::Key_K:
+ panAxes(0, pan_pixels);
+ break;
+ case Qt::Key_Down:
+ case Qt::Key_J:
+ panAxes(0, -1 * pan_pixels);
+ break;
+
+ case Qt::Key_Space:
+ toggleTracerStyle();
+ break;
+
+ case Qt::Key_0:
+ case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
+ case Qt::Key_Home:
+ resetAxes();
+ break;
+
+ case Qt::Key_G:
+ on_actionGoToPacket_triggered();
+ break;
+ case Qt::Key_T:
+ on_actionToggleTimeOrigin_triggered();
+ break;
+ case Qt::Key_Z:
+ on_actionDragZoom_triggered();
+ break;
+ }
+
+ QDialog::keyPressEvent(event);
+}
+
+void IOGraphDialog::reject()
+{
+ // Catch escape keys.
+ QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ << yfield_line_edit_;
+
+ foreach (QWidget *w, editors) {
+ if (w && w->hasFocus()) {
+ ui->graphTreeWidget->setFocus(); // Trigger itemEditingFinished
+ return;
+ }
+ }
+
+ QList<QComboBox *>combos = QList<QComboBox *>() << color_combo_box_ << style_combo_box_ <<
+ yaxis_combo_box_ << sma_combo_box_;
+ foreach (QComboBox *cb, combos) {
+ if (cb && (cb->hasFocus() || cb->view()->hasFocus())) {
+ ui->graphTreeWidget->setFocus(); // Trigger itemEditingFinished
+ return;
+ }
+ }
+
+ if (iog_uat_) {
+ uat_clear(iog_uat_);
+
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
+ IOGraph *iog = NULL;
+ if (item) {
+ iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ io_graph_settings_t iogs;
+ QColor color(iog->color());
+ iogs.enabled = iog->visible() ? 1 : 0;
+ iogs.name = qstring_strdup(iog->name());
+ iogs.dfilter = qstring_strdup(iog->filter());
+ iogs.color = qstring_strdup(color.name());
+ iogs.style = qstring_strdup(plot_style_to_name_[(IOGraph::PlotStyles)item->data(style_col_, Qt::UserRole).toInt()]);
+ iogs.yaxis = qstring_strdup(iog->valueUnitLabel());
+ iogs.yfield = qstring_strdup(iog->valueUnitField());
+ iogs.sma_period = iog->movingAveragePeriod();
+ uat_add_record(iog_uat_, &iogs, TRUE);
+ io_graph_free_cb(&iogs);
+ }
+ }
+ const char* err = NULL;
+ uat_save(iog_uat_, &err);
+ }
+
+ QDialog::reject();
+}
+
+void IOGraphDialog::zoomAxes(bool in)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
+ double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
+
+ auto_axes_ = false;
+
+ if (!in) {
+ h_factor = pow(h_factor, -1);
+ v_factor = pow(v_factor, -1);
+ }
+
+ iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
+ iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
+ iop->replot();
+}
+
+void IOGraphDialog::panAxes(int x_pixels, int y_pixels)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ double h_pan = 0.0;
+ double v_pan = 0.0;
+
+ auto_axes_ = false;
+
+ h_pan = iop->xAxis->range().size() * x_pixels / iop->xAxis->axisRect()->width();
+ v_pan = iop->yAxis->range().size() * y_pixels / iop->yAxis->axisRect()->height();
+ // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
+ if (h_pan) {
+ iop->xAxis->moveRange(h_pan);
+ iop->replot();
+ }
+ if (v_pan) {
+ iop->yAxis->moveRange(v_pan);
+ iop->replot();
+ }
+}
+
+QIcon IOGraphDialog::graphColorIcon(int color_idx)
+{
+ int h = fontMetrics().height() * 3 / 4;
+ QPixmap pm(h * 2, h);
+ pm.fill(colors_[color_idx % colors_.size()]);
+ return QIcon(pm);
+}
+
+void IOGraphDialog::toggleTracerStyle(bool force_default)
+{
+ if (!tracer_->visible() && !force_default) return;
+ if (!ui->ioPlot->graph(0)) return;
+
+ QPen sp_pen = ui->ioPlot->graph(0)->pen();
+ QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
+ QPen tr_pen = QPen(tracer_->pen());
+ QColor tr_color = sp_pen.color();
+
+ if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
+ tstyle = QCPItemTracer::tsCircle;
+ tr_color.setAlphaF(1.0);
+ tr_pen.setWidthF(1.5);
+ } else {
+ tr_color.setAlphaF(0.5);
+ tr_pen.setWidthF(1.0);
+ }
+
+ tracer_->setStyle(tstyle);
+ tr_pen.setColor(tr_color);
+ tracer_->setPen(tr_pen);
+ ui->ioPlot->replot();
+}
+
+// Scan through our graphs and gather information.
+// QCPItemTracers can only be associated with QCPGraphs. Find the first one
+// and associate it with our tracer. Set bar stacking order while we're here.
+void IOGraphDialog::getGraphInfo()
+{
+ base_graph_ = NULL;
+ QCPBars *prev_bars = NULL;
+ start_time_ = 0.0;
+
+ tracer_->setGraph(NULL);
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
+ IOGraph *iog = NULL;
+ if (item) {
+ iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ QCPGraph *graph = iog->graph();
+ QCPBars *bars = iog->bars();
+ int style = item->data(style_col_, Qt::UserRole).toInt();
+ if (graph && !base_graph_) {
+ base_graph_ = graph;
+ } else if (bars && style == IOGraph::psStackedBar && iog->visible()) {
+ bars->moveBelow(NULL); // Remove from existing stack
+ bars->moveBelow(prev_bars);
+ prev_bars = bars;
+ }
+ if (iog->visible()) {
+ double iog_start = iog->startOffset();
+ if (start_time_ == 0.0 || iog_start < start_time_) {
+ start_time_ = iog_start;
+ }
+ }
+ }
+ }
+ if (base_graph_ && base_graph_->data()->size() > 0) {
+ tracer_->setGraph(base_graph_);
+ tracer_->setVisible(true);
+ }
+}
+
+void IOGraphDialog::updateLegend()
+{
+ QCustomPlot *iop = ui->ioPlot;
+ QSet<QString> vu_label_set;
+
+ iop->legend->setVisible(false);
+ iop->yAxis->setLabel(QString());
+
+ // Find unique labels
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(i);
+ IOGraph *iog = NULL;
+ if (ti) {
+ iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ vu_label_set.insert(iog->valueUnitLabel());
+ }
+ }
+
+ // Nothing.
+ if (vu_label_set.size() < 1) {
+ return;
+ }
+
+ // All the same. Use the Y Axis label.
+ if (vu_label_set.size() == 1) {
+ iop->yAxis->setLabel(vu_label_set.values()[0]);
+ return;
+ }
+
+ // Differing labels. Create a legend.
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(i);
+ IOGraph *iog = NULL;
+ if (ti) {
+ iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ iog->addToLegend();
+ }
+ }
+ iop->legend->setVisible(true);
+}
+
+QRectF IOGraphDialog::getZoomRanges(QRect zoom_rect)
+{
+ QRectF zoom_ranges = QRectF();
+
+ if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) {
+ return zoom_ranges;
+ }
+
+ QCustomPlot *iop = ui->ioPlot;
+ QRect zr = zoom_rect.normalized();
+ QRect ar = iop->axisRect()->rect();
+ if (ar.intersects(zr)) {
+ QRect zsr = ar.intersected(zr);
+ zoom_ranges.setX(iop->xAxis->range().lower
+ + iop->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
+ zoom_ranges.setWidth(iop->xAxis->range().size() * zsr.width() / ar.width());
+
+ // QRects grow down
+ zoom_ranges.setY(iop->yAxis->range().lower
+ + iop->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
+ zoom_ranges.setHeight(iop->yAxis->range().size() * zsr.height() / ar.height());
+ }
+ return zoom_ranges;
+}
+
+void IOGraphDialog::graphClicked(QMouseEvent *event)
+{
+ QCustomPlot *iop = ui->ioPlot;
+
+ if (event->button() == Qt::RightButton) {
+ // XXX We should find some way to get ioPlot to handle a
+ // contextMenuEvent instead.
+ ctx_menu_.exec(event->globalPos());
+ } else if (mouse_drags_) {
+ if (iop->axisRect()->rect().contains(event->pos())) {
+ iop->setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+ on_actionGoToPacket_triggered();
+ } else {
+ if (!rubber_band_) {
+ rubber_band_ = new QRubberBand(QRubberBand::Rectangle, iop);
+ }
+ rb_origin_ = event->pos();
+ rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
+ rubber_band_->show();
+ }
+ iop->setFocus();
+}
+
+void IOGraphDialog::mouseMoved(QMouseEvent *event)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ QString hint;
+ Qt::CursorShape shape = Qt::ArrowCursor;
+
+ if (!hint_err_.isEmpty()) {
+ hint += QString("<b>%1</b> ").arg(hint_err_);
+ }
+ if (event) {
+ if (event->buttons().testFlag(Qt::LeftButton)) {
+ if (mouse_drags_) {
+ shape = Qt::ClosedHandCursor;
+ } else {
+ shape = Qt::CrossCursor;
+ }
+ } else if (iop->axisRect()->rect().contains(event->pos())) {
+ if (mouse_drags_) {
+ shape = Qt::OpenHandCursor;
+ } else {
+ shape = Qt::CrossCursor;
+ }
+ }
+ iop->setCursor(QCursor(shape));
+ }
+
+ if (mouse_drags_) {
+ double ts = 0;
+ packet_num_ = 0;
+ int interval_packet = -1;
+
+ if (event && tracer_->graph()) {
+ tracer_->setGraphKey(iop->xAxis->pixelToCoord(event->pos().x()));
+ ts = tracer_->position->key();
+
+ QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(0);
+ IOGraph *iog = NULL;
+ if (ti) {
+ iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ interval_packet = iog->packetFromTime(ts);
+ }
+ }
+
+ if (interval_packet < 0) {
+ hint += tr("Hover over the graph for details.");
+ } else {
+ QString msg = tr("No packets in interval");
+ QString val;
+ if (interval_packet > 0) {
+ packet_num_ = (guint32) interval_packet;
+ msg = tr("%1 %2")
+ .arg(cap_file_ ? tr("Click to select packet") : tr("Packet"))
+ .arg(packet_num_);
+ val = " = " + QString::number(tracer_->position->value(), 'g', 4);
+ }
+ hint += tr("%1 (%2s%3).")
+ .arg(msg)
+ .arg(QString::number(ts, 'g', 4))
+ .arg(val);
+ }
+ iop->replot();
+ } else {
+ if (event && rubber_band_ && rubber_band_->isVisible()) {
+ rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
+ QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
+ if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
+ hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
+ .arg(zoom_ranges.x())
+ .arg(zoom_ranges.x() + zoom_ranges.width())
+ .arg(zoom_ranges.y())
+ .arg(zoom_ranges.y() + zoom_ranges.height());
+ } else {
+ hint += tr("Unable to select range.");
+ }
+ } else {
+ hint += tr("Click to select a portion of the graph.");
+ }
+ }
+
+ hint.prepend("<small><i>");
+ hint.append("</i></small>");
+ ui->hintLabel->setText(hint);
+}
+
+void IOGraphDialog::mouseReleased(QMouseEvent *event)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ auto_axes_ = false;
+ if (rubber_band_) {
+ rubber_band_->hide();
+ if (!mouse_drags_) {
+ QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
+ if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
+ iop->xAxis->setRangeLower(zoom_ranges.x());
+ iop->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
+ iop->yAxis->setRangeLower(zoom_ranges.y());
+ iop->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
+ iop->replot();
+ }
+ }
+ } else if (iop->cursor().shape() == Qt::ClosedHandCursor) {
+ iop->setCursor(QCursor(Qt::OpenHandCursor));
+ }
+}
+
+void IOGraphDialog::focusChanged(QWidget *previous, QWidget *current)
+{
+ Q_UNUSED(previous);
+ QTreeWidgetItem *item = ui->graphTreeWidget->currentItem();
+ if (!item) {
+ return;
+ }
+
+ // If we navigated away from an editing session, clear it.
+ QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ <<
+ color_combo_box_ << style_combo_box_ <<
+ yaxis_combo_box_ << yfield_line_edit_ <<
+ sma_combo_box_;
+ bool edit_active = false;
+ foreach (QWidget *w, editors) {
+ if (w) {
+ edit_active = true;
+ }
+ }
+ if (!edit_active) {
+ return;
+ }
+ editors.append(color_combo_box_->view());
+ editors.append(style_combo_box_->view());
+ editors.append(yaxis_combo_box_->view());
+ editors.append(sma_combo_box_->view());
+
+ if (! editors.contains(current)) {
+ itemEditingFinished(item);
+ }
+}
+
+void IOGraphDialog::activateLastItem()
+{
+ int last_idx = ui->graphTreeWidget->topLevelItemCount() - 1;
+ if (last_idx < 0) return;
+
+ QTreeWidgetItem *last_item = ui->graphTreeWidget->invisibleRootItem()->child(last_idx);
+ if (!last_item) return;
+
+ ui->graphTreeWidget->setCurrentItem(last_item);
+ on_graphTreeWidget_itemActivated(last_item, name_col_);
+}
+
+void IOGraphDialog::resetAxes()
+{
+ QCustomPlot *iop = ui->ioPlot;
+ QCPRange x_range = iop->xAxis->scaleType() == QCPAxis::stLogarithmic ?
+ iop->xAxis->range().sanitizedForLogScale() : iop->xAxis->range();
+
+ double pixel_pad = 10.0; // per side
+
+ iop->rescaleAxes(true);
+
+ double axis_pixels = iop->xAxis->axisRect()->width();
+ iop->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
+
+ axis_pixels = iop->yAxis->axisRect()->height();
+ iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, iop->yAxis->range().center());
+
+ auto_axes_ = true;
+ iop->replot();
+}
+
+void IOGraphDialog::updateStatistics()
+{
+ if (!isVisible()) return;
+
+ if (need_retap_) {
+ need_retap_ = false;
+ cf_retap_packets(cap_file_);
+ ui->ioPlot->setFocus();
+ } else {
+ if (need_recalc_) {
+ need_recalc_ = false;
+ need_replot_ = true;
+ emit recalcGraphData(cap_file_);
+ if (!tracer_->graph()) {
+ if (base_graph_ && base_graph_->data()->size() > 0) {
+ tracer_->setGraph(base_graph_);
+ tracer_->setVisible(true);
+ } else {
+ tracer_->setVisible(false);
+ }
+ }
+ }
+ if (need_replot_) {
+ need_replot_ = false;
+ if (auto_axes_) {
+ resetAxes();
+ }
+ ui->ioPlot->replot();
+ }
+ }
+}
+
+// We're done editing a treewidgetitem. Set its values based on its
+// widgets, remove each widget, then sync with our associated graph.
+void IOGraphDialog::itemEditingFinished(QTreeWidgetItem *item)
+{
+ if (item) {
+ bool recalc = false;
+ // Don't force a retap here. Disable the graph instead.
+ Qt::CheckState check_state = item->checkState(name_col_);
+ hint_err_.clear();
+ io_graph_item_unit_t item_unit = IOG_ITEM_UNIT_PACKETS;
+ QString field_name;
+
+ if (name_line_edit_) {
+ item->setText(name_col_, name_line_edit_->text());
+ }
+ if (dfilter_line_edit_) {
+ QString df = dfilter_line_edit_->text();
+ if (item->text(dfilter_col_).compare(df)) {
+ check_state = Qt::Unchecked;
+ }
+ item->setText(dfilter_col_, df);
+ }
+ if (color_combo_box_) {
+ int index = color_combo_box_->currentIndex();
+ item->setData(color_col_, Qt::UserRole, index);
+ item->setIcon(color_col_, graphColorIcon(index));
+ }
+ if (style_combo_box_) {
+ IOGraph::PlotStyles ps = IOGraph::psLine;
+ int index = style_combo_box_->currentIndex();
+ if (index < plot_style_to_name_.size()) {
+ ps = plot_style_to_name_.keys()[index];
+ }
+ item->setText(style_col_, plot_style_to_name_[ps]);
+ item->setData(style_col_, Qt::UserRole, ps);
+ }
+ if (yaxis_combo_box_) {
+ int index = yaxis_combo_box_->currentIndex();
+ if (index != item->data(yaxis_col_, Qt::UserRole).toInt()) {
+ if (index <= IOG_ITEM_UNIT_CALC_SUM) {
+ recalc = true;
+ } else {
+ check_state = Qt::Unchecked;
+ }
+ }
+ if (index < value_unit_to_name_.size()) {
+ item_unit = value_unit_to_name_.keys()[index];
+ }
+ item->setText(yaxis_col_, value_unit_to_name_[item_unit]);
+ item->setData(yaxis_col_, Qt::UserRole, item_unit);
+ }
+ if (yfield_line_edit_) {
+ if (item->text(yfield_col_).compare(yfield_line_edit_->text())) {
+ check_state = Qt::Unchecked;
+ }
+ item->setText(yfield_col_, yfield_line_edit_->text());
+ field_name = yfield_line_edit_->text();
+ }
+ if (sma_combo_box_) {
+ int index = sma_combo_box_->currentIndex();
+ if (index != item->data(sma_period_col_, Qt::UserRole).toInt()) {
+ recalc = true;
+ }
+ QString text = sma_combo_box_->itemText(index);
+ int sma = sma_combo_box_->itemData(index, Qt::UserRole).toInt();
+ item->setText(sma_period_col_, text);
+ item->setData(sma_period_col_, Qt::UserRole, sma);
+ }
+
+ for (int col = 0; col < num_cols_; col++) {
+ QWidget *w = ui->graphTreeWidget->itemWidget(item, col);
+ if (w) {
+ ui->graphTreeWidget->removeItemWidget(item, col);
+ delete w;
+ }
+ }
+
+ item->setCheckState(name_col_, check_state);
+ syncGraphSettings(item);
+
+ if (recalc) {
+ scheduleRecalc(true);
+ } else {
+ scheduleReplot(true);
+ }
+ }
+}
+
+void IOGraphDialog::loadProfileGraphs()
+{
+ if (iog_uat_) return;
+
+ iog_uat_ = uat_new("I/O Graphs",
+ sizeof(io_graph_settings_t),
+ "io_graphs",
+ TRUE,
+ &iog_settings_,
+ &num_io_graphs_,
+ 0, /* doesn't affect anything that requires a GUI update */
+ "ChStatIOGraphs",
+ io_graph_copy_cb,
+ NULL,
+ io_graph_free_cb,
+ NULL,
+ io_graph_fields);
+ const char* err = NULL;
+ uat_load(iog_uat_, &err);
+}
+
+
+// Slots
+
+void IOGraphDialog::lineEditDestroyed()
+{
+ if (QObject::sender() == name_line_edit_) {
+ name_line_edit_ = NULL;
+ } else if (QObject::sender() == dfilter_line_edit_) {
+ dfilter_line_edit_ = NULL;
+ } else if (QObject::sender() == yfield_line_edit_) {
+ yfield_line_edit_ = NULL;
+ }
+}
+
+void IOGraphDialog::comboDestroyed()
+{
+ if (QObject::sender() == color_combo_box_) {
+ color_combo_box_ = NULL;
+ } else if (QObject::sender() == style_combo_box_) {
+ style_combo_box_ = NULL;
+ } else if (QObject::sender() == yaxis_combo_box_) {
+ yaxis_combo_box_ = NULL;
+ } else if (QObject::sender() == sma_combo_box_) {
+ sma_combo_box_ = NULL;
+ }
+}
+
+void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int index)
+{
+ Q_UNUSED(index);
+
+ int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
+ bool need_retap = false;
+
+ for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
+ QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
+ IOGraph *iog = NULL;
+ if (item) {
+ iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
+ if (iog) {
+ iog->setInterval(interval);
+ if (iog->visible()) {
+ need_retap = true;
+ }
+ }
+ }
+ }
+
+ if (need_retap) {
+ scheduleRetap(true);
+ }
+}
+
+void IOGraphDialog::on_todCheckBox_toggled(bool checked)
+{
+ double orig_start = start_time_;
+ bool orig_auto = auto_axes_;
+
+ ui->ioPlot->xAxis->setTickLabelType(checked ? QCPAxis::ltDateTime : QCPAxis::ltNumber);
+ auto_axes_ = false;
+ scheduleRecalc(true);
+ auto_axes_ = orig_auto;
+ getGraphInfo();
+ ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
+}
+
+void IOGraphDialog::on_graphTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
+{
+ Q_UNUSED(current);
+
+ if (previous && ui->graphTreeWidget->itemWidget(previous, name_col_)) {
+ itemEditingFinished(previous);
+ }
+}
+
+void IOGraphDialog::on_graphTreeWidget_itemActivated(QTreeWidgetItem *item, int column)
+{
+ if (!item || name_line_edit_) return;
+
+ QWidget *editor = NULL;
+ int cur_idx;
+
+ name_line_edit_ = new QLineEdit();
+ name_line_edit_->setText(item->text(name_col_));
+ connect(name_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
+
+ dfilter_line_edit_ = new SyntaxLineEdit();
+ connect(dfilter_line_edit_, SIGNAL(textChanged(QString)),
+ dfilter_line_edit_, SLOT(checkDisplayFilter(QString)));
+ connect(dfilter_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
+ dfilter_line_edit_->setText(item->text(dfilter_col_));
+
+ color_combo_box_ = new QComboBox();
+ cur_idx = item->data(color_col_, Qt::UserRole).toInt();
+ for (int i = 0; i < colors_.size(); i++) {
+ color_combo_box_->addItem(QString());
+ color_combo_box_->setItemIcon(i, graphColorIcon(i));
+ if (i == cur_idx) {
+ color_combo_box_->setCurrentIndex(i);
+ }
+ }
+ item->setIcon(color_col_, QIcon());
+ color_combo_box_->setFocusPolicy(Qt::StrongFocus);
+ connect(color_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
+
+ style_combo_box_ = new QComboBox();
+ cur_idx = item->data(style_col_, Qt::UserRole).toInt();
+ for (int i = 0; i < plot_style_to_name_.size(); i++) {
+ IOGraph::PlotStyles ps = plot_style_to_name_.keys()[i];
+ style_combo_box_->addItem(plot_style_to_name_[ps], ps);
+ if (ps == cur_idx) {
+ style_combo_box_->setCurrentIndex(i);
+ }
+ }
+ style_combo_box_->setFocusPolicy(Qt::StrongFocus);
+ connect(style_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
+
+ yaxis_combo_box_ = new QComboBox();
+ cur_idx = item->data(yaxis_col_, Qt::UserRole).toInt();
+ for (int i = 0; i < value_unit_to_name_.size(); i++) {
+ io_graph_item_unit_t vu = value_unit_to_name_.keys()[i];
+ yaxis_combo_box_->addItem(value_unit_to_name_[vu], vu);
+ if (vu == cur_idx) {
+ yaxis_combo_box_->setCurrentIndex(i);
+ }
+ }
+ yaxis_combo_box_->setFocusPolicy(Qt::StrongFocus);
+ connect(yaxis_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
+
+ yfield_line_edit_ = new SyntaxLineEdit();
+ connect(yfield_line_edit_, SIGNAL(textChanged(QString)),
+ yfield_line_edit_, SLOT(checkFieldName(QString)));
+ connect(yfield_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
+ yfield_line_edit_->setText(item->text(yfield_col_));
+
+ sma_combo_box_ = new QComboBox();
+ cur_idx = item->data(sma_period_col_, Qt::UserRole).toInt();
+ for (int i = 0; i < moving_average_to_name_.size(); i++) {
+ int sma = moving_average_to_name_.keys()[i];
+ sma_combo_box_->addItem(moving_average_to_name_[sma], sma);
+ if (sma == cur_idx) {
+ sma_combo_box_->setCurrentIndex(i);
+ }
+ }
+ sma_combo_box_->setFocusPolicy(Qt::StrongFocus);
+ connect(sma_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
+
+ switch (column) {
+ case name_col_:
+ editor = name_line_edit_;
+ name_line_edit_->selectAll();
+ break;
+ case dfilter_col_:
+ editor = dfilter_line_edit_;
+ dfilter_line_edit_->selectAll();
+ break;
+ case color_col_:
+ {
+ editor = color_combo_box_;
+ break;
+ }
+ case style_col_:
+ {
+ editor = style_combo_box_;
+ break;
+ }
+ case yaxis_col_:
+ {
+ editor = yaxis_combo_box_;
+ break;
+ }
+ case yfield_col_:
+ editor = yfield_line_edit_;
+ yfield_line_edit_->selectAll();
+ break;
+ case sma_period_col_:
+ {
+ editor = sma_combo_box_;
+ break;
+ }
+ default:
+ return;
+ }
+
+ QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ <<
+ color_combo_box_ << style_combo_box_ <<
+ yaxis_combo_box_ << yfield_line_edit_ <<
+ sma_combo_box_;
+ int cur_col = name_col_;
+ QWidget *prev_widget = ui->graphTreeWidget;
+ foreach (QWidget *editor, editors) {
+ QFrame *edit_frame = new QFrame();
+ QHBoxLayout *hb = new QHBoxLayout();
+ QSpacerItem *spacer = new QSpacerItem(5, 10);
+
+ hb->addWidget(editor, 0);
+ hb->addSpacerItem(spacer);
+ hb->setStretch(1, 1);
+ hb->setContentsMargins(0, 0, 0, 0);
+
+ edit_frame->setLineWidth(0);
+ edit_frame->setFrameStyle(QFrame::NoFrame);
+ edit_frame->setLayout(hb);
+ ui->graphTreeWidget->setItemWidget(item, cur_col, edit_frame);
+ setTabOrder(prev_widget, editor);
+ prev_widget = editor;
+ cur_col++;
+ }
+
+// setTabOrder(prev_widget, ui->graphTreeWidget);
+ editor->setFocus();
+}
+
+void IOGraphDialog::on_graphTreeWidget_itemSelectionChanged()
+{
+ if (ui->graphTreeWidget->selectedItems().length() > 0) {
+ ui->deleteToolButton->setEnabled(true);
+ ui->copyToolButton->setEnabled(true);
+ } else {
+ ui->deleteToolButton->setEnabled(false);
+ ui->copyToolButton->setEnabled(false);
+ }
+}
+
+void IOGraphDialog::on_graphTreeWidget_itemChanged(QTreeWidgetItem *item, int column)
+{
+ if (!item) {
+ return;
+ }
+
+ if (column == name_col_ && !name_line_edit_) {
+ syncGraphSettings(item);
+ }
+
+}
+
+void IOGraphDialog::on_resetButton_clicked()
+{
+ resetAxes();
+}
+
+void IOGraphDialog::on_newToolButton_clicked()
+{
+ addGraph();
+}
+
+void IOGraphDialog::on_deleteToolButton_clicked()
+{
+ QTreeWidgetItem *item = ui->graphTreeWidget->currentItem();
+ if (!item) return;
+
+ IOGraph *iog = qvariant_cast<IOGraph *>(item->data(name_col_, Qt::UserRole));
+ delete iog;
+
+ delete item;
+
+ // We should probably be smarter about this.
+ hint_err_.clear();
+ mouseMoved(NULL);
+}
+
+void IOGraphDialog::on_copyToolButton_clicked()
+{
+ addGraph(true);
+}
+
+void IOGraphDialog::on_dragRadioButton_toggled(bool checked)
+{
+ if (checked) mouse_drags_ = true;
+ ui->ioPlot->setInteractions(
+ QCP::iRangeDrag |
+ QCP::iRangeZoom
+ );
+}
+
+void IOGraphDialog::on_zoomRadioButton_toggled(bool checked)
+{
+ if (checked) mouse_drags_ = false;
+ ui->ioPlot->setInteractions(0);
+}
+
+void IOGraphDialog::on_logCheckBox_toggled(bool checked)
+{
+ QCustomPlot *iop = ui->ioPlot;
+
+ iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
+ iop->replot();
+}
+
+void IOGraphDialog::on_actionReset_triggered()
+{
+ on_resetButton_clicked();
+}
+
+void IOGraphDialog::on_actionZoomIn_triggered()
+{
+ zoomAxes(true);
+}
+
+void IOGraphDialog::on_actionZoomOut_triggered()
+{
+ zoomAxes(false);
+}
+
+void IOGraphDialog::on_actionMoveUp10_triggered()
+{
+ panAxes(0, 10);
+}
+
+void IOGraphDialog::on_actionMoveLeft10_triggered()
+{
+ panAxes(-10, 0);
+}
+
+void IOGraphDialog::on_actionMoveRight10_triggered()
+{
+ panAxes(10, 0);
+}
+
+void IOGraphDialog::on_actionMoveDown10_triggered()
+{
+ panAxes(0, -10);
+}
+
+void IOGraphDialog::on_actionMoveUp1_triggered()
+{
+ panAxes(0, 1);
+}
+
+void IOGraphDialog::on_actionMoveLeft1_triggered()
+{
+ panAxes(-1, 0);
+}
+
+void IOGraphDialog::on_actionMoveRight1_triggered()
+{
+ panAxes(1, 0);
+}
+
+void IOGraphDialog::on_actionMoveDown1_triggered()
+{
+ panAxes(0, -1);
+}
+
+void IOGraphDialog::on_actionGoToPacket_triggered()
+{
+ if (tracer_->visible() && cap_file_ && packet_num_ > 0) {
+ emit goToPacket(packet_num_);
+ }
+}
+
+void IOGraphDialog::on_actionDragZoom_triggered()
+{
+ if (mouse_drags_) {
+ ui->zoomRadioButton->toggle();
+ } else {
+ ui->dragRadioButton->toggle();
+ }
+}
+
+void IOGraphDialog::on_actionToggleTimeOrigin_triggered()
+{
+
+}
+
+void IOGraphDialog::on_actionCrosshairs_triggered()
+{
+
+}
+
+void IOGraphDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_STATS_IO_GRAPH_DIALOG);
+}
+
+// XXX - Copied from tcp_stream_dialog. This should be common code.
+void IOGraphDialog::on_buttonBox_accepted()
+{
+ QString file_name, extension;
+ QDir path(wsApp->lastOpenDir());
+ QString pdf_filter = tr("Portable Document Format (*.pdf)");
+ QString png_filter = tr("Portable Network Graphics (*.png)");
+ QString bmp_filter = tr("Windows Bitmap (*.bmp)");
+ // Gaze upon my beautiful graph with lossy artifacts!
+ QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
+ QString filter = QString("%1;;%2;;%3;;%4")
+ .arg(pdf_filter)
+ .arg(png_filter)
+ .arg(bmp_filter)
+ .arg(jpeg_filter);
+
+ QString save_file = path.canonicalPath();
+ if (cap_file_) {
+ save_file += QString("/%1").arg(cf_get_display_name(cap_file_));
+ }
+ file_name = QFileDialog::getSaveFileName(this, tr("Wireshark: Save Graph As..."),
+ save_file, filter, &extension);
+
+ if (file_name.length() > 0) {
+ bool save_ok = false;
+ if (extension.compare(pdf_filter) == 0) {
+ save_ok = ui->ioPlot->savePdf(file_name);
+ } else if (extension.compare(png_filter) == 0) {
+ save_ok = ui->ioPlot->savePng(file_name);
+ } else if (extension.compare(bmp_filter) == 0) {
+ save_ok = ui->ioPlot->saveBmp(file_name);
+ } else if (extension.compare(jpeg_filter) == 0) {
+ save_ok = ui->ioPlot->saveJpg(file_name);
+ }
+ // else error dialog?
+ if (save_ok) {
+ path = QDir(file_name);
+ wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
+ }
+ }
+}
+
+// IOGraph
+
+IOGraph::IOGraph(QCustomPlot *parent) :
+ parent_(parent),
+ visible_(false),
+ graph_(NULL),
+ bars_(NULL),
+ hf_index_(-1),
+ cur_idx_(-1)
+{
+ Q_ASSERT(parent_ != NULL);
+ graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
+ Q_ASSERT(graph_ != NULL);
+
+ GString *error_string;
+ error_string = register_tap_listener("frame",
+ this,
+ "",
+ TL_REQUIRES_PROTO_TREE,
+ tapReset,
+ tapPacket,
+ tapDraw);
+ if (error_string) {
+// QMessageBox::critical(this, tr("%1 failed to register tap listener").arg(name_),
+// error_string->str);
+ g_string_free(error_string, TRUE);
+ }
+
+ setFilter(QString());
+}
+
+IOGraph::~IOGraph() {
+ remove_tap_listener(this);
+ if (graph_) {
+ parent_->removeGraph(graph_);
+ }
+ if (bars_) {
+ parent_->removePlottable(bars_);
+ }
+}
+
+// Construct a full filter string from the display filter and value unit / Y axis.
+// Check for errors and sets config_err_ if any are found.
+void IOGraph::setFilter(const QString &filter)
+{
+ GString *error_string;
+ QString full_filter(filter.trimmed());
+
+ config_err_.clear();
+
+ // Make sure we have a good display filter
+ if (!full_filter.isEmpty()) {
+ dfilter_t *dfilter;
+ bool status;
+ status = dfilter_compile(full_filter.toUtf8().constData(), &dfilter);
+ dfilter_free(dfilter);
+ if (!status) {
+ config_err_ = dfilter_error_msg;
+ filter_ = full_filter;
+ return;
+ }
+ }
+
+ // Check our value unit + field combo.
+ error_string = check_field_unit(vu_field_.toUtf8().constData(), NULL, val_units_);
+ if (error_string) {
+ config_err_ = error_string->str;
+ g_string_free(error_string, TRUE);
+ return;
+ }
+
+ // Make sure vu_field_ survives edt tree pruning by adding it to our filter
+ // expression.
+ if (val_units_ >= IOG_ITEM_UNIT_CALC_SUM && !vu_field_.isEmpty() && hf_index_ >= 0) {
+ if (full_filter.isEmpty()) {
+ full_filter = vu_field_;
+ } else {
+ full_filter += QString(" && (%1)").arg(vu_field_);
+ }
+ }
+
+ error_string = set_tap_dfilter(this, full_filter.toUtf8().constData());
+ if (error_string) {
+ config_err_ = error_string->str;
+ g_string_free(error_string, TRUE);
+ return;
+ } else {
+ if (filter_.compare(filter) && visible_) {
+ emit requestRetap();
+ }
+ filter_ = filter;
+ }
+}
+
+void IOGraph::applyCurrentColor()
+{
+ if (graph_) {
+ graph_->setPen(QPen(color_, graph_line_width_));
+ } else if (bars_) {
+ bars_->setPen(QPen(QBrush(colors_[0]), graph_line_width_)); // ...or omit it altogether?
+ bars_->setBrush(color_);
+ }
+}
+
+void IOGraph::setVisible(bool visible)
+{
+ bool old_visibility = visible_;
+ visible_ = visible;
+ if (graph_) {
+ graph_->setVisible(visible_);
+ }
+ if (bars_) {
+ bars_->setVisible(visible_);
+ }
+ if (old_visibility != visible_) {
+ emit requestReplot();
+ }
+}
+
+void IOGraph::setName(const QString &name)
+{
+ name_ = name;
+ if (graph_) {
+ graph_->setName(name_);
+ }
+ if (bars_) {
+ bars_->setName(name_);
+ }
+}
+
+QRgb IOGraph::color()
+{
+ return color_.color().rgb();
+}
+
+void IOGraph::setColor(const QRgb color)
+{
+ color_ = QBrush(color);
+ applyCurrentColor();
+}
+
+void IOGraph::setPlotStyle(int style)
+{
+ // Switch plottable if needed
+ switch (style) {
+ case psBar:
+ case psStackedBar:
+ if (graph_) {
+ bars_ = new QCPBars(parent_->xAxis, parent_->yAxis);
+ parent_->addPlottable(bars_);
+ parent_->removeGraph(graph_);
+ graph_ = NULL;
+ }
+ break;
+ default:
+ if (bars_) {
+ graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
+ parent_->removePlottable(bars_);
+ bars_ = NULL;
+ }
+ break;
+ }
+ setValueUnits(val_units_);
+
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsNone);
+ graph_->setScatterStyle(QCPScatterStyle::ssNone);
+ }
+ switch (style) {
+ case psLine:
+ graph_->setLineStyle(QCPGraph::lsLine);
+ break;
+ case psImpulse:
+ graph_->setLineStyle(QCPGraph::lsImpulse);
+ break;
+ case psDot:
+ graph_->setScatterStyle(QCPScatterStyle::ssDisc);
+ break;
+ case psSquare:
+ graph_->setScatterStyle(QCPScatterStyle::ssSquare);
+ break;
+ case psDiamond:
+ graph_->setScatterStyle(QCPScatterStyle::ssDiamond);
+ break;
+ case psBar:
+ case IOGraph::psStackedBar:
+ // Stacking set in scanGraphs
+ bars_->moveBelow(NULL);
+ break;
+ }
+
+ setName(name_);
+ applyCurrentColor();
+}
+
+const QString IOGraph::valueUnitLabel()
+{
+ if (val_units_ >= IOG_ITEM_UNIT_FIRST && val_units_ <= IOG_ITEM_UNIT_LAST) {
+ return value_unit_to_name_[val_units_];
+ }
+ return tr("Unknown");
+}
+
+void IOGraph::setValueUnits(int val_units)
+{
+ if (val_units >= IOG_ITEM_UNIT_FIRST && val_units <= IOG_ITEM_UNIT_LAST) {
+ int old_val_units = val_units_;
+ val_units_ = (io_graph_item_unit_t)val_units;
+
+ if (old_val_units != val_units) {
+ setFilter(filter_); // Check config & prime vu field
+ if (val_units < IOG_ITEM_UNIT_CALC_SUM) {
+ emit requestRecalc();
+ }
+ }
+ }
+}
+
+void IOGraph::setValueUnitField(const QString &vu_field)
+{
+ int old_hf_index = hf_index_;
+
+ vu_field_ = vu_field.trimmed();
+ hf_index_ = -1;
+
+ header_field_info *hfi = proto_registrar_get_byname(vu_field_.toUtf8().constData());
+ if (hfi) {
+ hf_index_ = hfi->id;
+ }
+
+ if (old_hf_index != hf_index_) {
+ setFilter(filter_); // Check config & prime vu field
+ }
+}
+
+bool IOGraph::addToLegend()
+{
+ if (graph_) {
+ return graph_->addToLegend();
+ }
+ if (bars_) {
+ return bars_->addToLegend();
+ }
+ return false;
+}
+
+double IOGraph::startOffset()
+{
+ if (graph_ && graph_->keyAxis()->tickLabelType() == QCPAxis::ltDateTime && graph_->data()->size() > 0) {
+ return graph_->data()->keys()[0];
+ }
+ if (bars_ && bars_->keyAxis()->tickLabelType() == QCPAxis::ltDateTime && bars_->data()->size() > 0) {
+ return bars_->data()->keys()[0];
+ }
+ return 0.0;
+}
+
+int IOGraph::packetFromTime(double ts)
+{
+ int idx = ts * 1000 / interval_;
+ if (idx >= 0 && idx < (int) cur_idx_) {
+ return items_[idx].last_frame_in_invl;
+ }
+ return -1;
+}
+
+void IOGraph::clearAllData()
+{
+ cur_idx_ = -1;
+ reset_io_graph_items(items_, max_io_items_);
+ if (graph_) {
+ graph_->clearData();
+ }
+ if (bars_) {
+ bars_->clearData();
+ }
+ start_time_ = 0.0;
+}
+
+QMap<io_graph_item_unit_t, QString> IOGraph::valueUnitsToNames()
+{
+ QMap<io_graph_item_unit_t, QString> vuton;
+
+ vuton[IOG_ITEM_UNIT_PACKETS] = QObject::tr("Packets/s");
+ vuton[IOG_ITEM_UNIT_BYTES] = QObject::tr("Bytes/s");
+ vuton[IOG_ITEM_UNIT_BITS] = QObject::tr("Bits/s");
+ vuton[IOG_ITEM_UNIT_CALC_SUM] = QObject::tr("SUM(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_FRAMES] = QObject::tr("COUNT FRAMES(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_FIELDS] = QObject::tr("COUNT FIELDS(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_MAX] = QObject::tr("MAX(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_MIN] = QObject::tr("MIN(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_AVERAGE] = QObject::tr("AVG(Y Field)");
+ vuton[IOG_ITEM_UNIT_CALC_LOAD] = QObject::tr("LOAD(Y Field)");
+
+ return vuton;
+}
+
+QMap<IOGraph::PlotStyles, QString> IOGraph::plotStylesToNames()
+{
+ QMap<IOGraph::PlotStyles, QString> pston;
+
+ pston[psLine] = QObject::tr("Line");
+ pston[psImpulse] = QObject::tr("Impulse");
+ pston[psBar] = QObject::tr("Bar");
+ pston[psStackedBar] = QObject::tr("Stacked Bar");
+ pston[psDot] = QObject::tr("Dot");
+ pston[psSquare] = QObject::tr("Square");
+ pston[psDiamond] = QObject::tr("Diamond");
+
+ return pston;
+}
+
+QMap<int, QString> IOGraph::movingAveragesToNames()
+{
+ QMap<int, QString> maton;
+ QList<int> averages = QList<int>()
+ /* << 8 */ << 10 /* << 16 */ << 20 << 50 << 100 << 200 << 500 << 1000; // Arbitrarily chosen
+
+ maton[0] = QObject::tr("None");
+ foreach (int avg, averages) {
+ maton[avg] = QString(QObject::tr("%1 interval SMA")).arg(avg);
+ }
+
+ return maton;
+}
+
+void IOGraph::recalcGraphData(capture_file *cap_file)
+{
+ /* Moving average variables */
+ unsigned int mavg_in_average_count = 0, mavg_left = 0, mavg_right = 0;
+ unsigned int mavg_to_remove = 0, mavg_to_add = 0;
+ double mavg_cumulated = 0;
+ QCPAxis *x_axis = NULL;
+
+ if (graph_) {
+ graph_->clearData();
+ x_axis = graph_->keyAxis();
+ }
+ if (bars_) {
+ bars_->clearData();
+ x_axis = bars_->keyAxis();
+ }
+
+ if (moving_avg_period_ > 0 && cur_idx_ >= 0) {
+ /* "Warm-up phase" - calculate average on some data not displayed;
+ * just to make sure average on leftmost and rightmost displayed
+ * values is as reliable as possible
+ */
+ guint64 warmup_interval = 0;
+
+// for (; warmup_interval < first_interval; warmup_interval += interval_) {
+// mavg_cumulated += get_it_value(io, i, (int)warmup_interval/interval_);
+// mavg_in_average_count++;
+// mavg_left++;
+// }
+ mavg_cumulated += getItemValue((int)warmup_interval/interval_, cap_file);
+ mavg_in_average_count++;
+ for (warmup_interval = interval_;
+ ((warmup_interval < (0 + (moving_avg_period_ / 2) * (guint64)interval_)) &&
+ (warmup_interval <= (cur_idx_ * (guint64)interval_)));
+ warmup_interval += interval_) {
+
+ mavg_cumulated += getItemValue((int)warmup_interval / interval_, cap_file);
+ mavg_in_average_count++;
+ mavg_right++;
+ }
+ mavg_to_add = warmup_interval;
+ }
+
+ for (int i = 0; i < cur_idx_; i++) {
+ double ts = (double) i * interval_ / 1000;
+ if (x_axis && x_axis->tickLabelType() == QCPAxis::ltDateTime) {
+ ts += start_time_;
+ }
+ double val = getItemValue(i, cap_file);
+
+ if (moving_avg_period_ > 0) {
+ if (i != 0) {
+ mavg_left++;
+ if (mavg_left > moving_avg_period_ / 2) {
+ mavg_left--;
+ mavg_in_average_count--;
+ mavg_cumulated -= getItemValue((int)mavg_to_remove / interval_, cap_file);
+ mavg_to_remove += interval_;
+ }
+ if (mavg_to_add <= (unsigned int) cur_idx_ * interval_) {
+ mavg_in_average_count++;
+ mavg_cumulated += getItemValue((int)mavg_to_add / interval_, cap_file);
+ mavg_to_add += interval_;
+ } else {
+ mavg_right--;
+ }
+ }
+ if (mavg_in_average_count > 0) {
+ val = mavg_cumulated / mavg_in_average_count;
+ }
+ }
+
+ if (graph_) {
+ graph_->addData(ts, val);
+ }
+ if (bars_) {
+ bars_->addData(ts, val);
+ }
+// qDebug() << "=rgd i" << i << ts << val;
+ }
+// qDebug() << "=rgd" << num_items_ << hf_index_;
+ emit requestReplot();
+}
+
+void IOGraph::setInterval(int interval)
+{
+ interval_ = interval;
+}
+
+// Get the value at the given interval (idx) for the current value unit.
+// Adapted from get_it_value in gtk/io_stat.c.
+double IOGraph::getItemValue(int idx, capture_file *cap_file)
+{
+ double value = 0; /* FIXME: loss of precision, visible on the graph for small values */
+ int adv_type;
+ io_graph_item_t *item;
+ guint32 interval;
+
+ g_assert(idx < max_io_items_);
+
+ item = &items_[idx];
+
+ // Basic units
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_PACKETS:
+ return item->frames;
+ case IOG_ITEM_UNIT_BYTES:
+ return item->bytes;
+ case IOG_ITEM_UNIT_BITS:
+ return (item->bytes * 8);
+ case IOG_ITEM_UNIT_CALC_FRAMES:
+ return item->frames;
+ case IOG_ITEM_UNIT_CALC_FIELDS:
+ return item->fields;
+ default:
+ /* If it's COUNT_TYPE_ADVANCED but not one of the
+ * generic ones we'll get it when we switch on the
+ * adv_type below. */
+ break;
+ }
+
+ if (hf_index_ < 0) {
+ return 0;
+ }
+ // Advanced units
+ adv_type = proto_registrar_get_ftype(hf_index_);
+ switch (adv_type) {
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ case FT_UINT64:
+ case FT_INT8:
+ case FT_INT16:
+ case FT_INT24:
+ case FT_INT32:
+ case FT_INT64:
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_SUM:
+ value = item->int_tot;
+ break;
+ case IOG_ITEM_UNIT_CALC_MAX:
+ value = item->int_max;
+ break;
+ case IOG_ITEM_UNIT_CALC_MIN:
+ value = item->int_min;
+ break;
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ if (item->fields) {
+ value = (double)item->int_tot / item->fields;
+ } else {
+ value = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case FT_FLOAT:
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_SUM:
+ value = (guint64)item->float_tot;
+ break;
+ case IOG_ITEM_UNIT_CALC_MAX:
+ value = (guint64)item->float_max;
+ break;
+ case IOG_ITEM_UNIT_CALC_MIN:
+ value = (guint64)item->float_min;
+ break;
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ if (item->fields) {
+ value = (guint64)item->float_tot / item->fields;
+ } else {
+ value = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case FT_DOUBLE:
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_SUM:
+ value = (guint64)item->double_tot;
+ break;
+ case IOG_ITEM_UNIT_CALC_MAX:
+ value = (guint64)item->double_max;
+ break;
+ case IOG_ITEM_UNIT_CALC_MIN:
+ value = (guint64)item->double_min;
+ break;
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ if (item->fields) {
+ value = (guint64)item->double_tot / item->fields;
+ } else {
+ value = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case FT_RELATIVE_TIME:
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_MAX:
+ value = (guint64) (item->time_max.secs*1000000 + item->time_max.nsecs/1000);
+ break;
+ case IOG_ITEM_UNIT_CALC_MIN:
+ value = (guint64) (item->time_min.secs*1000000 + item->time_min.nsecs/1000);
+ break;
+ case IOG_ITEM_UNIT_CALC_SUM:
+ value = (guint64) (item->time_tot.secs*1000000 + item->time_tot.nsecs/1000);
+ break;
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ if (item->fields) {
+ guint64 t; /* time in us */
+
+ t = item->time_tot.secs;
+ t = t*1000000+item->time_tot.nsecs/1000;
+ value = (guint64) (t/item->fields);
+ } else {
+ value = 0;
+ }
+ break;
+ case IOG_ITEM_UNIT_CALC_LOAD:
+ if (idx == (int)cur_idx_ && cap_file) {
+ interval = (guint32)((cap_file->elapsed_time.secs*1000) +
+ ((cap_file->elapsed_time.nsecs+500000)/1000000));
+ interval -= (interval_ * idx);
+ } else {
+ interval = interval_;
+ }
+ value = (guint64) ((item->time_tot.secs*1000000 + item->time_tot.nsecs/1000) / interval);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return value;
+}
+
+// "tap_reset" callback for register_tap_listener
+void IOGraph::tapReset(void *iog_ptr)
+{
+ IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
+ if (!iog) return;
+
+// qDebug() << "=tapReset" << iog->name_;
+ iog->clearAllData();
+}
+
+// "tap_packet" callback for register_tap_listener
+gboolean IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *data)
+{
+ Q_UNUSED(data);
+ IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
+ if (!pinfo || !iog) {
+ return FALSE;
+ }
+
+ int idx = get_io_graph_index(pinfo, iog->interval_);
+ bool recalc = false;
+
+ /* some sanity checks */
+ if ((idx < 0) || (idx >= max_io_items_)) {
+ iog->cur_idx_ = max_io_items_ - 1;
+ return FALSE;
+ }
+
+ /* update num_items */
+ if (idx > iog->cur_idx_) {
+ iog->cur_idx_ = (guint32) idx;
+ recalc = true;
+ }
+
+ /* set start time */
+ if (iog->start_time_ == 0.0) {
+ nstime_t start_nstime;
+ nstime_set_zero(&start_nstime);
+ nstime_delta(&start_nstime, &pinfo->fd->abs_ts, &pinfo->rel_ts);
+ iog->start_time_ = nstime_to_sec(&start_nstime);
+ }
+
+ epan_dissect_t *adv_edt = NULL;
+ /* For ADVANCED mode we need to keep track of some more stuff than just frame and byte counts */
+ if (iog->val_units_ >= IOG_ITEM_UNIT_CALC_SUM) {
+ adv_edt = edt;
+ }
+
+ if (!update_io_graph_item(iog->items_, idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
+ return FALSE;
+ }
+
+// qDebug() << "=tapPacket" << iog->name_ << idx << iog->hf_index_ << iog->val_units_ << iog->num_items_;
+
+ if (recalc) {
+ emit iog->requestRecalc();
+ }
+ return TRUE;
+}
+
+// "tap_draw" callback for register_tap_listener
+void IOGraph::tapDraw(void *iog_ptr)
+{
+ IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
+ if (!iog) return;
+ emit iog->requestRecalc();
+
+ if (iog->graph_) {
+// qDebug() << "=tapDraw g" << iog->name_ << iog->graph_->data()->keys().size();
+ }
+ if (iog->bars_) {
+// qDebug() << "=tapDraw b" << iog->name_ << iog->bars_->data()->keys().size();
+ }
+}
+
+/*
+ * 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/io_graph_dialog.h b/ui/qt/io_graph_dialog.h
new file mode 100644
index 0000000000..9363a0df50
--- /dev/null
+++ b/ui/qt/io_graph_dialog.h
@@ -0,0 +1,253 @@
+/* io_graph_dialog.h
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * 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 IO_GRAPH_DIALOG_H
+#define IO_GRAPH_DIALOG_H
+
+#include "config.h"
+
+#include <glib.h>
+
+#include <file.h>
+
+#include "epan/epan_dissect.h"
+#include "epan/uat.h"
+
+#include "ui/io_graph_item.h"
+
+#include "syntax_line_edit.h"
+
+#include <QComboBox>
+#include <QDialog>
+#include <QIcon>
+#include <QLineEdit>
+#include <QMenu>
+#include <QRubberBand>
+#include <QTimer>
+#include <QTreeWidgetItem>
+#include "qcustomplot.h"
+
+// GTK+ sets this to 100000 (NUM_IO_ITEMS)
+const int max_io_items_ = 250000;
+
+// XXX - Move to its own file?
+class IOGraph : public QObject {
+Q_OBJECT
+public:
+ // COUNT_TYPE_* in gtk/io_graph.c
+ enum PlotStyles { psLine, psImpulse, psBar, psStackedBar, psDot, psSquare, psDiamond };
+
+ explicit IOGraph(QCustomPlot *parent);
+ ~IOGraph();
+ const QString configError() { return config_err_; }
+ const QString name() { return name_; }
+ void setName(const QString &name);
+ const QString filter() { return filter_; }
+ void setFilter(const QString &filter);
+ void applyCurrentColor();
+ bool visible() { return visible_; }
+ void setVisible(bool visible);
+ QRgb color();
+ void setColor(const QRgb color);
+ void setPlotStyle(int style);
+ const QString valueUnitLabel();
+ void setValueUnits(int val_units);
+ const QString valueUnitField() { return vu_field_; }
+ void setValueUnitField(const QString &vu_field);
+ unsigned int movingAveragePeriod() { return moving_avg_period_; }
+ void setInterval(int interval);
+ bool addToLegend();
+ QCPGraph *graph() { return graph_; }
+ QCPBars *bars() { return bars_; }
+ double startOffset();
+ int packetFromTime(double ts);
+
+ void clearAllData();
+
+ static QMap<io_graph_item_unit_t, QString> valueUnitsToNames();
+ static QMap<PlotStyles, QString> plotStylesToNames();
+ static QMap<int, QString> movingAveragesToNames();
+
+ unsigned int moving_avg_period_;
+
+public slots:
+ void recalcGraphData(capture_file *cap_file);
+
+signals:
+ void requestReplot();
+ void requestRecalc();
+ void requestRetap();
+
+private:
+ double getItemValue(int idx, capture_file *cap_file);
+ // Callbacks for register_tap_listener
+ static void tapReset(void *iog_ptr);
+ static gboolean tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *data);
+ static void tapDraw(void *iog_ptr);
+
+ QCustomPlot *parent_;
+ QString config_err_;
+ QString name_;
+ bool visible_;
+ QCPGraph *graph_;
+ QCPBars *bars_;
+ QString filter_;
+ QBrush color_;
+ io_graph_item_unit_t val_units_;
+ QString vu_field_;
+ int hf_index_;
+ int interval_;
+ double start_time_;
+
+ // Cached data. We should be able to change the Y axis without retapping as
+ // much as is feasible.
+ io_graph_item_t items_[max_io_items_];
+ int cur_idx_;
+};
+
+namespace Ui {
+class IOGraphDialog;
+}
+
+class IOGraphDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit IOGraphDialog(QWidget *parent = 0, capture_file *cf = NULL);
+ ~IOGraphDialog();
+
+ void addGraph(bool checked, QString name, QString dfilter, int color_idx, IOGraph::PlotStyles style,
+ io_graph_item_unit_t value_units, QString yfield, int moving_average);
+ void addGraph(bool copy_from_current = false);
+ void addDefaultGraph(bool enabled, int idx = 0);
+ void syncGraphSettings(QTreeWidgetItem *item);
+
+public slots:
+ void setCaptureFile(capture_file *cf);
+ void scheduleReplot(bool now = false);
+ void scheduleRecalc(bool now = false);
+ void scheduleRetap(bool now = false);
+
+protected:
+ void keyPressEvent(QKeyEvent *event);
+ void reject();
+
+signals:
+ void goToPacket(int packet_num);
+ void recalcGraphData(capture_file *);
+ void intervalChanged(int interval);
+
+private:
+ Ui::IOGraphDialog *ui;
+
+ capture_file *cap_file_;
+ QLineEdit *name_line_edit_;
+ SyntaxLineEdit *dfilter_line_edit_;
+ SyntaxLineEdit *yfield_line_edit_;
+ QComboBox *color_combo_box_;
+ QComboBox *style_combo_box_;
+ QComboBox *yaxis_combo_box_;
+ QComboBox *sma_combo_box_;
+ QString hint_err_;
+ QCPGraph *base_graph_;
+ QCPItemTracer *tracer_;
+ guint32 packet_num_;
+ double start_time_;
+ bool mouse_drags_;
+ QRubberBand *rubber_band_;
+ QPoint rb_origin_;
+ QMenu ctx_menu_;
+ QTimer *stat_timer_;
+ bool need_replot_; // Light weight: tell QCP to replot existing data
+ bool need_recalc_; // Medium weight: recalculate values, then replot
+ bool need_retap_; // Heavy weight: re-read packet data
+ bool auto_axes_;
+
+// void fillGraph();
+ void zoomAxes(bool in);
+ void panAxes(int x_pixels, int y_pixels);
+ QIcon graphColorIcon(int color_idx);
+ void toggleTracerStyle(bool force_default = false);
+ void getGraphInfo();
+ void updateLegend();
+ QRectF getZoomRanges(QRect zoom_rect);
+ void itemEditingFinished(QTreeWidgetItem *item);
+ void loadProfileGraphs();
+
+private slots:
+ void graphClicked(QMouseEvent *event);
+ void mouseMoved(QMouseEvent *event);
+ void mouseReleased(QMouseEvent *event);
+ void focusChanged(QWidget *previous, QWidget *current);
+ void activateLastItem();
+ void lineEditDestroyed();
+ void comboDestroyed();
+ void resetAxes();
+ void updateStatistics(void);
+
+ void on_intervalComboBox_currentIndexChanged(int index);
+ void on_todCheckBox_toggled(bool checked);
+ void on_graphTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
+ void on_graphTreeWidget_itemActivated(QTreeWidgetItem *item, int column);
+ void on_graphTreeWidget_itemSelectionChanged();
+ void on_graphTreeWidget_itemChanged(QTreeWidgetItem *item, int column);
+
+ void on_resetButton_clicked();
+ void on_logCheckBox_toggled(bool checked);
+ void on_newToolButton_clicked();
+ void on_deleteToolButton_clicked();
+ void on_copyToolButton_clicked();
+ void on_dragRadioButton_toggled(bool checked);
+ void on_zoomRadioButton_toggled(bool checked);
+ void on_actionReset_triggered();
+ void on_actionZoomIn_triggered();
+ void on_actionZoomOut_triggered();
+ void on_actionMoveUp10_triggered();
+ void on_actionMoveLeft10_triggered();
+ void on_actionMoveRight10_triggered();
+ void on_actionMoveDown10_triggered();
+ void on_actionMoveUp1_triggered();
+ void on_actionMoveLeft1_triggered();
+ void on_actionMoveRight1_triggered();
+ void on_actionMoveDown1_triggered();
+ void on_actionGoToPacket_triggered();
+ void on_actionDragZoom_triggered();
+ void on_actionToggleTimeOrigin_triggered();
+ void on_actionCrosshairs_triggered();
+ void on_buttonBox_helpRequested();
+ void on_buttonBox_accepted();
+};
+
+#endif // IO_GRAPH_DIALOG_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:
+ */
diff --git a/ui/qt/io_graph_dialog.ui b/ui/qt/io_graph_dialog.ui
new file mode 100644
index 0000000000..c2b43846ad
--- /dev/null
+++ b/ui/qt/io_graph_dialog.ui
@@ -0,0 +1,506 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IOGraphDialog</class>
+ <widget class="QDialog" name="IOGraphDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>850</width>
+ <height>640</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCustomPlot" name="ioPlot" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>4</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ElidedLabel" name="hintLabel">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+
+&lt;h3&gt;Valuable and amazing time-saving keyboard shortcuts&lt;/h3&gt;
+&lt;table&gt;&lt;tbody&gt;
+
+&lt;tr&gt;&lt;th&gt;+&lt;/th&gt;&lt;td&gt;Zoom in&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;-&lt;/th&gt;&lt;td&gt;Zoom out&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;0&lt;/th&gt;&lt;td&gt;Reset graph to its initial state&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;→&lt;/th&gt;&lt;td&gt;Move right 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;←&lt;/th&gt;&lt;td&gt;Move left 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;↑&lt;/th&gt;&lt;td&gt;Move up 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;↓&lt;/th&gt;&lt;td&gt;Move down 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;→&lt;/th&gt;&lt;td&gt;Move right 1 pixel&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;←&lt;/th&gt;&lt;td&gt;Move left 1 pixel&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;↑&lt;/th&gt;&lt;td&gt;Move up 1 pixel&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;↓&lt;/th&gt;&lt;td&gt;Move down 1 pixel&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;g&lt;/th&gt;&lt;td&gt;Go to packet under cursor&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;z&lt;/th&gt;&lt;td&gt;Toggle mouse drag / zoom&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;t&lt;/th&gt;&lt;td&gt;Toggle capture / session time origin&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;Space&lt;/th&gt;&lt;td&gt;Toggle crosshairs&lt;/td&gt;&lt;/th&gt;
+
+&lt;/tbody&gt;&lt;/table&gt;
+&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="graphTreeWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Display filter</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Color</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Style</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Y Axis</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Y Field</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Smoothing</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="newToolButton">
+ <property name="toolTip">
+ <string>Change the dissection behavior for a protocol.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="../../image/toolbar.qrc">
+ <normaloff>:/stock/plus-8.png</normaloff>:/stock/plus-8.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="deleteToolButton">
+ <property name="toolTip">
+ <string>Remove this dissection behavior.</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../image/toolbar.qrc">
+ <normaloff>:/stock/minus-8.png</normaloff>:/stock/minus-8.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="copyToolButton">
+ <property name="toolTip">
+ <string>Copy this dissection behavior.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="../../image/toolbar.qrc">
+ <normaloff>:/stock/copy-8.png</normaloff>:/stock/copy-8.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="mouseLabel">
+ <property name="text">
+ <string>Mouse</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="dragRadioButton">
+ <property name="toolTip">
+ <string>Drag using the mouse button.</string>
+ </property>
+ <property name="text">
+ <string>drags</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="zoomRadioButton">
+ <property name="toolTip">
+ <string>Select using the mouse button.</string>
+ </property>
+ <property name="text">
+ <string>zooms</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Interval</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="intervalComboBox"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="todCheckBox">
+ <property name="text">
+ <string>Time of day</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="logCheckBox">
+ <property name="text">
+ <string>Log scale</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="resetButton">
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Save</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <action name="actionReset">
+ <property name="text">
+ <string>Reset Graph</string>
+ </property>
+ <property name="toolTip">
+ <string>Reset the graph to its initial state.</string>
+ </property>
+ <property name="shortcut">
+ <string>0</string>
+ </property>
+ </action>
+ <action name="actionZoomIn">
+ <property name="text">
+ <string>Zoom In</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom in</string>
+ </property>
+ <property name="shortcut">
+ <string>+</string>
+ </property>
+ </action>
+ <action name="actionZoomOut">
+ <property name="text">
+ <string>Zoom Out</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom out</string>
+ </property>
+ <property name="shortcut">
+ <string>-</string>
+ </property>
+ </action>
+ <action name="actionMoveUp10">
+ <property name="text">
+ <string>Move Up 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move up 10 pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Up</string>
+ </property>
+ </action>
+ <action name="actionMoveLeft10">
+ <property name="text">
+ <string>Move Left 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move left 10 pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Left</string>
+ </property>
+ </action>
+ <action name="actionMoveRight10">
+ <property name="text">
+ <string>Move Right 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move right 10 pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Right</string>
+ </property>
+ </action>
+ <action name="actionMoveDown10">
+ <property name="text">
+ <string>Move Down 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move down 10 pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Down</string>
+ </property>
+ </action>
+ <action name="actionMoveUp1">
+ <property name="text">
+ <string>Move Up 1 Pixel</string>
+ </property>
+ <property name="toolTip">
+ <string>Move up 1 pixel</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Up</string>
+ </property>
+ </action>
+ <action name="actionMoveLeft1">
+ <property name="text">
+ <string>Move Left 1 Pixel</string>
+ </property>
+ <property name="toolTip">
+ <string>Move left 1 pixel</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Left</string>
+ </property>
+ </action>
+ <action name="actionMoveRight1">
+ <property name="text">
+ <string>Move Right 1 Pixel</string>
+ </property>
+ <property name="toolTip">
+ <string>Move right 1 pixel</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Right</string>
+ </property>
+ </action>
+ <action name="actionMoveDown1">
+ <property name="text">
+ <string>Move Down 1 Pixel</string>
+ </property>
+ <property name="toolTip">
+ <string>Move down 1 pixel</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Down</string>
+ </property>
+ </action>
+ <action name="actionGoToPacket">
+ <property name="text">
+ <string>Go To Packet Under Cursor</string>
+ </property>
+ <property name="toolTip">
+ <string>Go to packet currently under the cursor</string>
+ </property>
+ <property name="shortcut">
+ <string>G</string>
+ </property>
+ </action>
+ <action name="actionDragZoom">
+ <property name="text">
+ <string>Drag / Zoom</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle mouse drag / zoom behavior</string>
+ </property>
+ <property name="shortcut">
+ <string>Z</string>
+ </property>
+ </action>
+ <action name="actionToggleTimeOrigin">
+ <property name="text">
+ <string>Capture / Session Time Origin</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle capture / session time origin</string>
+ </property>
+ <property name="shortcut">
+ <string>T</string>
+ </property>
+ </action>
+ <action name="actionCrosshairs">
+ <property name="text">
+ <string>Crosshairs</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle crosshairs</string>
+ </property>
+ <property name="shortcut">
+ <string>Space</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QCustomPlot</class>
+ <extends>QWidget</extends>
+ <header>qcustomplot.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>ElidedLabel</class>
+ <extends>QLabel</extends>
+ <header>elided_label.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="../../image/toolbar.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>IOGraphDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>IOGraphDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp
index b35aed006d..1d5cdf243b 100644
--- a/ui/qt/main_window.cpp
+++ b/ui/qt/main_window.cpp
@@ -1521,6 +1521,7 @@ void MainWindow::setForCapturedPackets(bool have_captured_packets)
// have_captured_packets);
// set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/StatisticsMenu/ProtocolHierarchy",
// have_captured_packets);
+ main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
}
void MainWindow::setMenusForFileSet(bool enable_list_files) {
diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h
index 17f9031c86..3cc5de5ec3 100644
--- a/ui/qt/main_window.h
+++ b/ui/qt/main_window.h
@@ -352,6 +352,7 @@ private slots:
void on_actionStatisticsHTTPRequests_triggered();
void on_actionStatisticsHTTPLoadDistribution_triggered();
void on_actionStatisticsPacketLen_triggered();
+ void on_actionStatisticsIOGraph_triggered();
void on_actionStatisticsSametime_triggered();
void on_actionTelephonyISUPMessages_triggered();
diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui
index 0aac799cfa..0d237c86d6 100644
--- a/ui/qt/main_window.ui
+++ b/ui/qt/main_window.ui
@@ -329,6 +329,7 @@
<addaction name="actionSummary"/>
<addaction name="actionProtocol_Hierarchy"/>
<addaction name="actionStatisticsPacketLen"/>
+ <addaction name="actionStatisticsIOGraph"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="actionStatisticsANCP"/>
@@ -1551,6 +1552,14 @@
<string>Export PDUs to File</string>
</property>
</action>
+ <action name="actionStatisticsIOGraph">
+ <property name="text">
+ <string>&amp;I/O Graph</string>
+ </property>
+ <property name="toolTip">
+ <string>Create graphs based on display filter fields</string>
+ </property>
+ </action>
<action name="actionViewToolbarMainToolbar">
<property name="checkable">
<bool>true</bool>
diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp
index 78a175d15b..4bc3fe2361 100644
--- a/ui/qt/main_window_slots.cpp
+++ b/ui/qt/main_window_slots.cpp
@@ -71,6 +71,7 @@
#include "decode_as_dialog.h"
#include "export_object_dialog.h"
#include "export_pdu_dialog.h"
+#include "io_graph_dialog.h"
#include "packet_comment_dialog.h"
#include "preferences_dialog.h"
#include "print_dialog.h"
@@ -1965,6 +1966,15 @@ void MainWindow::on_actionStatisticsPacketLen_triggered()
openStatisticsTreeDialog("plen");
}
+void MainWindow::on_actionStatisticsIOGraph_triggered()
+{
+ IOGraphDialog *iog_dialog = new IOGraphDialog(this, cap_file_);
+ connect(iog_dialog, SIGNAL(goToPacket(int)), packet_list_, SLOT(goToPacket(int)));
+ connect(this, SIGNAL(setCaptureFile(capture_file*)),
+ iog_dialog, SLOT(setCaptureFile(capture_file*)));
+ iog_dialog->show();
+}
+
void MainWindow::on_actionStatisticsSametime_triggered()
{
openStatisticsTreeDialog("sametime");
diff --git a/ui/qt/search_frame.cpp b/ui/qt/search_frame.cpp
index 50610f7a92..ca37afb54d 100644
--- a/ui/qt/search_frame.cpp
+++ b/ui/qt/search_frame.cpp
@@ -133,7 +133,6 @@ void SearchFrame::enableWidgets()
return;
}
- dfilter_t *dfp = NULL;
bool enable = sf_ui_->searchTypeComboBox->currentIndex() == string_search;
sf_ui_->searchInComboBox->setEnabled(enable);
sf_ui_->caseCheckBox->setEnabled(enable);
@@ -141,24 +140,7 @@ void SearchFrame::enableWidgets()
switch (sf_ui_->searchTypeComboBox->currentIndex()) {
case df_search:
- // XXX - Merge this with DisplayFitlerEdit::checkFilter
- if (dfilter_compile(sf_ui_->searchLineEdit->text().toUtf8().constData(), &dfp)) {
- GPtrArray *depr = NULL;
- if (dfp != NULL) {
- depr = dfilter_deprecated_tokens(dfp);
- }
- if (sf_ui_->searchLineEdit->text().isEmpty()) {
- sf_ui_->searchLineEdit->setSyntaxState(SyntaxLineEdit::Empty);
- } else if (depr) {
- /* You keep using that word. I do not think it means what you think it means. */
- sf_ui_->searchLineEdit->setSyntaxState(SyntaxLineEdit::Deprecated);
- } else {
- sf_ui_->searchLineEdit->setSyntaxState(SyntaxLineEdit::Valid);
- }
- dfilter_free(dfp);
- } else {
- sf_ui_->searchLineEdit->setSyntaxState(SyntaxLineEdit::Invalid);
- }
+ sf_ui_->searchLineEdit->checkDisplayFilter(sf_ui_->searchLineEdit->text());
break;
case hex_search:
if (sf_ui_->searchLineEdit->text().isEmpty()) {
diff --git a/ui/qt/sequence_dialog.cpp b/ui/qt/sequence_dialog.cpp
index d3169da96e..fdceba4a36 100644
--- a/ui/qt/sequence_dialog.cpp
+++ b/ui/qt/sequence_dialog.cpp
@@ -184,11 +184,11 @@ void SequenceDialog::keyPressEvent(QKeyEvent *event)
break;
case Qt::Key_Up:
case Qt::Key_K:
- panAxes(0, -1 * pan_pixels);
+ panAxes(0, pan_pixels);
break;
case Qt::Key_Down:
case Qt::Key_J:
- panAxes(0, pan_pixels);
+ panAxes(0, -1 * pan_pixels);
break;
case Qt::Key_0:
@@ -474,12 +474,12 @@ void SequenceDialog::on_actionMoveLeft10_triggered()
void SequenceDialog::on_actionMoveUp10_triggered()
{
- panAxes(0, -10);
+ panAxes(0, 10);
}
void SequenceDialog::on_actionMoveDown10_triggered()
{
- panAxes(0, 10);
+ panAxes(0, -10);
}
void SequenceDialog::on_actionMoveRight1_triggered()
@@ -494,12 +494,12 @@ void SequenceDialog::on_actionMoveLeft1_triggered()
void SequenceDialog::on_actionMoveUp1_triggered()
{
- panAxes(0, -1);
+ panAxes(0, 1);
}
void SequenceDialog::on_actionMoveDown1_triggered()
{
- panAxes(0, 1);
+ panAxes(0, -1);
}
/*
diff --git a/ui/qt/syntax_line_edit.cpp b/ui/qt/syntax_line_edit.cpp
index f432a120b9..6264c13b4a 100644
--- a/ui/qt/syntax_line_edit.cpp
+++ b/ui/qt/syntax_line_edit.cpp
@@ -24,11 +24,12 @@
#include <glib.h>
#include <epan/prefs.h>
+#include <epan/proto.h>
+#include <epan/dfilter/dfilter.h>
#include "syntax_line_edit.h"
#include "color_utils.h"
-#include <QDebug>
SyntaxLineEdit::SyntaxLineEdit(QWidget *parent) :
QLineEdit(parent)
@@ -69,7 +70,55 @@ QString SyntaxLineEdit::styleSheet() const {
return style_sheet_;
}
+QString SyntaxLineEdit::deprecatedToken()
+{
+ return deprecated_token_;
+}
+
void SyntaxLineEdit::setStyleSheet(const QString &style_sheet) {
style_sheet_ = style_sheet;
QLineEdit::setStyleSheet(style_sheet_ + state_style_sheet_);
}
+
+void SyntaxLineEdit::checkDisplayFilter(QString filter)
+{
+ if (filter.isEmpty()) {
+ setSyntaxState(SyntaxLineEdit::Empty);
+ return;
+ }
+
+ deprecated_token_.clear();
+ dfilter_t *dfp = NULL;
+ bool valid = dfilter_compile(filter.toUtf8().constData(), &dfp);
+
+ if (valid) {
+ setSyntaxState(SyntaxLineEdit::Valid);
+ } else {
+ GPtrArray *depr = NULL;
+ if (dfp) {
+ depr = dfilter_deprecated_tokens(dfp);
+ }
+ if (depr) {
+ setSyntaxState(SyntaxLineEdit::Deprecated);
+ deprecated_token_ = (const char *) g_ptr_array_index(depr, 0);
+ } else {
+ setSyntaxState(SyntaxLineEdit::Invalid);
+ }
+ }
+ dfilter_free(dfp);
+}
+
+void SyntaxLineEdit::checkFieldName(QString field)
+{
+ if (field.isEmpty()) {
+ setSyntaxState(SyntaxLineEdit::Empty);
+ return;
+ }
+
+ char invalid_char = proto_check_field_name(field.toUtf8().constData());
+ if (invalid_char) {
+ setSyntaxState(SyntaxLineEdit::Invalid);
+ } else {
+ checkDisplayFilter(field);
+ }
+}
diff --git a/ui/qt/syntax_line_edit.h b/ui/qt/syntax_line_edit.h
index df702b1a8d..748dab95c0 100644
--- a/ui/qt/syntax_line_edit.h
+++ b/ui/qt/syntax_line_edit.h
@@ -36,16 +36,23 @@ public:
SyntaxState syntaxState() const { return syntax_state_; }
void setSyntaxState(SyntaxState state = Empty);
QString styleSheet() const;
+ QString deprecatedToken();
+
+public slots:
+ void setStyleSheet(const QString &style_sheet);
+
+ // Built-in syntax checks. Connect textChanged to these as needed.
+ void checkDisplayFilter(QString filter);
+ void checkFieldName(QString field);
private:
SyntaxState syntax_state_;
QString style_sheet_;
QString state_style_sheet_;
+ QString deprecated_token_;
signals:
-public slots:
- void setStyleSheet(const QString &style_sheet);
};
#endif // SYNTAX_LINE_EDIT_H
diff --git a/ui/qt/tcp_stream_dialog.cpp b/ui/qt/tcp_stream_dialog.cpp
index bd461edba1..90b0d56c14 100644
--- a/ui/qt/tcp_stream_dialog.cpp
+++ b/ui/qt/tcp_stream_dialog.cpp
@@ -60,6 +60,7 @@
#ifndef MA_1_SECOND
const int moving_avg_period_ = 20;
#endif
+
const QRgb graph_color_1 = tango_sky_blue_5;
const QRgb graph_color_2 = tango_butter_6;
const QRgb graph_color_3 = tango_chameleon_5;
@@ -256,11 +257,11 @@ void TCPStreamDialog::keyPressEvent(QKeyEvent *event)
break;
case Qt::Key_Up:
case Qt::Key_K:
- panAxes(0, -1 * pan_pixels);
+ panAxes(0, pan_pixels);
break;
case Qt::Key_Down:
case Qt::Key_J:
- panAxes(0, pan_pixels);
+ panAxes(0, -1 * pan_pixels);
break;
case Qt::Key_Space:
@@ -805,7 +806,7 @@ void TCPStreamDialog::graphClicked(QMouseEvent *event)
on_actionGoToPacket_triggered();
} else {
if (!rubber_band_) {
- rubber_band_ = new QRubberBand(QRubberBand::Rectangle, ui->streamPlot);
+ rubber_band_ = new QRubberBand(QRubberBand::Rectangle, sp);
}
rb_origin_ = event->pos();
rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
@@ -917,7 +918,7 @@ void TCPStreamDialog::mouseMoved(QMouseEvent *event)
.arg(packet_seg->th_ack)
.arg(packet_seg->th_win);
tracer_->setGraphKey(ui->streamPlot->xAxis->pixelToCoord(event->pos().x()));
- ui->streamPlot->replot();
+ sp->replot();
} else {
if (rubber_band_ && rubber_band_->isVisible() && event) {
rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
@@ -1085,12 +1086,12 @@ void TCPStreamDialog::on_actionMoveLeft10_triggered()
void TCPStreamDialog::on_actionMoveUp10_triggered()
{
- panAxes(0, -10);
+ panAxes(0, 10);
}
void TCPStreamDialog::on_actionMoveDown10_triggered()
{
- panAxes(0, 10);
+ panAxes(0, -10);
}
void TCPStreamDialog::on_actionMoveRight1_triggered()
@@ -1105,12 +1106,12 @@ void TCPStreamDialog::on_actionMoveLeft1_triggered()
void TCPStreamDialog::on_actionMoveUp1_triggered()
{
- panAxes(0, -1);
+ panAxes(0, 1);
}
void TCPStreamDialog::on_actionMoveDown1_triggered()
{
- panAxes(0, 1);
+ panAxes(0, -1);
}
void TCPStreamDialog::on_actionNextStream_triggered()