Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
/* io_graph_dialog.cpp
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "io_graph_dialog.h"
|
|
|
|
#include "ui_io_graph_dialog.h"
|
|
|
|
|
|
|
|
#include "epan/stats_tree_priv.h"
|
|
|
|
#include "epan/uat-int.h"
|
|
|
|
|
|
|
|
#include "qt_ui_utils.h"
|
|
|
|
#include "tango_colors.h"
|
|
|
|
|
|
|
|
#include "wireshark_application.h"
|
|
|
|
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QComboBox>
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QFontMetrics>
|
|
|
|
#include <QFrame>
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QSpacerItem>
|
|
|
|
#include <QTreeWidget>
|
|
|
|
#include <QVariant>
|
|
|
|
|
|
|
|
// Bugs and uncertainties:
|
|
|
|
// - Regular (non-stacked) bar graphs are drawn on top of each other on the Z axis.
|
|
|
|
// The QCP forum suggests drawing them side by side:
|
|
|
|
// http://www.qcustomplot.com/index.php/support/forum/62
|
|
|
|
// - You can't manually set a graph color other than manually editing the io_graphs
|
|
|
|
// UAT. We should add a "graph color" preference.
|
|
|
|
// - We retap and redraw more than we should.
|
|
|
|
// - We don't use scroll bars. Should we?
|
|
|
|
// - We should automatically scroll during live captures.
|
|
|
|
// - Smoothing doesn't seem to match GTK+
|
|
|
|
// - We don't register a tap listener ("-z io,stat", bottom of gtk/io_stat.c)
|
|
|
|
|
|
|
|
const int name_col_ = 0;
|
|
|
|
const int dfilter_col_ = 1;
|
|
|
|
const int color_col_ = 2;
|
|
|
|
const int style_col_ = 3;
|
|
|
|
const int yaxis_col_ = 4;
|
|
|
|
const int yfield_col_ = 5;
|
|
|
|
const int sma_period_col_ = 6;
|
|
|
|
const int num_cols_ = 7;
|
|
|
|
|
|
|
|
// Available colors
|
|
|
|
// XXX - Add custom
|
|
|
|
QList<QRgb> colors_ = QList<QRgb>()
|
|
|
|
<< tango_aluminium_6 // Bar outline (use black instead)?
|
|
|
|
<< tango_sky_blue_5
|
|
|
|
<< tango_butter_6
|
|
|
|
<< tango_chameleon_5
|
|
|
|
<< tango_scarlet_red_5
|
|
|
|
<< tango_plum_5
|
|
|
|
<< tango_orange_6
|
|
|
|
<< tango_aluminium_3
|
|
|
|
<< tango_sky_blue_3
|
|
|
|
<< tango_butter_3
|
|
|
|
<< tango_chameleon_3
|
|
|
|
<< tango_scarlet_red_3
|
|
|
|
<< tango_plum_3
|
|
|
|
<< tango_orange_3;
|
|
|
|
|
|
|
|
const qreal graph_line_width_ = 1.0;
|
|
|
|
|
|
|
|
// When we drop support for Qt <5 we can initialize these with
|
|
|
|
// datastreams.
|
|
|
|
const QMap<io_graph_item_unit_t, QString> value_unit_to_name_ = IOGraph::valueUnitsToNames();
|
|
|
|
const QMap<IOGraph::PlotStyles, QString> plot_style_to_name_ = IOGraph::plotStylesToNames();
|
|
|
|
const QMap<int, QString> moving_average_to_name_ = IOGraph::movingAveragesToNames();
|
|
|
|
|
|
|
|
const int default_moving_average_ = 0;
|
|
|
|
|
|
|
|
// Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
|
|
|
|
// in zoom mode.
|
|
|
|
const int min_zoom_pixels_ = 20;
|
|
|
|
|
|
|
|
const int stat_update_interval_ = 200; // ms
|
|
|
|
|
|
|
|
// Saved graph settings
|
|
|
|
|
|
|
|
static const value_string graph_enabled_vs[] = {
|
|
|
|
{ 0, "Disabled" },
|
|
|
|
{ 1, "Enabled" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct _io_graph_settings_t {
|
|
|
|
guint32 enabled;
|
|
|
|
char* name;
|
|
|
|
char* dfilter;
|
|
|
|
char* color;
|
|
|
|
char* style;
|
|
|
|
char* yaxis;
|
|
|
|
char* yfield;
|
|
|
|
int sma_period;
|
|
|
|
} io_graph_settings_t;
|
|
|
|
|
|
|
|
static io_graph_settings_t *iog_settings_ = NULL;
|
|
|
|
static guint num_io_graphs_ = 0;
|
|
|
|
static uat_t *iog_uat_ = NULL;
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
UAT_VS_DEF(io_graph, enabled, io_graph_settings_t, guint32, 0, "Disabled")
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, name, io_graph_settings_t)
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, dfilter, io_graph_settings_t)
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, color, io_graph_settings_t)
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, style, io_graph_settings_t)
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, yaxis, io_graph_settings_t)
|
|
|
|
UAT_CSTRING_CB_DEF(io_graph, yfield, io_graph_settings_t)
|
|
|
|
UAT_DEC_CB_DEF(io_graph, sma_period, io_graph_settings_t)
|
|
|
|
|
|
|
|
static uat_field_t io_graph_fields[] = {
|
|
|
|
UAT_FLD_VS(io_graph, enabled, "Enabled", graph_enabled_vs, "Graph visibility"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, name, "Graph Name", "The name of the graph"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, dfilter, "Display Filter", "Graph packets matching this display filter"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, color, "Color", "Graph color (#RRGGBB)"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, style, "Style", "Graph style (Line, Bars, etc.)"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, yaxis, "Y Axis", "Y Axis units"),
|
|
|
|
UAT_FLD_CSTRING(io_graph, yfield, "Y Field", "Apply calculations to this field"),
|
|
|
|
UAT_FLD_DEC(io_graph, sma_period, "SMA Period", "Simple moving average period"),
|
|
|
|
UAT_END_FIELDS
|
|
|
|
};
|
|
|
|
|
2014-05-28 17:14:37 +00:00
|
|
|
static void* io_graph_copy_cb(void* dst_ptr, const void* src_ptr, size_t len) {
|
|
|
|
Q_UNUSED(len);
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
io_graph_settings_t* dst = (io_graph_settings_t *)dst_ptr;
|
|
|
|
const io_graph_settings_t* src = (const io_graph_settings_t *)src_ptr;
|
|
|
|
|
|
|
|
dst->enabled = src->enabled;
|
|
|
|
dst->name = g_strdup(src->name);
|
|
|
|
dst->dfilter = g_strdup(src->dfilter);
|
|
|
|
dst->color = g_strdup(src->color);
|
|
|
|
dst->style = g_strdup(src->style);
|
|
|
|
dst->yaxis = g_strdup(src->yaxis);
|
|
|
|
dst->yfield = g_strdup(src->yfield);
|
|
|
|
dst->sma_period = src->sma_period;
|
|
|
|
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void io_graph_free_cb(void* p) {
|
|
|
|
io_graph_settings_t *iogs = (io_graph_settings_t *)p;
|
|
|
|
g_free(iogs->name);
|
|
|
|
g_free(iogs->dfilter);
|
|
|
|
g_free(iogs->color);
|
|
|
|
g_free(iogs->yfield);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // extern "C"
|
|
|
|
|
|
|
|
|
2014-04-24 06:29:19 +00:00
|
|
|
Q_DECLARE_METATYPE(IOGraph *)
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
|
|
|
|
IOGraphDialog::IOGraphDialog(QWidget *parent, capture_file *cf) :
|
|
|
|
QDialog(parent),
|
|
|
|
ui(new Ui::IOGraphDialog),
|
|
|
|
cap_file_(cf),
|
|
|
|
name_line_edit_(NULL),
|
|
|
|
dfilter_line_edit_(NULL),
|
|
|
|
yfield_line_edit_(NULL),
|
|
|
|
color_combo_box_(NULL),
|
|
|
|
style_combo_box_(NULL),
|
|
|
|
yaxis_combo_box_(NULL),
|
|
|
|
sma_combo_box_(NULL),
|
|
|
|
base_graph_(NULL),
|
|
|
|
tracer_(NULL),
|
|
|
|
start_time_(0.0),
|
|
|
|
mouse_drags_(true),
|
|
|
|
rubber_band_(NULL),
|
|
|
|
stat_timer_(NULL),
|
|
|
|
need_replot_(false),
|
|
|
|
need_retap_(false),
|
|
|
|
auto_axes_(true)
|
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
|
|
|
|
QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
|
|
|
|
save_bt->setText(tr("Save As..."));
|
|
|
|
|
|
|
|
stat_timer_ = new QTimer(this);
|
|
|
|
connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
|
|
|
|
stat_timer_->start(stat_update_interval_);
|
|
|
|
|
|
|
|
// Intervals (ms)
|
|
|
|
ui->intervalComboBox->addItem(tr("0.001 sec"), 1);
|
|
|
|
ui->intervalComboBox->addItem(tr("0.01 sec"), 10);
|
|
|
|
ui->intervalComboBox->addItem(tr("0.1 sec"), 100);
|
|
|
|
ui->intervalComboBox->addItem(tr("1 sec"), 1000);
|
|
|
|
ui->intervalComboBox->addItem(tr("10 sec"), 10000);
|
|
|
|
ui->intervalComboBox->addItem(tr("1 min"), 60000);
|
|
|
|
ui->intervalComboBox->addItem(tr("10 min"), 600000);
|
|
|
|
ui->intervalComboBox->setCurrentIndex(3);
|
|
|
|
|
|
|
|
ui->todCheckBox->setChecked(false);
|
|
|
|
|
|
|
|
ui->dragRadioButton->setChecked(mouse_drags_);
|
|
|
|
|
|
|
|
ctx_menu_.addAction(ui->actionZoomIn);
|
|
|
|
ctx_menu_.addAction(ui->actionZoomOut);
|
|
|
|
ctx_menu_.addAction(ui->actionReset);
|
|
|
|
ctx_menu_.addSeparator();
|
|
|
|
ctx_menu_.addAction(ui->actionMoveRight10);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveLeft10);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveUp10);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveDown10);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveRight1);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveLeft1);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveUp1);
|
|
|
|
ctx_menu_.addAction(ui->actionMoveDown1);
|
|
|
|
ctx_menu_.addSeparator();
|
|
|
|
ctx_menu_.addAction(ui->actionGoToPacket);
|
|
|
|
ctx_menu_.addSeparator();
|
|
|
|
ctx_menu_.addAction(ui->actionDragZoom);
|
|
|
|
ctx_menu_.addAction(ui->actionToggleTimeOrigin);
|
|
|
|
ctx_menu_.addAction(ui->actionCrosshairs);
|
|
|
|
|
|
|
|
iop->xAxis->setLabel(tr("Time (s)"));
|
|
|
|
|
|
|
|
iop->setMouseTracking(true);
|
|
|
|
iop->setEnabled(true);
|
|
|
|
|
|
|
|
QString dlg_title = tr("Wireshark IO Graphs: ");
|
|
|
|
if (cap_file_) {
|
|
|
|
dlg_title += cf_get_display_name(cap_file_);
|
|
|
|
} else {
|
|
|
|
dlg_title += tr("No Capture Data");
|
|
|
|
}
|
|
|
|
setWindowTitle(dlg_title);
|
|
|
|
QCPPlotTitle *title = new QCPPlotTitle(iop);
|
|
|
|
iop->plotLayout()->insertRow(0);
|
|
|
|
iop->plotLayout()->addElement(0, 0, title);
|
|
|
|
title->setText(dlg_title);
|
|
|
|
|
|
|
|
tracer_ = new QCPItemTracer(iop);
|
|
|
|
iop->addItem(tracer_);
|
|
|
|
|
|
|
|
loadProfileGraphs();
|
|
|
|
if (num_io_graphs_ > 0) {
|
|
|
|
for (guint i = 0; i < num_io_graphs_; i++) {
|
|
|
|
io_graph_settings_t *iogs = &iog_settings_[i];
|
|
|
|
QRgb pcolor = QColor(iogs->color).rgb();
|
|
|
|
int color_idx;
|
|
|
|
IOGraph::PlotStyles style = plot_style_to_name_.key(iogs->style, IOGraph::psLine);
|
|
|
|
io_graph_item_unit_t value_units = value_unit_to_name_.key(iogs->yaxis, IOG_ITEM_UNIT_PACKETS);
|
|
|
|
|
|
|
|
for (color_idx = 0; color_idx < colors_.size(); color_idx++) {
|
|
|
|
if (pcolor == colors_[color_idx]) break;
|
|
|
|
}
|
|
|
|
if (color_idx >= colors_.size()) {
|
|
|
|
colors_ << pcolor;
|
|
|
|
}
|
|
|
|
|
|
|
|
addGraph(iogs->enabled == 1, iogs->name, iogs->dfilter, color_idx, style, value_units, iogs->yfield, iogs->sma_period);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
addDefaultGraph(true, 0);
|
|
|
|
addDefaultGraph(true, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
on_graphTreeWidget_itemSelectionChanged();
|
|
|
|
|
|
|
|
toggleTracerStyle(true);
|
|
|
|
iop->setFocus();
|
|
|
|
|
|
|
|
iop->rescaleAxes();
|
|
|
|
|
|
|
|
// Shrink columns down, then expand as needed
|
|
|
|
QTreeWidget *gtw = ui->graphTreeWidget;
|
|
|
|
int one_em = fontMetrics().height();
|
|
|
|
gtw->setRootIsDecorated(false);
|
|
|
|
gtw->setColumnWidth(name_col_, one_em * 10);
|
|
|
|
gtw->setColumnWidth(dfilter_col_, one_em * 10);
|
|
|
|
gtw->setColumnWidth(color_col_, one_em * 2.5);
|
|
|
|
gtw->setColumnWidth(style_col_, one_em * 5.5);
|
|
|
|
gtw->setColumnWidth(yaxis_col_, one_em * 6.5);
|
|
|
|
gtw->setColumnWidth(yfield_col_, one_em * 6);
|
|
|
|
gtw->setColumnWidth(sma_period_col_, one_em * 6);
|
|
|
|
|
|
|
|
connect(wsApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(focusChanged(QWidget*,QWidget*)));
|
|
|
|
connect(iop, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
|
|
|
|
connect(iop, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
|
|
|
|
connect(iop, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
|
|
|
|
disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
|
|
|
}
|
|
|
|
|
|
|
|
IOGraphDialog::~IOGraphDialog()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
IOGraph *iog = qvariant_cast<IOGraph *>(ui->graphTreeWidget->topLevelItem(i)->data(name_col_, Qt::UserRole));
|
|
|
|
delete iog;
|
|
|
|
}
|
|
|
|
delete ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::addGraph(bool checked, QString name, QString dfilter, int color_idx, IOGraph::PlotStyles style, io_graph_item_unit_t value_units, QString yfield, int moving_average)
|
|
|
|
{
|
|
|
|
QTreeWidgetItem *ti = new QTreeWidgetItem();
|
|
|
|
ui->graphTreeWidget->addTopLevelItem(ti);
|
|
|
|
|
|
|
|
IOGraph *iog = new IOGraph(ui->ioPlot);
|
|
|
|
ti->setData(name_col_, Qt::UserRole, qVariantFromValue(iog));
|
|
|
|
ti->setCheckState(name_col_, checked ? Qt::Checked : Qt::Unchecked);
|
|
|
|
ti->setText(name_col_, name);
|
|
|
|
ti->setText(dfilter_col_, dfilter);
|
|
|
|
color_idx = color_idx % colors_.size();
|
|
|
|
ti->setData(color_col_, Qt::UserRole, color_idx);
|
|
|
|
ti->setIcon(color_col_, graphColorIcon(color_idx));
|
|
|
|
ti->setText(style_col_, plot_style_to_name_[style]);
|
|
|
|
ti->setData(style_col_, Qt::UserRole, style);
|
|
|
|
ti->setText(yaxis_col_, value_unit_to_name_[value_units]);
|
|
|
|
ti->setData(yaxis_col_, Qt::UserRole, value_units);
|
|
|
|
ti->setText(yfield_col_, yfield);
|
|
|
|
ti->setText(sma_period_col_, moving_average_to_name_[moving_average]);
|
|
|
|
ti->setData(sma_period_col_, Qt::UserRole, moving_average);
|
|
|
|
|
|
|
|
connect(this, SIGNAL(recalcGraphData(capture_file *)), iog, SLOT(recalcGraphData(capture_file *)));
|
|
|
|
connect(iog, SIGNAL(requestRetap()), this, SLOT(scheduleRetap()));
|
|
|
|
connect(iog, SIGNAL(requestRecalc()), this, SLOT(scheduleRecalc()));
|
|
|
|
connect(iog, SIGNAL(requestReplot()), this, SLOT(scheduleReplot()));
|
|
|
|
|
|
|
|
syncGraphSettings(ti);
|
|
|
|
if (iog->visible()) {
|
|
|
|
scheduleRetap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::addGraph(bool copy_from_current)
|
|
|
|
{
|
|
|
|
QTreeWidgetItem *cur_ti = NULL;
|
|
|
|
|
|
|
|
if (copy_from_current) {
|
|
|
|
cur_ti = ui->graphTreeWidget->currentItem();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (copy_from_current && cur_ti) {
|
|
|
|
addGraph(cur_ti->checkState(name_col_) == Qt::Checked,
|
|
|
|
cur_ti->text(name_col_),
|
|
|
|
cur_ti->text(dfilter_col_),
|
|
|
|
cur_ti->data(color_col_, Qt::UserRole).toInt(),
|
|
|
|
(IOGraph::PlotStyles)cur_ti->data(style_col_, Qt::UserRole).toInt(),
|
|
|
|
(io_graph_item_unit_t)cur_ti->data(yaxis_col_, Qt::UserRole).toInt(),
|
|
|
|
cur_ti->text(yfield_col_),
|
|
|
|
cur_ti->data(sma_period_col_, Qt::UserRole).toInt());
|
|
|
|
} else {
|
|
|
|
addDefaultGraph(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
|
|
|
|
{
|
|
|
|
switch (idx % 2) {
|
|
|
|
case 0:
|
|
|
|
addGraph(enabled, tr("All packets"), QString(), ui->graphTreeWidget->topLevelItemCount(),
|
|
|
|
IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), default_moving_average_);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
addGraph(enabled, tr("TCP errors"), "tcp.analysis.flags", ui->graphTreeWidget->topLevelItemCount(),
|
|
|
|
IOGraph::psBar, IOG_ITEM_UNIT_PACKETS, QString(), default_moving_average_);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sync the settings from a graphTreeWidget item to its IOGraph.
|
|
|
|
// Disables the graph if any errors are found.
|
|
|
|
void IOGraphDialog::syncGraphSettings(QTreeWidgetItem *item)
|
|
|
|
{
|
|
|
|
if (!item) return;
|
|
|
|
IOGraph *iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
if (!iog) return;
|
|
|
|
|
|
|
|
bool visible = item->checkState(name_col_) == Qt::Checked;
|
|
|
|
bool retap = !iog->visible() && visible;
|
|
|
|
|
|
|
|
iog->setName(item->text(name_col_));
|
|
|
|
|
|
|
|
iog->setFilter(item->text(dfilter_col_));
|
|
|
|
iog->setColor(colors_[item->data(color_col_, Qt::UserRole).toInt() % colors_.size()]);
|
|
|
|
iog->setPlotStyle(item->data(style_col_, Qt::UserRole).toInt());
|
|
|
|
|
|
|
|
iog->setValueUnits(item->data(yaxis_col_, Qt::UserRole).toInt());
|
|
|
|
iog->setValueUnitField(item->text(yfield_col_));
|
|
|
|
|
|
|
|
iog->moving_avg_period_ = item->data(sma_period_col_, Qt::UserRole).toUInt();
|
|
|
|
|
|
|
|
iog->setInterval(ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt());
|
|
|
|
|
|
|
|
ui->graphTreeWidget->blockSignals(true); // setFlags emits itemChanged
|
|
|
|
if (!iog->configError().isEmpty()) {
|
|
|
|
hint_err_ = iog->configError();
|
|
|
|
visible = false;
|
|
|
|
retap = false;
|
|
|
|
// On OS X the "not user checkable" checkbox isn't obviously disabled.
|
|
|
|
// For now show it as partially checked.
|
|
|
|
item->setCheckState(name_col_, Qt::PartiallyChecked);
|
|
|
|
item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
|
|
|
|
} else {
|
|
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
|
|
}
|
|
|
|
ui->graphTreeWidget->blockSignals(false);
|
|
|
|
|
|
|
|
iog->setVisible(visible);
|
|
|
|
|
|
|
|
getGraphInfo();
|
|
|
|
mouseMoved(NULL); // Update hint
|
|
|
|
updateLegend();
|
|
|
|
|
|
|
|
if (visible) {
|
|
|
|
if (retap) {
|
|
|
|
scheduleRetap();
|
|
|
|
} else {
|
|
|
|
scheduleReplot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::setCaptureFile(capture_file *cf)
|
|
|
|
{
|
|
|
|
if (!cf) { // We only want to know when the file closes.
|
|
|
|
cap_file_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::scheduleReplot(bool now)
|
|
|
|
{
|
|
|
|
need_replot_ = true;
|
|
|
|
if (now) updateStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::scheduleRecalc(bool now)
|
|
|
|
{
|
|
|
|
need_recalc_ = true;
|
|
|
|
if (now) updateStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::scheduleRetap(bool now)
|
|
|
|
{
|
|
|
|
need_retap_ = true;
|
|
|
|
if (now) updateStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
|
|
|
|
|
|
|
|
switch(event->key()) {
|
|
|
|
case Qt::Key_Minus:
|
|
|
|
case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
|
|
|
|
case Qt::Key_O: // GTK+
|
|
|
|
case Qt::Key_R:
|
|
|
|
zoomAxes(false);
|
|
|
|
break;
|
|
|
|
case Qt::Key_Plus:
|
|
|
|
case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
|
|
|
|
case Qt::Key_I: // GTK+
|
|
|
|
zoomAxes(true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::Key_Right:
|
|
|
|
case Qt::Key_L:
|
|
|
|
panAxes(pan_pixels, 0);
|
|
|
|
break;
|
|
|
|
case Qt::Key_Left:
|
|
|
|
case Qt::Key_H:
|
|
|
|
panAxes(-1 * pan_pixels, 0);
|
|
|
|
break;
|
|
|
|
case Qt::Key_Up:
|
|
|
|
case Qt::Key_K:
|
|
|
|
panAxes(0, pan_pixels);
|
|
|
|
break;
|
|
|
|
case Qt::Key_Down:
|
|
|
|
case Qt::Key_J:
|
|
|
|
panAxes(0, -1 * pan_pixels);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::Key_Space:
|
|
|
|
toggleTracerStyle();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::Key_0:
|
|
|
|
case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
|
|
|
|
case Qt::Key_Home:
|
|
|
|
resetAxes();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Qt::Key_G:
|
|
|
|
on_actionGoToPacket_triggered();
|
|
|
|
break;
|
|
|
|
case Qt::Key_T:
|
|
|
|
on_actionToggleTimeOrigin_triggered();
|
|
|
|
break;
|
|
|
|
case Qt::Key_Z:
|
|
|
|
on_actionDragZoom_triggered();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDialog::keyPressEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::reject()
|
|
|
|
{
|
|
|
|
// Catch escape keys.
|
|
|
|
QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ << yfield_line_edit_;
|
|
|
|
|
|
|
|
foreach (QWidget *w, editors) {
|
|
|
|
if (w && w->hasFocus()) {
|
|
|
|
ui->graphTreeWidget->setFocus(); // Trigger itemEditingFinished
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<QComboBox *>combos = QList<QComboBox *>() << color_combo_box_ << style_combo_box_ <<
|
|
|
|
yaxis_combo_box_ << sma_combo_box_;
|
|
|
|
foreach (QComboBox *cb, combos) {
|
|
|
|
if (cb && (cb->hasFocus() || cb->view()->hasFocus())) {
|
|
|
|
ui->graphTreeWidget->setFocus(); // Trigger itemEditingFinished
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iog_uat_) {
|
|
|
|
uat_clear(iog_uat_);
|
|
|
|
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (item) {
|
|
|
|
iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
io_graph_settings_t iogs;
|
|
|
|
QColor color(iog->color());
|
|
|
|
iogs.enabled = iog->visible() ? 1 : 0;
|
|
|
|
iogs.name = qstring_strdup(iog->name());
|
|
|
|
iogs.dfilter = qstring_strdup(iog->filter());
|
|
|
|
iogs.color = qstring_strdup(color.name());
|
|
|
|
iogs.style = qstring_strdup(plot_style_to_name_[(IOGraph::PlotStyles)item->data(style_col_, Qt::UserRole).toInt()]);
|
|
|
|
iogs.yaxis = qstring_strdup(iog->valueUnitLabel());
|
|
|
|
iogs.yfield = qstring_strdup(iog->valueUnitField());
|
|
|
|
iogs.sma_period = iog->movingAveragePeriod();
|
|
|
|
uat_add_record(iog_uat_, &iogs, TRUE);
|
|
|
|
io_graph_free_cb(&iogs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const char* err = NULL;
|
|
|
|
uat_save(iog_uat_, &err);
|
|
|
|
}
|
|
|
|
|
|
|
|
QDialog::reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::zoomAxes(bool in)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
|
|
|
|
double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
|
|
|
|
|
|
|
|
auto_axes_ = false;
|
|
|
|
|
|
|
|
if (!in) {
|
|
|
|
h_factor = pow(h_factor, -1);
|
|
|
|
v_factor = pow(v_factor, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
|
|
|
|
iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::panAxes(int x_pixels, int y_pixels)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
double h_pan = 0.0;
|
|
|
|
double v_pan = 0.0;
|
|
|
|
|
|
|
|
auto_axes_ = false;
|
|
|
|
|
|
|
|
h_pan = iop->xAxis->range().size() * x_pixels / iop->xAxis->axisRect()->width();
|
|
|
|
v_pan = iop->yAxis->range().size() * y_pixels / iop->yAxis->axisRect()->height();
|
|
|
|
// The GTK+ version won't pan unless we're zoomed. Should we do the same here?
|
|
|
|
if (h_pan) {
|
|
|
|
iop->xAxis->moveRange(h_pan);
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
if (v_pan) {
|
|
|
|
iop->yAxis->moveRange(v_pan);
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon IOGraphDialog::graphColorIcon(int color_idx)
|
|
|
|
{
|
|
|
|
int h = fontMetrics().height() * 3 / 4;
|
|
|
|
QPixmap pm(h * 2, h);
|
|
|
|
pm.fill(colors_[color_idx % colors_.size()]);
|
|
|
|
return QIcon(pm);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::toggleTracerStyle(bool force_default)
|
|
|
|
{
|
|
|
|
if (!tracer_->visible() && !force_default) return;
|
|
|
|
if (!ui->ioPlot->graph(0)) return;
|
|
|
|
|
|
|
|
QPen sp_pen = ui->ioPlot->graph(0)->pen();
|
|
|
|
QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
|
|
|
|
QPen tr_pen = QPen(tracer_->pen());
|
|
|
|
QColor tr_color = sp_pen.color();
|
|
|
|
|
|
|
|
if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
|
|
|
|
tstyle = QCPItemTracer::tsCircle;
|
|
|
|
tr_color.setAlphaF(1.0);
|
|
|
|
tr_pen.setWidthF(1.5);
|
|
|
|
} else {
|
|
|
|
tr_color.setAlphaF(0.5);
|
|
|
|
tr_pen.setWidthF(1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
tracer_->setStyle(tstyle);
|
|
|
|
tr_pen.setColor(tr_color);
|
|
|
|
tracer_->setPen(tr_pen);
|
|
|
|
ui->ioPlot->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scan through our graphs and gather information.
|
|
|
|
// QCPItemTracers can only be associated with QCPGraphs. Find the first one
|
|
|
|
// and associate it with our tracer. Set bar stacking order while we're here.
|
|
|
|
void IOGraphDialog::getGraphInfo()
|
|
|
|
{
|
|
|
|
base_graph_ = NULL;
|
|
|
|
QCPBars *prev_bars = NULL;
|
|
|
|
start_time_ = 0.0;
|
|
|
|
|
|
|
|
tracer_->setGraph(NULL);
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (item) {
|
|
|
|
iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
QCPGraph *graph = iog->graph();
|
|
|
|
QCPBars *bars = iog->bars();
|
|
|
|
int style = item->data(style_col_, Qt::UserRole).toInt();
|
|
|
|
if (graph && !base_graph_) {
|
|
|
|
base_graph_ = graph;
|
|
|
|
} else if (bars && style == IOGraph::psStackedBar && iog->visible()) {
|
|
|
|
bars->moveBelow(NULL); // Remove from existing stack
|
|
|
|
bars->moveBelow(prev_bars);
|
|
|
|
prev_bars = bars;
|
|
|
|
}
|
|
|
|
if (iog->visible()) {
|
|
|
|
double iog_start = iog->startOffset();
|
|
|
|
if (start_time_ == 0.0 || iog_start < start_time_) {
|
|
|
|
start_time_ = iog_start;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (base_graph_ && base_graph_->data()->size() > 0) {
|
|
|
|
tracer_->setGraph(base_graph_);
|
|
|
|
tracer_->setVisible(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::updateLegend()
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
QSet<QString> vu_label_set;
|
|
|
|
|
|
|
|
iop->legend->setVisible(false);
|
|
|
|
iop->yAxis->setLabel(QString());
|
|
|
|
|
|
|
|
// Find unique labels
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(i);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (ti) {
|
|
|
|
iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
vu_label_set.insert(iog->valueUnitLabel());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing.
|
|
|
|
if (vu_label_set.size() < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// All the same. Use the Y Axis label.
|
|
|
|
if (vu_label_set.size() == 1) {
|
|
|
|
iop->yAxis->setLabel(vu_label_set.values()[0]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Differing labels. Create a legend.
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(i);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (ti) {
|
|
|
|
iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
iog->addToLegend();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
iop->legend->setVisible(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRectF IOGraphDialog::getZoomRanges(QRect zoom_rect)
|
|
|
|
{
|
|
|
|
QRectF zoom_ranges = QRectF();
|
|
|
|
|
|
|
|
if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) {
|
|
|
|
return zoom_ranges;
|
|
|
|
}
|
|
|
|
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
QRect zr = zoom_rect.normalized();
|
|
|
|
QRect ar = iop->axisRect()->rect();
|
|
|
|
if (ar.intersects(zr)) {
|
|
|
|
QRect zsr = ar.intersected(zr);
|
|
|
|
zoom_ranges.setX(iop->xAxis->range().lower
|
|
|
|
+ iop->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
|
|
|
|
zoom_ranges.setWidth(iop->xAxis->range().size() * zsr.width() / ar.width());
|
|
|
|
|
|
|
|
// QRects grow down
|
|
|
|
zoom_ranges.setY(iop->yAxis->range().lower
|
|
|
|
+ iop->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
|
|
|
|
zoom_ranges.setHeight(iop->yAxis->range().size() * zsr.height() / ar.height());
|
|
|
|
}
|
|
|
|
return zoom_ranges;
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::graphClicked(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
|
|
|
|
if (event->button() == Qt::RightButton) {
|
|
|
|
// XXX We should find some way to get ioPlot to handle a
|
|
|
|
// contextMenuEvent instead.
|
|
|
|
ctx_menu_.exec(event->globalPos());
|
|
|
|
} else if (mouse_drags_) {
|
|
|
|
if (iop->axisRect()->rect().contains(event->pos())) {
|
|
|
|
iop->setCursor(QCursor(Qt::ClosedHandCursor));
|
|
|
|
}
|
|
|
|
on_actionGoToPacket_triggered();
|
|
|
|
} else {
|
|
|
|
if (!rubber_band_) {
|
|
|
|
rubber_band_ = new QRubberBand(QRubberBand::Rectangle, iop);
|
|
|
|
}
|
|
|
|
rb_origin_ = event->pos();
|
|
|
|
rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
|
|
|
|
rubber_band_->show();
|
|
|
|
}
|
|
|
|
iop->setFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::mouseMoved(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
QString hint;
|
|
|
|
Qt::CursorShape shape = Qt::ArrowCursor;
|
|
|
|
|
|
|
|
if (!hint_err_.isEmpty()) {
|
|
|
|
hint += QString("<b>%1</b> ").arg(hint_err_);
|
|
|
|
}
|
|
|
|
if (event) {
|
|
|
|
if (event->buttons().testFlag(Qt::LeftButton)) {
|
|
|
|
if (mouse_drags_) {
|
|
|
|
shape = Qt::ClosedHandCursor;
|
|
|
|
} else {
|
|
|
|
shape = Qt::CrossCursor;
|
|
|
|
}
|
|
|
|
} else if (iop->axisRect()->rect().contains(event->pos())) {
|
|
|
|
if (mouse_drags_) {
|
|
|
|
shape = Qt::OpenHandCursor;
|
|
|
|
} else {
|
|
|
|
shape = Qt::CrossCursor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
iop->setCursor(QCursor(shape));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mouse_drags_) {
|
|
|
|
double ts = 0;
|
|
|
|
packet_num_ = 0;
|
|
|
|
int interval_packet = -1;
|
|
|
|
|
|
|
|
if (event && tracer_->graph()) {
|
|
|
|
tracer_->setGraphKey(iop->xAxis->pixelToCoord(event->pos().x()));
|
|
|
|
ts = tracer_->position->key();
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = ui->graphTreeWidget->topLevelItem(0);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (ti) {
|
|
|
|
iog = ti->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
interval_packet = iog->packetFromTime(ts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (interval_packet < 0) {
|
|
|
|
hint += tr("Hover over the graph for details.");
|
|
|
|
} else {
|
|
|
|
QString msg = tr("No packets in interval");
|
|
|
|
QString val;
|
|
|
|
if (interval_packet > 0) {
|
|
|
|
packet_num_ = (guint32) interval_packet;
|
|
|
|
msg = tr("%1 %2")
|
|
|
|
.arg(cap_file_ ? tr("Click to select packet") : tr("Packet"))
|
|
|
|
.arg(packet_num_);
|
|
|
|
val = " = " + QString::number(tracer_->position->value(), 'g', 4);
|
|
|
|
}
|
|
|
|
hint += tr("%1 (%2s%3).")
|
|
|
|
.arg(msg)
|
|
|
|
.arg(QString::number(ts, 'g', 4))
|
|
|
|
.arg(val);
|
|
|
|
}
|
|
|
|
iop->replot();
|
|
|
|
} else {
|
|
|
|
if (event && rubber_band_ && rubber_band_->isVisible()) {
|
|
|
|
rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
|
|
|
|
QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
|
|
|
|
if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
|
|
|
|
hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
|
|
|
|
.arg(zoom_ranges.x())
|
|
|
|
.arg(zoom_ranges.x() + zoom_ranges.width())
|
|
|
|
.arg(zoom_ranges.y())
|
|
|
|
.arg(zoom_ranges.y() + zoom_ranges.height());
|
|
|
|
} else {
|
|
|
|
hint += tr("Unable to select range.");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hint += tr("Click to select a portion of the graph.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hint.prepend("<small><i>");
|
|
|
|
hint.append("</i></small>");
|
|
|
|
ui->hintLabel->setText(hint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::mouseReleased(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
auto_axes_ = false;
|
|
|
|
if (rubber_band_) {
|
|
|
|
rubber_band_->hide();
|
|
|
|
if (!mouse_drags_) {
|
|
|
|
QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
|
|
|
|
if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
|
|
|
|
iop->xAxis->setRangeLower(zoom_ranges.x());
|
|
|
|
iop->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
|
|
|
|
iop->yAxis->setRangeLower(zoom_ranges.y());
|
|
|
|
iop->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (iop->cursor().shape() == Qt::ClosedHandCursor) {
|
|
|
|
iop->setCursor(QCursor(Qt::OpenHandCursor));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::focusChanged(QWidget *previous, QWidget *current)
|
|
|
|
{
|
|
|
|
Q_UNUSED(previous);
|
|
|
|
QTreeWidgetItem *item = ui->graphTreeWidget->currentItem();
|
|
|
|
if (!item) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we navigated away from an editing session, clear it.
|
|
|
|
QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ <<
|
|
|
|
color_combo_box_ << style_combo_box_ <<
|
|
|
|
yaxis_combo_box_ << yfield_line_edit_ <<
|
|
|
|
sma_combo_box_;
|
|
|
|
bool edit_active = false;
|
|
|
|
foreach (QWidget *w, editors) {
|
|
|
|
if (w) {
|
|
|
|
edit_active = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!edit_active) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
editors.append(color_combo_box_->view());
|
|
|
|
editors.append(style_combo_box_->view());
|
|
|
|
editors.append(yaxis_combo_box_->view());
|
|
|
|
editors.append(sma_combo_box_->view());
|
|
|
|
|
|
|
|
if (! editors.contains(current)) {
|
|
|
|
itemEditingFinished(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::activateLastItem()
|
|
|
|
{
|
|
|
|
int last_idx = ui->graphTreeWidget->topLevelItemCount() - 1;
|
|
|
|
if (last_idx < 0) return;
|
|
|
|
|
|
|
|
QTreeWidgetItem *last_item = ui->graphTreeWidget->invisibleRootItem()->child(last_idx);
|
|
|
|
if (!last_item) return;
|
|
|
|
|
|
|
|
ui->graphTreeWidget->setCurrentItem(last_item);
|
|
|
|
on_graphTreeWidget_itemActivated(last_item, name_col_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::resetAxes()
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
QCPRange x_range = iop->xAxis->scaleType() == QCPAxis::stLogarithmic ?
|
|
|
|
iop->xAxis->range().sanitizedForLogScale() : iop->xAxis->range();
|
|
|
|
|
|
|
|
double pixel_pad = 10.0; // per side
|
|
|
|
|
|
|
|
iop->rescaleAxes(true);
|
|
|
|
|
|
|
|
double axis_pixels = iop->xAxis->axisRect()->width();
|
|
|
|
iop->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
|
|
|
|
|
|
|
|
axis_pixels = iop->yAxis->axisRect()->height();
|
|
|
|
iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, iop->yAxis->range().center());
|
|
|
|
|
|
|
|
auto_axes_ = true;
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::updateStatistics()
|
|
|
|
{
|
|
|
|
if (!isVisible()) return;
|
|
|
|
|
|
|
|
if (need_retap_) {
|
|
|
|
need_retap_ = false;
|
|
|
|
cf_retap_packets(cap_file_);
|
|
|
|
ui->ioPlot->setFocus();
|
|
|
|
} else {
|
|
|
|
if (need_recalc_) {
|
|
|
|
need_recalc_ = false;
|
|
|
|
need_replot_ = true;
|
|
|
|
emit recalcGraphData(cap_file_);
|
|
|
|
if (!tracer_->graph()) {
|
|
|
|
if (base_graph_ && base_graph_->data()->size() > 0) {
|
|
|
|
tracer_->setGraph(base_graph_);
|
|
|
|
tracer_->setVisible(true);
|
|
|
|
} else {
|
|
|
|
tracer_->setVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (need_replot_) {
|
|
|
|
need_replot_ = false;
|
|
|
|
if (auto_axes_) {
|
|
|
|
resetAxes();
|
|
|
|
}
|
|
|
|
ui->ioPlot->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're done editing a treewidgetitem. Set its values based on its
|
|
|
|
// widgets, remove each widget, then sync with our associated graph.
|
|
|
|
void IOGraphDialog::itemEditingFinished(QTreeWidgetItem *item)
|
|
|
|
{
|
|
|
|
if (item) {
|
|
|
|
bool recalc = false;
|
|
|
|
// Don't force a retap here. Disable the graph instead.
|
|
|
|
Qt::CheckState check_state = item->checkState(name_col_);
|
|
|
|
hint_err_.clear();
|
|
|
|
io_graph_item_unit_t item_unit = IOG_ITEM_UNIT_PACKETS;
|
|
|
|
QString field_name;
|
|
|
|
|
|
|
|
if (name_line_edit_) {
|
|
|
|
item->setText(name_col_, name_line_edit_->text());
|
|
|
|
}
|
|
|
|
if (dfilter_line_edit_) {
|
|
|
|
QString df = dfilter_line_edit_->text();
|
|
|
|
if (item->text(dfilter_col_).compare(df)) {
|
|
|
|
check_state = Qt::Unchecked;
|
|
|
|
}
|
|
|
|
item->setText(dfilter_col_, df);
|
|
|
|
}
|
|
|
|
if (color_combo_box_) {
|
|
|
|
int index = color_combo_box_->currentIndex();
|
|
|
|
item->setData(color_col_, Qt::UserRole, index);
|
|
|
|
item->setIcon(color_col_, graphColorIcon(index));
|
|
|
|
}
|
|
|
|
if (style_combo_box_) {
|
|
|
|
IOGraph::PlotStyles ps = IOGraph::psLine;
|
|
|
|
int index = style_combo_box_->currentIndex();
|
|
|
|
if (index < plot_style_to_name_.size()) {
|
|
|
|
ps = plot_style_to_name_.keys()[index];
|
|
|
|
}
|
|
|
|
item->setText(style_col_, plot_style_to_name_[ps]);
|
|
|
|
item->setData(style_col_, Qt::UserRole, ps);
|
|
|
|
}
|
|
|
|
if (yaxis_combo_box_) {
|
|
|
|
int index = yaxis_combo_box_->currentIndex();
|
|
|
|
if (index != item->data(yaxis_col_, Qt::UserRole).toInt()) {
|
|
|
|
if (index <= IOG_ITEM_UNIT_CALC_SUM) {
|
|
|
|
recalc = true;
|
|
|
|
} else {
|
|
|
|
check_state = Qt::Unchecked;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (index < value_unit_to_name_.size()) {
|
|
|
|
item_unit = value_unit_to_name_.keys()[index];
|
|
|
|
}
|
|
|
|
item->setText(yaxis_col_, value_unit_to_name_[item_unit]);
|
|
|
|
item->setData(yaxis_col_, Qt::UserRole, item_unit);
|
|
|
|
}
|
|
|
|
if (yfield_line_edit_) {
|
|
|
|
if (item->text(yfield_col_).compare(yfield_line_edit_->text())) {
|
|
|
|
check_state = Qt::Unchecked;
|
|
|
|
}
|
|
|
|
item->setText(yfield_col_, yfield_line_edit_->text());
|
|
|
|
field_name = yfield_line_edit_->text();
|
|
|
|
}
|
|
|
|
if (sma_combo_box_) {
|
|
|
|
int index = sma_combo_box_->currentIndex();
|
|
|
|
if (index != item->data(sma_period_col_, Qt::UserRole).toInt()) {
|
|
|
|
recalc = true;
|
|
|
|
}
|
|
|
|
QString text = sma_combo_box_->itemText(index);
|
|
|
|
int sma = sma_combo_box_->itemData(index, Qt::UserRole).toInt();
|
|
|
|
item->setText(sma_period_col_, text);
|
|
|
|
item->setData(sma_period_col_, Qt::UserRole, sma);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int col = 0; col < num_cols_; col++) {
|
|
|
|
QWidget *w = ui->graphTreeWidget->itemWidget(item, col);
|
|
|
|
if (w) {
|
|
|
|
ui->graphTreeWidget->removeItemWidget(item, col);
|
|
|
|
delete w;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
item->setCheckState(name_col_, check_state);
|
|
|
|
syncGraphSettings(item);
|
|
|
|
|
|
|
|
if (recalc) {
|
|
|
|
scheduleRecalc(true);
|
|
|
|
} else {
|
|
|
|
scheduleReplot(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::loadProfileGraphs()
|
|
|
|
{
|
|
|
|
if (iog_uat_) return;
|
|
|
|
|
|
|
|
iog_uat_ = uat_new("I/O Graphs",
|
|
|
|
sizeof(io_graph_settings_t),
|
|
|
|
"io_graphs",
|
|
|
|
TRUE,
|
|
|
|
&iog_settings_,
|
|
|
|
&num_io_graphs_,
|
|
|
|
0, /* doesn't affect anything that requires a GUI update */
|
|
|
|
"ChStatIOGraphs",
|
|
|
|
io_graph_copy_cb,
|
|
|
|
NULL,
|
|
|
|
io_graph_free_cb,
|
|
|
|
NULL,
|
|
|
|
io_graph_fields);
|
|
|
|
const char* err = NULL;
|
|
|
|
uat_load(iog_uat_, &err);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Slots
|
|
|
|
|
|
|
|
void IOGraphDialog::lineEditDestroyed()
|
|
|
|
{
|
|
|
|
if (QObject::sender() == name_line_edit_) {
|
|
|
|
name_line_edit_ = NULL;
|
|
|
|
} else if (QObject::sender() == dfilter_line_edit_) {
|
|
|
|
dfilter_line_edit_ = NULL;
|
|
|
|
} else if (QObject::sender() == yfield_line_edit_) {
|
|
|
|
yfield_line_edit_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::comboDestroyed()
|
|
|
|
{
|
|
|
|
if (QObject::sender() == color_combo_box_) {
|
|
|
|
color_combo_box_ = NULL;
|
|
|
|
} else if (QObject::sender() == style_combo_box_) {
|
|
|
|
style_combo_box_ = NULL;
|
|
|
|
} else if (QObject::sender() == yaxis_combo_box_) {
|
|
|
|
yaxis_combo_box_ = NULL;
|
|
|
|
} else if (QObject::sender() == sma_combo_box_) {
|
|
|
|
sma_combo_box_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int index)
|
|
|
|
{
|
|
|
|
Q_UNUSED(index);
|
|
|
|
|
|
|
|
int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
|
|
|
|
bool need_retap = false;
|
|
|
|
|
|
|
|
for (int i = 0; i < ui->graphTreeWidget->topLevelItemCount(); i++) {
|
|
|
|
QTreeWidgetItem *item = ui->graphTreeWidget->topLevelItem(i);
|
|
|
|
IOGraph *iog = NULL;
|
|
|
|
if (item) {
|
|
|
|
iog = item->data(name_col_, Qt::UserRole).value<IOGraph *>();
|
|
|
|
if (iog) {
|
|
|
|
iog->setInterval(interval);
|
|
|
|
if (iog->visible()) {
|
|
|
|
need_retap = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_retap) {
|
|
|
|
scheduleRetap(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_todCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
double orig_start = start_time_;
|
|
|
|
bool orig_auto = auto_axes_;
|
|
|
|
|
|
|
|
ui->ioPlot->xAxis->setTickLabelType(checked ? QCPAxis::ltDateTime : QCPAxis::ltNumber);
|
|
|
|
auto_axes_ = false;
|
|
|
|
scheduleRecalc(true);
|
|
|
|
auto_axes_ = orig_auto;
|
|
|
|
getGraphInfo();
|
|
|
|
ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_graphTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
|
|
|
|
{
|
|
|
|
Q_UNUSED(current);
|
|
|
|
|
|
|
|
if (previous && ui->graphTreeWidget->itemWidget(previous, name_col_)) {
|
|
|
|
itemEditingFinished(previous);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_graphTreeWidget_itemActivated(QTreeWidgetItem *item, int column)
|
|
|
|
{
|
|
|
|
if (!item || name_line_edit_) return;
|
|
|
|
|
|
|
|
QWidget *editor = NULL;
|
|
|
|
int cur_idx;
|
|
|
|
|
|
|
|
name_line_edit_ = new QLineEdit();
|
|
|
|
name_line_edit_->setText(item->text(name_col_));
|
|
|
|
connect(name_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
|
|
|
|
|
|
|
|
dfilter_line_edit_ = new SyntaxLineEdit();
|
|
|
|
connect(dfilter_line_edit_, SIGNAL(textChanged(QString)),
|
|
|
|
dfilter_line_edit_, SLOT(checkDisplayFilter(QString)));
|
|
|
|
connect(dfilter_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
|
|
|
|
dfilter_line_edit_->setText(item->text(dfilter_col_));
|
|
|
|
|
|
|
|
color_combo_box_ = new QComboBox();
|
|
|
|
cur_idx = item->data(color_col_, Qt::UserRole).toInt();
|
|
|
|
for (int i = 0; i < colors_.size(); i++) {
|
|
|
|
color_combo_box_->addItem(QString());
|
|
|
|
color_combo_box_->setItemIcon(i, graphColorIcon(i));
|
|
|
|
if (i == cur_idx) {
|
|
|
|
color_combo_box_->setCurrentIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
item->setIcon(color_col_, QIcon());
|
|
|
|
color_combo_box_->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
connect(color_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
|
|
|
|
|
|
|
|
style_combo_box_ = new QComboBox();
|
|
|
|
cur_idx = item->data(style_col_, Qt::UserRole).toInt();
|
|
|
|
for (int i = 0; i < plot_style_to_name_.size(); i++) {
|
|
|
|
IOGraph::PlotStyles ps = plot_style_to_name_.keys()[i];
|
|
|
|
style_combo_box_->addItem(plot_style_to_name_[ps], ps);
|
|
|
|
if (ps == cur_idx) {
|
|
|
|
style_combo_box_->setCurrentIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
style_combo_box_->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
connect(style_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
|
|
|
|
|
|
|
|
yaxis_combo_box_ = new QComboBox();
|
|
|
|
cur_idx = item->data(yaxis_col_, Qt::UserRole).toInt();
|
|
|
|
for (int i = 0; i < value_unit_to_name_.size(); i++) {
|
|
|
|
io_graph_item_unit_t vu = value_unit_to_name_.keys()[i];
|
|
|
|
yaxis_combo_box_->addItem(value_unit_to_name_[vu], vu);
|
|
|
|
if (vu == cur_idx) {
|
|
|
|
yaxis_combo_box_->setCurrentIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
yaxis_combo_box_->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
connect(yaxis_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
|
|
|
|
|
|
|
|
yfield_line_edit_ = new SyntaxLineEdit();
|
|
|
|
connect(yfield_line_edit_, SIGNAL(textChanged(QString)),
|
|
|
|
yfield_line_edit_, SLOT(checkFieldName(QString)));
|
|
|
|
connect(yfield_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditDestroyed()));
|
|
|
|
yfield_line_edit_->setText(item->text(yfield_col_));
|
|
|
|
|
|
|
|
sma_combo_box_ = new QComboBox();
|
|
|
|
cur_idx = item->data(sma_period_col_, Qt::UserRole).toInt();
|
|
|
|
for (int i = 0; i < moving_average_to_name_.size(); i++) {
|
|
|
|
int sma = moving_average_to_name_.keys()[i];
|
|
|
|
sma_combo_box_->addItem(moving_average_to_name_[sma], sma);
|
|
|
|
if (sma == cur_idx) {
|
|
|
|
sma_combo_box_->setCurrentIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sma_combo_box_->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
connect(sma_combo_box_, SIGNAL(destroyed()), this, SLOT(comboDestroyed()));
|
|
|
|
|
|
|
|
switch (column) {
|
|
|
|
case name_col_:
|
|
|
|
editor = name_line_edit_;
|
|
|
|
name_line_edit_->selectAll();
|
|
|
|
break;
|
|
|
|
case dfilter_col_:
|
|
|
|
editor = dfilter_line_edit_;
|
|
|
|
dfilter_line_edit_->selectAll();
|
|
|
|
break;
|
|
|
|
case color_col_:
|
|
|
|
{
|
|
|
|
editor = color_combo_box_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case style_col_:
|
|
|
|
{
|
|
|
|
editor = style_combo_box_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case yaxis_col_:
|
|
|
|
{
|
|
|
|
editor = yaxis_combo_box_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case yfield_col_:
|
|
|
|
editor = yfield_line_edit_;
|
|
|
|
yfield_line_edit_->selectAll();
|
|
|
|
break;
|
|
|
|
case sma_period_col_:
|
|
|
|
{
|
|
|
|
editor = sma_combo_box_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<QWidget *>editors = QList<QWidget *>() << name_line_edit_ << dfilter_line_edit_ <<
|
|
|
|
color_combo_box_ << style_combo_box_ <<
|
|
|
|
yaxis_combo_box_ << yfield_line_edit_ <<
|
|
|
|
sma_combo_box_;
|
|
|
|
int cur_col = name_col_;
|
|
|
|
QWidget *prev_widget = ui->graphTreeWidget;
|
|
|
|
foreach (QWidget *editor, editors) {
|
|
|
|
QFrame *edit_frame = new QFrame();
|
|
|
|
QHBoxLayout *hb = new QHBoxLayout();
|
|
|
|
QSpacerItem *spacer = new QSpacerItem(5, 10);
|
|
|
|
|
|
|
|
hb->addWidget(editor, 0);
|
|
|
|
hb->addSpacerItem(spacer);
|
|
|
|
hb->setStretch(1, 1);
|
|
|
|
hb->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
|
|
|
edit_frame->setLineWidth(0);
|
|
|
|
edit_frame->setFrameStyle(QFrame::NoFrame);
|
|
|
|
edit_frame->setLayout(hb);
|
|
|
|
ui->graphTreeWidget->setItemWidget(item, cur_col, edit_frame);
|
|
|
|
setTabOrder(prev_widget, editor);
|
|
|
|
prev_widget = editor;
|
|
|
|
cur_col++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// setTabOrder(prev_widget, ui->graphTreeWidget);
|
|
|
|
editor->setFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_graphTreeWidget_itemSelectionChanged()
|
|
|
|
{
|
|
|
|
if (ui->graphTreeWidget->selectedItems().length() > 0) {
|
|
|
|
ui->deleteToolButton->setEnabled(true);
|
|
|
|
ui->copyToolButton->setEnabled(true);
|
|
|
|
} else {
|
|
|
|
ui->deleteToolButton->setEnabled(false);
|
|
|
|
ui->copyToolButton->setEnabled(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_graphTreeWidget_itemChanged(QTreeWidgetItem *item, int column)
|
|
|
|
{
|
|
|
|
if (!item) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (column == name_col_ && !name_line_edit_) {
|
|
|
|
syncGraphSettings(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_resetButton_clicked()
|
|
|
|
{
|
|
|
|
resetAxes();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_newToolButton_clicked()
|
|
|
|
{
|
|
|
|
addGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_deleteToolButton_clicked()
|
|
|
|
{
|
|
|
|
QTreeWidgetItem *item = ui->graphTreeWidget->currentItem();
|
|
|
|
if (!item) return;
|
|
|
|
|
|
|
|
IOGraph *iog = qvariant_cast<IOGraph *>(item->data(name_col_, Qt::UserRole));
|
|
|
|
delete iog;
|
|
|
|
|
|
|
|
delete item;
|
|
|
|
|
|
|
|
// We should probably be smarter about this.
|
|
|
|
hint_err_.clear();
|
|
|
|
mouseMoved(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_copyToolButton_clicked()
|
|
|
|
{
|
|
|
|
addGraph(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_dragRadioButton_toggled(bool checked)
|
|
|
|
{
|
|
|
|
if (checked) mouse_drags_ = true;
|
|
|
|
ui->ioPlot->setInteractions(
|
|
|
|
QCP::iRangeDrag |
|
|
|
|
QCP::iRangeZoom
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_zoomRadioButton_toggled(bool checked)
|
|
|
|
{
|
|
|
|
if (checked) mouse_drags_ = false;
|
|
|
|
ui->ioPlot->setInteractions(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_logCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
QCustomPlot *iop = ui->ioPlot;
|
|
|
|
|
|
|
|
iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
|
|
|
|
iop->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionReset_triggered()
|
|
|
|
{
|
|
|
|
on_resetButton_clicked();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionZoomIn_triggered()
|
|
|
|
{
|
|
|
|
zoomAxes(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionZoomOut_triggered()
|
|
|
|
{
|
|
|
|
zoomAxes(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveUp10_triggered()
|
|
|
|
{
|
|
|
|
panAxes(0, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveLeft10_triggered()
|
|
|
|
{
|
|
|
|
panAxes(-10, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveRight10_triggered()
|
|
|
|
{
|
|
|
|
panAxes(10, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveDown10_triggered()
|
|
|
|
{
|
|
|
|
panAxes(0, -10);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveUp1_triggered()
|
|
|
|
{
|
|
|
|
panAxes(0, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveLeft1_triggered()
|
|
|
|
{
|
|
|
|
panAxes(-1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveRight1_triggered()
|
|
|
|
{
|
|
|
|
panAxes(1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionMoveDown1_triggered()
|
|
|
|
{
|
|
|
|
panAxes(0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionGoToPacket_triggered()
|
|
|
|
{
|
|
|
|
if (tracer_->visible() && cap_file_ && packet_num_ > 0) {
|
|
|
|
emit goToPacket(packet_num_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionDragZoom_triggered()
|
|
|
|
{
|
|
|
|
if (mouse_drags_) {
|
|
|
|
ui->zoomRadioButton->toggle();
|
|
|
|
} else {
|
|
|
|
ui->dragRadioButton->toggle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionToggleTimeOrigin_triggered()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_actionCrosshairs_triggered()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraphDialog::on_buttonBox_helpRequested()
|
|
|
|
{
|
|
|
|
wsApp->helpTopicAction(HELP_STATS_IO_GRAPH_DIALOG);
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX - Copied from tcp_stream_dialog. This should be common code.
|
|
|
|
void IOGraphDialog::on_buttonBox_accepted()
|
|
|
|
{
|
|
|
|
QString file_name, extension;
|
|
|
|
QDir path(wsApp->lastOpenDir());
|
|
|
|
QString pdf_filter = tr("Portable Document Format (*.pdf)");
|
|
|
|
QString png_filter = tr("Portable Network Graphics (*.png)");
|
|
|
|
QString bmp_filter = tr("Windows Bitmap (*.bmp)");
|
|
|
|
// Gaze upon my beautiful graph with lossy artifacts!
|
|
|
|
QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
|
|
|
|
QString filter = QString("%1;;%2;;%3;;%4")
|
|
|
|
.arg(pdf_filter)
|
|
|
|
.arg(png_filter)
|
|
|
|
.arg(bmp_filter)
|
|
|
|
.arg(jpeg_filter);
|
|
|
|
|
|
|
|
QString save_file = path.canonicalPath();
|
|
|
|
if (cap_file_) {
|
|
|
|
save_file += QString("/%1").arg(cf_get_display_name(cap_file_));
|
|
|
|
}
|
|
|
|
file_name = QFileDialog::getSaveFileName(this, tr("Wireshark: Save Graph As..."),
|
|
|
|
save_file, filter, &extension);
|
|
|
|
|
|
|
|
if (file_name.length() > 0) {
|
|
|
|
bool save_ok = false;
|
|
|
|
if (extension.compare(pdf_filter) == 0) {
|
|
|
|
save_ok = ui->ioPlot->savePdf(file_name);
|
|
|
|
} else if (extension.compare(png_filter) == 0) {
|
|
|
|
save_ok = ui->ioPlot->savePng(file_name);
|
|
|
|
} else if (extension.compare(bmp_filter) == 0) {
|
|
|
|
save_ok = ui->ioPlot->saveBmp(file_name);
|
|
|
|
} else if (extension.compare(jpeg_filter) == 0) {
|
|
|
|
save_ok = ui->ioPlot->saveJpg(file_name);
|
|
|
|
}
|
|
|
|
// else error dialog?
|
|
|
|
if (save_ok) {
|
|
|
|
path = QDir(file_name);
|
|
|
|
wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IOGraph
|
|
|
|
|
|
|
|
IOGraph::IOGraph(QCustomPlot *parent) :
|
|
|
|
parent_(parent),
|
|
|
|
visible_(false),
|
|
|
|
graph_(NULL),
|
|
|
|
bars_(NULL),
|
|
|
|
hf_index_(-1),
|
|
|
|
cur_idx_(-1)
|
|
|
|
{
|
|
|
|
Q_ASSERT(parent_ != NULL);
|
|
|
|
graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
|
|
|
|
Q_ASSERT(graph_ != NULL);
|
|
|
|
|
|
|
|
GString *error_string;
|
|
|
|
error_string = register_tap_listener("frame",
|
|
|
|
this,
|
|
|
|
"",
|
|
|
|
TL_REQUIRES_PROTO_TREE,
|
|
|
|
tapReset,
|
|
|
|
tapPacket,
|
|
|
|
tapDraw);
|
|
|
|
if (error_string) {
|
|
|
|
// QMessageBox::critical(this, tr("%1 failed to register tap listener").arg(name_),
|
|
|
|
// error_string->str);
|
|
|
|
g_string_free(error_string, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
setFilter(QString());
|
|
|
|
}
|
|
|
|
|
|
|
|
IOGraph::~IOGraph() {
|
|
|
|
remove_tap_listener(this);
|
|
|
|
if (graph_) {
|
|
|
|
parent_->removeGraph(graph_);
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
parent_->removePlottable(bars_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct a full filter string from the display filter and value unit / Y axis.
|
|
|
|
// Check for errors and sets config_err_ if any are found.
|
|
|
|
void IOGraph::setFilter(const QString &filter)
|
|
|
|
{
|
|
|
|
GString *error_string;
|
|
|
|
QString full_filter(filter.trimmed());
|
|
|
|
|
|
|
|
config_err_.clear();
|
|
|
|
|
|
|
|
// Make sure we have a good display filter
|
|
|
|
if (!full_filter.isEmpty()) {
|
|
|
|
dfilter_t *dfilter;
|
|
|
|
bool status;
|
|
|
|
status = dfilter_compile(full_filter.toUtf8().constData(), &dfilter);
|
|
|
|
dfilter_free(dfilter);
|
|
|
|
if (!status) {
|
|
|
|
config_err_ = dfilter_error_msg;
|
|
|
|
filter_ = full_filter;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check our value unit + field combo.
|
|
|
|
error_string = check_field_unit(vu_field_.toUtf8().constData(), NULL, val_units_);
|
|
|
|
if (error_string) {
|
|
|
|
config_err_ = error_string->str;
|
|
|
|
g_string_free(error_string, TRUE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure vu_field_ survives edt tree pruning by adding it to our filter
|
|
|
|
// expression.
|
|
|
|
if (val_units_ >= IOG_ITEM_UNIT_CALC_SUM && !vu_field_.isEmpty() && hf_index_ >= 0) {
|
|
|
|
if (full_filter.isEmpty()) {
|
|
|
|
full_filter = vu_field_;
|
|
|
|
} else {
|
|
|
|
full_filter += QString(" && (%1)").arg(vu_field_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
error_string = set_tap_dfilter(this, full_filter.toUtf8().constData());
|
|
|
|
if (error_string) {
|
|
|
|
config_err_ = error_string->str;
|
|
|
|
g_string_free(error_string, TRUE);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (filter_.compare(filter) && visible_) {
|
|
|
|
emit requestRetap();
|
|
|
|
}
|
|
|
|
filter_ = filter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::applyCurrentColor()
|
|
|
|
{
|
|
|
|
if (graph_) {
|
|
|
|
graph_->setPen(QPen(color_, graph_line_width_));
|
|
|
|
} else if (bars_) {
|
|
|
|
bars_->setPen(QPen(QBrush(colors_[0]), graph_line_width_)); // ...or omit it altogether?
|
|
|
|
bars_->setBrush(color_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setVisible(bool visible)
|
|
|
|
{
|
|
|
|
bool old_visibility = visible_;
|
|
|
|
visible_ = visible;
|
|
|
|
if (graph_) {
|
|
|
|
graph_->setVisible(visible_);
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
bars_->setVisible(visible_);
|
|
|
|
}
|
|
|
|
if (old_visibility != visible_) {
|
|
|
|
emit requestReplot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setName(const QString &name)
|
|
|
|
{
|
|
|
|
name_ = name;
|
|
|
|
if (graph_) {
|
|
|
|
graph_->setName(name_);
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
bars_->setName(name_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QRgb IOGraph::color()
|
|
|
|
{
|
|
|
|
return color_.color().rgb();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setColor(const QRgb color)
|
|
|
|
{
|
|
|
|
color_ = QBrush(color);
|
|
|
|
applyCurrentColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setPlotStyle(int style)
|
|
|
|
{
|
|
|
|
// Switch plottable if needed
|
|
|
|
switch (style) {
|
|
|
|
case psBar:
|
|
|
|
case psStackedBar:
|
|
|
|
if (graph_) {
|
|
|
|
bars_ = new QCPBars(parent_->xAxis, parent_->yAxis);
|
|
|
|
parent_->addPlottable(bars_);
|
|
|
|
parent_->removeGraph(graph_);
|
|
|
|
graph_ = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (bars_) {
|
|
|
|
graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
|
|
|
|
parent_->removePlottable(bars_);
|
|
|
|
bars_ = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
setValueUnits(val_units_);
|
|
|
|
|
|
|
|
if (graph_) {
|
|
|
|
graph_->setLineStyle(QCPGraph::lsNone);
|
|
|
|
graph_->setScatterStyle(QCPScatterStyle::ssNone);
|
|
|
|
}
|
|
|
|
switch (style) {
|
|
|
|
case psLine:
|
2014-04-10 20:53:11 +00:00
|
|
|
if (graph_) {
|
|
|
|
graph_->setLineStyle(QCPGraph::lsLine);
|
|
|
|
}
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
break;
|
|
|
|
case psImpulse:
|
2014-04-10 20:53:11 +00:00
|
|
|
if (graph_) {
|
|
|
|
graph_->setLineStyle(QCPGraph::lsImpulse);
|
|
|
|
}
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
break;
|
|
|
|
case psDot:
|
2014-04-10 20:53:11 +00:00
|
|
|
if (graph_) {
|
|
|
|
graph_->setScatterStyle(QCPScatterStyle::ssDisc);
|
|
|
|
}
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
break;
|
|
|
|
case psSquare:
|
2014-04-10 20:53:11 +00:00
|
|
|
if (graph_) {
|
|
|
|
graph_->setScatterStyle(QCPScatterStyle::ssSquare);
|
|
|
|
}
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
break;
|
|
|
|
case psDiamond:
|
2014-04-10 20:53:11 +00:00
|
|
|
if (graph_) {
|
|
|
|
graph_->setScatterStyle(QCPScatterStyle::ssDiamond);
|
|
|
|
}
|
Add a Qt I/O Graph dialog.
For each graph you can set:
- Its visibility
- A name
- A display filter
- Color, from a fixed list
- Plot style: Line, Impulse, Bar, Stacked Bar, Dot, Square, Diamond
- Basic Y Axes (packets/s, bytes/s, bits/s)
- Computed Y Axes (SUM, MIN, AVG, MAX)
- Smoothing
You can pan and zoom using the mouse and keyboard. Clicking on a graph
selects the last packet for that interval. If all graphs have the same Y
axis a single label is shown, otherwise a legend is shown.
The time scale (X axis) can be toggled between relative seconds and the
time of day.
Graphs can be saved as PDF, PNG, BMP, and JPEG. Settings are "sticky"
via the io_graphs UAT.
To do:
- Minimize graph drawing delays.
- Figure out why smoothing differs from GTK+
- Everything else at the top of io_graph_dialog.cpp
- Fix empty resets.
A fair amount of code was copied from TCPStreamDialog. We might want to
subclass QCustomPlot and place the shared code there.
Move common syntax checking to SyntaxLineEdit.
Move some common code from ui/gtk/io_stat.c to ui/io_graph_item.[ch] and
use it in both GTK+ and Qt.
Make the io_graph_item_t array allocation in io_stat.c static. The
behavior should be identical and this gives us additional compile-time
checks.
Change-Id: I9a3d544469b7048f0761fdbf7bcf20f44ae76577
Reviewed-on: https://code.wireshark.org/review/435
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Tested-by: Gerald Combs <gerald@wireshark.org>
2014-02-12 00:07:10 +00:00
|
|
|
break;
|
|
|
|
case psBar:
|
|
|
|
case IOGraph::psStackedBar:
|
|
|
|
// Stacking set in scanGraphs
|
|
|
|
bars_->moveBelow(NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
setName(name_);
|
|
|
|
applyCurrentColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString IOGraph::valueUnitLabel()
|
|
|
|
{
|
|
|
|
if (val_units_ >= IOG_ITEM_UNIT_FIRST && val_units_ <= IOG_ITEM_UNIT_LAST) {
|
|
|
|
return value_unit_to_name_[val_units_];
|
|
|
|
}
|
|
|
|
return tr("Unknown");
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setValueUnits(int val_units)
|
|
|
|
{
|
|
|
|
if (val_units >= IOG_ITEM_UNIT_FIRST && val_units <= IOG_ITEM_UNIT_LAST) {
|
|
|
|
int old_val_units = val_units_;
|
|
|
|
val_units_ = (io_graph_item_unit_t)val_units;
|
|
|
|
|
|
|
|
if (old_val_units != val_units) {
|
|
|
|
setFilter(filter_); // Check config & prime vu field
|
|
|
|
if (val_units < IOG_ITEM_UNIT_CALC_SUM) {
|
|
|
|
emit requestRecalc();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setValueUnitField(const QString &vu_field)
|
|
|
|
{
|
|
|
|
int old_hf_index = hf_index_;
|
|
|
|
|
|
|
|
vu_field_ = vu_field.trimmed();
|
|
|
|
hf_index_ = -1;
|
|
|
|
|
|
|
|
header_field_info *hfi = proto_registrar_get_byname(vu_field_.toUtf8().constData());
|
|
|
|
if (hfi) {
|
|
|
|
hf_index_ = hfi->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (old_hf_index != hf_index_) {
|
|
|
|
setFilter(filter_); // Check config & prime vu field
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IOGraph::addToLegend()
|
|
|
|
{
|
|
|
|
if (graph_) {
|
|
|
|
return graph_->addToLegend();
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
return bars_->addToLegend();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
double IOGraph::startOffset()
|
|
|
|
{
|
|
|
|
if (graph_ && graph_->keyAxis()->tickLabelType() == QCPAxis::ltDateTime && graph_->data()->size() > 0) {
|
|
|
|
return graph_->data()->keys()[0];
|
|
|
|
}
|
|
|
|
if (bars_ && bars_->keyAxis()->tickLabelType() == QCPAxis::ltDateTime && bars_->data()->size() > 0) {
|
|
|
|
return bars_->data()->keys()[0];
|
|
|
|
}
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int IOGraph::packetFromTime(double ts)
|
|
|
|
{
|
|
|
|
int idx = ts * 1000 / interval_;
|
|
|
|
if (idx >= 0 && idx < (int) cur_idx_) {
|
|
|
|
return items_[idx].last_frame_in_invl;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::clearAllData()
|
|
|
|
{
|
|
|
|
cur_idx_ = -1;
|
|
|
|
reset_io_graph_items(items_, max_io_items_);
|
|
|
|
if (graph_) {
|
|
|
|
graph_->clearData();
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
bars_->clearData();
|
|
|
|
}
|
|
|
|
start_time_ = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<io_graph_item_unit_t, QString> IOGraph::valueUnitsToNames()
|
|
|
|
{
|
|
|
|
QMap<io_graph_item_unit_t, QString> vuton;
|
|
|
|
|
|
|
|
vuton[IOG_ITEM_UNIT_PACKETS] = QObject::tr("Packets/s");
|
|
|
|
vuton[IOG_ITEM_UNIT_BYTES] = QObject::tr("Bytes/s");
|
|
|
|
vuton[IOG_ITEM_UNIT_BITS] = QObject::tr("Bits/s");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_SUM] = QObject::tr("SUM(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_FRAMES] = QObject::tr("COUNT FRAMES(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_FIELDS] = QObject::tr("COUNT FIELDS(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_MAX] = QObject::tr("MAX(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_MIN] = QObject::tr("MIN(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_AVERAGE] = QObject::tr("AVG(Y Field)");
|
|
|
|
vuton[IOG_ITEM_UNIT_CALC_LOAD] = QObject::tr("LOAD(Y Field)");
|
|
|
|
|
|
|
|
return vuton;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<IOGraph::PlotStyles, QString> IOGraph::plotStylesToNames()
|
|
|
|
{
|
|
|
|
QMap<IOGraph::PlotStyles, QString> pston;
|
|
|
|
|
|
|
|
pston[psLine] = QObject::tr("Line");
|
|
|
|
pston[psImpulse] = QObject::tr("Impulse");
|
|
|
|
pston[psBar] = QObject::tr("Bar");
|
|
|
|
pston[psStackedBar] = QObject::tr("Stacked Bar");
|
|
|
|
pston[psDot] = QObject::tr("Dot");
|
|
|
|
pston[psSquare] = QObject::tr("Square");
|
|
|
|
pston[psDiamond] = QObject::tr("Diamond");
|
|
|
|
|
|
|
|
return pston;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<int, QString> IOGraph::movingAveragesToNames()
|
|
|
|
{
|
|
|
|
QMap<int, QString> maton;
|
|
|
|
QList<int> averages = QList<int>()
|
|
|
|
/* << 8 */ << 10 /* << 16 */ << 20 << 50 << 100 << 200 << 500 << 1000; // Arbitrarily chosen
|
|
|
|
|
|
|
|
maton[0] = QObject::tr("None");
|
|
|
|
foreach (int avg, averages) {
|
|
|
|
maton[avg] = QString(QObject::tr("%1 interval SMA")).arg(avg);
|
|
|
|
}
|
|
|
|
|
|
|
|
return maton;
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::recalcGraphData(capture_file *cap_file)
|
|
|
|
{
|
|
|
|
/* Moving average variables */
|
|
|
|
unsigned int mavg_in_average_count = 0, mavg_left = 0, mavg_right = 0;
|
|
|
|
unsigned int mavg_to_remove = 0, mavg_to_add = 0;
|
|
|
|
double mavg_cumulated = 0;
|
|
|
|
QCPAxis *x_axis = NULL;
|
|
|
|
|
|
|
|
if (graph_) {
|
|
|
|
graph_->clearData();
|
|
|
|
x_axis = graph_->keyAxis();
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
bars_->clearData();
|
|
|
|
x_axis = bars_->keyAxis();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (moving_avg_period_ > 0 && cur_idx_ >= 0) {
|
|
|
|
/* "Warm-up phase" - calculate average on some data not displayed;
|
|
|
|
* just to make sure average on leftmost and rightmost displayed
|
|
|
|
* values is as reliable as possible
|
|
|
|
*/
|
|
|
|
guint64 warmup_interval = 0;
|
|
|
|
|
|
|
|
// for (; warmup_interval < first_interval; warmup_interval += interval_) {
|
|
|
|
// mavg_cumulated += get_it_value(io, i, (int)warmup_interval/interval_);
|
|
|
|
// mavg_in_average_count++;
|
|
|
|
// mavg_left++;
|
|
|
|
// }
|
|
|
|
mavg_cumulated += getItemValue((int)warmup_interval/interval_, cap_file);
|
|
|
|
mavg_in_average_count++;
|
|
|
|
for (warmup_interval = interval_;
|
|
|
|
((warmup_interval < (0 + (moving_avg_period_ / 2) * (guint64)interval_)) &&
|
|
|
|
(warmup_interval <= (cur_idx_ * (guint64)interval_)));
|
|
|
|
warmup_interval += interval_) {
|
|
|
|
|
|
|
|
mavg_cumulated += getItemValue((int)warmup_interval / interval_, cap_file);
|
|
|
|
mavg_in_average_count++;
|
|
|
|
mavg_right++;
|
|
|
|
}
|
|
|
|
mavg_to_add = warmup_interval;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < cur_idx_; i++) {
|
|
|
|
double ts = (double) i * interval_ / 1000;
|
|
|
|
if (x_axis && x_axis->tickLabelType() == QCPAxis::ltDateTime) {
|
|
|
|
ts += start_time_;
|
|
|
|
}
|
|
|
|
double val = getItemValue(i, cap_file);
|
|
|
|
|
|
|
|
if (moving_avg_period_ > 0) {
|
|
|
|
if (i != 0) {
|
|
|
|
mavg_left++;
|
|
|
|
if (mavg_left > moving_avg_period_ / 2) {
|
|
|
|
mavg_left--;
|
|
|
|
mavg_in_average_count--;
|
|
|
|
mavg_cumulated -= getItemValue((int)mavg_to_remove / interval_, cap_file);
|
|
|
|
mavg_to_remove += interval_;
|
|
|
|
}
|
|
|
|
if (mavg_to_add <= (unsigned int) cur_idx_ * interval_) {
|
|
|
|
mavg_in_average_count++;
|
|
|
|
mavg_cumulated += getItemValue((int)mavg_to_add / interval_, cap_file);
|
|
|
|
mavg_to_add += interval_;
|
|
|
|
} else {
|
|
|
|
mavg_right--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mavg_in_average_count > 0) {
|
|
|
|
val = mavg_cumulated / mavg_in_average_count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (graph_) {
|
|
|
|
graph_->addData(ts, val);
|
|
|
|
}
|
|
|
|
if (bars_) {
|
|
|
|
bars_->addData(ts, val);
|
|
|
|
}
|
|
|
|
// qDebug() << "=rgd i" << i << ts << val;
|
|
|
|
}
|
|
|
|
// qDebug() << "=rgd" << num_items_ << hf_index_;
|
|
|
|
emit requestReplot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOGraph::setInterval(int interval)
|
|
|
|
{
|
|
|
|
interval_ = interval;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the value at the given interval (idx) for the current value unit.
|
|
|
|
// Adapted from get_it_value in gtk/io_stat.c.
|
|
|
|
double IOGraph::getItemValue(int idx, capture_file *cap_file)
|
|
|
|
{
|
|
|
|
double value = 0; /* FIXME: loss of precision, visible on the graph for small values */
|
|
|
|
int adv_type;
|
|
|
|
io_graph_item_t *item;
|
|
|
|
guint32 interval;
|
|
|
|
|
|
|
|
g_assert(idx < max_io_items_);
|
|
|
|
|
|
|
|
item = &items_[idx];
|
|
|
|
|
|
|
|
// Basic units
|
|
|
|
switch (val_units_) {
|
|
|
|
case IOG_ITEM_UNIT_PACKETS:
|
|
|
|
return item->frames;
|
|
|
|
case IOG_ITEM_UNIT_BYTES:
|
|
|
|
return item->bytes;
|
|
|
|
case IOG_ITEM_UNIT_BITS:
|
|
|
|
return (item->bytes * 8);
|
|
|
|
case IOG_ITEM_UNIT_CALC_FRAMES:
|
|
|
|
return item->frames;
|
|
|
|
case IOG_ITEM_UNIT_CALC_FIELDS:
|
|
|
|
return item->fields;
|
|
|
|
default:
|
|
|
|
/* If it's COUNT_TYPE_ADVANCED but not one of the
|
|
|
|
* generic ones we'll get it when we switch on the
|
|
|
|
* adv_type below. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hf_index_ < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// Advanced units
|
|
|
|
adv_type = proto_registrar_get_ftype(hf_index_);
|
|
|
|
switch (adv_type) {
|
|
|
|
case FT_UINT8:
|
|
|
|
case FT_UINT16:
|
|
|
|
case FT_UINT24:
|
|
|
|
case FT_UINT32:
|
|
|
|
case FT_UINT64:
|
|
|
|
case FT_INT8:
|
|
|
|
case FT_INT16:
|
|
|
|
case FT_INT24:
|
|
|
|
case FT_INT32:
|
|
|
|
case FT_INT64:
|
|
|
|
switch (val_units_) {
|
|
|
|
case IOG_ITEM_UNIT_CALC_SUM:
|
|
|
|
value = item->int_tot;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MAX:
|
|
|
|
value = item->int_max;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MIN:
|
|
|
|
value = item->int_min;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_AVERAGE:
|
|
|
|
if (item->fields) {
|
|
|
|
value = (double)item->int_tot / item->fields;
|
|
|
|
} else {
|
|
|
|
value = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FT_FLOAT:
|
|
|
|
switch (val_units_) {
|
|
|
|
case IOG_ITEM_UNIT_CALC_SUM:
|
|
|
|
value = (guint64)item->float_tot;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MAX:
|
|
|
|
value = (guint64)item->float_max;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MIN:
|
|
|
|
value = (guint64)item->float_min;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_AVERAGE:
|
|
|
|
if (item->fields) {
|
|
|
|
value = (guint64)item->float_tot / item->fields;
|
|
|
|
} else {
|
|
|
|
value = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FT_DOUBLE:
|
|
|
|
switch (val_units_) {
|
|
|
|
case IOG_ITEM_UNIT_CALC_SUM:
|
|
|
|
value = (guint64)item->double_tot;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MAX:
|
|
|
|
value = (guint64)item->double_max;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MIN:
|
|
|
|
value = (guint64)item->double_min;
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_AVERAGE:
|
|
|
|
if (item->fields) {
|
|
|
|
value = (guint64)item->double_tot / item->fields;
|
|
|
|
} else {
|
|
|
|
value = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FT_RELATIVE_TIME:
|
|
|
|
switch (val_units_) {
|
|
|
|
case IOG_ITEM_UNIT_CALC_MAX:
|
|
|
|
value = (guint64) (item->time_max.secs*1000000 + item->time_max.nsecs/1000);
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_MIN:
|
|
|
|
value = (guint64) (item->time_min.secs*1000000 + item->time_min.nsecs/1000);
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_SUM:
|
|
|
|
value = (guint64) (item->time_tot.secs*1000000 + item->time_tot.nsecs/1000);
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_AVERAGE:
|
|
|
|
if (item->fields) {
|
|
|
|
guint64 t; /* time in us */
|
|
|
|
|
|
|
|
t = item->time_tot.secs;
|
|
|
|
t = t*1000000+item->time_tot.nsecs/1000;
|
|
|
|
value = (guint64) (t/item->fields);
|
|
|
|
} else {
|
|
|
|
value = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case IOG_ITEM_UNIT_CALC_LOAD:
|
|
|
|
if (idx == (int)cur_idx_ && cap_file) {
|
|
|
|
interval = (guint32)((cap_file->elapsed_time.secs*1000) +
|
|
|
|
((cap_file->elapsed_time.nsecs+500000)/1000000));
|
|
|
|
interval -= (interval_ * idx);
|
|
|
|
} else {
|
|
|
|
interval = interval_;
|
|
|
|
}
|
|
|
|
value = (guint64) ((item->time_tot.secs*1000000 + item->time_tot.nsecs/1000) / interval);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// "tap_reset" callback for register_tap_listener
|
|
|
|
void IOGraph::tapReset(void *iog_ptr)
|
|
|
|
{
|
|
|
|
IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
|
|
|
|
if (!iog) return;
|
|
|
|
|
|
|
|
// qDebug() << "=tapReset" << iog->name_;
|
|
|
|
iog->clearAllData();
|
|
|
|
}
|
|
|
|
|
|
|
|
// "tap_packet" callback for register_tap_listener
|
|
|
|
gboolean IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *data)
|
|
|
|
{
|
|
|
|
Q_UNUSED(data);
|
|
|
|
IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
|
|
|
|
if (!pinfo || !iog) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int idx = get_io_graph_index(pinfo, iog->interval_);
|
|
|
|
bool recalc = false;
|
|
|
|
|
|
|
|
/* some sanity checks */
|
|
|
|
if ((idx < 0) || (idx >= max_io_items_)) {
|
|
|
|
iog->cur_idx_ = max_io_items_ - 1;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update num_items */
|
|
|
|
if (idx > iog->cur_idx_) {
|
|
|
|
iog->cur_idx_ = (guint32) idx;
|
|
|
|
recalc = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set start time */
|
|
|
|
if (iog->start_time_ == 0.0) {
|
|
|
|
nstime_t start_nstime;
|
|
|
|
nstime_set_zero(&start_nstime);
|
|
|
|
nstime_delta(&start_nstime, &pinfo->fd->abs_ts, &pinfo->rel_ts);
|
|
|
|
iog->start_time_ = nstime_to_sec(&start_nstime);
|
|
|
|
}
|
|
|
|
|
|
|
|
epan_dissect_t *adv_edt = NULL;
|
|
|
|
/* For ADVANCED mode we need to keep track of some more stuff than just frame and byte counts */
|
|
|
|
if (iog->val_units_ >= IOG_ITEM_UNIT_CALC_SUM) {
|
|
|
|
adv_edt = edt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!update_io_graph_item(iog->items_, idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// qDebug() << "=tapPacket" << iog->name_ << idx << iog->hf_index_ << iog->val_units_ << iog->num_items_;
|
|
|
|
|
|
|
|
if (recalc) {
|
|
|
|
emit iog->requestRecalc();
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// "tap_draw" callback for register_tap_listener
|
|
|
|
void IOGraph::tapDraw(void *iog_ptr)
|
|
|
|
{
|
|
|
|
IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
|
|
|
|
if (!iog) return;
|
|
|
|
emit iog->requestRecalc();
|
|
|
|
|
|
|
|
if (iog->graph_) {
|
|
|
|
// qDebug() << "=tapDraw g" << iog->name_ << iog->graph_->data()->keys().size();
|
|
|
|
}
|
|
|
|
if (iog->bars_) {
|
|
|
|
// qDebug() << "=tapDraw b" << iog->name_ << iog->bars_->data()->keys().size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Editor modelines
|
|
|
|
*
|
|
|
|
* Local Variables:
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* tab-width: 8
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
* End:
|
|
|
|
*
|
|
|
|
* ex: set shiftwidth=4 tabstop=8 expandtab:
|
|
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
|
|
*/
|