summaryrefslogtreecommitdiffstats
path: root/ui/qt/io_graph_dialog.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/io_graph_dialog.cpp
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/io_graph_dialog.cpp')
-rw-r--r--ui/qt/io_graph_dialog.cpp2379
1 files changed, 2379 insertions, 0 deletions
diff --git a/ui/qt/io_graph_dialog.cpp b/ui/qt/io_graph_dialog.cpp
new file mode 100644
index 0000000..002b98f
--- /dev/null
+++ b/ui/qt/io_graph_dialog.cpp
@@ -0,0 +1,2379 @@
+/* io_graph_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "io_graph_dialog.h"
+#include <ui_io_graph_dialog.h>
+
+#include "file.h"
+
+#include <epan/stat_tap_ui.h>
+#include "epan/stats_tree_priv.h"
+#include "epan/uat-int.h"
+
+#include <wsutil/utf8_entities.h>
+#include <wsutil/ws_assert.h>
+
+#include <ui/qt/utils/qt_ui_utils.h>
+
+#include <ui/qt/utils/variant_pointer.h>
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/widgets/qcustomplot.h>
+#include "progress_frame.h"
+#include "main_application.h"
+
+#include <wsutil/filesystem.h>
+#include <wsutil/report_message.h>
+
+#include <ui/qt/utils/tango_colors.h> //provides some default colors
+#include <ui/qt/widgets/copy_from_profile_button.h>
+#include "ui/qt/widgets/wireshark_file_dialog.h"
+
+#include <QClipboard>
+#include <QFontMetrics>
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QRubberBand>
+#include <QSpacerItem>
+#include <QTimer>
+#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:
+// https://www.qcustomplot.com/index.php/support/forum/62
+// - We retap and redraw more than we should.
+// - Smoothing doesn't seem to match GTK+
+// - Closing the color picker on macOS sends the dialog to the background.
+// - The color picker triggers https://bugreports.qt.io/browse/QTBUG-58699.
+
+// To do:
+// - Use scroll bars?
+// - Scroll during live captures
+// - Set ticks per pixel (e.g. pressing "2" sets 2 tpp).
+// - Explicitly handle missing values, e.g. via NAN.
+// - Add a "show missing" or "show zero" option to the UAT?
+// It would add yet another graph configuration column.
+
+const qreal graph_line_width_ = 1.0;
+
+const int DEFAULT_MOVING_AVERAGE = 0;
+const int DEFAULT_Y_AXIS_FACTOR = 1;
+
+// 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
+typedef struct _io_graph_settings_t {
+ gboolean enabled;
+ char* name;
+ char* dfilter;
+ guint color;
+ guint32 style;
+ guint32 yaxis;
+ char* yfield;
+ guint32 sma_period;
+ guint32 y_axis_factor;
+} io_graph_settings_t;
+
+static const value_string graph_style_vs[] = {
+ { IOGraph::psLine, "Line" },
+ { IOGraph::psDotLine, "Dot Line" },
+ { IOGraph::psStepLine, "Step Line" },
+ { IOGraph::psDotStepLine, "Dot Step Line" },
+ { IOGraph::psImpulse, "Impulse" },
+ { IOGraph::psBar, "Bar" },
+ { IOGraph::psStackedBar, "Stacked Bar" },
+ { IOGraph::psDot, "Dot" },
+ { IOGraph::psSquare, "Square" },
+ { IOGraph::psDiamond, "Diamond" },
+ { IOGraph::psCross, "Cross" },
+ { IOGraph::psCircle, "Circle" },
+ { IOGraph::psPlus, "Plus" },
+ { 0, NULL }
+};
+
+static const value_string y_axis_vs[] = {
+ { IOG_ITEM_UNIT_PACKETS, "Packets" },
+ { IOG_ITEM_UNIT_BYTES, "Bytes" },
+ { IOG_ITEM_UNIT_BITS, "Bits" },
+ { IOG_ITEM_UNIT_CALC_SUM, "SUM(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_FRAMES, "COUNT FRAMES(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_FIELDS, "COUNT FIELDS(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_MAX, "MAX(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_MIN, "MIN(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_AVERAGE, "AVG(Y Field)" },
+ { IOG_ITEM_UNIT_CALC_LOAD, "LOAD(Y Field)" },
+ { 0, NULL }
+};
+
+static const value_string moving_avg_vs[] = {
+ { 0, "None" },
+ { 10, "10 interval SMA" },
+ { 20, "20 interval SMA" },
+ { 50, "50 interval SMA" },
+ { 100, "100 interval SMA" },
+ { 200, "200 interval SMA" },
+ { 500, "500 interval SMA" },
+ { 1000, "1000 interval SMA" },
+ { 0, NULL }
+};
+
+static io_graph_settings_t *iog_settings_ = NULL;
+static guint num_io_graphs_ = 0;
+static uat_t *iog_uat_ = NULL;
+
+// y_axis_factor was added in 3.6. Provide backward compatibility.
+static const char *iog_uat_defaults_[] = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "1"
+};
+
+extern "C" {
+
+//Allow the enable/disable field to be a checkbox, but for backwards compatibility,
+//the strings have to be "Enabled"/"Disabled", not "TRUE"/"FALSE"
+#define UAT_BOOL_ENABLE_CB_DEF(basename,field_name,rec_t) \
+static void basename ## _ ## field_name ## _set_cb(void* rec, const char* buf, guint len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
+ char* tmp_str = g_strndup(buf,len); \
+ if ((g_strcmp0(tmp_str, "Enabled") == 0) || \
+ (g_strcmp0(tmp_str, "TRUE") == 0)) \
+ ((rec_t*)rec)->field_name = 1; \
+ else \
+ ((rec_t*)rec)->field_name = 0; \
+ g_free(tmp_str); } \
+static void basename ## _ ## field_name ## _tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
+ *out_ptr = ws_strdup_printf("%s",((rec_t*)rec)->field_name ? "Enabled" : "Disabled"); \
+ *out_len = (unsigned)strlen(*out_ptr); }
+
+static bool uat_fld_chk_enable(void* u1 _U_, const char* strptr, guint len, const void* u2 _U_, const void* u3 _U_, char** err)
+{
+ char* str = g_strndup(strptr,len);
+
+ if ((g_strcmp0(str, "Enabled") == 0) ||
+ (g_strcmp0(str, "Disabled") == 0) ||
+ (g_strcmp0(str, "TRUE") == 0) || //just for UAT functionality
+ (g_strcmp0(str, "FALSE") == 0)) {
+ *err = NULL;
+ g_free(str);
+ return TRUE;
+ }
+
+ //User should never see this unless they are manually modifying UAT
+ *err = ws_strdup_printf("invalid value: %s (must be Enabled or Disabled)", str);
+ g_free(str);
+ return FALSE;
+}
+
+#define UAT_FLD_BOOL_ENABLE(basename,field_name,title,desc) \
+{#field_name, title, PT_TXTMOD_BOOL,{uat_fld_chk_enable,basename ## _ ## field_name ## _set_cb,basename ## _ ## field_name ## _tostr_cb},{0,0,0},0,desc,FLDFILL}
+
+//"Custom" handler for sma_period enumeration for backwards compatibility
+static void io_graph_sma_period_set_cb(void* rec, const char* buf, guint len, const void* vs, const void* u2 _U_)
+{
+ guint i;
+ char* str = g_strndup(buf,len);
+ const char* cstr;
+ ((io_graph_settings_t*)rec)->sma_period = 0;
+
+ //Original UAT had just raw numbers and not enumerated values with "interval SMA"
+ if (strstr(str, "interval SMA") == NULL) {
+ if (strcmp(str, "None") == 0) { //Valid enumerated value
+ } else if (strcmp(str, "0") == 0) {
+ g_free(str);
+ str = g_strdup("None");
+ } else {
+ char *str2 = ws_strdup_printf("%s interval SMA", str);
+ g_free(str);
+ str = str2;
+ }
+ }
+
+ for (i=0; (cstr = ((const value_string*)vs)[i].strptr) ;i++) {
+ if (g_str_equal(cstr,str)) {
+ ((io_graph_settings_t*)rec)->sma_period = (guint32)((const value_string*)vs)[i].value;
+ g_free(str);
+ return;
+ }
+ }
+ g_free(str);
+}
+//Duplicated because macro covers both functions
+static void io_graph_sma_period_tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* vs, const void* u2 _U_)
+{
+ guint i;
+ for (i=0;((const value_string*)vs)[i].strptr;i++) {
+ if (((const value_string*)vs)[i].value == ((io_graph_settings_t*)rec)->sma_period) {
+ *out_ptr = g_strdup(((const value_string*)vs)[i].strptr);
+ *out_len = (unsigned)strlen(*out_ptr);
+ return;
+ }
+ }
+ *out_ptr = g_strdup("None");
+ *out_len = (unsigned)strlen("None");
+}
+
+static bool sma_period_chk_enum(void* u1 _U_, const char* strptr, guint len, const void* v, const void* u3 _U_, char** err) {
+ char *str = g_strndup(strptr,len);
+ guint i;
+ const value_string* vs = (const value_string *)v;
+
+ //Original UAT had just raw numbers and not enumerated values with "interval SMA"
+ if (strstr(str, "interval SMA") == NULL) {
+ if (strcmp(str, "None") == 0) { //Valid enumerated value
+ } else if (strcmp(str, "0") == 0) {
+ g_free(str);
+ str = g_strdup("None");
+ } else {
+ char *str2 = ws_strdup_printf("%s interval SMA", str);
+ g_free(str);
+ str = str2;
+ }
+ }
+
+ for (i=0;vs[i].strptr;i++) {
+ if (g_strcmp0(vs[i].strptr,str) == 0) {
+ *err = NULL;
+ g_free(str);
+ return TRUE;
+ }
+ }
+
+ *err = ws_strdup_printf("invalid value: %s",str);
+ g_free(str);
+ return FALSE;
+}
+
+#define UAT_FLD_SMA_PERIOD(basename,field_name,title,enum,desc) \
+ {#field_name, title, PT_TXTMOD_ENUM,{sma_period_chk_enum,basename ## _ ## field_name ## _set_cb,basename ## _ ## field_name ## _tostr_cb},{&(enum),&(enum),&(enum)},&(enum),desc,FLDFILL}
+
+
+UAT_BOOL_ENABLE_CB_DEF(io_graph, enabled, io_graph_settings_t)
+UAT_CSTRING_CB_DEF(io_graph, name, io_graph_settings_t)
+UAT_DISPLAY_FILTER_CB_DEF(io_graph, dfilter, io_graph_settings_t)
+UAT_COLOR_CB_DEF(io_graph, color, io_graph_settings_t)
+UAT_VS_DEF(io_graph, style, io_graph_settings_t, guint32, 0, "Line")
+UAT_VS_DEF(io_graph, yaxis, io_graph_settings_t, guint32, 0, "Packets")
+UAT_PROTO_FIELD_CB_DEF(io_graph, yfield, io_graph_settings_t)
+UAT_DEC_CB_DEF(io_graph, y_axis_factor, io_graph_settings_t)
+
+static uat_field_t io_graph_fields[] = {
+ UAT_FLD_BOOL_ENABLE(io_graph, enabled, "Enabled", "Graph visibility"),
+ UAT_FLD_CSTRING(io_graph, name, "Graph Name", "The name of the graph"),
+ UAT_FLD_DISPLAY_FILTER(io_graph, dfilter, "Display Filter", "Graph packets matching this display filter"),
+ UAT_FLD_COLOR(io_graph, color, "Color", "Graph color (#RRGGBB)"),
+ UAT_FLD_VS(io_graph, style, "Style", graph_style_vs, "Graph style (Line, Bars, etc.)"),
+ UAT_FLD_VS(io_graph, yaxis, "Y Axis", y_axis_vs, "Y Axis units"),
+ UAT_FLD_PROTO_FIELD(io_graph, yfield, "Y Field", "Apply calculations to this field"),
+ UAT_FLD_SMA_PERIOD(io_graph, sma_period, "SMA Period", moving_avg_vs, "Simple moving average period"),
+ UAT_FLD_DEC(io_graph, y_axis_factor, "Y Axis Factor", "Y Axis Factor"),
+
+ UAT_END_FIELDS
+};
+
+static void* io_graph_copy_cb(void* dst_ptr, const void* src_ptr, size_t) {
+ 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 = src->color;
+ dst->style = src->style;
+ dst->yaxis = src->yaxis;
+ dst->yfield = g_strdup(src->yfield);
+ dst->sma_period = src->sma_period;
+ dst->y_axis_factor = src->y_axis_factor;
+
+ 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->yfield);
+}
+
+} // extern "C"
+
+IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFilter) :
+ WiresharkDialog(parent, cf),
+ ui(new Ui::IOGraphDialog),
+ uat_model_(nullptr),
+ uat_delegate_(nullptr),
+ base_graph_(nullptr),
+ tracer_(nullptr),
+ start_time_(0.0),
+ mouse_drags_(true),
+ rubber_band_(nullptr),
+ stat_timer_(nullptr),
+ need_replot_(false),
+ need_retap_(false),
+ auto_axes_(true),
+ number_ticker_(new QCPAxisTicker),
+ datetime_ticker_(new QCPAxisTickerDateTime)
+{
+ ui->setupUi(this);
+ ui->hintLabel->setSmallText();
+ loadGeometry();
+
+ setWindowSubtitle(tr("I/O Graphs"));
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ QCustomPlot *iop = ui->ioPlot;
+
+ ui->newToolButton->setStockIcon("list-add");
+ ui->deleteToolButton->setStockIcon("list-remove");
+ ui->copyToolButton->setStockIcon("list-copy");
+ ui->clearToolButton->setStockIcon("list-clear");
+ ui->moveUpwardsToolButton->setStockIcon("list-move-up");
+ ui->moveDownwardsToolButton->setStockIcon("list-move-down");
+
+#ifdef Q_OS_MAC
+ ui->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+ ui->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+ ui->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+ ui->clearToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+ ui->moveUpwardsToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+ ui->moveDownwardsToolButton->setAttribute(Qt::WA_MacSmallSize, true);
+#endif
+
+ QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
+ save_bt->setText(tr("Save As…"));
+
+ QPushButton *copy_bt = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
+ connect (copy_bt, SIGNAL(clicked()), this, SLOT(copyAsCsvClicked()));
+
+ CopyFromProfileButton * copy_button = new CopyFromProfileButton(this, "io_graphs", tr("Copy graphs from another profile."));
+ ui->buttonBox->addButton(copy_button, QDialogButtonBox::ActionRole);
+ connect(copy_button, &CopyFromProfileButton::copyProfile, this, &IOGraphDialog::copyFromProfile);
+
+ QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
+ if (close_bt) {
+ close_bt->setDefault(true);
+ }
+
+ ui->automaticUpdateCheckBox->setChecked(prefs.gui_io_graph_automatic_update ? true : false);
+
+ ui->enableLegendCheckBox->setChecked(prefs.gui_io_graph_enable_legend ? true : false);
+
+ 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("1 ms"), 1);
+ ui->intervalComboBox->addItem(tr("2 ms"), 2);
+ ui->intervalComboBox->addItem(tr("5 ms"), 5);
+ ui->intervalComboBox->addItem(tr("10 ms"), 10);
+ ui->intervalComboBox->addItem(tr("20 ms"), 20);
+ ui->intervalComboBox->addItem(tr("50 ms"), 50);
+ ui->intervalComboBox->addItem(tr("100 ms"), 100);
+ ui->intervalComboBox->addItem(tr("200 ms"), 200);
+ ui->intervalComboBox->addItem(tr("500 ms"), 500);
+ ui->intervalComboBox->addItem(tr("1 sec"), 1000);
+ ui->intervalComboBox->addItem(tr("2 sec"), 2000);
+ ui->intervalComboBox->addItem(tr("5 sec"), 5000);
+ ui->intervalComboBox->addItem(tr("10 sec"), 10000);
+ ui->intervalComboBox->addItem(tr("1 min"), 60000);
+ ui->intervalComboBox->addItem(tr("10 min"), 600000);
+ ui->intervalComboBox->setCurrentIndex(9);
+
+ ui->todCheckBox->setChecked(false);
+ iop->xAxis->setTicker(number_ticker_);
+
+ ui->dragRadioButton->setChecked(mouse_drags_);
+
+ ctx_menu_.addAction(ui->actionZoomIn);
+ ctx_menu_.addAction(ui->actionZoomInX);
+ ctx_menu_.addAction(ui->actionZoomInY);
+ ctx_menu_.addAction(ui->actionZoomOut);
+ ctx_menu_.addAction(ui->actionZoomOutX);
+ ctx_menu_.addAction(ui->actionZoomOutY);
+ 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);
+ set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
+
+ iop->xAxis->setLabel(tr("Time (s)"));
+
+ iop->setMouseTracking(true);
+ iop->setEnabled(true);
+
+ QCPTextElement *title = new QCPTextElement(iop);
+ iop->plotLayout()->insertRow(0);
+ iop->plotLayout()->addElement(0, 0, title);
+ title->setText(tr("Wireshark I/O Graphs: %1").arg(cap_file_.fileDisplayName()));
+
+ tracer_ = new QCPItemTracer(iop);
+
+ loadProfileGraphs();
+ bool filterExists = false;
+ QString graph_name = is_packet_configuration_namespace() ? tr("Filtered packets") : tr("Filtered events");
+ if (uat_model_->rowCount() > 0) {
+ for (int i = 0; i < uat_model_->rowCount(); i++) {
+ createIOGraph(i);
+ if (ioGraphs_.at(i)->filter().compare(displayFilter) == 0)
+ filterExists = true;
+ }
+ if (! filterExists && displayFilter.length() > 0)
+ addGraph(true, graph_name, displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
+ IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ } else {
+ addDefaultGraph(true, 0);
+ addDefaultGraph(true, 1);
+ if (displayFilter.length() > 0)
+ addGraph(true, graph_name, displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
+ IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ }
+
+ toggleTracerStyle(true);
+ iop->setFocus();
+
+ iop->rescaleAxes();
+
+ ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
+
+ ui->splitter->setStretchFactor(0, 95);
+ ui->splitter->setStretchFactor(1, 5);
+
+ //XXX - resize columns?
+
+ ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
+
+ 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*)));
+}
+
+IOGraphDialog::~IOGraphDialog()
+{
+ cap_file_.stopLoading();
+ foreach(IOGraph* iog, ioGraphs_) {
+ delete iog;
+ }
+ delete ui;
+ ui = NULL;
+}
+
+void IOGraphDialog::copyFromProfile(QString filename)
+{
+ guint orig_data_len = iog_uat_->raw_data->len;
+
+ gchar *err = NULL;
+ if (uat_load(iog_uat_, filename.toUtf8().constData(), &err)) {
+ iog_uat_->changed = TRUE;
+ uat_model_->reloadUat();
+ for (guint i = orig_data_len; i < iog_uat_->raw_data->len; i++) {
+ createIOGraph(i);
+ }
+ } else {
+ report_failure("Error while loading %s: %s", iog_uat_->name, err);
+ g_free(err);
+ }
+}
+
+void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, QRgb color_idx, IOGraph::PlotStyles style, io_graph_item_unit_t value_units, QString yfield, int moving_average, int y_axis_factor)
+{
+
+ QVariantList newRowData;
+ newRowData.append(checked ? Qt::Checked : Qt::Unchecked);
+ newRowData.append(name);
+ newRowData.append(dfilter);
+ newRowData.append(QColor(color_idx));
+ newRowData.append(val_to_str_const(style, graph_style_vs, "None"));
+ if (is_packet_configuration_namespace()) {
+ newRowData.append(val_to_str_const(value_units, y_axis_vs, "Packets"));
+ } else {
+ newRowData.append(val_to_str_const(value_units, y_axis_vs, "Events"));
+ }
+ newRowData.append(yfield);
+ newRowData.append(val_to_str_const((guint32) moving_average, moving_avg_vs, "None"));
+ newRowData.append(y_axis_factor);
+
+ QModelIndex newIndex = uat_model_->appendEntry(newRowData);
+ if ( !newIndex.isValid() )
+ {
+ qDebug() << "Failed to add a new record";
+ return;
+ }
+ ui->graphUat->setCurrentIndex(newIndex);
+ createIOGraph(newIndex.row());
+}
+
+void IOGraphDialog::addGraph(bool copy_from_current)
+{
+ const QModelIndex &current = ui->graphUat->currentIndex();
+ if (copy_from_current && !current.isValid())
+ return;
+
+ QModelIndex copyIdx;
+
+ if (copy_from_current) {
+ copyIdx = uat_model_->copyRow(current);
+ if (!copyIdx.isValid())
+ {
+ qDebug() << "Failed to add a new record";
+ return;
+ }
+ createIOGraph(copyIdx.row());
+
+ ui->graphUat->setCurrentIndex(copyIdx);
+ } else {
+ addDefaultGraph(false);
+ copyIdx = uat_model_->index(uat_model_->rowCount() - 1, 0);
+ }
+
+ ui->graphUat->setCurrentIndex(copyIdx);
+}
+
+void IOGraphDialog::createIOGraph(int currentRow)
+{
+ // XXX - Should IOGraph have it's own list that has to sync with UAT?
+ ioGraphs_.append(new IOGraph(ui->ioPlot));
+ IOGraph* iog = ioGraphs_[currentRow];
+
+ connect(this, SIGNAL(recalcGraphData(capture_file *, bool)), iog, SLOT(recalcGraphData(capture_file *, bool)));
+ connect(this, SIGNAL(reloadValueUnitFields()), iog, SLOT(reloadValueUnitField()));
+ connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
+ iog, SLOT(captureEvent(CaptureEvent)));
+ connect(iog, SIGNAL(requestRetap()), this, SLOT(scheduleRetap()));
+ connect(iog, SIGNAL(requestRecalc()), this, SLOT(scheduleRecalc()));
+ connect(iog, SIGNAL(requestReplot()), this, SLOT(scheduleReplot()));
+
+ syncGraphSettings(currentRow);
+ if (iog->visible()) {
+ scheduleRetap();
+ }
+}
+
+void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
+{
+ if (is_packet_configuration_namespace()) {
+ switch (idx % 2) {
+ case 0:
+ addGraph(enabled, tr("All Packets"), QString(), ColorUtils::graphColor(idx),
+ IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ break;
+ default:
+ addGraph(enabled, tr("TCP Errors"), "tcp.analysis.flags", ColorUtils::graphColor(4), // 4 = red
+ IOGraph::psBar, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ break;
+ }
+ } else {
+ switch (idx % 2) {
+ case 0:
+ addGraph(enabled, tr("All Events"), QString(), ColorUtils::graphColor(idx),
+ IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ break;
+ default:
+ addGraph(enabled, tr("Access Denied"), "ct.error == \"AccessDenied\"", ColorUtils::graphColor(4), // 4 = red
+ IOGraph::psDot, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
+ break;
+ }
+ }
+}
+
+// Sync the settings from UAT model to its IOGraph.
+// Disables the graph if any errors are found.
+//
+// NOTE: Setting dfilter, yaxis and yfield here will all end up in setFilter() and this
+// has a chicken-and-egg problem because setFilter() depends on previous assigned
+// values for filter_, val_units_ and vu_field_. Setting values in wrong order
+// may give unpredicted results because setFilter() does not always set filter_
+// on errors.
+// TODO: The issues in the above note should be fixed and setFilter() should not be
+// called so frequently.
+
+void IOGraphDialog::syncGraphSettings(int row)
+{
+ IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
+
+ if (!uat_model_->index(row, colEnabled).isValid() || !iog)
+ return;
+
+ bool visible = graphIsEnabled(row);
+ bool retap = !iog->visible() && visible;
+ QString data_str;
+
+ iog->setName(uat_model_->data(uat_model_->index(row, colName)).toString());
+ iog->setFilter(uat_model_->data(uat_model_->index(row, colDFilter)).toString());
+
+ /* plot style depend on the value unit, so set it first. */
+ data_str = uat_model_->data(uat_model_->index(row, colYAxis)).toString();
+ iog->setValueUnits((int) str_to_val(qUtf8Printable(data_str), y_axis_vs, IOG_ITEM_UNIT_PACKETS));
+ iog->setValueUnitField(uat_model_->data(uat_model_->index(row, colYField)).toString());
+
+ iog->setColor(uat_model_->data(uat_model_->index(row, colColor), Qt::DecorationRole).value<QColor>().rgb());
+ data_str = uat_model_->data(uat_model_->index(row, colStyle)).toString();
+ iog->setPlotStyle((int) str_to_val(qUtf8Printable(data_str), graph_style_vs, 0));
+
+ data_str = uat_model_->data(uat_model_->index(row, colSMAPeriod)).toString();
+ iog->moving_avg_period_ = str_to_val(qUtf8Printable(data_str), moving_avg_vs, 0);
+
+ iog->y_axis_factor_ = uat_model_->data(uat_model_->index(row, colYAxisFactor)).toInt();
+
+ iog->setInterval(ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt());
+
+ if (!iog->configError().isEmpty()) {
+ hint_err_ = iog->configError();
+ visible = false;
+ retap = false;
+ } else {
+ hint_err_.clear();
+ }
+
+ iog->setVisible(visible);
+
+ getGraphInfo();
+ mouseMoved(NULL); // Update hint
+ updateLegend();
+
+ if (visible) {
+ if (retap) {
+ scheduleRetap();
+ } else {
+ scheduleReplot();
+ }
+ }
+}
+
+void IOGraphDialog::updateWidgets()
+{
+ WiresharkDialog::updateWidgets();
+}
+
+void IOGraphDialog::scheduleReplot(bool now)
+{
+ need_replot_ = true;
+ if (now) updateStatistics();
+ // A plot finished, force an update of the legend now in case a time unit
+ // was involved (which might append "(ms)" to the label).
+ updateLegend();
+}
+
+void IOGraphDialog::scheduleRecalc(bool now)
+{
+ need_recalc_ = true;
+ if (now) updateStatistics();
+}
+
+void IOGraphDialog::scheduleRetap(bool now)
+{
+ need_retap_ = true;
+ if (now) updateStatistics();
+}
+
+void IOGraphDialog::reloadFields()
+{
+ emit reloadValueUnitFields();
+}
+
+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_X: // Zoom X axis only
+ if (event->modifiers() & Qt::ShiftModifier) {
+ zoomXAxis(false); // upper case X -> Zoom out
+ } else {
+ zoomXAxis(true); // lower case x -> Zoom in
+ }
+ break;
+ case Qt::Key_Y: // Zoom Y axis only
+ if (event->modifiers() & Qt::ShiftModifier) {
+ zoomYAxis(false); // upper case Y -> Zoom out
+ } else {
+ zoomYAxis(true); // lower case y -> Zoom in
+ }
+ 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()
+{
+ if (!uat_model_)
+ return;
+
+ // Changes to the I/O Graphs settings are always saved,
+ // there is no possibility for "rejection".
+ QString error;
+ if (uat_model_->applyChanges(error)) {
+ if (!error.isEmpty()) {
+ report_failure("%s", qPrintable(error));
+ }
+ }
+
+ 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::zoomXAxis(bool in)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
+
+ auto_axes_ = false;
+
+ if (!in) {
+ h_factor = pow(h_factor, -1);
+ }
+
+ iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
+ iop->replot();
+}
+
+void IOGraphDialog::zoomYAxis(bool in)
+{
+ QCustomPlot *iop = ui->ioPlot;
+ double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
+
+ auto_axes_ = false;
+
+ if (!in) {
+ v_factor = pow(v_factor, -1);
+ }
+
+ 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();
+ }
+}
+
+
+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();
+}
+
+// Returns the IOGraph which is most likely to be used by the user. This is the
+// currently selected, visible graph or the first visible graph otherwise.
+IOGraph *IOGraphDialog::currentActiveGraph() const
+{
+ QModelIndex index = ui->graphUat->currentIndex();
+ if (index.isValid()) {
+ return ioGraphs_.value(index.row(), NULL);
+ }
+
+ //if no currently selected item, go with first item enabled
+ for (int row = 0; row < uat_model_->rowCount(); row++)
+ {
+ if (graphIsEnabled(row)) {
+ return ioGraphs_.value(row, NULL);
+ }
+ }
+
+ return NULL;
+}
+
+bool IOGraphDialog::graphIsEnabled(int row) const
+{
+ Qt::CheckState state = static_cast<Qt::CheckState>(uat_model_->data(uat_model_->index(row, colEnabled), Qt::CheckStateRole).toInt());
+ return state == Qt::Checked;
+}
+
+// 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);
+ IOGraph *selectedGraph = currentActiveGraph();
+
+ if (uat_model_ != NULL) {
+ //all graphs may not be created yet, so bounds check the graph array
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ IOGraph* iog = ioGraphs_.value(row, Q_NULLPTR);
+ if (iog && graphIsEnabled(row)) {
+ QCPGraph *graph = iog->graph();
+ QCPBars *bars = iog->bars();
+ if (graph && (!base_graph_ || iog == selectedGraph)) {
+ base_graph_ = graph;
+ } else if (bars &&
+ (uat_model_->data(uat_model_->index(row, colStyle), Qt::DisplayRole).toString().compare(graph_style_vs[IOGraph::psStackedBar].strptr) == 0) &&
+ iog->visible()) {
+ bars->moveBelow(NULL); // Remove from existing stack
+ bars->moveBelow(prev_bars);
+ prev_bars = bars;
+ }
+ if (iog->visible() && iog->maxInterval() >= 0) {
+ 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;
+ QString intervalText = ui->intervalComboBox->itemText(ui->intervalComboBox->currentIndex());
+
+ iop->legend->setVisible(false);
+ iop->yAxis->setLabel(QString());
+
+ // Find unique labels
+ if (uat_model_ != NULL) {
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
+ if (graphIsEnabled(row) && iog) {
+ QString label(iog->valueUnitLabel());
+ if (!iog->scaledValueUnit().isEmpty()) {
+ label += " (" + iog->scaledValueUnit() + ")";
+ }
+ vu_label_set.insert(label);
+ }
+ }
+ }
+
+ // 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] + "/" + intervalText);
+ return;
+ }
+
+ // Differing labels. Create a legend with a Title label at top.
+ // Legend Title thanks to: https://www.qcustomplot.com/index.php/support/forum/443
+ QCPTextElement* legendTitle = qobject_cast<QCPTextElement*>(iop->legend->elementAt(0));
+ if (legendTitle == NULL) {
+ legendTitle = new QCPTextElement(iop, QString(""));
+ iop->legend->insertRow(0);
+ iop->legend->addElement(0, 0, legendTitle);
+ }
+ legendTitle->setText(QString(intervalText + " Intervals "));
+
+ if (uat_model_ != NULL) {
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
+ if (iog) {
+ if (graphIsEnabled(row)) {
+ iog->addToLegend();
+ } else {
+ iog->removeFromLegend();
+ }
+ }
+ }
+ }
+
+ // Only show legend if the user requested it
+ if (prefs.gui_io_graph_enable_legend) {
+ iop->legend->setVisible(true);
+ }
+ else {
+ iop->legend->setVisible(false);
+ }
+}
+
+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.
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0 ,0)
+ ctx_menu_.popup(event->globalPosition().toPoint());
+#else
+ ctx_menu_.popup(event->globalPos());
+#endif
+ } 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;
+
+ // XXX: ElidedLabel doesn't support rich text / HTML, we
+ // used to bold this error
+ if (!hint_err_.isEmpty()) {
+ hint += QString("%1 ").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();
+ if (IOGraph *iog = currentActiveGraph()) {
+ interval_packet = iog->packetFromTime(ts - start_time_);
+ }
+ }
+
+ if (interval_packet < 0) {
+ hint += tr("Hover over the graph for details.");
+ } else {
+ QString msg = is_packet_configuration_namespace() ? tr("No packets in interval") : tr("No events in interval");
+ QString val;
+ if (interval_packet > 0) {
+ packet_num_ = (guint32) interval_packet;
+ if (is_packet_configuration_namespace()) {
+ msg = QString("%1 %2")
+ .arg(!file_closed_ ? tr("Click to select packet") : tr("Packet"))
+ .arg(packet_num_);
+ } else {
+ msg = QString("%1 %2")
+ .arg(!file_closed_ ? tr("Click to select event") : tr("Event"))
+ .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.");
+ }
+ }
+
+ 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::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_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
+ need_retap_ = false;
+ cap_file_.retapPackets();
+ // The user might have closed the window while tapping, which means
+ // we might no longer exist.
+ } else {
+ if (need_recalc_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
+ need_recalc_ = false;
+ need_replot_ = true;
+ int enabled_graphs = 0;
+
+ if (uat_model_ != NULL) {
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ if (graphIsEnabled(row)) {
+ ++enabled_graphs;
+ }
+ }
+ }
+ // With multiple visible graphs, disable Y scaling to avoid
+ // multiple, distinct units.
+ emit recalcGraphData(cap_file_.capFile(), enabled_graphs == 1);
+ 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();
+ }
+ }
+}
+
+void IOGraphDialog::loadProfileGraphs()
+{
+ if (iog_uat_ == NULL) {
+
+ 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,
+ NULL,
+ io_graph_fields);
+
+ uat_set_default_values(iog_uat_, iog_uat_defaults_);
+
+ char* err = NULL;
+ if (!uat_load(iog_uat_, NULL, &err)) {
+ report_failure("Error while loading %s: %s. Default graph values will be used", iog_uat_->name, err);
+ g_free(err);
+ uat_clear(iog_uat_);
+ }
+ }
+
+ uat_model_ = new UatModel(NULL, iog_uat_);
+ uat_delegate_ = new UatDelegate;
+ ui->graphUat->setModel(uat_model_);
+ ui->graphUat->setItemDelegate(uat_delegate_);
+
+ connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(modelDataChanged(QModelIndex)));
+ connect(uat_model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
+}
+
+// Slots
+
+void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
+{
+ int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
+ bool need_retap = false;
+
+ if (uat_model_ != NULL) {
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ IOGraph *iog = ioGraphs_.value(row, NULL);
+ if (iog) {
+ iog->setInterval(interval);
+ if (iog->visible()) {
+ need_retap = true;
+ }
+ }
+ }
+ }
+
+ if (need_retap) {
+ scheduleRetap(true);
+ }
+
+ updateLegend();
+}
+
+void IOGraphDialog::on_todCheckBox_toggled(bool checked)
+{
+ double orig_start = start_time_;
+ bool orig_auto = auto_axes_;
+
+ if (checked) {
+ ui->ioPlot->xAxis->setTicker(datetime_ticker_);
+ } else {
+ ui->ioPlot->xAxis->setTicker(number_ticker_);
+ }
+ auto_axes_ = false;
+ scheduleRecalc(true);
+ auto_axes_ = orig_auto;
+ getGraphInfo();
+ ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
+ mouseMoved(NULL); // Update hint
+}
+
+void IOGraphDialog::modelRowsReset()
+{
+ ui->deleteToolButton->setEnabled(false);
+ ui->copyToolButton->setEnabled(false);
+ ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
+}
+
+void IOGraphDialog::on_graphUat_currentItemChanged(const QModelIndex &current, const QModelIndex&)
+{
+ if (current.isValid()) {
+ ui->deleteToolButton->setEnabled(true);
+ ui->copyToolButton->setEnabled(true);
+ ui->clearToolButton->setEnabled(true);
+ ui->moveUpwardsToolButton->setEnabled(true);
+ ui->moveDownwardsToolButton->setEnabled(true);
+ } else {
+ ui->deleteToolButton->setEnabled(false);
+ ui->copyToolButton->setEnabled(false);
+ ui->clearToolButton->setEnabled(false);
+ ui->moveUpwardsToolButton->setEnabled(false);
+ ui->moveDownwardsToolButton->setEnabled(false);
+ }
+}
+
+void IOGraphDialog::modelDataChanged(const QModelIndex &index)
+{
+ bool recalc = false;
+
+ switch (index.column())
+ {
+ case colYAxis:
+ case colSMAPeriod:
+ recalc = true;
+ }
+
+ syncGraphSettings(index.row());
+
+ if (recalc) {
+ scheduleRecalc(true);
+ } else {
+ scheduleReplot(true);
+ }
+}
+
+void IOGraphDialog::on_resetButton_clicked()
+{
+ resetAxes();
+}
+
+void IOGraphDialog::on_newToolButton_clicked()
+{
+ addGraph();
+}
+
+void IOGraphDialog::on_deleteToolButton_clicked()
+{
+ const QModelIndex &current = ui->graphUat->currentIndex();
+ if (uat_model_ && current.isValid()) {
+ delete ioGraphs_[current.row()];
+ ioGraphs_.remove(current.row());
+
+ if (!uat_model_->removeRows(current.row(), 1)) {
+ qDebug() << "Failed to remove row";
+ }
+ }
+
+ // We should probably be smarter about this.
+ hint_err_.clear();
+ mouseMoved(NULL);
+}
+
+void IOGraphDialog::on_copyToolButton_clicked()
+{
+ addGraph(true);
+}
+
+void IOGraphDialog::on_clearToolButton_clicked()
+{
+ if (uat_model_) {
+ foreach(IOGraph* iog, ioGraphs_) {
+ delete iog;
+ }
+ ioGraphs_.clear();
+ uat_model_->clearAll();
+ }
+
+ hint_err_.clear();
+ mouseMoved(NULL);
+}
+
+void IOGraphDialog::on_moveUpwardsToolButton_clicked()
+{
+ const QModelIndex& current = ui->graphUat->currentIndex();
+ if (uat_model_ && current.isValid()) {
+
+ int current_row = current.row();
+ if (current_row > 0){
+ // Swap current row with the one above
+ IOGraph* temp = ioGraphs_[current_row - 1];
+ ioGraphs_[current_row - 1] = ioGraphs_[current_row];
+ ioGraphs_[current_row] = temp;
+
+ uat_model_->moveRow(current_row, current_row - 1);
+ }
+ }
+}
+
+void IOGraphDialog::on_moveDownwardsToolButton_clicked()
+{
+ const QModelIndex& current = ui->graphUat->currentIndex();
+ if (uat_model_ && current.isValid()) {
+
+ int current_row = current.row();
+ if (current_row < uat_model_->rowCount() - 1) {
+ // Swap current row with the one below
+ IOGraph* temp = ioGraphs_[current_row + 1];
+ ioGraphs_[current_row + 1] = ioGraphs_[current_row];
+ ioGraphs_[current_row] = temp;
+
+ uat_model_->moveRow(current_row, current_row + 1);
+ }
+ }
+}
+
+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(QCP::Interactions());
+}
+
+void IOGraphDialog::on_logCheckBox_toggled(bool checked)
+{
+ QCustomPlot *iop = ui->ioPlot;
+
+ iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
+ iop->replot();
+}
+
+void IOGraphDialog::on_automaticUpdateCheckBox_toggled(bool checked)
+{
+ prefs.gui_io_graph_automatic_update = checked ? TRUE : FALSE;
+
+ prefs_main_write();
+
+ if(prefs.gui_io_graph_automatic_update)
+ {
+ updateStatistics();
+ }
+}
+
+void IOGraphDialog::on_enableLegendCheckBox_toggled(bool checked)
+{
+ prefs.gui_io_graph_enable_legend = checked ? TRUE : FALSE;
+
+ prefs_main_write();
+
+ updateLegend();
+}
+
+void IOGraphDialog::on_actionReset_triggered()
+{
+ on_resetButton_clicked();
+}
+
+void IOGraphDialog::on_actionZoomIn_triggered()
+{
+ zoomAxes(true);
+}
+
+void IOGraphDialog::on_actionZoomInX_triggered()
+{
+ zoomXAxis(true);
+}
+
+void IOGraphDialog::on_actionZoomInY_triggered()
+{
+ zoomYAxis(true);
+}
+
+void IOGraphDialog::on_actionZoomOut_triggered()
+{
+ zoomAxes(false);
+}
+
+void IOGraphDialog::on_actionZoomOutX_triggered()
+{
+ zoomXAxis(false);
+}
+
+void IOGraphDialog::on_actionZoomOutY_triggered()
+{
+ zoomYAxis(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() && !file_closed_ && 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()
+{
+ mainApp->helpTopicAction(HELP_STATS_IO_GRAPH_DIALOG);
+}
+
+// XXX - We have similar code in tcp_stream_dialog and packet_diagram. Should this be a common routine?
+void IOGraphDialog::on_buttonBox_accepted()
+{
+ QString file_name, extension;
+ QDir path(mainApp->openDialogInitialDir());
+ 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 csv_filter = tr("Comma Separated Values (*.csv)");
+ QString filter = QString("%1;;%2;;%3;;%4;;%5")
+ .arg(pdf_filter)
+ .arg(png_filter)
+ .arg(bmp_filter)
+ .arg(jpeg_filter)
+ .arg(csv_filter);
+
+ QString save_file = path.canonicalPath();
+ if (!file_closed_) {
+ save_file += QString("/%1").arg(cap_file_.fileBaseName());
+ }
+ file_name = WiresharkFileDialog::getSaveFileName(this, mainApp->windowTitleString(tr("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 if (extension.compare(csv_filter) == 0) {
+ save_ok = saveCsv(file_name);
+ }
+ // else error dialog?
+ if (save_ok) {
+ mainApp->setLastOpenDirFromFilename(file_name);
+ }
+ }
+}
+
+void IOGraphDialog::makeCsv(QTextStream &stream) const
+{
+ QList<IOGraph *> activeGraphs;
+
+ int ui_interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
+ int max_interval = 0;
+
+ stream << "\"Interval start\"";
+ if (uat_model_ != NULL) {
+ for (int row = 0; row < uat_model_->rowCount(); row++) {
+ if (graphIsEnabled(row) && ioGraphs_[row] != NULL) {
+ activeGraphs.append(ioGraphs_[row]);
+ if (max_interval < ioGraphs_[row]->maxInterval()) {
+ max_interval = ioGraphs_[row]->maxInterval();
+ }
+ QString name = ioGraphs_[row]->name().toUtf8();
+ name = QString("\"%1\"").arg(name.replace("\"", "\"\"")); // RFC 4180
+ stream << "," << name;
+ }
+ }
+ }
+
+ stream << '\n';
+
+ for (int interval = 0; interval <= max_interval; interval++) {
+ double interval_start = (double)interval * ((double)ui_interval / 1000.0);
+ stream << interval_start;
+ foreach (IOGraph *iog, activeGraphs) {
+ double value = 0.0;
+ if (interval <= iog->maxInterval()) {
+ value = iog->getItemValue(interval, cap_file_.capFile());
+ }
+ stream << "," << value;
+ }
+ stream << '\n';
+ }
+}
+
+void IOGraphDialog::copyAsCsvClicked()
+{
+ QString csv;
+ QTextStream stream(&csv, QIODevice::Text);
+ makeCsv(stream);
+ mainApp->clipboard()->setText(stream.readAll());
+}
+
+bool IOGraphDialog::saveCsv(const QString &file_name) const
+{
+ QFile save_file(file_name);
+ save_file.open(QFile::WriteOnly | QFile::Text);
+ QTextStream out(&save_file);
+ makeCsv(out);
+
+ return true;
+}
+
+// IOGraph
+
+IOGraph::IOGraph(QCustomPlot *parent) :
+ parent_(parent),
+ visible_(false),
+ graph_(NULL),
+ bars_(NULL),
+ val_units_(IOG_ITEM_UNIT_FIRST),
+ 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,
+ NULL);
+ if (error_string) {
+// QMessageBox::critical(this, tr("%1 failed to register tap listener").arg(name_),
+// error_string->str);
+// config_err_ = error_string->str;
+ g_string_free(error_string, TRUE);
+ }
+}
+
+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;
+ df_error_t *df_err = NULL;
+ status = dfilter_compile(full_filter.toUtf8().constData(), &dfilter, &df_err);
+ dfilter_free(dfilter);
+ if (!status) {
+ config_err_ = QString::fromUtf8(df_err->msg);
+ df_error_free(&df_err);
+ 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(ColorUtils::graphColor(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_->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:
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsLine);
+ }
+ break;
+ case psDotLine:
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsLine);
+ graph_->setScatterStyle(QCPScatterStyle::ssDisc);
+ }
+ break;
+ case psStepLine:
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsStepLeft);
+ }
+ break;
+ case psDotStepLine:
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsStepLeft);
+ graph_->setScatterStyle(QCPScatterStyle::ssDisc);
+ }
+ break;
+ case psImpulse:
+ if (graph_) {
+ graph_->setLineStyle(QCPGraph::lsImpulse);
+ }
+ break;
+ case psDot:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssDisc);
+ }
+ break;
+ case psSquare:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssSquare);
+ }
+ break;
+ case psDiamond:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssDiamond);
+ }
+ break;
+ case psCross:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssCross);
+ }
+ break;
+ case psPlus:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssPlus);
+ }
+ break;
+ case psCircle:
+ if (graph_) {
+ graph_->setScatterStyle(QCPScatterStyle::ssCircle);
+ }
+ break;
+
+ case psBar:
+ case IOGraph::psStackedBar:
+ // Stacking set in scanGraphs
+ bars_->moveBelow(NULL);
+ break;
+ }
+
+ setName(name_);
+ applyCurrentColor();
+}
+
+const QString IOGraph::valueUnitLabel()
+{
+ return val_to_str_const(val_units_, y_axis_vs, "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;
+}
+
+bool IOGraph::removeFromLegend()
+{
+ if (graph_) {
+ return graph_->removeFromLegend();
+ }
+ if (bars_) {
+ return bars_->removeFromLegend();
+ }
+ return false;
+}
+
+double IOGraph::startOffset()
+{
+ if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker()) && graph_->data()->size() > 0) {
+ return graph_->data()->at(0)->key;
+ }
+ if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker()) && bars_->data()->size() > 0) {
+ return bars_->data()->at(0)->key;
+ }
+ return 0.0;
+}
+
+int IOGraph::packetFromTime(double ts)
+{
+ int idx = ts * 1000 / interval_;
+ if (idx >= 0 && idx < (int) cur_idx_) {
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_MAX:
+ case IOG_ITEM_UNIT_CALC_MIN:
+ return items_[idx].extreme_frame_in_invl;
+ default:
+ 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_->data()->clear();
+ }
+ if (bars_) {
+ bars_->data()->clear();
+ }
+ start_time_ = 0.0;
+}
+
+void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
+{
+ /* Moving average variables */
+ unsigned int mavg_in_average_count = 0, mavg_left = 0;
+ unsigned int mavg_to_remove = 0, mavg_to_add = 0;
+ double mavg_cumulated = 0;
+ QCPAxis *x_axis = nullptr;
+
+ if (graph_) {
+ graph_->data()->clear();
+ x_axis = graph_->keyAxis();
+ }
+ if (bars_) {
+ bars_->data()->clear();
+ 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_to_add = (unsigned int)warmup_interval;
+ }
+
+ for (int i = 0; i <= cur_idx_; i++) {
+ double ts = (double) i * interval_ / 1000;
+ if (x_axis && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(x_axis->ticker())) {
+ 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_;
+ }
+ }
+ if (mavg_in_average_count > 0) {
+ val = mavg_cumulated / mavg_in_average_count;
+ }
+ }
+
+ val *= y_axis_factor_;
+
+ if (hasItemToShow(i, val))
+ {
+ if (graph_) {
+ graph_->addData(ts, val);
+ }
+ if (bars_) {
+ bars_->addData(ts, val);
+ }
+ }
+// qDebug() << "=rgd i" << i << ts << val;
+ }
+
+ // attempt to rescale time values to specific units
+ if (enable_scaling) {
+ calculateScaledValueUnit();
+ } else {
+ scaled_value_unit_.clear();
+ }
+
+ emit requestReplot();
+}
+
+void IOGraph::calculateScaledValueUnit()
+{
+ // Reset unit and recalculate if needed.
+ scaled_value_unit_.clear();
+
+ // If there is no field, scaling is not possible.
+ if (hf_index_ < 0) {
+ return;
+ }
+
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_CALC_SUM:
+ case IOG_ITEM_UNIT_CALC_MAX:
+ case IOG_ITEM_UNIT_CALC_MIN:
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ // Unit is not yet known, continue detecting it.
+ break;
+ default:
+ // Unit is Packets, Bytes, Bits, etc.
+ return;
+ }
+
+ if (proto_registrar_get_ftype(hf_index_) == FT_RELATIVE_TIME) {
+ // find maximum absolute value and scale accordingly
+ double maxValue = 0;
+ if (graph_) {
+ maxValue = maxValueFromGraphData(*graph_->data());
+ } else if (bars_) {
+ maxValue = maxValueFromGraphData(*bars_->data());
+ }
+ // If the maximum value is zero, then either we have no data or
+ // everything is zero, do not scale the unit in this case.
+ if (maxValue == 0) {
+ return;
+ }
+
+ // XXX GTK+ always uses "ms" for log scale, should we do that too?
+ int value_multiplier;
+ if (maxValue >= 1.0) {
+ scaled_value_unit_ = "s";
+ value_multiplier = 1;
+ } else if (maxValue >= 0.001) {
+ scaled_value_unit_ = "ms";
+ value_multiplier = 1000;
+ } else {
+ scaled_value_unit_ = "us";
+ value_multiplier = 1000000;
+ }
+
+ if (graph_) {
+ scaleGraphData(*graph_->data(), value_multiplier);
+ } else if (bars_) {
+ scaleGraphData(*bars_->data(), value_multiplier);
+ }
+ }
+}
+
+template<class DataMap>
+double IOGraph::maxValueFromGraphData(const DataMap &map)
+{
+ double maxValue = 0;
+ typename DataMap::const_iterator it = map.constBegin();
+ while (it != map.constEnd()) {
+ maxValue = MAX(fabs((*it).value), maxValue);
+ ++it;
+ }
+ return maxValue;
+}
+
+template<class DataMap>
+void IOGraph::scaleGraphData(DataMap &map, int scalar)
+{
+ if (scalar != 1) {
+ typename DataMap::iterator it = map.begin();
+ while (it != map.end()) {
+ (*it).value *= scalar;
+ ++it;
+ }
+ }
+}
+
+void IOGraph::captureEvent(CaptureEvent e)
+{
+ if ((e.captureContext() == CaptureEvent::File) &&
+ (e.eventType() == CaptureEvent::Closing))
+ {
+ remove_tap_listener(this);
+ }
+}
+
+void IOGraph::reloadValueUnitField()
+{
+ if (vu_field_.length() > 0) {
+ setValueUnitField(vu_field_);
+ }
+}
+
+// Check if a packet is available at the given interval (idx).
+bool IOGraph::hasItemToShow(int idx, double value) const
+{
+ ws_assert(idx < max_io_items_);
+
+ bool result = false;
+
+ const io_graph_item_t *item = &items_[idx];
+
+ switch (val_units_) {
+ case IOG_ITEM_UNIT_PACKETS:
+ case IOG_ITEM_UNIT_BYTES:
+ case IOG_ITEM_UNIT_BITS:
+ case IOG_ITEM_UNIT_CALC_FRAMES:
+ case IOG_ITEM_UNIT_CALC_FIELDS:
+ if(value == 0.0 && (graph_ && graph_->scatterStyle().shape() != QCPScatterStyle::ssNone)) {
+ result = false;
+ }
+ else {
+ result = true;
+ }
+ break;
+
+ case IOG_ITEM_UNIT_CALC_SUM:
+ case IOG_ITEM_UNIT_CALC_MAX:
+ case IOG_ITEM_UNIT_CALC_MIN:
+ case IOG_ITEM_UNIT_CALC_AVERAGE:
+ case IOG_ITEM_UNIT_CALC_LOAD:
+ if (item->fields) {
+ result = true;
+ }
+ break;
+
+ default:
+ result = true;
+ break;
+ }
+
+ return result;
+}
+
+void IOGraph::setInterval(int interval)
+{
+ interval_ = interval;
+}
+
+// Get the value at the given interval (idx) for the current value unit.
+double IOGraph::getItemValue(int idx, const capture_file *cap_file) const
+{
+ ws_assert(idx < max_io_items_);
+
+ return get_io_graph_item(items_, val_units_, idx, hf_index_, cap_file, interval_, cur_idx_);
+}
+
+// "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
+tap_packet_status IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *, tap_flags_t)
+{
+ IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
+ if (!pinfo || !iog) {
+ return TAP_PACKET_DONT_REDRAW;
+ }
+
+ 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 TAP_PACKET_DONT_REDRAW;
+ }
+
+ /* 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->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 TAP_PACKET_DONT_REDRAW;
+ }
+
+// qDebug() << "=tapPacket" << iog->name_ << idx << iog->hf_index_ << iog->val_units_ << iog->num_items_;
+
+ if (recalc) {
+ emit iog->requestRecalc();
+ }
+ return TAP_PACKET_REDRAW;
+}
+
+// "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();
+ }
+}
+
+// Stat command + args
+
+static void
+io_graph_init(const char *, void*) {
+ mainApp->emitStatCommandSignal("IOGraph", NULL, NULL);
+}
+
+static stat_tap_ui io_stat_ui = {
+ REGISTER_STAT_GROUP_GENERIC,
+ NULL,
+ "io,stat",
+ io_graph_init,
+ 0,
+ NULL
+};
+
+extern "C" {
+
+void register_tap_listener_qt_iostat(void);
+
+void
+register_tap_listener_qt_iostat(void)
+{
+ register_stat_tap_ui(&io_stat_ui, NULL);
+}
+
+}