summaryrefslogtreecommitdiffstats
path: root/ui/qt/io_graph_dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/io_graph_dialog.cpp')
-rw-r--r--ui/qt/io_graph_dialog.cpp1454
1 files changed, 1024 insertions, 430 deletions
diff --git a/ui/qt/io_graph_dialog.cpp b/ui/qt/io_graph_dialog.cpp
index 002b98f9..8e7821a4 100644
--- a/ui/qt/io_graph_dialog.cpp
+++ b/ui/qt/io_graph_dialog.cpp
@@ -7,13 +7,14 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
+#define WS_LOG_DOMAIN LOG_DOMAIN_QTUI
#include "io_graph_dialog.h"
#include <ui_io_graph_dialog.h>
#include "file.h"
+#include "locale.h"
#include <epan/stat_tap_ui.h>
-#include "epan/stats_tree_priv.h"
#include "epan/uat-int.h"
#include <wsutil/utf8_entities.h>
@@ -25,11 +26,16 @@
#include <ui/qt/utils/color_utils.h>
#include <ui/qt/widgets/qcustomplot.h>
+#include <ui/qt/widgets/qcp_string_legend_item.h>
+#include <ui/qt/widgets/qcp_axis_ticker_si.h>
#include "progress_frame.h"
#include "main_application.h"
+#include <ui/qt/main_window.h>
#include <wsutil/filesystem.h>
#include <wsutil/report_message.h>
+#include <wsutil/nstime.h>
+#include <wsutil/to_str.h>
#include <ui/qt/utils/tango_colors.h> //provides some default colors
#include <ui/qt/widgets/copy_from_profile_button.h>
@@ -47,6 +53,8 @@
#include <QTimer>
#include <QVariant>
+#include <new> // std::bad_alloc
+
// 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:
@@ -54,15 +62,30 @@
// - 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.
+// - X-axis time buckets are based on the file relative time, even in
+// Time of Day / absolute time mode. (See io_graph_item.c/get_io_graph_index)
+// Changing this would mean retapping when switching to ToD mode, though.
// To do:
// - Use scroll bars?
-// - Scroll during live captures
+// https://www.qcustomplot.com/index.php/tutorials/specialcases/scrollbar
+// - Scroll during live captures (currently the graph auto rescales instead)
// - 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.
+// - Increase max number of items (or make configurable)
+// - Dark Mode support, e.g.
+// https://www.qcustomplot.com/index.php/demos/barchartdemo
+// - Multiple y-axes?
+// https://www.qcustomplot.com/index.php/demos/multiaxisdemo
+// https://www.qcustomplot.com/index.php/tutorials/specialcases/axistags
+
+// Scale factor to convert the units the interval is stored in to seconds.
+// Must match what get_io_graph_index() in io_graph_item expects.
+// Increase this in order to make smaller intervals possible.
+const int SCALE = 1000000;
+const double SCALE_F = (double)SCALE;
const qreal graph_line_width_ = 1.0;
@@ -77,15 +100,15 @@ const int stat_update_interval_ = 200; // ms
// Saved graph settings
typedef struct _io_graph_settings_t {
- gboolean enabled;
+ bool enabled;
char* name;
char* dfilter;
- guint color;
- guint32 style;
- guint32 yaxis;
+ unsigned color;
+ uint32_t style;
+ uint32_t yaxis;
char* yfield;
- guint32 sma_period;
- guint32 y_axis_factor;
+ uint32_t sma_period;
+ uint32_t y_axis_factor;
} io_graph_settings_t;
static const value_string graph_style_vs[] = {
@@ -105,7 +128,7 @@ static const value_string graph_style_vs[] = {
{ 0, NULL }
};
-static const value_string y_axis_vs[] = {
+static const value_string y_axis_packet_vs[] = {
{ IOG_ITEM_UNIT_PACKETS, "Packets" },
{ IOG_ITEM_UNIT_BYTES, "Bytes" },
{ IOG_ITEM_UNIT_BITS, "Bits" },
@@ -115,10 +138,25 @@ static const value_string y_axis_vs[] = {
{ 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_THROUGHPUT, "THROUGHPUT(Y Field)" },
{ IOG_ITEM_UNIT_CALC_LOAD, "LOAD(Y Field)" },
{ 0, NULL }
};
+static const value_string y_axis_event_vs[] = {
+ { IOG_ITEM_UNIT_PACKETS, "Events" },
+ y_axis_packet_vs[1],
+ y_axis_packet_vs[2],
+ y_axis_packet_vs[3],
+ y_axis_packet_vs[4],
+ y_axis_packet_vs[5],
+ y_axis_packet_vs[6],
+ y_axis_packet_vs[7],
+ y_axis_packet_vs[8],
+ y_axis_packet_vs[9],
+ { 0, NULL }
+};
+
static const value_string moving_avg_vs[] = {
{ 0, "None" },
{ 10, "10 interval SMA" },
@@ -131,24 +169,31 @@ static const value_string moving_avg_vs[] = {
{ 0, NULL }
};
-static io_graph_settings_t *iog_settings_ = NULL;
-static guint num_io_graphs_ = 0;
-static uat_t *iog_uat_ = NULL;
+static io_graph_settings_t *iog_settings_;
+static unsigned num_io_graphs_;
+static uat_t *iog_uat_;
+// XXX - Multiple UatModels with the same uat can crash if one is
+// edited, because the underlying uat_t* data changes but the
+// record_errors and dirty_records lists do not.
+static QPointer<UatModel> static_uat_model_;
// 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"
};
+static char *decimal_point;
+
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"
+//Allow the enable/disable field to be a checkbox, but for backwards
+//compatibility with pre-2.6 versions, the strings are "Enabled"/"Disabled",
+//not "true"/"false". (Pre-4.4 versions require "true" to be all-caps.)
#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)) {\
+static void basename ## _ ## field_name ## _set_cb(void* rec, const char* buf, unsigned 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)) \
+ if (tmp_str && ((g_strcmp0(tmp_str, "Enabled") == 0) || \
+ (g_ascii_strcasecmp(tmp_str, "true") == 0))) \
((rec_t*)rec)->field_name = 1; \
else \
((rec_t*)rec)->field_name = 0; \
@@ -157,32 +202,33 @@ static void basename ## _ ## field_name ## _tostr_cb(void* rec, char** out_ptr,
*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)
+static bool uat_fld_chk_enable(void* u1 _U_, const char* strptr, unsigned len, const void* u2 _U_, const void* u3 _U_, char** err)
{
char* str = g_strndup(strptr,len);
- if ((g_strcmp0(str, "Enabled") == 0) ||
+ if (str &&
+ ((g_strcmp0(str, "Enabled") == 0) ||
(g_strcmp0(str, "Disabled") == 0) ||
- (g_strcmp0(str, "TRUE") == 0) || //just for UAT functionality
- (g_strcmp0(str, "FALSE") == 0)) {
+ (g_ascii_strcasecmp(str, "true") == 0) || //just for UAT functionality
+ (g_ascii_strcasecmp(str, "false") == 0))) {
*err = NULL;
g_free(str);
- return TRUE;
+ 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;
+ 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_)
+static void io_graph_sma_period_set_cb(void* rec, const char* buf, unsigned len, const void* vs, const void* u2 _U_)
{
- guint i;
+ unsigned i;
char* str = g_strndup(buf,len);
const char* cstr;
((io_graph_settings_t*)rec)->sma_period = 0;
@@ -202,7 +248,7 @@ static void io_graph_sma_period_set_cb(void* rec, const char* buf, guint len, co
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;
+ ((io_graph_settings_t*)rec)->sma_period = (uint32_t)((const value_string*)vs)[i].value;
g_free(str);
return;
}
@@ -212,7 +258,7 @@ static void io_graph_sma_period_set_cb(void* rec, const char* buf, guint len, co
//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;
+ unsigned 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);
@@ -224,9 +270,9 @@ static void io_graph_sma_period_tostr_cb(void* rec, char** out_ptr, unsigned* ou
*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) {
+static bool sma_period_chk_enum(void* u1 _U_, const char* strptr, unsigned len, const void* v, const void* u3 _U_, char** err) {
char *str = g_strndup(strptr,len);
- guint i;
+ unsigned i;
const value_string* vs = (const value_string *)v;
//Original UAT had just raw numbers and not enumerated values with "interval SMA"
@@ -246,13 +292,13 @@ static bool sma_period_chk_enum(void* u1 _U_, const char* strptr, guint len, con
if (g_strcmp0(vs[i].strptr,str) == 0) {
*err = NULL;
g_free(str);
- return TRUE;
+ return true;
}
}
*err = ws_strdup_printf("invalid value: %s",str);
g_free(str);
- return FALSE;
+ return false;
}
#define UAT_FLD_SMA_PERIOD(basename,field_name,title,enum,desc) \
@@ -263,18 +309,33 @@ 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_VS_DEF(io_graph, style, io_graph_settings_t, uint32_t, 0, "Line")
+// XXX Need to use "Events" where appropriate.
+UAT_VS_DEF(io_graph, yaxis, io_graph_settings_t, uint32_t, 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[] = {
+static uat_field_t io_graph_packet_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_packet_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 uat_field_t io_graph_event_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_VS(io_graph, yaxis, "Y Axis", y_axis_event_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"),
@@ -306,16 +367,25 @@ static void io_graph_free_cb(void* p) {
g_free(iogs->yfield);
}
+// If the uat changes outside the model, e.g. when changing profiles,
+// we need to tell the UatModel.
+static void io_graph_post_update_cb() {
+ if (static_uat_model_) {
+ static_uat_model_->reloadUat();
+ }
+}
+
} // extern "C"
-IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFilter) :
+IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFilter,
+ io_graph_item_unit_t value_units, QString yfield) :
WiresharkDialog(parent, cf),
ui(new Ui::IOGraphDialog),
uat_model_(nullptr),
uat_delegate_(nullptr),
base_graph_(nullptr),
tracer_(nullptr),
- start_time_(0.0),
+ start_time_(NSTIME_INIT_ZERO),
mouse_drags_(true),
rubber_band_(nullptr),
stat_timer_(nullptr),
@@ -355,15 +425,17 @@ IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFi
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);
+ copy_profile_bt_ = new CopyFromProfileButton(this, "io_graphs", tr("Copy graphs from another profile."));
+ ui->buttonBox->addButton(copy_profile_bt_, QDialogButtonBox::ActionRole);
+ connect(copy_profile_bt_, &CopyFromProfileButton::copyProfile, this, &IOGraphDialog::copyFromProfile);
QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
if (close_bt) {
close_bt->setDefault(true);
}
+ connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &IOGraphDialog::buttonBoxClicked);
+
ui->automaticUpdateCheckBox->setChecked(prefs.gui_io_graph_automatic_update ? true : false);
ui->enableLegendCheckBox->setChecked(prefs.gui_io_graph_enable_legend ? true : false);
@@ -373,22 +445,38 @@ IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFi
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);
+ // #6441 asks for arbitrary values. We could probably do that with
+ // a QSpinBox, e.g. using QAbstractSpinBox::AdaptiveDecimalStepType
+ // or similar (it only exists starting in Qt 5.12) and suffix(),
+ // or something fancier with valueFromText() and textFromValue() to
+ // convert to and from SI prefixes.
+ ui->intervalComboBox->addItem(tr("1 μs"), SCALE / 1000000);
+ ui->intervalComboBox->addItem(tr("2 μs"), SCALE / 500000);
+ ui->intervalComboBox->addItem(tr("5 μs"), SCALE / 200000);
+ ui->intervalComboBox->addItem(tr("10 μs"), SCALE / 100000);
+ ui->intervalComboBox->addItem(tr("20 μs"), SCALE / 50000);
+ ui->intervalComboBox->addItem(tr("50 μs"), SCALE / 20000);
+ ui->intervalComboBox->addItem(tr("100 μs"), SCALE / 10000);
+ ui->intervalComboBox->addItem(tr("200 μs"), SCALE / 5000);
+ ui->intervalComboBox->addItem(tr("500 μs"), SCALE / 2000);
+ ui->intervalComboBox->addItem(tr("1 ms"), SCALE / 1000);
+ ui->intervalComboBox->addItem(tr("2 ms"), SCALE / 500);
+ ui->intervalComboBox->addItem(tr("5 ms"), SCALE / 200);
+ ui->intervalComboBox->addItem(tr("10 ms"), SCALE / 100);
+ ui->intervalComboBox->addItem(tr("20 ms"), SCALE / 50);
+ ui->intervalComboBox->addItem(tr("50 ms"), SCALE / 20);
+ ui->intervalComboBox->addItem(tr("100 ms"), SCALE / 10);
+ ui->intervalComboBox->addItem(tr("200 ms"), SCALE / 5);
+ ui->intervalComboBox->addItem(tr("500 ms"), SCALE / 2);
+ ui->intervalComboBox->addItem(tr("1 sec"), SCALE);
+ ui->intervalComboBox->addItem(tr("2 sec"), SCALE * 2);
+ ui->intervalComboBox->addItem(tr("5 sec"), SCALE * 5);
+ ui->intervalComboBox->addItem(tr("10 sec"), SCALE * 10);
+ ui->intervalComboBox->addItem(tr("1 min"), SCALE * 60);
+ ui->intervalComboBox->addItem(tr("2 min"), SCALE * 120);
+ ui->intervalComboBox->addItem(tr("5 min"), SCALE * 300);
+ ui->intervalComboBox->addItem(tr("10 min"), SCALE * 600);
+ ui->intervalComboBox->setCurrentIndex(18);
ui->todCheckBox->setChecked(false);
iop->xAxis->setTicker(number_ticker_);
@@ -419,6 +507,9 @@ IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFi
ctx_menu_.addAction(ui->actionCrosshairs);
set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
+ iop->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(iop, &QCustomPlot::customContextMenuRequested, this, &IOGraphDialog::showContextMenu);
+
iop->xAxis->setLabel(tr("Time (s)"));
iop->setMouseTracking(true);
@@ -433,22 +524,23 @@ IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFi
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)
+ IOGraph *iog = ioGraphs_.at(i);
+ if (iog->filter().compare(displayFilter) == 0 &&
+ iog->valueUnitField().compare(yfield) == 0 &&
+ iog->valueUnits() == value_units) {
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);
+ }
+
+ if (! filterExists && (!displayFilter.isEmpty() || !yfield.isEmpty())) {
+ addGraph(true, displayFilter, value_units, yfield);
}
toggleTracerStyle(true);
@@ -460,14 +552,23 @@ IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFi
ui->splitter->setStretchFactor(0, 95);
ui->splitter->setStretchFactor(1, 5);
+ loadSplitterState(ui->splitter);
//XXX - resize columns?
+ //ui->graphUat->header()->resizeSections(QHeaderView::ResizeToContents);
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*)));
+
+ connect(iop, &QCustomPlot::beforeReplot, this, &IOGraphDialog::updateLegend);
+
+ MainWindow *main_window = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ if (main_window != nullptr) {
+ connect(main_window, &MainWindow::framesSelected, this, &IOGraphDialog::selectedFrameChanged);
+ }
}
IOGraphDialog::~IOGraphDialog()
@@ -482,15 +583,18 @@ IOGraphDialog::~IOGraphDialog()
void IOGraphDialog::copyFromProfile(QString filename)
{
- guint orig_data_len = iog_uat_->raw_data->len;
+ if (uat_model_ == nullptr)
+ return;
- gchar *err = NULL;
+ char *err = NULL;
+ // uat_load appends rows to the current UAT, using filename.
+ // We should let the UatModel handle it, and have the UatModel
+ // call beginInsertRows() and endInsertRows(), so that we can
+ // just add the new rows instead of resetting the information.
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);
- }
+ iog_uat_->changed = true;
+ // uat_load calls the post update cb, which reloads the Uat.
+ //uat_model_->reloadUat();
} else {
report_failure("Error while loading %s: %s", iog_uat_->name, err);
g_free(err);
@@ -499,6 +603,8 @@ void IOGraphDialog::copyFromProfile(QString filename)
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)
{
+ if (uat_model_ == nullptr)
+ return;
QVariantList newRowData;
newRowData.append(checked ? Qt::Checked : Qt::Unchecked);
@@ -507,12 +613,12 @@ void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, QRgb c
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"));
+ newRowData.append(val_to_str_const(value_units, y_axis_packet_vs, "Packets"));
} else {
- newRowData.append(val_to_str_const(value_units, y_axis_vs, "Events"));
+ newRowData.append(val_to_str_const(value_units, y_axis_event_vs, "Events"));
}
newRowData.append(yfield);
- newRowData.append(val_to_str_const((guint32) moving_average, moving_avg_vs, "None"));
+ newRowData.append(val_to_str_const((uint32_t) moving_average, moving_avg_vs, "None"));
newRowData.append(y_axis_factor);
QModelIndex newIndex = uat_model_->appendEntry(newRowData);
@@ -522,11 +628,36 @@ void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, QRgb c
return;
}
ui->graphUat->setCurrentIndex(newIndex);
- createIOGraph(newIndex.row());
+}
+
+void IOGraphDialog::addGraph(bool checked, QString dfilter, io_graph_item_unit_t value_units, QString yfield)
+{
+ if (uat_model_ == nullptr)
+ return;
+
+ QString graph_name;
+ if (yfield.isEmpty()) {
+ if (!dfilter.isEmpty()) {
+ graph_name = is_packet_configuration_namespace() ? tr("Filtered packets") : tr("Filtered events");
+ } else {
+ graph_name = is_packet_configuration_namespace() ? tr("All packets") : tr("All events");
+ }
+ } else {
+ if (is_packet_configuration_namespace()) {
+ graph_name = QString(val_to_str_const(value_units, y_axis_packet_vs, "Unknown")).replace("Y Field", yfield);
+ } else {
+ graph_name = QString(val_to_str_const(value_units, y_axis_event_vs, "Unknown")).replace("Y Field", yfield);
+ }
+ }
+ addGraph(checked, std::move(graph_name), dfilter, ColorUtils::graphColor(uat_model_->rowCount()),
+ IOGraph::psLine, value_units, yfield, DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
}
void IOGraphDialog::addGraph(bool copy_from_current)
{
+ if (uat_model_ == nullptr)
+ return;
+
const QModelIndex &current = ui->graphUat->currentIndex();
if (copy_from_current && !current.isValid())
return;
@@ -540,8 +671,6 @@ void IOGraphDialog::addGraph(bool copy_from_current)
qDebug() << "Failed to add a new record";
return;
}
- createIOGraph(copyIdx.row());
-
ui->graphUat->setCurrentIndex(copyIdx);
} else {
addDefaultGraph(false);
@@ -553,22 +682,19 @@ void IOGraphDialog::addGraph(bool copy_from_current)
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));
+ // XXX - Should IOGraph have its own list that has to sync with UAT?
+ ioGraphs_.insert(currentRow, 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()));
+ connect(this, &IOGraphDialog::recalcGraphData, iog, &IOGraph::recalcGraphData);
+ connect(this, &IOGraphDialog::reloadValueUnitFields, iog, &IOGraph::reloadValueUnitField);
+ connect(&cap_file_, &CaptureFile::captureEvent, iog, &IOGraph::captureEvent);
+ connect(iog, &IOGraph::requestRetap, this, [=]() { scheduleRetap(); });
+ connect(iog, &IOGraph::requestRecalc, this, [=]() { scheduleRecalc(); });
+ connect(iog, &IOGraph::requestReplot, this, [=]() { scheduleReplot(); });
syncGraphSettings(currentRow);
- if (iog->visible()) {
- scheduleRetap();
- }
+ iog->setNeedRetap(true);
}
void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
@@ -591,7 +717,7 @@ void IOGraphDialog::addDefaultGraph(bool enabled, int 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
+ addGraph(enabled, tr("All Execs"), "evt.type == \"execve\"", ColorUtils::graphColor(4), // 4 = red
IOGraph::psDot, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
break;
}
@@ -613,11 +739,10 @@ void IOGraphDialog::syncGraphSettings(int row)
{
IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
- if (!uat_model_->index(row, colEnabled).isValid() || !iog)
+ if (!uat_model_ || !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());
@@ -625,7 +750,11 @@ void IOGraphDialog::syncGraphSettings(int row)
/* 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));
+ if (is_packet_configuration_namespace()) {
+ iog->setValueUnits((int) str_to_val(qUtf8Printable(data_str), y_axis_packet_vs, IOG_ITEM_UNIT_PACKETS));
+ } else {
+ iog->setValueUnits((int) str_to_val(qUtf8Printable(data_str), y_axis_event_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());
@@ -642,7 +771,6 @@ void IOGraphDialog::syncGraphSettings(int row)
if (!iog->configError().isEmpty()) {
hint_err_ = iog->configError();
visible = false;
- retap = false;
} else {
hint_err_.clear();
}
@@ -650,18 +778,18 @@ void IOGraphDialog::syncGraphSettings(int row)
iog->setVisible(visible);
getGraphInfo();
- mouseMoved(NULL); // Update hint
- updateLegend();
+ updateHint();
if (visible) {
- if (retap) {
- scheduleRetap();
- } else {
- scheduleReplot();
- }
+ scheduleReplot();
}
}
+qsizetype IOGraphDialog::graphCount() const
+{
+ return uat_model_ ? uat_model_->rowCount() : ioGraphs_.size();
+}
+
void IOGraphDialog::updateWidgets()
{
WiresharkDialog::updateWidgets();
@@ -671,9 +799,6 @@ 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)
@@ -693,6 +818,27 @@ void IOGraphDialog::reloadFields()
emit reloadValueUnitFields();
}
+void IOGraphDialog::captureFileClosing()
+{
+ // The other buttons will be disabled when the model is set to null.
+ ui->newToolButton->setEnabled(false);
+ ui->intervalComboBox->setEnabled(false);
+ copy_profile_bt_->setEnabled(false);
+ if (uat_model_) {
+ applyChanges();
+ disconnect(uat_model_, nullptr, this, nullptr);
+ }
+ // It would be nice to keep the information in the UAT about the graphs
+ // visible in a read-only state after closing, but if the view is just
+ // disabled, updating the model from elsewhere (e.g., other dialogs)
+ // will still change it, so we'd need to copy the information into
+ // a new model.
+ uat_model_ = nullptr;
+ ui->graphUat->setModel(nullptr);
+ ui->graphUat->setVisible(false);
+ WiresharkDialog::captureFileClosing();
+}
+
void IOGraphDialog::keyPressEvent(QKeyEvent *event)
{
int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
@@ -764,19 +910,25 @@ void IOGraphDialog::keyPressEvent(QKeyEvent *event)
QDialog::keyPressEvent(event);
}
-void IOGraphDialog::reject()
+void IOGraphDialog::applyChanges()
{
- if (!uat_model_)
+ if (!static_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 (static_uat_model_->applyChanges(error)) {
if (!error.isEmpty()) {
report_failure("%s", qPrintable(error));
}
}
+}
+
+void IOGraphDialog::reject()
+{
+ if (uat_model_)
+ applyChanges();
QDialog::reject();
}
@@ -881,12 +1033,12 @@ void IOGraphDialog::toggleTracerStyle(bool force_default)
IOGraph *IOGraphDialog::currentActiveGraph() const
{
QModelIndex index = ui->graphUat->currentIndex();
- if (index.isValid()) {
+ if (index.isValid() && graphIsEnabled(index.row())) {
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++)
+ for (int row = 0; row < graphCount(); row++)
{
if (graphIsEnabled(row)) {
return ioGraphs_.value(row, NULL);
@@ -898,8 +1050,13 @@ IOGraph *IOGraphDialog::currentActiveGraph() const
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;
+ if (uat_model_) {
+ Qt::CheckState state = static_cast<Qt::CheckState>(uat_model_->data(uat_model_->index(row, colEnabled), Qt::CheckStateRole).toInt());
+ return state == Qt::Checked;
+ } else {
+ IOGraph* iog = ioGraphs_.value(row, nullptr);
+ return (iog && iog->visible());
+ }
}
// Scan through our graphs and gather information.
@@ -909,7 +1066,7 @@ void IOGraphDialog::getGraphInfo()
{
base_graph_ = NULL;
QCPBars *prev_bars = NULL;
- start_time_ = 0.0;
+ nstime_set_zero(&start_time_);
tracer_->setGraph(NULL);
IOGraph *selectedGraph = currentActiveGraph();
@@ -931,9 +1088,9 @@ void IOGraphDialog::getGraphInfo()
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;
+ nstime_t iog_start = iog->startTime();
+ if (nstime_is_zero(&start_time_) || nstime_cmp(&iog_start, &start_time_) < 0) {
+ nstime_copy(&start_time_, &iog_start);
}
}
@@ -946,9 +1103,78 @@ void IOGraphDialog::getGraphInfo()
}
}
+void IOGraphDialog::updateHint()
+{
+ QCustomPlot *iop = ui->ioPlot;
+ QString hint;
+
+ // 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 (mouse_drags_) {
+ double ts = 0;
+ packet_num_ = 0;
+ int interval_packet = -1;
+
+ if (tracer_->graph()) {
+ ts = tracer_->position->key();
+ if (IOGraph *iog = currentActiveGraph()) {
+ interval_packet = iog->packetFromTime(ts - nstime_to_sec(&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_ = (uint32_t) 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);
+ }
+ // XXX - If Time of Day is selected, should we use ISO 8601
+ // timestamps or something similar here instead of epoch time?
+ hint += tr("%1 (%2s%3).")
+ .arg(msg)
+ .arg(QString::number(ts, 'f', precision_))
+ .arg(val);
+ }
+ iop->replot(QCustomPlot::rpQueuedReplot);
+ } else {
+ if (rubber_band_ && rubber_band_->isVisible()) {
+ QRectF zoom_ranges = getZoomRanges(rubber_band_->geometry());
+ 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::updateLegend()
{
QCustomPlot *iop = ui->ioPlot;
+ QSet<format_size_units_e> format_units_set;
QSet<QString> vu_label_set;
QString intervalText = ui->intervalComboBox->itemText(ui->intervalComboBox->currentIndex());
@@ -956,49 +1182,60 @@ void IOGraphDialog::updateLegend()
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);
- }
+ for (int row = 0; row < graphCount(); row++) {
+ IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
+ if (graphIsEnabled(row) && iog) {
+ QString label(iog->valueUnitLabel());
+ vu_label_set.insert(label);
+ format_units_set.insert(iog->formatUnits());
}
}
// Nothing.
if (vu_label_set.size() < 1) {
+ iop->legend->layer()->replot();
return;
}
+ format_size_units_e format_units = FORMAT_SIZE_UNIT_NONE;
+ if (format_units_set.size() == 1) {
+ format_units = format_units_set.values().constFirst();
+ }
+
+ QSharedPointer<QCPAxisTickerSi> si_ticker = qSharedPointerDynamicCast<QCPAxisTickerSi>(iop->yAxis->ticker());
+ if (format_units != FORMAT_SIZE_UNIT_NONE) {
+ if (si_ticker) {
+ si_ticker->setUnit(format_units);
+ } else {
+ iop->yAxis->setTicker(QSharedPointer<QCPAxisTickerSi>(new QCPAxisTickerSi(format_units, QString(), ui->logCheckBox->isChecked())));
+ }
+ } else {
+ if (si_ticker) {
+ if (ui->logCheckBox->isChecked()) {
+ iop->yAxis->setTicker(QSharedPointer<QCPAxisTickerLog>(new QCPAxisTickerLog));
+ } else {
+ iop->yAxis->setTicker(QSharedPointer<QCPAxisTicker>(new QCPAxisTicker));
+ }
+ }
+ }
+
// All the same. Use the Y Axis label.
if (vu_label_set.size() == 1) {
- iop->yAxis->setLabel(vu_label_set.values()[0] + "/" + intervalText);
- return;
+ iop->yAxis->setLabel(vu_label_set.values().constFirst() + "/" + intervalText);
}
- // Differing labels. Create a legend with a Title label at top.
+ // 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();
- }
+ iop->legend->clearItems();
+ QCPStringLegendItem *legendTitle = new QCPStringLegendItem(iop->legend, QString(tr("%1 Intervals ").arg(intervalText)));
+ iop->legend->insertRow(0);
+ iop->legend->addElement(0, 0, legendTitle);
+
+ for (int row = 0; row < graphCount(); row++) {
+ IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
+ if (iog) {
+ if (graphIsEnabled(row)) {
+ iog->addToLegend();
}
}
}
@@ -1037,19 +1274,37 @@ QRectF IOGraphDialog::getZoomRanges(QRect zoom_rect)
return zoom_ranges;
}
+void IOGraphDialog::showContextMenu(const QPoint &pos)
+{
+ if (ui->ioPlot->legend->selectTest(pos, false) >= 0) {
+ QMenu *menu = new QMenu(this);
+ menu->setAttribute(Qt::WA_DeleteOnClose);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
+ menu->addAction(tr("Move to top left"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignTop|Qt::AlignLeft).toInt());
+ menu->addAction(tr("Move to top center"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignTop|Qt::AlignHCenter).toInt());
+ menu->addAction(tr("Move to top right"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignTop|Qt::AlignRight).toInt());
+ menu->addAction(tr("Move to bottom left"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignBottom|Qt::AlignLeft).toInt());
+ menu->addAction(tr("Move to bottom center"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignBottom|Qt::AlignHCenter).toInt());
+ menu->addAction(tr("Move to bottom right"), this, &IOGraphDialog::moveLegend)->setData((Qt::AlignBottom|Qt::AlignRight).toInt());
+#else
+ menu->addAction(tr("Move to top left"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignTop|Qt::AlignLeft));
+ menu->addAction(tr("Move to top center"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignTop|Qt::AlignHCenter));
+ menu->addAction(tr("Move to top right"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignTop|Qt::AlignRight));
+ menu->addAction(tr("Move to bottom left"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignBottom|Qt::AlignLeft));
+ menu->addAction(tr("Move to bottom center"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignBottom|Qt::AlignHCenter));
+ menu->addAction(tr("Move to bottom right"), this, &IOGraphDialog::moveLegend)->setData(static_cast<Qt::Alignment::Int>(Qt::AlignBottom|Qt::AlignRight));
+#endif
+ menu->popup(ui->ioPlot->mapToGlobal(pos));
+ } else {
+ ctx_menu_.popup(ui->ioPlot->mapToGlobal(pos));
+ }
+}
+
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 (mouse_drags_) {
if (iop->axisRect()->rect().contains(event->pos())) {
iop->setCursor(QCursor(Qt::ClosedHandCursor));
}
@@ -1068,87 +1323,35 @@ void IOGraphDialog::graphClicked(QMouseEvent *event)
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;
- }
+ 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));
}
+ iop->setCursor(QCursor(shape));
if (mouse_drags_) {
- double ts = 0;
- packet_num_ = 0;
- int interval_packet = -1;
-
- if (event && tracer_->graph()) {
+ if (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()) {
+ if (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);
+ updateHint();
}
void IOGraphDialog::mouseReleased(QMouseEvent *event)
@@ -1172,51 +1375,99 @@ void IOGraphDialog::mouseReleased(QMouseEvent *event)
}
}
+void IOGraphDialog::moveLegend()
+{
+ if (QAction *contextAction = qobject_cast<QAction*>(sender())) {
+ if (contextAction->data().canConvert<Qt::Alignment::Int>()) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
+ ui->ioPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::Alignment::fromInt(contextAction->data().value<Qt::Alignment::Int>()));
+#else
+ ui->ioPlot->axisRect()->insetLayout()->setInsetAlignment(0, static_cast<Qt::Alignment>(contextAction->data().value<Qt::Alignment::Int>()));
+#endif
+ ui->ioPlot->replot();
+ }
+ }
+}
+
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);
+ QCPRange x_range = iop->xAxis->scaleType() == QCPAxis::stLogarithmic ?
+ iop->xAxis->range().sanitizedForLogScale() : iop->xAxis->range();
double axis_pixels = iop->xAxis->axisRect()->width();
iop->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
+ QCPRange y_range = iop->yAxis->scaleType() == QCPAxis::stLogarithmic ?
+ iop->yAxis->range().sanitizedForLogScale() : iop->yAxis->range();
axis_pixels = iop->yAxis->axisRect()->height();
- iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, iop->yAxis->range().center());
+ iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, y_range.center());
auto_axes_ = true;
iop->replot();
}
+void IOGraphDialog::selectedFrameChanged(QList<int> frames)
+{
+ if (frames.count() == 1 && cap_file_.isValid() && !file_closed_ && tracer_->graph() && cap_file_.packetInfo() != nullptr) {
+ packet_info *pinfo = cap_file_.packetInfo();
+ if (pinfo->num != packet_num_) {
+ // This prevents being triggered by the IOG's own GoToPacketAction,
+ // although that is mostly harmless.
+ int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
+
+ /*
+ * setGraphKey (with Interpolation false, as it is by default)
+ * finds the nearest point to the key. Our buckets are derived
+ * from rounding down (XXX - which is appropriate for relative
+ * time but less so when absolute time of day is selected.)
+ * We could call get_io_graph_index() and then multiply to get
+ * the exact ts for the bucket, but it's fewer math operations
+ * operations simply to subtract half the interval.
+ * XXX - Getting the exact value would be superior if we wished
+ * to avoid doing anything in the case that the tracer is
+ * already pointing at the correct bucket. (Is the hint always
+ * correct in that case?)
+ */
+#if 0
+ int64_t idx = get_io_graph_index(pinfo, interval);
+ double ts = (double)idx * interval / SCALE_F + nstime_to_sec(&start_time);
+#endif
+ double key = nstime_to_sec(&pinfo->rel_ts) - (interval / (2 * SCALE_F)) + nstime_to_sec(&start_time_);
+ tracer_->setGraphKey(key);
+ ui->ioPlot->replot();
+ updateHint();
+ }
+ }
+}
+
void IOGraphDialog::updateStatistics()
{
if (!isVisible()) return;
- if (need_retap_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
+ /* XXX - If we're currently retapping, what we really want to do is
+ * abort the current tap and start over. process_specified_records()
+ * in file.c doesn't let us do that, because it doesn't know whether
+ * it's holding cf->read_lock for something that could be restarted
+ * (like tapping or dissection) or something that needs to run to
+ * completion (saving, printing.)
+ *
+ * So we wait and see if we're no longer tapping the next check.
+ */
+ if (need_retap_ && !file_closed_ && !retapDepth() && prefs.gui_io_graph_automatic_update) {
need_retap_ = false;
- cap_file_.retapPackets();
+ QTimer::singleShot(0, &cap_file_, &CaptureFile::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);
+ emit recalcGraphData(cap_file_.capFile());
if (!tracer_->graph()) {
if (base_graph_ && base_graph_->data()->size() > 0) {
tracer_->setGraph(base_graph_);
@@ -1239,11 +1490,15 @@ void IOGraphDialog::updateStatistics()
void IOGraphDialog::loadProfileGraphs()
{
if (iog_uat_ == NULL) {
+ uat_field_t *io_graph_fields = io_graph_packet_fields;
+ if (!is_packet_configuration_namespace()) {
+ io_graph_fields = io_graph_event_fields;
+ }
iog_uat_ = uat_new("I/O Graphs",
sizeof(io_graph_settings_t),
"io_graphs",
- TRUE,
+ true,
&iog_settings_,
&num_io_graphs_,
0, /* doesn't affect anything that requires a GUI update */
@@ -1251,7 +1506,7 @@ void IOGraphDialog::loadProfileGraphs()
io_graph_copy_cb,
NULL,
io_graph_free_cb,
- NULL,
+ io_graph_post_update_cb,
NULL,
io_graph_fields);
@@ -1263,16 +1518,26 @@ void IOGraphDialog::loadProfileGraphs()
g_free(err);
uat_clear(iog_uat_);
}
+
+ static_uat_model_ = new UatModel(mainApp, iog_uat_);
+ connect(mainApp, &MainApplication::profileChanging, IOGraphDialog::applyChanges);
}
- uat_model_ = new UatModel(NULL, iog_uat_);
- uat_delegate_ = new UatDelegate;
+ uat_model_ = static_uat_model_;
+ uat_delegate_ = new UatDelegate(ui->graphUat);
ui->graphUat->setModel(uat_model_);
ui->graphUat->setItemDelegate(uat_delegate_);
+ ui->graphUat->setSelectionMode(QAbstractItemView::ContiguousSelection);
+
+ ui->graphUat->setHeader(new ResizeHeaderView(Qt::Horizontal, ui->graphUat));
- connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- this, SLOT(modelDataChanged(QModelIndex)));
- connect(uat_model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
+ connect(uat_model_, &UatModel::dataChanged, this, &IOGraphDialog::modelDataChanged);
+ connect(uat_model_, &UatModel::modelReset, this, &IOGraphDialog::modelRowsReset);
+ connect(uat_model_, &UatModel::rowsInserted, this, &IOGraphDialog::modelRowsInserted);
+ connect(uat_model_, &UatModel::rowsRemoved, this, &IOGraphDialog::modelRowsRemoved);
+ connect(uat_model_, &UatModel::rowsMoved, this, &IOGraphDialog::modelRowsMoved);
+
+ connect(ui->graphUat->selectionModel(), &QItemSelectionModel::selectionChanged, this, &IOGraphDialog::graphUatSelectionChanged);
}
// Slots
@@ -1282,6 +1547,22 @@ void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
bool need_retap = false;
+ precision_ = ceil(log10(SCALE_F / interval));
+ if (precision_ < 0) {
+ precision_ = 0;
+ }
+
+ // XXX - This is the default QCP date time format, but adding fractional
+ // seconds when our interval is small. Should we make it something else,
+ // like ISO 8601 (but still with a line break between time and date)?
+ // Note this is local time, with no time zone offset displayed. Should
+ // it be in UTC? (call setDateTimeSpec())
+ if (precision_) {
+ datetime_ticker_->setDateTimeFormat("hh:mm:ss.z\ndd.MM.yy");
+ } else {
+ datetime_ticker_->setDateTimeFormat("hh:mm:ss\ndd.MM.yy");
+ }
+
if (uat_model_ != NULL) {
for (int row = 0; row < uat_model_->rowCount(); row++) {
IOGraph *iog = ioGraphs_.value(row, NULL);
@@ -1289,6 +1570,8 @@ void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
iog->setInterval(interval);
if (iog->visible()) {
need_retap = true;
+ } else {
+ iog->setNeedRetap(true);
}
}
}
@@ -1297,13 +1580,12 @@ void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
if (need_retap) {
scheduleRetap(true);
}
-
- updateLegend();
}
void IOGraphDialog::on_todCheckBox_toggled(bool checked)
{
- double orig_start = start_time_;
+ nstime_t orig_start;
+ nstime_copy(&orig_start, &start_time_);
bool orig_auto = auto_axes_;
if (checked) {
@@ -1315,46 +1597,135 @@ void IOGraphDialog::on_todCheckBox_toggled(bool checked)
scheduleRecalc(true);
auto_axes_ = orig_auto;
getGraphInfo();
- ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
- mouseMoved(NULL); // Update hint
+ nstime_delta(&orig_start, &start_time_, &orig_start);
+ ui->ioPlot->xAxis->moveRange(nstime_to_sec(&orig_start));
+ updateHint();
}
void IOGraphDialog::modelRowsReset()
{
+ foreach(IOGraph* iog, ioGraphs_) {
+ delete iog;
+ }
+ ioGraphs_.clear();
+
+ for (int i = 0; i < uat_model_->rowCount(); i++) {
+ createIOGraph(i);
+ }
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&)
+void IOGraphDialog::modelRowsInserted(const QModelIndex &, int first, int last)
{
- if (current.isValid()) {
+ // first to last is inclusive
+ for (int i = first; i <= last; i++) {
+ createIOGraph(i);
+ }
+}
+
+void IOGraphDialog::modelRowsRemoved(const QModelIndex &, int first, int last)
+{
+ // first to last is inclusive
+ for (int i = last; i >= first; i--) {
+ IOGraph *iog = ioGraphs_.takeAt(i);
+ delete iog;
+ }
+}
+
+void IOGraphDialog::modelRowsMoved(const QModelIndex &source, int sourceStart, int sourceEnd, const QModelIndex &dest, int destinationRow)
+{
+ // The source and destination parent are always the same for UatModel.
+ ws_assert(source == dest);
+ // Either destinationRow < sourceStart, or destinationRow > sourceEnd.
+ // When moving rows down the same parent, the rows are placed _before_
+ // destinationRow, otherwise it's the row to which items are moved.
+ if (destinationRow < sourceStart) {
+ for (int i = 0; i <= sourceEnd - sourceStart; i++) {
+ // When moving up the same parent, moving an earlier
+ // item doesn't change the row.
+ ioGraphs_.move(sourceStart + i, destinationRow + i);
+ }
+ } else {
+ for (int i = 0; i <= sourceEnd - sourceStart; i++) {
+ // When moving down the same parent, moving an earlier
+ // item means the next items move up (so all the moved
+ // rows are always at sourceStart.)
+ ioGraphs_.move(sourceStart, destinationRow - 1);
+ }
+ }
+
+ // setting a QCPLayerable to its current layer moves it to the end
+ // as though it were the last added. Do that for all the plottables
+ // starting with the first one that changed, so that the graphs appear
+ // as though they were added in the current order.
+ // (moveToLayer() is the same thing but with a parameter to prepend
+ // instead, which would be faster if we're in the top half of the
+ // list, except that's a protected function. There's no function
+ // to swap layerables in a layer.)
+ IOGraph *iog;
+ for (int row = qMin(sourceStart, destinationRow); row < uat_model_->rowCount(); row++) {
+ iog = ioGraphs_.at(row);
+ if (iog->graph()) {
+ iog->graph()->setLayer(iog->graph()->layer());
+ } else if (iog->bars()) {
+ iog->bars()->setLayer(iog->bars()->layer());
+ }
+ }
+ ui->ioPlot->replot();
+}
+
+void IOGraphDialog::graphUatSelectionChanged(const QItemSelection&, const QItemSelection&)
+{
+ QModelIndexList selectedRows = ui->graphUat->selectionModel()->selectedRows();
+ qsizetype num_selected = selectedRows.size();
+ if (num_selected > 0) {
+ std::sort(selectedRows.begin(), selectedRows.end());
ui->deleteToolButton->setEnabled(true);
ui->copyToolButton->setEnabled(true);
- ui->clearToolButton->setEnabled(true);
- ui->moveUpwardsToolButton->setEnabled(true);
- ui->moveDownwardsToolButton->setEnabled(true);
+ ui->moveUpwardsToolButton->setEnabled(selectedRows.first().row() > 0);
+ ui->moveDownwardsToolButton->setEnabled(selectedRows.last().row() < uat_model_->rowCount() - 1);
} 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)
+void IOGraphDialog::on_graphUat_currentItemChanged(const QModelIndex &current, const QModelIndex&)
+{
+ if (current.isValid()) {
+ ui->clearToolButton->setEnabled(true);
+ if (graphIsEnabled(current.row())) {
+ // Try to set the tracer to the new current graph.
+ // If it's not enabled, don't try to switch from the
+ // old graph to the one in the first row.
+ getGraphInfo();
+ }
+ } else {
+ ui->clearToolButton->setEnabled(false);
+ }
+}
+
+void IOGraphDialog::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &)
{
bool recalc = false;
- switch (index.column())
- {
- case colYAxis:
- case colSMAPeriod:
- recalc = true;
+ for (int col = topLeft.column(); col <= bottomRight.column(); col++) {
+ switch (col)
+ {
+ case colYAxis:
+ case colSMAPeriod:
+ case colYAxisFactor:
+ recalc = true;
+ }
}
- syncGraphSettings(index.row());
+ for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
+ syncGraphSettings(row);
+ }
if (recalc) {
scheduleRecalc(true);
@@ -1363,11 +1734,6 @@ void IOGraphDialog::modelDataChanged(const QModelIndex &index)
}
}
-void IOGraphDialog::on_resetButton_clicked()
-{
- resetAxes();
-}
-
void IOGraphDialog::on_newToolButton_clicked()
{
addGraph();
@@ -1375,70 +1741,95 @@ void IOGraphDialog::on_newToolButton_clicked()
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_ == nullptr) {
+ return;
+ }
- if (!uat_model_->removeRows(current.row(), 1)) {
- qDebug() << "Failed to remove row";
+ for (const auto &range : ui->graphUat->selectionModel()->selection()) {
+ // Each QItemSelectionRange is contiguous
+ if (!range.isEmpty()) {
+ if (!uat_model_->removeRows(range.top(), range.bottom() - range.top() + 1)) {
+ qDebug() << "Failed to remove rows" << range.top() << "to" << range.bottom();
+ }
}
}
// We should probably be smarter about this.
hint_err_.clear();
- mouseMoved(NULL);
+ updateHint();
}
void IOGraphDialog::on_copyToolButton_clicked()
{
- addGraph(true);
+ if (uat_model_ == nullptr) {
+ return;
+ }
+
+ QModelIndexList selectedRows = ui->graphUat->selectionModel()->selectedRows();
+ if (selectedRows.size() > 0) {
+ std::sort(selectedRows.begin(), selectedRows.end());
+
+ QModelIndex copyIdx;
+
+ for (const auto &idx : selectedRows) {
+ copyIdx = uat_model_->copyRow(idx);
+ if (!copyIdx.isValid())
+ {
+ qDebug() << "Failed to copy row" << idx.row();
+ }
+ }
+ ui->graphUat->setCurrentIndex(copyIdx);
+ }
}
void IOGraphDialog::on_clearToolButton_clicked()
{
if (uat_model_) {
- foreach(IOGraph* iog, ioGraphs_) {
- delete iog;
- }
- ioGraphs_.clear();
uat_model_->clearAll();
}
hint_err_.clear();
- mouseMoved(NULL);
+ updateHint();
}
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;
+ if (uat_model_ == nullptr) {
+ return;
+ }
- uat_model_->moveRow(current_row, current_row - 1);
+ for (const auto &range : ui->graphUat->selectionModel()->selection()) {
+ // Each QItemSelectionRange is contiguous
+ if (!range.isEmpty() && range.top() > 0) {
+ // Swap range of rows with the row above the top
+ if (! uat_model_->moveRows(QModelIndex(), range.top(), range.bottom() - range.top() + 1, QModelIndex(), range.top() - 1)) {
+ qDebug() << "Failed to move up rows" << range.top() << "to" << range.bottom();
+ }
+ // Our moveRows implementation calls begin/endMoveRows(), so
+ // range.top() already has the new row number.
+ ui->moveUpwardsToolButton->setEnabled(range.top() > 0);
+ ui->moveDownwardsToolButton->setEnabled(true);
}
}
}
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;
+ if (uat_model_ == nullptr) {
+ return;
+ }
- uat_model_->moveRow(current_row, current_row + 1);
+ for (const auto &range : ui->graphUat->selectionModel()->selection()) {
+ // Each QItemSelectionRange is contiguous
+ if (!range.isEmpty() && range.bottom() + 1 < uat_model_->rowCount()) {
+ // Swap range of rows with the row below the top
+ if (! uat_model_->moveRows(QModelIndex(), range.top(), range.bottom() - range.top() + 1, QModelIndex(), range.bottom() + 1)) {
+ qDebug() << "Failed to move down rows" << range.top() << "to" << range.bottom();
+ }
+ // Our moveRows implementation calls begin/endMoveRows, so
+ // range.bottom() already has the new row number.
+ ui->moveUpwardsToolButton->setEnabled(true);
+ ui->moveDownwardsToolButton->setEnabled(range.bottom() < uat_model_->rowCount() - 1);
}
}
}
@@ -1461,14 +1852,28 @@ void IOGraphDialog::on_zoomRadioButton_toggled(bool checked)
void IOGraphDialog::on_logCheckBox_toggled(bool checked)
{
QCustomPlot *iop = ui->ioPlot;
+ QSharedPointer<QCPAxisTickerSi> si_ticker = qSharedPointerDynamicCast<QCPAxisTickerSi>(iop->yAxis->ticker());
+ if (si_ticker != nullptr) {
+ si_ticker->setLog(checked);
+ }
- iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
+ if (checked) {
+ iop->yAxis->setScaleType(QCPAxis::stLogarithmic);
+ if (si_ticker == nullptr) {
+ iop->yAxis->setTicker(QSharedPointer<QCPAxisTickerLog>(new QCPAxisTickerLog));
+ }
+ } else {
+ iop->yAxis->setScaleType(QCPAxis::stLinear);
+ if (si_ticker == nullptr) {
+ iop->yAxis->setTicker(QSharedPointer<QCPAxisTicker>(new QCPAxisTicker));
+ }
+ }
iop->replot();
}
void IOGraphDialog::on_automaticUpdateCheckBox_toggled(bool checked)
{
- prefs.gui_io_graph_automatic_update = checked ? TRUE : FALSE;
+ prefs.gui_io_graph_automatic_update = checked ? true : false;
prefs_main_write();
@@ -1480,16 +1885,16 @@ void IOGraphDialog::on_automaticUpdateCheckBox_toggled(bool checked)
void IOGraphDialog::on_enableLegendCheckBox_toggled(bool checked)
{
- prefs.gui_io_graph_enable_legend = checked ? TRUE : FALSE;
+ prefs.gui_io_graph_enable_legend = checked ? true : false;
prefs_main_write();
- updateLegend();
+ ui->ioPlot->legend->layer()->replot();
}
void IOGraphDialog::on_actionReset_triggered()
{
- on_resetButton_clicked();
+ resetAxes();
}
void IOGraphDialog::on_actionZoomIn_triggered()
@@ -1585,7 +1990,7 @@ void IOGraphDialog::on_actionToggleTimeOrigin_triggered()
void IOGraphDialog::on_actionCrosshairs_triggered()
{
-
+ toggleTracerStyle();
}
void IOGraphDialog::on_buttonBox_helpRequested()
@@ -1638,6 +2043,17 @@ void IOGraphDialog::on_buttonBox_accepted()
}
}
+void IOGraphDialog::buttonBoxClicked(QAbstractButton *button)
+{
+ switch (ui->buttonBox->buttonRole(button)) {
+ case QDialogButtonBox::ResetRole:
+ resetAxes();
+ break;
+ default:
+ break;
+ }
+}
+
void IOGraphDialog::makeCsv(QTextStream &stream) const
{
QList<IOGraph *> activeGraphs;
@@ -1646,25 +2062,47 @@ void IOGraphDialog::makeCsv(QTextStream &stream) const
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;
+ for (int row = 0; row < graphCount(); 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;
+ int64_t interval_start = (int64_t)interval * ui_interval;
+ if (qSharedPointerDynamicCast<QCPAxisTickerDateTime>(ui->ioPlot->xAxis->ticker()) != nullptr) {
+ nstime_t interval_time = NSTIME_INIT_SECS_USECS((time_t)(interval_start / SCALE), (int)(interval_start % SCALE));
+
+ nstime_add(&interval_time, &start_time_);
+
+ static char time_string_buf[39];
+
+ if (decimal_point == nullptr) {
+ decimal_point = g_strdup(localeconv()->decimal_point);
+ }
+ // Should we convert to UTC for output, even if the graph axis has
+ // local time?
+ // The question of what precision to use is somewhat tricky.
+ // The buckets are aligned to the relative time start, not to
+ // absolute time, so the timestamp precision should be used instead
+ // of the bucket precision. We can save the precision of the
+ // start time timestamp for each graph, but we don't necessarily
+ // have a guarantee that all timestamps in the file have the same
+ // precision. Possibly nstime_t should store precision, cf. #15579
+ format_nstime_as_iso8601(time_string_buf, sizeof time_string_buf, &interval_time, decimal_point, true, 9); // precision_);
+
+ stream << time_string_buf;
+ } else {
+ stream << (double)interval_start / SCALE_F;
+ }
foreach (IOGraph *iog, activeGraphs) {
double value = 0.0;
if (interval <= iog->maxInterval()) {
@@ -1698,11 +2136,14 @@ bool IOGraphDialog::saveCsv(const QString &file_name) const
IOGraph::IOGraph(QCustomPlot *parent) :
parent_(parent),
+ tap_registered_(true),
visible_(false),
graph_(NULL),
bars_(NULL),
val_units_(IOG_ITEM_UNIT_FIRST),
hf_index_(-1),
+ interval_(0),
+ start_time_(NSTIME_INIT_ZERO),
cur_idx_(-1)
{
Q_ASSERT(parent_ != NULL);
@@ -1723,11 +2164,12 @@ IOGraph::IOGraph(QCustomPlot *parent) :
// error_string->str);
// config_err_ = error_string->str;
g_string_free(error_string, TRUE);
+ tap_registered_ = false;
}
}
IOGraph::~IOGraph() {
- remove_tap_listener(this);
+ removeTapListener();
if (graph_) {
parent_->removeGraph(graph_);
}
@@ -1736,9 +2178,17 @@ IOGraph::~IOGraph() {
}
}
+void IOGraph::removeTapListener()
+{
+ if (tap_registered_) {
+ remove_tap_listener(this);
+ tap_registered_ = false;
+ }
+}
+
// 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)
+// Check for errors and sets config_err_ and returns false if any are found.
+bool IOGraph::setFilter(const QString &filter)
{
GString *error_string;
QString full_filter(filter.trimmed());
@@ -1756,7 +2206,7 @@ void IOGraph::setFilter(const QString &filter)
config_err_ = QString::fromUtf8(df_err->msg);
df_error_free(&df_err);
filter_ = full_filter;
- return;
+ return false;
}
}
@@ -1765,7 +2215,7 @@ void IOGraph::setFilter(const QString &filter)
if (error_string) {
config_err_ = error_string->str;
g_string_free(error_string, TRUE);
- return;
+ return false;
}
// Make sure vu_field_ survives edt tree pruning by adding it to our filter
@@ -1778,17 +2228,36 @@ void IOGraph::setFilter(const QString &filter)
}
}
- 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();
+ if (full_filter_.compare(full_filter)) {
+ 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 false;
}
+
filter_ = filter;
+ full_filter_ = full_filter;
+ /* If we changed the tap filter the graph is visible, we need to
+ * retap.
+ * Note that setting the tap dfilter will mark the tap as needing a
+ * redraw, which will cause a recalculation (via tapDraw) via the
+ * (fairly long) main application timer.
+ */
+ /* XXX - When changing from an advanced graph to one that doesn't
+ * use the field, we don't actually need to retap if filter and
+ * full_filter produce the same results. (We do have to retap
+ * regardless if changing _to_ an advanced graph, because the
+ * extra fields in the io_graph_item_t aren't filled in from the
+ * edt for the basic graph.)
+ * Checking that in full generality would require more optimization
+ * in the dfilter engine plus functions to compare filters, but
+ * we could test the simple case where filter and vu_field are
+ * the same string.
+ */
+ setNeedRetap(true);
}
+ return true;
}
void IOGraph::applyCurrentColor()
@@ -1796,7 +2265,15 @@ 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_->setPen(QPen(color_.color().darker(110), graph_line_width_));
+ // ...or omit it altogether?
+ // bars_->setPen(QPen(color_);
+ // XXX - We should do something like
+ // bars_->setPen(QPen(ColorUtils::alphaBlend(color_, palette().windowText(), 0.65));
+ // to get a darker outline in light mode and a lighter outline in dark
+ // mode, but we don't yet respect dark mode in IOGraph (or anything
+ // that uses QCustomPlot) - see link below for how to set QCP colors:
+ // https://www.qcustomplot.com/index.php/demos/barchartdemo
bars_->setBrush(color_);
}
}
@@ -1812,7 +2289,28 @@ void IOGraph::setVisible(bool visible)
bars_->setVisible(visible_);
}
if (old_visibility != visible_) {
- emit requestReplot();
+ if (visible_ && need_retap_) {
+ need_retap_ = false;
+ emit requestRetap();
+ } else {
+ // XXX - If the number of enabled graphs changed to or from 1, we
+ // need to recalculate to possibly change the rescaling. (This is
+ // why QCP recommends doing scaling in the axis ticker instead.)
+ // If we can't determine the number of enabled graphs here, always
+ // request a recalculation instead of a replot. (At least until we
+ // change the scaling to be done in the ticker.)
+ //emit requestReplot();
+ emit requestRecalc();
+ }
+ }
+}
+
+void IOGraph::setNeedRetap(bool retap)
+{
+ if (visible_ && retap) {
+ emit requestRetap();
+ } else {
+ need_retap_ = retap;
}
}
@@ -1827,7 +2325,7 @@ void IOGraph::setName(const QString &name)
}
}
-QRgb IOGraph::color()
+QRgb IOGraph::color() const
{
return color_.color().rgb();
}
@@ -1840,14 +2338,24 @@ void IOGraph::setColor(const QRgb color)
void IOGraph::setPlotStyle(int style)
{
+ bool recalc = false;
+ bool shows_zero = showsZero();
+
// Switch plottable if needed
switch (style) {
case psBar:
case psStackedBar:
if (graph_) {
bars_ = new QCPBars(parent_->xAxis, parent_->yAxis);
+ // default widthType is wtPlotCoords. Scale with the interval
+ // size to prevent overlap. (Multiply this by a factor to have
+ // a gap between bars; the QCustomPlot default is 0.75.)
+ if (interval_) {
+ bars_->setWidth(interval_ / SCALE_F);
+ }
parent_->removeGraph(graph_);
graph_ = NULL;
+ recalc = true;
}
break;
default:
@@ -1855,6 +2363,7 @@ void IOGraph::setPlotStyle(int style)
graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
parent_->removePlottable(bars_);
bars_ = NULL;
+ recalc = true;
}
break;
}
@@ -1930,13 +2439,26 @@ void IOGraph::setPlotStyle(int style)
break;
}
+ if (shows_zero != showsZero()) {
+ // recalculate if whether zero is added changed
+ recalc = true;
+ }
+
setName(name_);
applyCurrentColor();
+
+ if (recalc) {
+ // switching the plottable requires recalculation to add the data
+ emit requestRecalc();
+ }
}
-const QString IOGraph::valueUnitLabel()
+QString IOGraph::valueUnitLabel() const
{
- return val_to_str_const(val_units_, y_axis_vs, "Unknown");
+ if (is_packet_configuration_namespace()) {
+ return val_to_str_const(val_units_, y_axis_packet_vs, "Unknown");
+ }
+ return val_to_str_const(val_units_, y_axis_event_vs, "Unknown");
}
void IOGraph::setValueUnits(int val_units)
@@ -1946,9 +2468,21 @@ void IOGraph::setValueUnits(int 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();
+ // If val_units changed, switching between a type that doesn't
+ // use the vu_field/hfi/edt to one of the advanced graphs that
+ // does requires a retap. setFilter will handle that, because
+ // the full filter strings will be different.
+ if (setFilter(filter_)) { // Check config & prime vu field
+ if (val_units == IOG_ITEM_UNIT_CALC_LOAD ||
+ old_val_units == IOG_ITEM_UNIT_CALC_LOAD) {
+ // LOAD graphs fill in the io_graph_item_t differently
+ // than other advanced graphs, so we have to retap even
+ // if the filter is the same. (update_io_graph_item could
+ // instead calculate and store LOAD information for any
+ // advanced graph type, but the tradeoff might not be
+ // worth it.)
+ setNeedRetap(true);
+ }
}
}
}
@@ -1967,6 +2501,8 @@ void IOGraph::setValueUnitField(const QString &vu_field)
}
if (old_hf_index != hf_index_) {
+ // If the field changed, and val_units is a type that uses it,
+ // we need to retap. setFilter will handle that.
setFilter(filter_); // Check config & prime vu field
}
}
@@ -1993,25 +2529,44 @@ bool IOGraph::removeFromLegend()
return false;
}
-double IOGraph::startOffset()
+// This returns what graph key offset corresponds with relative time 0.0,
+// i.e. when absolute times are used the difference between abs_ts and
+// rel_ts of the first tapped packet. Generally the same for all graphs
+// that are displayed and have some data, unless they're on the opposite
+// sides of time references.
+// XXX - If the graph spans a time reference, it's not clear how we want
+// to switch from relative to absolute times.
+double IOGraph::startOffset() const
{
- if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker()) && graph_->data()->size() > 0) {
- return graph_->data()->at(0)->key;
+ if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker())) {
+ return nstime_to_sec(&start_time_);
}
- if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker()) && bars_->data()->size() > 0) {
- return bars_->data()->at(0)->key;
+ if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker())) {
+ return nstime_to_sec(&start_time_);
}
return 0.0;
}
-int IOGraph::packetFromTime(double ts)
+nstime_t IOGraph::startTime() const
{
- int idx = ts * 1000 / interval_;
- if (idx >= 0 && idx < (int) cur_idx_) {
+ if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker())) {
+ return start_time_;
+ }
+ if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker())) {
+ return start_time_;
+ }
+ return nstime_t(NSTIME_INIT_ZERO);
+}
+
+int IOGraph::packetFromTime(double ts) const
+{
+ int idx = ts * SCALE_F / interval_;
+ if (idx >= 0 && idx <= cur_idx_) {
switch (val_units_) {
case IOG_ITEM_UNIT_CALC_MAX:
+ return items_[idx].max_frame_in_invl;
case IOG_ITEM_UNIT_CALC_MIN:
- return items_[idx].extreme_frame_in_invl;
+ return items_[idx].min_frame_in_invl;
default:
return items_[idx].last_frame_in_invl;
}
@@ -2022,31 +2577,30 @@ int IOGraph::packetFromTime(double ts)
void IOGraph::clearAllData()
{
cur_idx_ = -1;
- reset_io_graph_items(items_, max_io_items_);
+ if (items_.size()) {
+ reset_io_graph_items(&items_[0], items_.size(), hf_index_);
+ }
if (graph_) {
graph_->data()->clear();
}
if (bars_) {
bars_->data()->clear();
}
- start_time_ = 0.0;
+ nstime_set_zero(&start_time_);
}
-void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
+void IOGraph::recalcGraphData(capture_file *cap_file)
{
/* 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) {
@@ -2054,7 +2608,7 @@ void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
* just to make sure average on leftmost and rightmost displayed
* values is as reliable as possible
*/
- guint64 warmup_interval = 0;
+ uint64_t warmup_interval = 0;
// for (; warmup_interval < first_interval; warmup_interval += interval_) {
// mavg_cumulated += get_it_value(io, i, (int)warmup_interval/interval_);
@@ -2064,8 +2618,8 @@ void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
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 < (0 + (moving_avg_period_ / 2) * (uint64_t)interval_)) &&
+ (warmup_interval <= (cur_idx_ * (uint64_t)interval_)));
warmup_interval += interval_) {
mavg_cumulated += getItemValue((int)warmup_interval / interval_, cap_file);
@@ -2074,11 +2628,9 @@ void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
mavg_to_add = (unsigned int)warmup_interval;
}
+ double ts_offset = startOffset();
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 ts = (double) i * interval_ / SCALE_F + ts_offset;
double val = getItemValue(i, cap_file);
if (moving_avg_period_ > 0) {
@@ -2112,73 +2664,46 @@ void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
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()
+format_size_units_e IOGraph::formatUnits() const
{
- // 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_PACKETS:
+ case IOG_ITEM_UNIT_CALC_FRAMES:
+ if (is_packet_configuration_namespace()) {
+ return FORMAT_SIZE_UNIT_PACKETS;
+ }
+ return FORMAT_SIZE_UNIT_EVENTS;
+ case IOG_ITEM_UNIT_BYTES:
+ return FORMAT_SIZE_UNIT_BYTES;
+ case IOG_ITEM_UNIT_BITS:
+ return FORMAT_SIZE_UNIT_BITS;
+ case IOG_ITEM_UNIT_CALC_LOAD:
+ return FORMAT_SIZE_UNIT_ERLANGS;
+ case IOG_ITEM_UNIT_CALC_FIELDS:
+ return FORMAT_SIZE_UNIT_FIELDS;
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);
+ if (hf_index_ > 0) {
+ if (proto_registrar_get_ftype(hf_index_) == FT_RELATIVE_TIME) {
+ return FORMAT_SIZE_UNIT_SECONDS;
+ }
+ // Could we look if it's BASE_UNIT_STRING and use that?
+ // One complication is that prefixes shouldn't be combined,
+ // and some unit strings are already prefixed units.
}
+ return FORMAT_SIZE_UNIT_NONE;
+ case IOG_ITEM_UNIT_CALC_THROUGHPUT:
+ return FORMAT_SIZE_UNIT_BITS_S;
+ default:
+ return FORMAT_SIZE_UNIT_NONE;
}
}
@@ -2211,7 +2736,7 @@ void IOGraph::captureEvent(CaptureEvent e)
if ((e.captureContext() == CaptureEvent::File) &&
(e.eventType() == CaptureEvent::Closing))
{
- remove_tap_listener(this);
+ removeTapListener();
}
}
@@ -2222,6 +2747,38 @@ void IOGraph::reloadValueUnitField()
}
}
+// returns true if the current plot style shows zero values,
+// false if null values are omitted.
+bool IOGraph::showsZero() const
+{
+ 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 (graph_ && graph_->lineStyle() == QCPGraph::lsNone) {
+ return false;
+ }
+ else {
+ return true;
+ }
+ 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:
+ case IOG_ITEM_UNIT_CALC_THROUGHPUT:
+ // These are not the same sort of "omitted zeros" as above,
+ // but changing val_units_ always results in a recalculation
+ // so it doesn't matter (see modelDataChanged)
+ return false;
+
+ default:
+ return true;
+ }
+}
+
// Check if a packet is available at the given interval (idx).
bool IOGraph::hasItemToShow(int idx, double value) const
{
@@ -2237,7 +2794,7 @@ bool IOGraph::hasItemToShow(int idx, double value) const
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)) {
+ if (value == 0.0 && (graph_ && graph_->lineStyle() == QCPGraph::lsNone)) {
result = false;
}
else {
@@ -2250,6 +2807,7 @@ bool IOGraph::hasItemToShow(int idx, double value) const
case IOG_ITEM_UNIT_CALC_MIN:
case IOG_ITEM_UNIT_CALC_AVERAGE:
case IOG_ITEM_UNIT_CALC_LOAD:
+ case IOG_ITEM_UNIT_CALC_THROUGHPUT:
if (item->fields) {
result = true;
}
@@ -2266,6 +2824,9 @@ bool IOGraph::hasItemToShow(int idx, double value) const
void IOGraph::setInterval(int interval)
{
interval_ = interval;
+ if (bars_) {
+ bars_->setWidth(interval_ / SCALE_F);
+ }
}
// Get the value at the given interval (idx) for the current value unit.
@@ -2273,7 +2834,7 @@ 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_);
+ return get_io_graph_item(&items_[0], val_units_, idx, hf_index_, cap_file, interval_, cur_idx_);
}
// "tap_reset" callback for register_tap_listener
@@ -2294,27 +2855,60 @@ tap_packet_status IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dis
return TAP_PACKET_DONT_REDRAW;
}
- int idx = get_io_graph_index(pinfo, iog->interval_);
+ int64_t tmp_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;
+ if ((tmp_idx < 0) || (tmp_idx >= max_io_items_)) {
+ iog->cur_idx_ = (int)iog->items_.size() - 1;
return TAP_PACKET_DONT_REDRAW;
}
+ int idx = (int)tmp_idx;
+ /* If the graph isn't visible, don't do the work or redraw, but mark
+ * the graph in need of a retap if it is ever enabled. The alternative
+ * is to do the work, but clear pending retaps when the taps are reset
+ * (which indicates something else triggered a retap.) The tradeoff would
+ * be more calculation and memory usage when a graph is disabled in
+ * exchange for fewer scenarios that involve retaps when toggling the
+ * enabled/disabled taps.
+ */
+ if (!iog->visible()) {
+ if (idx > iog->cur_idx_) {
+ iog->need_retap_ = true;
+ }
+ return TAP_PACKET_DONT_REDRAW;
+ }
+
+ if ((size_t)idx >= iog->items_.size()) {
+ const size_t old_size = iog->items_.size();
+ size_t new_size;
+ if (old_size == 0) {
+ new_size = 1024;
+ } else {
+ new_size = MIN((old_size * 3) / 2, max_io_items_);
+ }
+ new_size = MAX(new_size, (size_t)idx + 1);
+ try {
+ iog->items_.resize(new_size);
+ } catch (std::bad_alloc&) {
+ // std::vector.resize() has strong exception safety
+ ws_warning("Failed memory allocation!");
+ return TAP_PACKET_DONT_REDRAW;
+ }
+ // resize zero-initializes new items, which is what we want
+ //reset_io_graph_items(&iog->items_[old_size], new_size - old_size);
+ }
+
/* update num_items */
if (idx > iog->cur_idx_) {
- iog->cur_idx_ = (guint32) idx;
+ iog->cur_idx_ = 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);
+ if (nstime_is_zero(&iog->start_time_)) {
+ nstime_delta(&iog->start_time_, &pinfo->abs_ts, &pinfo->rel_ts);
}
epan_dissect_t *adv_edt = NULL;
@@ -2323,7 +2917,7 @@ tap_packet_status IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dis
adv_edt = edt;
}
- if (!update_io_graph_item(iog->items_, idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
+ if (!update_io_graph_item(&iog->items_[0], idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
return TAP_PACKET_DONT_REDRAW;
}