1 /* io_graph_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "io_graph_dialog.h"
11 #include <ui_io_graph_dialog.h>
12 
13 #include "file.h"
14 
15 #include <epan/stat_tap_ui.h>
16 #include "epan/stats_tree_priv.h"
17 #include "epan/uat-int.h"
18 
19 #include <wsutil/utf8_entities.h>
20 #include <wsutil/ws_assert.h>
21 
22 #include <ui/qt/utils/qt_ui_utils.h>
23 
24 #include <ui/qt/utils/variant_pointer.h>
25 
26 #include <ui/qt/utils/color_utils.h>
27 #include <ui/qt/widgets/qcustomplot.h>
28 #include "progress_frame.h"
29 #include "wireshark_application.h"
30 #include <wsutil/report_message.h>
31 
32 #include <ui/qt/utils/tango_colors.h> //provides some default colors
33 #include <ui/qt/widgets/copy_from_profile_button.h>
34 #include "ui/qt/widgets/wireshark_file_dialog.h"
35 
36 #include <QClipboard>
37 #include <QFontMetrics>
38 #include <QFrame>
39 #include <QHBoxLayout>
40 #include <QLineEdit>
41 #include <QMessageBox>
42 #include <QPushButton>
43 #include <QRubberBand>
44 #include <QSpacerItem>
45 #include <QTimer>
46 #include <QVariant>
47 
48 // Bugs and uncertainties:
49 // - Regular (non-stacked) bar graphs are drawn on top of each other on the Z axis.
50 //   The QCP forum suggests drawing them side by side:
51 //   https://www.qcustomplot.com/index.php/support/forum/62
52 // - We retap and redraw more than we should.
53 // - Smoothing doesn't seem to match GTK+
54 // - Closing the color picker on macOS sends the dialog to the background.
55 // - The color picker triggers https://bugreports.qt.io/browse/QTBUG-58699.
56 
57 // To do:
58 // - Use scroll bars?
59 // - Scroll during live captures
60 // - Set ticks per pixel (e.g. pressing "2" sets 2 tpp).
61 // - Explicitly handle missing values, e.g. via NAN.
62 // - Add a "show missing" or "show zero" option to the UAT?
63 //   It would add yet another graph configuration column.
64 
65 const qreal graph_line_width_ = 1.0;
66 
67 const int DEFAULT_MOVING_AVERAGE = 0;
68 const int DEFAULT_Y_AXIS_FACTOR = 1;
69 
70 // Don't accidentally zoom into a 1x1 rect if you happen to click on the graph
71 // in zoom mode.
72 const int min_zoom_pixels_ = 20;
73 
74 const int stat_update_interval_ = 200; // ms
75 
76 // Saved graph settings
77 typedef struct _io_graph_settings_t {
78     gboolean enabled;
79     char* name;
80     char* dfilter;
81     guint color;
82     guint32 style;
83     guint32 yaxis;
84     char* yfield;
85     guint32 sma_period;
86     guint32 y_axis_factor;
87 } io_graph_settings_t;
88 
89 static const value_string graph_style_vs[] = {
90     { IOGraph::psLine, "Line" },
91     { IOGraph::psImpulse, "Impulse" },
92     { IOGraph::psBar, "Bar" },
93     { IOGraph::psStackedBar, "Stacked Bar" },
94     { IOGraph::psDot, "Dot" },
95     { IOGraph::psSquare, "Square" },
96     { IOGraph::psDiamond, "Diamond" },
97     { IOGraph::psCross, "Cross" },
98     { IOGraph::psCircle, "Circle" },
99     { IOGraph::psPlus, "Plus" },
100     { 0, NULL }
101 };
102 
103 static const value_string y_axis_vs[] = {
104     { IOG_ITEM_UNIT_PACKETS, "Packets" },
105     { IOG_ITEM_UNIT_BYTES, "Bytes" },
106     { IOG_ITEM_UNIT_BITS, "Bits" },
107     { IOG_ITEM_UNIT_CALC_SUM, "SUM(Y Field)" },
108     { IOG_ITEM_UNIT_CALC_FRAMES, "COUNT FRAMES(Y Field)" },
109     { IOG_ITEM_UNIT_CALC_FIELDS, "COUNT FIELDS(Y Field)" },
110     { IOG_ITEM_UNIT_CALC_MAX, "MAX(Y Field)" },
111     { IOG_ITEM_UNIT_CALC_MIN, "MIN(Y Field)" },
112     { IOG_ITEM_UNIT_CALC_AVERAGE, "AVG(Y Field)" },
113     { IOG_ITEM_UNIT_CALC_LOAD, "LOAD(Y Field)" },
114     { 0, NULL }
115 };
116 
117 static const value_string moving_avg_vs[] = {
118     { 0, "None" },
119     { 10, "10 interval SMA" },
120     { 20, "20 interval SMA" },
121     { 50, "50 interval SMA" },
122     { 100, "100 interval SMA" },
123     { 200, "200 interval SMA" },
124     { 500, "500 interval SMA" },
125     { 1000, "1000 interval SMA" },
126     { 0, NULL }
127 };
128 
129 static io_graph_settings_t *iog_settings_ = NULL;
130 static guint num_io_graphs_ = 0;
131 static uat_t *iog_uat_ = NULL;
132 
133 // y_axis_factor was added in 3.6. Provide backward compatibility.
134 static const char *iog_uat_defaults_[] = {
135     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "1"
136 };
137 
138 extern "C" {
139 
140 //Allow the enable/disable field to be a checkbox, but for backwards compatibility,
141 //the strings have to be "Enabled"/"Disabled", not "TRUE"/"FALSE"
142 #define UAT_BOOL_ENABLE_CB_DEF(basename,field_name,rec_t) \
143 static void basename ## _ ## field_name ## _set_cb(void* rec, const char* buf, guint len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
144     char* tmp_str = g_strndup(buf,len); \
145     if ((g_strcmp0(tmp_str, "Enabled") == 0) || \
146         (g_strcmp0(tmp_str, "TRUE") == 0)) \
147         ((rec_t*)rec)->field_name = 1; \
148     else \
149         ((rec_t*)rec)->field_name = 0; \
150     g_free(tmp_str); } \
151 static void basename ## _ ## field_name ## _tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* UNUSED_PARAMETER(u1), const void* UNUSED_PARAMETER(u2)) {\
152     *out_ptr = g_strdup_printf("%s",((rec_t*)rec)->field_name ? "Enabled" : "Disabled"); \
153     *out_len = (unsigned)strlen(*out_ptr); }
154 
uat_fld_chk_enable(void * u1 _U_,const char * strptr,guint len,const void * u2 _U_,const void * u3 _U_,char ** err)155 static gboolean uat_fld_chk_enable(void* u1 _U_, const char* strptr, guint len, const void* u2 _U_, const void* u3 _U_, char** err)
156 {
157     char* str = g_strndup(strptr,len);
158 
159     if ((g_strcmp0(str, "Enabled") == 0) ||
160         (g_strcmp0(str, "Disabled") == 0) ||
161         (g_strcmp0(str, "TRUE") == 0) ||    //just for UAT functionality
162         (g_strcmp0(str, "FALSE") == 0)) {
163         *err = NULL;
164         g_free(str);
165         return TRUE;
166     }
167 
168     //User should never see this unless they are manually modifying UAT
169     *err = g_strdup_printf("invalid value: %s (must be Enabled or Disabled)", str);
170     g_free(str);
171     return FALSE;
172 }
173 
174 #define UAT_FLD_BOOL_ENABLE(basename,field_name,title,desc) \
175 {#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}
176 
177 //"Custom" handler for sma_period enumeration for backwards compatibility
io_graph_sma_period_set_cb(void * rec,const char * buf,guint len,const void * vs,const void * u2 _U_)178 static void io_graph_sma_period_set_cb(void* rec, const char* buf, guint len, const void* vs, const void* u2 _U_)
179 {
180     guint i;
181     char* str = g_strndup(buf,len);
182     const char* cstr;
183     ((io_graph_settings_t*)rec)->sma_period = 0;
184 
185     //Original UAT had just raw numbers and not enumerated values with "interval SMA"
186     if (strstr(str, "interval SMA") == NULL) {
187         if (strcmp(str, "None") == 0) {    //Valid enumerated value
188         } else if (strcmp(str, "0") == 0) {
189             g_free(str);
190             str = g_strdup("None");
191         } else {
192             char *str2 = g_strdup_printf("%s interval SMA", str);
193             g_free(str);
194             str = str2;
195         }
196     }
197 
198     for (i=0; (cstr = ((const value_string*)vs)[i].strptr) ;i++) {
199         if (g_str_equal(cstr,str)) {
200             ((io_graph_settings_t*)rec)->sma_period = (guint32)((const value_string*)vs)[i].value;
201             g_free(str);
202             return;
203         }
204     }
205     g_free(str);
206 }
207 //Duplicated because macro covers both functions
io_graph_sma_period_tostr_cb(void * rec,char ** out_ptr,unsigned * out_len,const void * vs,const void * u2 _U_)208 static void io_graph_sma_period_tostr_cb(void* rec, char** out_ptr, unsigned* out_len, const void* vs, const void* u2 _U_)
209 {
210     guint i;
211     for (i=0;((const value_string*)vs)[i].strptr;i++) {
212         if (((const value_string*)vs)[i].value == ((io_graph_settings_t*)rec)->sma_period) {
213             *out_ptr = g_strdup(((const value_string*)vs)[i].strptr);
214             *out_len = (unsigned)strlen(*out_ptr);
215             return;
216         }
217     }
218     *out_ptr = g_strdup("None");
219     *out_len = (unsigned)strlen("None");
220 }
221 
sma_period_chk_enum(void * u1 _U_,const char * strptr,guint len,const void * v,const void * u3 _U_,char ** err)222 static gboolean sma_period_chk_enum(void* u1 _U_, const char* strptr, guint len, const void* v, const void* u3 _U_, char** err) {
223     char *str = g_strndup(strptr,len);
224     guint i;
225     const value_string* vs = (const value_string *)v;
226 
227     //Original UAT had just raw numbers and not enumerated values with "interval SMA"
228     if (strstr(str, "interval SMA") == NULL) {
229         if (strcmp(str, "None") == 0) {    //Valid enumerated value
230         } else if (strcmp(str, "0") == 0) {
231             g_free(str);
232             str = g_strdup("None");
233         } else {
234             char *str2 = g_strdup_printf("%s interval SMA", str);
235             g_free(str);
236             str = str2;
237         }
238     }
239 
240     for (i=0;vs[i].strptr;i++) {
241         if (g_strcmp0(vs[i].strptr,str) == 0) {
242             *err = NULL;
243             g_free(str);
244             return TRUE;
245         }
246     }
247 
248     *err = g_strdup_printf("invalid value: %s",str);
249     g_free(str);
250     return FALSE;
251 }
252 
253 #define UAT_FLD_SMA_PERIOD(basename,field_name,title,enum,desc) \
254     {#field_name, title, PT_TXTMOD_ENUM,{sma_period_chk_enum,basename ## _ ## field_name ## _set_cb,basename ## _ ## field_name ## _tostr_cb},{&(enum),&(enum),&(enum)},&(enum),desc,FLDFILL}
255 
256 
257 UAT_BOOL_ENABLE_CB_DEF(io_graph, enabled, io_graph_settings_t)
258 UAT_CSTRING_CB_DEF(io_graph, name, io_graph_settings_t)
259 UAT_DISPLAY_FILTER_CB_DEF(io_graph, dfilter, io_graph_settings_t)
260 UAT_COLOR_CB_DEF(io_graph, color, io_graph_settings_t)
261 UAT_VS_DEF(io_graph, style, io_graph_settings_t, guint32, 0, "Line")
262 UAT_VS_DEF(io_graph, yaxis, io_graph_settings_t, guint32, 0, "Packets")
263 UAT_PROTO_FIELD_CB_DEF(io_graph, yfield, io_graph_settings_t)
264 UAT_DEC_CB_DEF(io_graph, y_axis_factor, io_graph_settings_t)
265 
266 static uat_field_t io_graph_fields[] = {
267     UAT_FLD_BOOL_ENABLE(io_graph, enabled, "Enabled", "Graph visibility"),
268     UAT_FLD_CSTRING(io_graph, name, "Graph Name", "The name of the graph"),
269     UAT_FLD_DISPLAY_FILTER(io_graph, dfilter, "Display Filter", "Graph packets matching this display filter"),
270     UAT_FLD_COLOR(io_graph, color, "Color", "Graph color (#RRGGBB)"),
271     UAT_FLD_VS(io_graph, style, "Style", graph_style_vs, "Graph style (Line, Bars, etc.)"),
272     UAT_FLD_VS(io_graph, yaxis, "Y Axis", y_axis_vs, "Y Axis units"),
273     UAT_FLD_PROTO_FIELD(io_graph, yfield, "Y Field", "Apply calculations to this field"),
274     UAT_FLD_SMA_PERIOD(io_graph, sma_period, "SMA Period", moving_avg_vs, "Simple moving average period"),
275     UAT_FLD_DEC(io_graph, y_axis_factor, "Y Axis Factor", "Y Axis Factor"),
276 
277     UAT_END_FIELDS
278 };
279 
io_graph_copy_cb(void * dst_ptr,const void * src_ptr,size_t)280 static void* io_graph_copy_cb(void* dst_ptr, const void* src_ptr, size_t) {
281     io_graph_settings_t* dst = (io_graph_settings_t *)dst_ptr;
282     const io_graph_settings_t* src = (const io_graph_settings_t *)src_ptr;
283 
284     dst->enabled = src->enabled;
285     dst->name = g_strdup(src->name);
286     dst->dfilter = g_strdup(src->dfilter);
287     dst->color = src->color;
288     dst->style = src->style;
289     dst->yaxis = src->yaxis;
290     dst->yfield = g_strdup(src->yfield);
291     dst->sma_period = src->sma_period;
292     dst->y_axis_factor = src->y_axis_factor;
293 
294     return dst;
295 }
296 
io_graph_free_cb(void * p)297 static void io_graph_free_cb(void* p) {
298     io_graph_settings_t *iogs = (io_graph_settings_t *)p;
299     g_free(iogs->name);
300     g_free(iogs->dfilter);
301     g_free(iogs->yfield);
302 }
303 
304 } // extern "C"
305 
IOGraphDialog(QWidget & parent,CaptureFile & cf,QString displayFilter)306 IOGraphDialog::IOGraphDialog(QWidget &parent, CaptureFile &cf, QString displayFilter) :
307     WiresharkDialog(parent, cf),
308     ui(new Ui::IOGraphDialog),
309     uat_model_(nullptr),
310     uat_delegate_(nullptr),
311     base_graph_(nullptr),
312     tracer_(nullptr),
313     start_time_(0.0),
314     mouse_drags_(true),
315     rubber_band_(nullptr),
316     stat_timer_(nullptr),
317     need_replot_(false),
318     need_retap_(false),
319     auto_axes_(true),
320     number_ticker_(new QCPAxisTicker),
321     datetime_ticker_(new QCPAxisTickerDateTime)
322 {
323     ui->setupUi(this);
324     loadGeometry();
325 
326     setWindowSubtitle(tr("I/O Graphs"));
327     setAttribute(Qt::WA_DeleteOnClose, true);
328     QCustomPlot *iop = ui->ioPlot;
329 
330     ui->newToolButton->setStockIcon("list-add");
331     ui->deleteToolButton->setStockIcon("list-remove");
332     ui->copyToolButton->setStockIcon("list-copy");
333     ui->clearToolButton->setStockIcon("list-clear");
334 
335 #ifdef Q_OS_MAC
336     ui->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
337     ui->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
338     ui->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
339     ui->clearToolButton->setAttribute(Qt::WA_MacSmallSize, true);
340 #endif
341 
342     QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
343     save_bt->setText(tr("Save As…"));
344 
345     QPushButton *copy_bt = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
346     connect (copy_bt, SIGNAL(clicked()), this, SLOT(copyAsCsvClicked()));
347 
348     CopyFromProfileButton * copy_button = new CopyFromProfileButton(this, "io_graphs", tr("Copy graphs from another profile."));
349     ui->buttonBox->addButton(copy_button, QDialogButtonBox::ActionRole);
350     connect(copy_button, &CopyFromProfileButton::copyProfile, this, &IOGraphDialog::copyFromProfile);
351 
352     QPushButton *close_bt = ui->buttonBox->button(QDialogButtonBox::Close);
353     if (close_bt) {
354         close_bt->setDefault(true);
355     }
356 
357     ui->automaticUpdateCheckBox->setChecked(prefs.gui_io_graph_automatic_update ? true : false);
358 
359     stat_timer_ = new QTimer(this);
360     connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
361     stat_timer_->start(stat_update_interval_);
362 
363     // Intervals (ms)
364     ui->intervalComboBox->addItem(tr("1 ms"),        1);
365     ui->intervalComboBox->addItem(tr("2 ms"),        2);
366     ui->intervalComboBox->addItem(tr("5 ms"),        5);
367     ui->intervalComboBox->addItem(tr("10 ms"),      10);
368     ui->intervalComboBox->addItem(tr("20 ms"),      20);
369     ui->intervalComboBox->addItem(tr("50 ms"),      50);
370     ui->intervalComboBox->addItem(tr("100 ms"),    100);
371     ui->intervalComboBox->addItem(tr("200 ms"),    200);
372     ui->intervalComboBox->addItem(tr("500 ms"),    500);
373     ui->intervalComboBox->addItem(tr("1 sec"),    1000);
374     ui->intervalComboBox->addItem(tr("2 sec"),    2000);
375     ui->intervalComboBox->addItem(tr("5 sec"),    5000);
376     ui->intervalComboBox->addItem(tr("10 sec"),  10000);
377     ui->intervalComboBox->addItem(tr("1 min"),   60000);
378     ui->intervalComboBox->addItem(tr("10 min"), 600000);
379     ui->intervalComboBox->setCurrentIndex(9);
380 
381     ui->todCheckBox->setChecked(false);
382     iop->xAxis->setTicker(number_ticker_);
383 
384     ui->dragRadioButton->setChecked(mouse_drags_);
385 
386     ctx_menu_.addAction(ui->actionZoomIn);
387     ctx_menu_.addAction(ui->actionZoomInX);
388     ctx_menu_.addAction(ui->actionZoomInY);
389     ctx_menu_.addAction(ui->actionZoomOut);
390     ctx_menu_.addAction(ui->actionZoomOutX);
391     ctx_menu_.addAction(ui->actionZoomOutY);
392     ctx_menu_.addAction(ui->actionReset);
393     ctx_menu_.addSeparator();
394     ctx_menu_.addAction(ui->actionMoveRight10);
395     ctx_menu_.addAction(ui->actionMoveLeft10);
396     ctx_menu_.addAction(ui->actionMoveUp10);
397     ctx_menu_.addAction(ui->actionMoveDown10);
398     ctx_menu_.addAction(ui->actionMoveRight1);
399     ctx_menu_.addAction(ui->actionMoveLeft1);
400     ctx_menu_.addAction(ui->actionMoveUp1);
401     ctx_menu_.addAction(ui->actionMoveDown1);
402     ctx_menu_.addSeparator();
403     ctx_menu_.addAction(ui->actionGoToPacket);
404     ctx_menu_.addSeparator();
405     ctx_menu_.addAction(ui->actionDragZoom);
406     ctx_menu_.addAction(ui->actionToggleTimeOrigin);
407     ctx_menu_.addAction(ui->actionCrosshairs);
408     set_action_shortcuts_visible_in_context_menu(ctx_menu_.actions());
409 
410     iop->xAxis->setLabel(tr("Time (s)"));
411 
412     iop->setMouseTracking(true);
413     iop->setEnabled(true);
414 
415     QCPTextElement *title = new QCPTextElement(iop);
416     iop->plotLayout()->insertRow(0);
417     iop->plotLayout()->addElement(0, 0, title);
418     title->setText(tr("Wireshark I/O Graphs: %1").arg(cap_file_.fileDisplayName()));
419 
420     tracer_ = new QCPItemTracer(iop);
421 
422     loadProfileGraphs();
423     bool filterExists = false;
424     if (uat_model_->rowCount() > 0) {
425         for (int i = 0; i < uat_model_->rowCount(); i++) {
426             createIOGraph(i);
427             if (ioGraphs_.at(i)->filter().compare(displayFilter) == 0)
428                 filterExists = true;
429         }
430         if (! filterExists && displayFilter.length() > 0)
431             addGraph(true, tr("Filtered packets"), displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
432                 IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
433     } else {
434         addDefaultGraph(true, 0);
435         addDefaultGraph(true, 1);
436         if (displayFilter.length() > 0)
437             addGraph(true, tr("Filtered packets"), displayFilter, ColorUtils::graphColor(uat_model_->rowCount()),
438                 IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
439     }
440 
441     toggleTracerStyle(true);
442     iop->setFocus();
443 
444     iop->rescaleAxes();
445 
446     ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
447 
448     ui->splitter->setStretchFactor(0, 95);
449     ui->splitter->setStretchFactor(1, 5);
450 
451     //XXX - resize columns?
452 
453     ProgressFrame::addToButtonBox(ui->buttonBox, &parent);
454 
455     connect(iop, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(graphClicked(QMouseEvent*)));
456     connect(iop, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMoved(QMouseEvent*)));
457     connect(iop, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseReleased(QMouseEvent*)));
458     disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
459 }
460 
~IOGraphDialog()461 IOGraphDialog::~IOGraphDialog()
462 {
463     cap_file_.stopLoading();
464     foreach(IOGraph* iog, ioGraphs_) {
465         delete iog;
466     }
467     delete ui;
468     ui = NULL;
469 }
470 
copyFromProfile(QString filename)471 void IOGraphDialog::copyFromProfile(QString filename)
472 {
473     guint orig_data_len = iog_uat_->raw_data->len;
474 
475     gchar *err = NULL;
476     if (uat_load(iog_uat_, filename.toUtf8().constData(), &err)) {
477         iog_uat_->changed = TRUE;
478         uat_model_->reloadUat();
479         for (guint i = orig_data_len; i < iog_uat_->raw_data->len; i++) {
480             createIOGraph(i);
481         }
482     } else {
483         report_failure("Error while loading %s: %s", iog_uat_->name, err);
484         g_free(err);
485     }
486 }
487 
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)488 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)
489 {
490 
491     QVariantList newRowData;
492     newRowData.append(checked ? Qt::Checked : Qt::Unchecked);
493     newRowData.append(name);
494     newRowData.append(dfilter);
495     newRowData.append(QColor(color_idx));
496     newRowData.append(val_to_str_const(style, graph_style_vs, "None"));
497     newRowData.append(val_to_str_const(value_units, y_axis_vs, "Packets"));
498     newRowData.append(yfield);
499     newRowData.append(val_to_str_const((guint32) moving_average, moving_avg_vs, "None"));
500     newRowData.append(y_axis_factor);
501 
502     QModelIndex newIndex = uat_model_->appendEntry(newRowData);
503     if ( !newIndex.isValid() )
504     {
505         qDebug() << "Failed to add a new record";
506         return;
507     }
508     ui->graphUat->setCurrentIndex(newIndex);
509     createIOGraph(newIndex.row());
510 }
511 
addGraph(bool copy_from_current)512 void IOGraphDialog::addGraph(bool copy_from_current)
513 {
514     const QModelIndex &current = ui->graphUat->currentIndex();
515     if (copy_from_current && !current.isValid())
516         return;
517 
518     QModelIndex copyIdx;
519 
520     if (copy_from_current) {
521         copyIdx = uat_model_->copyRow(current);
522         if (!copyIdx.isValid())
523         {
524             qDebug() << "Failed to add a new record";
525             return;
526         }
527         createIOGraph(copyIdx.row());
528 
529         ui->graphUat->setCurrentIndex(copyIdx);
530     } else {
531         addDefaultGraph(false);
532         copyIdx = uat_model_->index(uat_model_->rowCount() - 1, 0);
533     }
534 
535     ui->graphUat->setCurrentIndex(copyIdx);
536 }
537 
createIOGraph(int currentRow)538 void IOGraphDialog::createIOGraph(int currentRow)
539 {
540     // XXX - Should IOGraph have it's own list that has to sync with UAT?
541     ioGraphs_.append(new IOGraph(ui->ioPlot));
542     IOGraph* iog = ioGraphs_[currentRow];
543 
544     connect(this, SIGNAL(recalcGraphData(capture_file *, bool)), iog, SLOT(recalcGraphData(capture_file *, bool)));
545     connect(this, SIGNAL(reloadValueUnitFields()), iog, SLOT(reloadValueUnitField()));
546     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
547             iog, SLOT(captureEvent(CaptureEvent)));
548     connect(iog, SIGNAL(requestRetap()), this, SLOT(scheduleRetap()));
549     connect(iog, SIGNAL(requestRecalc()), this, SLOT(scheduleRecalc()));
550     connect(iog, SIGNAL(requestReplot()), this, SLOT(scheduleReplot()));
551 
552     syncGraphSettings(currentRow);
553     if (iog->visible()) {
554         scheduleRetap();
555     }
556 }
557 
addDefaultGraph(bool enabled,int idx)558 void IOGraphDialog::addDefaultGraph(bool enabled, int idx)
559 {
560     switch (idx % 2) {
561     case 0:
562         addGraph(enabled, tr("All Packets"), QString(), ColorUtils::graphColor(idx),
563                  IOGraph::psLine, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
564         break;
565     default:
566         addGraph(enabled, tr("TCP Errors"), "tcp.analysis.flags", ColorUtils::graphColor(4), // 4 = red
567                  IOGraph::psBar, IOG_ITEM_UNIT_PACKETS, QString(), DEFAULT_MOVING_AVERAGE, DEFAULT_Y_AXIS_FACTOR);
568         break;
569     }
570 }
571 
572 // Sync the settings from UAT model to its IOGraph.
573 // Disables the graph if any errors are found.
574 //
575 // NOTE: Setting dfilter, yaxis and yfield here will all end up in setFilter() and this
576 //       has a chicken-and-egg problem because setFilter() depends on previous assigned
577 //       values for filter_, val_units_ and vu_field_.  Setting values in wrong order
578 //       may give unpredicted results because setFilter() does not always set filter_
579 //       on errors.
580 // TODO: The issues in the above note should be fixed and setFilter() should not be
581 //       called so frequently.
582 
syncGraphSettings(int row)583 void IOGraphDialog::syncGraphSettings(int row)
584 {
585     IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
586 
587     if (!uat_model_->index(row, colEnabled).isValid() || !iog)
588         return;
589 
590     bool visible = graphIsEnabled(row);
591     bool retap = !iog->visible() && visible;
592     QString data_str;
593 
594     iog->setName(uat_model_->data(uat_model_->index(row, colName)).toString());
595     iog->setFilter(uat_model_->data(uat_model_->index(row, colDFilter)).toString());
596 
597     /* plot style depend on the value unit, so set it first. */
598     data_str = uat_model_->data(uat_model_->index(row, colYAxis)).toString();
599     iog->setValueUnits((int) str_to_val(qUtf8Printable(data_str), y_axis_vs, IOG_ITEM_UNIT_PACKETS));
600     iog->setValueUnitField(uat_model_->data(uat_model_->index(row, colYField)).toString());
601 
602     iog->setColor(uat_model_->data(uat_model_->index(row, colColor), Qt::DecorationRole).value<QColor>().rgb());
603     data_str = uat_model_->data(uat_model_->index(row, colStyle)).toString();
604     iog->setPlotStyle((int) str_to_val(qUtf8Printable(data_str), graph_style_vs, 0));
605 
606     data_str = uat_model_->data(uat_model_->index(row, colSMAPeriod)).toString();
607     iog->moving_avg_period_ = str_to_val(qUtf8Printable(data_str), moving_avg_vs, 0);
608 
609     iog->y_axis_factor_ = uat_model_->data(uat_model_->index(row, colYAxisFactor)).toInt();
610 
611     iog->setInterval(ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt());
612 
613     if (!iog->configError().isEmpty()) {
614         hint_err_ = iog->configError();
615         visible = false;
616         retap = false;
617     }
618 
619     iog->setVisible(visible);
620 
621     getGraphInfo();
622     mouseMoved(NULL); // Update hint
623     updateLegend();
624 
625     if (visible) {
626         if (retap) {
627             scheduleRetap();
628         } else {
629             scheduleReplot();
630         }
631     }
632 }
633 
updateWidgets()634 void IOGraphDialog::updateWidgets()
635 {
636     WiresharkDialog::updateWidgets();
637 }
638 
scheduleReplot(bool now)639 void IOGraphDialog::scheduleReplot(bool now)
640 {
641     need_replot_ = true;
642     if (now) updateStatistics();
643     // A plot finished, force an update of the legend now in case a time unit
644     // was involved (which might append "(ms)" to the label).
645     updateLegend();
646 }
647 
scheduleRecalc(bool now)648 void IOGraphDialog::scheduleRecalc(bool now)
649 {
650     need_recalc_ = true;
651     if (now) updateStatistics();
652 }
653 
scheduleRetap(bool now)654 void IOGraphDialog::scheduleRetap(bool now)
655 {
656     need_retap_ = true;
657     if (now) updateStatistics();
658 }
659 
reloadFields()660 void IOGraphDialog::reloadFields()
661 {
662     emit reloadValueUnitFields();
663 }
664 
keyPressEvent(QKeyEvent * event)665 void IOGraphDialog::keyPressEvent(QKeyEvent *event)
666 {
667     int pan_pixels = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
668 
669     switch(event->key()) {
670     case Qt::Key_Minus:
671     case Qt::Key_Underscore:    // Shifted minus on U.S. keyboards
672     case Qt::Key_O:             // GTK+
673     case Qt::Key_R:
674         zoomAxes(false);
675         break;
676     case Qt::Key_Plus:
677     case Qt::Key_Equal:         // Unshifted plus on U.S. keyboards
678     case Qt::Key_I:             // GTK+
679         zoomAxes(true);
680         break;
681     case Qt::Key_X:             // Zoom X axis only
682         if (event->modifiers() & Qt::ShiftModifier) {
683             zoomXAxis(false);   // upper case X -> Zoom out
684         } else {
685             zoomXAxis(true);    // lower case x -> Zoom in
686         }
687         break;
688     case Qt::Key_Y:             // Zoom Y axis only
689         if (event->modifiers() & Qt::ShiftModifier) {
690             zoomYAxis(false);   // upper case Y -> Zoom out
691         } else {
692             zoomYAxis(true);    // lower case y -> Zoom in
693         }
694         break;
695     case Qt::Key_Right:
696     case Qt::Key_L:
697         panAxes(pan_pixels, 0);
698         break;
699     case Qt::Key_Left:
700     case Qt::Key_H:
701         panAxes(-1 * pan_pixels, 0);
702         break;
703     case Qt::Key_Up:
704     case Qt::Key_K:
705         panAxes(0, pan_pixels);
706         break;
707     case Qt::Key_Down:
708     case Qt::Key_J:
709         panAxes(0, -1 * pan_pixels);
710         break;
711 
712     case Qt::Key_Space:
713         toggleTracerStyle();
714         break;
715 
716     case Qt::Key_0:
717     case Qt::Key_ParenRight:    // Shifted 0 on U.S. keyboards
718     case Qt::Key_Home:
719         resetAxes();
720         break;
721 
722     case Qt::Key_G:
723         on_actionGoToPacket_triggered();
724         break;
725     case Qt::Key_T:
726         on_actionToggleTimeOrigin_triggered();
727         break;
728     case Qt::Key_Z:
729         on_actionDragZoom_triggered();
730         break;
731     }
732 
733     QDialog::keyPressEvent(event);
734 }
735 
reject()736 void IOGraphDialog::reject()
737 {
738     if (!uat_model_)
739         return;
740 
741     // Changes to the I/O Graphs settings are always saved,
742     // there is no possibility for "rejection".
743     QString error;
744     if (uat_model_->applyChanges(error)) {
745         if (!error.isEmpty()) {
746             report_failure("%s", qPrintable(error));
747         }
748     }
749 
750     QDialog::reject();
751 }
752 
zoomAxes(bool in)753 void IOGraphDialog::zoomAxes(bool in)
754 {
755     QCustomPlot *iop = ui->ioPlot;
756     double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
757     double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
758 
759     auto_axes_ = false;
760 
761     if (!in) {
762         h_factor = pow(h_factor, -1);
763         v_factor = pow(v_factor, -1);
764     }
765 
766     iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
767     iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
768     iop->replot();
769 }
770 
zoomXAxis(bool in)771 void IOGraphDialog::zoomXAxis(bool in)
772 {
773     QCustomPlot *iop = ui->ioPlot;
774     double h_factor = iop->axisRect()->rangeZoomFactor(Qt::Horizontal);
775 
776     auto_axes_ = false;
777 
778     if (!in) {
779         h_factor = pow(h_factor, -1);
780     }
781 
782     iop->xAxis->scaleRange(h_factor, iop->xAxis->range().center());
783     iop->replot();
784 }
785 
zoomYAxis(bool in)786 void IOGraphDialog::zoomYAxis(bool in)
787 {
788     QCustomPlot *iop = ui->ioPlot;
789     double v_factor = iop->axisRect()->rangeZoomFactor(Qt::Vertical);
790 
791     auto_axes_ = false;
792 
793     if (!in) {
794         v_factor = pow(v_factor, -1);
795     }
796 
797     iop->yAxis->scaleRange(v_factor, iop->yAxis->range().center());
798     iop->replot();
799 }
800 
panAxes(int x_pixels,int y_pixels)801 void IOGraphDialog::panAxes(int x_pixels, int y_pixels)
802 {
803     QCustomPlot *iop = ui->ioPlot;
804     double h_pan = 0.0;
805     double v_pan = 0.0;
806 
807     auto_axes_ = false;
808 
809     h_pan = iop->xAxis->range().size() * x_pixels / iop->xAxis->axisRect()->width();
810     v_pan = iop->yAxis->range().size() * y_pixels / iop->yAxis->axisRect()->height();
811     // The GTK+ version won't pan unless we're zoomed. Should we do the same here?
812     if (h_pan) {
813         iop->xAxis->moveRange(h_pan);
814         iop->replot();
815     }
816     if (v_pan) {
817         iop->yAxis->moveRange(v_pan);
818         iop->replot();
819     }
820 }
821 
822 
toggleTracerStyle(bool force_default)823 void IOGraphDialog::toggleTracerStyle(bool force_default)
824 {
825     if (!tracer_->visible() && !force_default) return;
826     if (!ui->ioPlot->graph(0)) return;
827 
828     QPen sp_pen = ui->ioPlot->graph(0)->pen();
829     QCPItemTracer::TracerStyle tstyle = QCPItemTracer::tsCrosshair;
830     QPen tr_pen = QPen(tracer_->pen());
831     QColor tr_color = sp_pen.color();
832 
833     if (force_default || tracer_->style() != QCPItemTracer::tsCircle) {
834         tstyle = QCPItemTracer::tsCircle;
835         tr_color.setAlphaF(1.0);
836         tr_pen.setWidthF(1.5);
837     } else {
838         tr_color.setAlphaF(0.5);
839         tr_pen.setWidthF(1.0);
840     }
841 
842     tracer_->setStyle(tstyle);
843     tr_pen.setColor(tr_color);
844     tracer_->setPen(tr_pen);
845     ui->ioPlot->replot();
846 }
847 
848 // Returns the IOGraph which is most likely to be used by the user. This is the
849 // currently selected, visible graph or the first visible graph otherwise.
currentActiveGraph() const850 IOGraph *IOGraphDialog::currentActiveGraph() const
851 {
852     QModelIndex index = ui->graphUat->currentIndex();
853     if (index.isValid()) {
854         return ioGraphs_.value(index.row(), NULL);
855     }
856 
857     //if no currently selected item, go with first item enabled
858     for (int row = 0; row < uat_model_->rowCount(); row++)
859     {
860         if (graphIsEnabled(row)) {
861             return ioGraphs_.value(row, NULL);
862         }
863     }
864 
865     return NULL;
866 }
867 
graphIsEnabled(int row) const868 bool IOGraphDialog::graphIsEnabled(int row) const
869 {
870     Qt::CheckState state = static_cast<Qt::CheckState>(uat_model_->data(uat_model_->index(row, colEnabled), Qt::CheckStateRole).toInt());
871     return state == Qt::Checked;
872 }
873 
874 // Scan through our graphs and gather information.
875 // QCPItemTracers can only be associated with QCPGraphs. Find the first one
876 // and associate it with our tracer. Set bar stacking order while we're here.
getGraphInfo()877 void IOGraphDialog::getGraphInfo()
878 {
879     base_graph_ = NULL;
880     QCPBars *prev_bars = NULL;
881     start_time_ = 0.0;
882 
883     tracer_->setGraph(NULL);
884     IOGraph *selectedGraph = currentActiveGraph();
885 
886     if (uat_model_ != NULL) {
887         //all graphs may not be created yet, so bounds check the graph array
888         for (int row = 0; row < uat_model_->rowCount(); row++) {
889             IOGraph* iog = ioGraphs_.value(row, Q_NULLPTR);
890             if (iog && graphIsEnabled(row)) {
891                 QCPGraph *graph = iog->graph();
892                 QCPBars *bars = iog->bars();
893                 if (graph && (!base_graph_ || iog == selectedGraph)) {
894                     base_graph_ = graph;
895                 } else if (bars &&
896                            (uat_model_->data(uat_model_->index(row, colStyle), Qt::DisplayRole).toString().compare(graph_style_vs[IOGraph::psStackedBar].strptr) == 0) &&
897                            iog->visible()) {
898                     bars->moveBelow(NULL); // Remove from existing stack
899                     bars->moveBelow(prev_bars);
900                     prev_bars = bars;
901                 }
902                 if (iog->visible() && iog->maxInterval() >= 0) {
903                     double iog_start = iog->startOffset();
904                     if (start_time_ == 0.0 || iog_start < start_time_) {
905                         start_time_ = iog_start;
906                     }
907                 }
908 
909             }
910         }
911     }
912     if (base_graph_ && base_graph_->data()->size() > 0) {
913         tracer_->setGraph(base_graph_);
914         tracer_->setVisible(true);
915     }
916 }
917 
updateLegend()918 void IOGraphDialog::updateLegend()
919 {
920     QCustomPlot *iop = ui->ioPlot;
921     QSet<QString> vu_label_set;
922     QString intervalText = ui->intervalComboBox->itemText(ui->intervalComboBox->currentIndex());
923 
924     iop->legend->setVisible(false);
925     iop->yAxis->setLabel(QString());
926 
927     // Find unique labels
928     if (uat_model_ != NULL) {
929         for (int row = 0; row < uat_model_->rowCount(); row++) {
930             IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
931             if (graphIsEnabled(row) && iog) {
932                 QString label(iog->valueUnitLabel());
933                 if (!iog->scaledValueUnit().isEmpty()) {
934                     label += " (" + iog->scaledValueUnit() + ")";
935                 }
936                 vu_label_set.insert(label);
937             }
938         }
939     }
940 
941     // Nothing.
942     if (vu_label_set.size() < 1) {
943         return;
944     }
945 
946     // All the same. Use the Y Axis label.
947     if (vu_label_set.size() == 1) {
948         iop->yAxis->setLabel(vu_label_set.values()[0] + "/" + intervalText);
949         return;
950     }
951 
952     // Differing labels. Create a legend with a Title label at top.
953     // Legend Title thanks to: https://www.qcustomplot.com/index.php/support/forum/443
954     QCPTextElement* legendTitle = qobject_cast<QCPTextElement*>(iop->legend->elementAt(0));
955     if (legendTitle == NULL) {
956         legendTitle = new QCPTextElement(iop, QString(""));
957         iop->legend->insertRow(0);
958         iop->legend->addElement(0, 0, legendTitle);
959     }
960     legendTitle->setText(QString(intervalText + " Intervals "));
961 
962     if (uat_model_ != NULL) {
963         for (int row = 0; row < uat_model_->rowCount(); row++) {
964             IOGraph *iog = ioGraphs_.value(row, Q_NULLPTR);
965             if (iog) {
966                 if (graphIsEnabled(row)) {
967                     iog->addToLegend();
968                 } else {
969                     iog->removeFromLegend();
970                 }
971             }
972         }
973     }
974     iop->legend->setVisible(true);
975 }
976 
getZoomRanges(QRect zoom_rect)977 QRectF IOGraphDialog::getZoomRanges(QRect zoom_rect)
978 {
979     QRectF zoom_ranges = QRectF();
980 
981     if (zoom_rect.width() < min_zoom_pixels_ && zoom_rect.height() < min_zoom_pixels_) {
982         return zoom_ranges;
983     }
984 
985     QCustomPlot *iop = ui->ioPlot;
986     QRect zr = zoom_rect.normalized();
987     QRect ar = iop->axisRect()->rect();
988     if (ar.intersects(zr)) {
989         QRect zsr = ar.intersected(zr);
990         zoom_ranges.setX(iop->xAxis->range().lower
991                          + iop->xAxis->range().size() * (zsr.left() - ar.left()) / ar.width());
992         zoom_ranges.setWidth(iop->xAxis->range().size() * zsr.width() / ar.width());
993 
994         // QRects grow down
995         zoom_ranges.setY(iop->yAxis->range().lower
996                          + iop->yAxis->range().size() * (ar.bottom() - zsr.bottom()) / ar.height());
997         zoom_ranges.setHeight(iop->yAxis->range().size() * zsr.height() / ar.height());
998     }
999     return zoom_ranges;
1000 }
1001 
graphClicked(QMouseEvent * event)1002 void IOGraphDialog::graphClicked(QMouseEvent *event)
1003 {
1004     QCustomPlot *iop = ui->ioPlot;
1005 
1006     if (event->button() == Qt::RightButton) {
1007         // XXX We should find some way to get ioPlot to handle a
1008         // contextMenuEvent instead.
1009         ctx_menu_.exec(event->globalPos());
1010     } else  if (mouse_drags_) {
1011         if (iop->axisRect()->rect().contains(event->pos())) {
1012             iop->setCursor(QCursor(Qt::ClosedHandCursor));
1013         }
1014         on_actionGoToPacket_triggered();
1015     } else {
1016         if (!rubber_band_) {
1017             rubber_band_ = new QRubberBand(QRubberBand::Rectangle, iop);
1018         }
1019         rb_origin_ = event->pos();
1020         rubber_band_->setGeometry(QRect(rb_origin_, QSize()));
1021         rubber_band_->show();
1022     }
1023     iop->setFocus();
1024 }
1025 
mouseMoved(QMouseEvent * event)1026 void IOGraphDialog::mouseMoved(QMouseEvent *event)
1027 {
1028     QCustomPlot *iop = ui->ioPlot;
1029     QString hint;
1030     Qt::CursorShape shape = Qt::ArrowCursor;
1031 
1032     if (!hint_err_.isEmpty()) {
1033         hint += QString("<b>%1</b> ").arg(hint_err_);
1034     }
1035     if (event) {
1036         if (event->buttons().testFlag(Qt::LeftButton)) {
1037             if (mouse_drags_) {
1038                 shape = Qt::ClosedHandCursor;
1039             } else {
1040                 shape = Qt::CrossCursor;
1041             }
1042         } else if (iop->axisRect()->rect().contains(event->pos())) {
1043             if (mouse_drags_) {
1044                 shape = Qt::OpenHandCursor;
1045             } else {
1046                 shape = Qt::CrossCursor;
1047             }
1048         }
1049         iop->setCursor(QCursor(shape));
1050     }
1051 
1052     if (mouse_drags_) {
1053         double ts = 0;
1054         packet_num_ = 0;
1055         int interval_packet = -1;
1056 
1057         if (event && tracer_->graph()) {
1058             tracer_->setGraphKey(iop->xAxis->pixelToCoord(event->pos().x()));
1059             ts = tracer_->position->key();
1060             if (IOGraph *iog = currentActiveGraph()) {
1061                 interval_packet = iog->packetFromTime(ts);
1062             }
1063         }
1064 
1065         if (interval_packet < 0) {
1066             hint += tr("Hover over the graph for details.");
1067         } else {
1068             QString msg = tr("No packets in interval");
1069             QString val;
1070             if (interval_packet > 0) {
1071                 packet_num_ = (guint32) interval_packet;
1072                 msg = QString("%1 %2")
1073                         .arg(!file_closed_ ? tr("Click to select packet") : tr("Packet"))
1074                         .arg(packet_num_);
1075                 val = " = " + QString::number(tracer_->position->value(), 'g', 4);
1076             }
1077             hint += tr("%1 (%2s%3).")
1078                     .arg(msg)
1079                     .arg(QString::number(ts, 'g', 4))
1080                     .arg(val);
1081         }
1082         iop->replot();
1083     } else {
1084         if (event && rubber_band_ && rubber_band_->isVisible()) {
1085             rubber_band_->setGeometry(QRect(rb_origin_, event->pos()).normalized());
1086             QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1087             if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1088                 hint += tr("Release to zoom, x = %1 to %2, y = %3 to %4")
1089                         .arg(zoom_ranges.x())
1090                         .arg(zoom_ranges.x() + zoom_ranges.width())
1091                         .arg(zoom_ranges.y())
1092                         .arg(zoom_ranges.y() + zoom_ranges.height());
1093             } else {
1094                 hint += tr("Unable to select range.");
1095             }
1096         } else {
1097             hint += tr("Click to select a portion of the graph.");
1098         }
1099     }
1100 
1101     hint.prepend("<small><i>");
1102     hint.append("</i></small>");
1103     ui->hintLabel->setText(hint);
1104 }
1105 
mouseReleased(QMouseEvent * event)1106 void IOGraphDialog::mouseReleased(QMouseEvent *event)
1107 {
1108     QCustomPlot *iop = ui->ioPlot;
1109     auto_axes_ = false;
1110     if (rubber_band_) {
1111         rubber_band_->hide();
1112         if (!mouse_drags_) {
1113             QRectF zoom_ranges = getZoomRanges(QRect(rb_origin_, event->pos()));
1114             if (zoom_ranges.width() > 0.0 && zoom_ranges.height() > 0.0) {
1115                 iop->xAxis->setRangeLower(zoom_ranges.x());
1116                 iop->xAxis->setRangeUpper(zoom_ranges.x() + zoom_ranges.width());
1117                 iop->yAxis->setRangeLower(zoom_ranges.y());
1118                 iop->yAxis->setRangeUpper(zoom_ranges.y() + zoom_ranges.height());
1119                 iop->replot();
1120             }
1121         }
1122     } else if (iop->cursor().shape() == Qt::ClosedHandCursor) {
1123         iop->setCursor(QCursor(Qt::OpenHandCursor));
1124     }
1125 }
1126 
resetAxes()1127 void IOGraphDialog::resetAxes()
1128 {
1129     QCustomPlot *iop = ui->ioPlot;
1130     QCPRange x_range = iop->xAxis->scaleType() == QCPAxis::stLogarithmic ?
1131                 iop->xAxis->range().sanitizedForLogScale() : iop->xAxis->range();
1132 
1133     double pixel_pad = 10.0; // per side
1134 
1135     iop->rescaleAxes(true);
1136 
1137     double axis_pixels = iop->xAxis->axisRect()->width();
1138     iop->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
1139 
1140     axis_pixels = iop->yAxis->axisRect()->height();
1141     iop->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, iop->yAxis->range().center());
1142 
1143     auto_axes_ = true;
1144     iop->replot();
1145 }
1146 
updateStatistics()1147 void IOGraphDialog::updateStatistics()
1148 {
1149     if (!isVisible()) return;
1150 
1151     if (need_retap_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
1152         need_retap_ = false;
1153         cap_file_.retapPackets();
1154         // The user might have closed the window while tapping, which means
1155         // we might no longer exist.
1156     } else {
1157         if (need_recalc_ && !file_closed_ && prefs.gui_io_graph_automatic_update) {
1158             need_recalc_ = false;
1159             need_replot_ = true;
1160             int enabled_graphs = 0;
1161 
1162             if (uat_model_ != NULL) {
1163                 for (int row = 0; row < uat_model_->rowCount(); row++) {
1164                     if (graphIsEnabled(row)) {
1165                         ++enabled_graphs;
1166                     }
1167                 }
1168             }
1169             // With multiple visible graphs, disable Y scaling to avoid
1170             // multiple, distinct units.
1171             emit recalcGraphData(cap_file_.capFile(), enabled_graphs == 1);
1172             if (!tracer_->graph()) {
1173                 if (base_graph_ && base_graph_->data()->size() > 0) {
1174                     tracer_->setGraph(base_graph_);
1175                     tracer_->setVisible(true);
1176                 } else {
1177                     tracer_->setVisible(false);
1178                 }
1179             }
1180         }
1181         if (need_replot_) {
1182             need_replot_ = false;
1183             if (auto_axes_) {
1184                 resetAxes();
1185             }
1186             ui->ioPlot->replot();
1187         }
1188     }
1189 }
1190 
loadProfileGraphs()1191 void IOGraphDialog::loadProfileGraphs()
1192 {
1193     if (iog_uat_ == NULL) {
1194 
1195         iog_uat_ = uat_new("I/O Graphs",
1196                            sizeof(io_graph_settings_t),
1197                            "io_graphs",
1198                            TRUE,
1199                            &iog_settings_,
1200                            &num_io_graphs_,
1201                            0, /* doesn't affect anything that requires a GUI update */
1202                            "ChStatIOGraphs",
1203                            io_graph_copy_cb,
1204                            NULL,
1205                            io_graph_free_cb,
1206                            NULL,
1207                            NULL,
1208                            io_graph_fields);
1209 
1210         uat_set_default_values(iog_uat_, iog_uat_defaults_);
1211 
1212         char* err = NULL;
1213         if (!uat_load(iog_uat_, NULL, &err)) {
1214             report_failure("Error while loading %s: %s.  Default graph values will be used", iog_uat_->name, err);
1215             g_free(err);
1216             uat_clear(iog_uat_);
1217         }
1218     }
1219 
1220     uat_model_ = new UatModel(NULL, iog_uat_);
1221     uat_delegate_ = new UatDelegate;
1222     ui->graphUat->setModel(uat_model_);
1223     ui->graphUat->setItemDelegate(uat_delegate_);
1224 
1225     connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1226             this, SLOT(modelDataChanged(QModelIndex)));
1227     connect(uat_model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
1228 }
1229 
1230 // Slots
1231 
on_intervalComboBox_currentIndexChanged(int)1232 void IOGraphDialog::on_intervalComboBox_currentIndexChanged(int)
1233 {
1234     int interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
1235     bool need_retap = false;
1236 
1237     if (uat_model_ != NULL) {
1238         for (int row = 0; row < uat_model_->rowCount(); row++) {
1239             IOGraph *iog = ioGraphs_.value(row, NULL);
1240             if (iog) {
1241                 iog->setInterval(interval);
1242                 if (iog->visible()) {
1243                     need_retap = true;
1244                 }
1245             }
1246         }
1247     }
1248 
1249     if (need_retap) {
1250         scheduleRetap(true);
1251     }
1252 
1253     updateLegend();
1254 }
1255 
on_todCheckBox_toggled(bool checked)1256 void IOGraphDialog::on_todCheckBox_toggled(bool checked)
1257 {
1258     double orig_start = start_time_;
1259     bool orig_auto = auto_axes_;
1260 
1261     if (checked) {
1262         ui->ioPlot->xAxis->setTicker(datetime_ticker_);
1263     } else {
1264         ui->ioPlot->xAxis->setTicker(number_ticker_);
1265     }
1266     auto_axes_ = false;
1267     scheduleRecalc(true);
1268     auto_axes_ = orig_auto;
1269     getGraphInfo();
1270     ui->ioPlot->xAxis->moveRange(start_time_ - orig_start);
1271     mouseMoved(NULL); // Update hint
1272 }
1273 
modelRowsReset()1274 void IOGraphDialog::modelRowsReset()
1275 {
1276     ui->deleteToolButton->setEnabled(false);
1277     ui->copyToolButton->setEnabled(false);
1278     ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
1279 }
1280 
on_graphUat_currentItemChanged(const QModelIndex & current,const QModelIndex &)1281 void IOGraphDialog::on_graphUat_currentItemChanged(const QModelIndex &current, const QModelIndex&)
1282 {
1283     if (current.isValid()) {
1284         ui->deleteToolButton->setEnabled(true);
1285         ui->copyToolButton->setEnabled(true);
1286         ui->clearToolButton->setEnabled(true);
1287     } else {
1288         ui->deleteToolButton->setEnabled(false);
1289         ui->copyToolButton->setEnabled(false);
1290         ui->clearToolButton->setEnabled(false);
1291     }
1292 }
1293 
modelDataChanged(const QModelIndex & index)1294 void IOGraphDialog::modelDataChanged(const QModelIndex &index)
1295 {
1296     bool recalc = false;
1297 
1298     switch (index.column())
1299     {
1300     case colYAxis:
1301     case colSMAPeriod:
1302         recalc = true;
1303     }
1304 
1305     syncGraphSettings(index.row());
1306 
1307     if (recalc) {
1308         scheduleRecalc(true);
1309     } else {
1310         scheduleReplot(true);
1311     }
1312 }
1313 
on_resetButton_clicked()1314 void IOGraphDialog::on_resetButton_clicked()
1315 {
1316     resetAxes();
1317 }
1318 
on_newToolButton_clicked()1319 void IOGraphDialog::on_newToolButton_clicked()
1320 {
1321     addGraph();
1322 }
1323 
on_deleteToolButton_clicked()1324 void IOGraphDialog::on_deleteToolButton_clicked()
1325 {
1326     const QModelIndex &current = ui->graphUat->currentIndex();
1327     if (uat_model_ && current.isValid()) {
1328         delete ioGraphs_[current.row()];
1329         ioGraphs_.remove(current.row());
1330 
1331         if (!uat_model_->removeRows(current.row(), 1)) {
1332             qDebug() << "Failed to remove row";
1333         }
1334     }
1335 
1336     // We should probably be smarter about this.
1337     hint_err_.clear();
1338     mouseMoved(NULL);
1339 }
1340 
on_copyToolButton_clicked()1341 void IOGraphDialog::on_copyToolButton_clicked()
1342 {
1343     addGraph(true);
1344 }
1345 
on_clearToolButton_clicked()1346 void IOGraphDialog::on_clearToolButton_clicked()
1347 {
1348     if (uat_model_) {
1349         foreach(IOGraph* iog, ioGraphs_) {
1350             delete iog;
1351         }
1352         ioGraphs_.clear();
1353         uat_model_->clearAll();
1354     }
1355 
1356     hint_err_.clear();
1357     mouseMoved(NULL);
1358 }
1359 
on_dragRadioButton_toggled(bool checked)1360 void IOGraphDialog::on_dragRadioButton_toggled(bool checked)
1361 {
1362     if (checked) mouse_drags_ = true;
1363     ui->ioPlot->setInteractions(
1364                 QCP::iRangeDrag |
1365                 QCP::iRangeZoom
1366                 );
1367 }
1368 
on_zoomRadioButton_toggled(bool checked)1369 void IOGraphDialog::on_zoomRadioButton_toggled(bool checked)
1370 {
1371     if (checked) mouse_drags_ = false;
1372     ui->ioPlot->setInteractions(QCP::Interactions());
1373 }
1374 
on_logCheckBox_toggled(bool checked)1375 void IOGraphDialog::on_logCheckBox_toggled(bool checked)
1376 {
1377     QCustomPlot *iop = ui->ioPlot;
1378 
1379     iop->yAxis->setScaleType(checked ? QCPAxis::stLogarithmic : QCPAxis::stLinear);
1380     iop->replot();
1381 }
1382 
on_automaticUpdateCheckBox_toggled(bool checked)1383 void IOGraphDialog::on_automaticUpdateCheckBox_toggled(bool checked)
1384 {
1385     prefs.gui_io_graph_automatic_update = checked ? TRUE : FALSE;
1386 
1387     prefs_main_write();
1388 
1389     if(prefs.gui_io_graph_automatic_update)
1390     {
1391         updateStatistics();
1392     }
1393 }
1394 
on_actionReset_triggered()1395 void IOGraphDialog::on_actionReset_triggered()
1396 {
1397     on_resetButton_clicked();
1398 }
1399 
on_actionZoomIn_triggered()1400 void IOGraphDialog::on_actionZoomIn_triggered()
1401 {
1402     zoomAxes(true);
1403 }
1404 
on_actionZoomInX_triggered()1405 void IOGraphDialog::on_actionZoomInX_triggered()
1406 {
1407     zoomXAxis(true);
1408 }
1409 
on_actionZoomInY_triggered()1410 void IOGraphDialog::on_actionZoomInY_triggered()
1411 {
1412     zoomYAxis(true);
1413 }
1414 
on_actionZoomOut_triggered()1415 void IOGraphDialog::on_actionZoomOut_triggered()
1416 {
1417     zoomAxes(false);
1418 }
1419 
on_actionZoomOutX_triggered()1420 void IOGraphDialog::on_actionZoomOutX_triggered()
1421 {
1422     zoomXAxis(false);
1423 }
1424 
on_actionZoomOutY_triggered()1425 void IOGraphDialog::on_actionZoomOutY_triggered()
1426 {
1427     zoomYAxis(false);
1428 }
1429 
on_actionMoveUp10_triggered()1430 void IOGraphDialog::on_actionMoveUp10_triggered()
1431 {
1432     panAxes(0, 10);
1433 }
1434 
on_actionMoveLeft10_triggered()1435 void IOGraphDialog::on_actionMoveLeft10_triggered()
1436 {
1437     panAxes(-10, 0);
1438 }
1439 
on_actionMoveRight10_triggered()1440 void IOGraphDialog::on_actionMoveRight10_triggered()
1441 {
1442     panAxes(10, 0);
1443 }
1444 
on_actionMoveDown10_triggered()1445 void IOGraphDialog::on_actionMoveDown10_triggered()
1446 {
1447     panAxes(0, -10);
1448 }
1449 
on_actionMoveUp1_triggered()1450 void IOGraphDialog::on_actionMoveUp1_triggered()
1451 {
1452     panAxes(0, 1);
1453 }
1454 
on_actionMoveLeft1_triggered()1455 void IOGraphDialog::on_actionMoveLeft1_triggered()
1456 {
1457     panAxes(-1, 0);
1458 }
1459 
on_actionMoveRight1_triggered()1460 void IOGraphDialog::on_actionMoveRight1_triggered()
1461 {
1462     panAxes(1, 0);
1463 }
1464 
on_actionMoveDown1_triggered()1465 void IOGraphDialog::on_actionMoveDown1_triggered()
1466 {
1467     panAxes(0, -1);
1468 }
1469 
on_actionGoToPacket_triggered()1470 void IOGraphDialog::on_actionGoToPacket_triggered()
1471 {
1472     if (tracer_->visible() && !file_closed_ && packet_num_ > 0) {
1473         emit goToPacket(packet_num_);
1474     }
1475 }
1476 
on_actionDragZoom_triggered()1477 void IOGraphDialog::on_actionDragZoom_triggered()
1478 {
1479     if (mouse_drags_) {
1480         ui->zoomRadioButton->toggle();
1481     } else {
1482         ui->dragRadioButton->toggle();
1483     }
1484 }
1485 
on_actionToggleTimeOrigin_triggered()1486 void IOGraphDialog::on_actionToggleTimeOrigin_triggered()
1487 {
1488 
1489 }
1490 
on_actionCrosshairs_triggered()1491 void IOGraphDialog::on_actionCrosshairs_triggered()
1492 {
1493 
1494 }
1495 
on_buttonBox_helpRequested()1496 void IOGraphDialog::on_buttonBox_helpRequested()
1497 {
1498     wsApp->helpTopicAction(HELP_STATS_IO_GRAPH_DIALOG);
1499 }
1500 
1501 // XXX - We have similar code in tcp_stream_dialog and packet_diagram. Should this be a common routine?
on_buttonBox_accepted()1502 void IOGraphDialog::on_buttonBox_accepted()
1503 {
1504     QString file_name, extension;
1505     QDir path(wsApp->lastOpenDir());
1506     QString pdf_filter = tr("Portable Document Format (*.pdf)");
1507     QString png_filter = tr("Portable Network Graphics (*.png)");
1508     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
1509     // Gaze upon my beautiful graph with lossy artifacts!
1510     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
1511     QString csv_filter = tr("Comma Separated Values (*.csv)");
1512     QString filter = QString("%1;;%2;;%3;;%4;;%5")
1513             .arg(pdf_filter)
1514             .arg(png_filter)
1515             .arg(bmp_filter)
1516             .arg(jpeg_filter)
1517             .arg(csv_filter);
1518 
1519     QString save_file = path.canonicalPath();
1520     if (!file_closed_) {
1521         save_file += QString("/%1").arg(cap_file_.fileBaseName());
1522     }
1523     file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As…")),
1524                                              save_file, filter, &extension);
1525 
1526     if (file_name.length() > 0) {
1527         bool save_ok = false;
1528         if (extension.compare(pdf_filter) == 0) {
1529             save_ok = ui->ioPlot->savePdf(file_name);
1530         } else if (extension.compare(png_filter) == 0) {
1531             save_ok = ui->ioPlot->savePng(file_name);
1532         } else if (extension.compare(bmp_filter) == 0) {
1533             save_ok = ui->ioPlot->saveBmp(file_name);
1534         } else if (extension.compare(jpeg_filter) == 0) {
1535             save_ok = ui->ioPlot->saveJpg(file_name);
1536         } else if (extension.compare(csv_filter) == 0) {
1537             save_ok = saveCsv(file_name);
1538         }
1539         // else error dialog?
1540         if (save_ok) {
1541             wsApp->setLastOpenDirFromFilename(file_name);
1542         }
1543     }
1544 }
1545 
makeCsv(QTextStream & stream) const1546 void IOGraphDialog::makeCsv(QTextStream &stream) const
1547 {
1548     QList<IOGraph *> activeGraphs;
1549 
1550     int ui_interval = ui->intervalComboBox->itemData(ui->intervalComboBox->currentIndex()).toInt();
1551     int max_interval = 0;
1552 
1553     stream << "\"Interval start\"";
1554     if (uat_model_ != NULL) {
1555         for (int row = 0; row < uat_model_->rowCount(); row++) {
1556             if (graphIsEnabled(row) && ioGraphs_[row] != NULL) {
1557                 activeGraphs.append(ioGraphs_[row]);
1558                 if (max_interval < ioGraphs_[row]->maxInterval()) {
1559                     max_interval = ioGraphs_[row]->maxInterval();
1560                 }
1561                 QString name = ioGraphs_[row]->name().toUtf8();
1562                 name = QString("\"%1\"").arg(name.replace("\"", "\"\""));  // RFC 4180
1563                 stream << "," << name;
1564             }
1565         }
1566     }
1567 
1568     stream << '\n';
1569 
1570     for (int interval = 0; interval <= max_interval; interval++) {
1571         double interval_start = (double)interval * ((double)ui_interval / 1000.0);
1572         stream << interval_start;
1573         foreach (IOGraph *iog, activeGraphs) {
1574             double value = 0.0;
1575             if (interval <= iog->maxInterval()) {
1576                 value = iog->getItemValue(interval, cap_file_.capFile());
1577             }
1578             stream << "," << value;
1579         }
1580         stream << '\n';
1581     }
1582 }
1583 
copyAsCsvClicked()1584 void IOGraphDialog::copyAsCsvClicked()
1585 {
1586     QString csv;
1587     QTextStream stream(&csv, QIODevice::Text);
1588     makeCsv(stream);
1589     wsApp->clipboard()->setText(stream.readAll());
1590 }
1591 
saveCsv(const QString & file_name) const1592 bool IOGraphDialog::saveCsv(const QString &file_name) const
1593 {
1594     QFile save_file(file_name);
1595     save_file.open(QFile::WriteOnly | QFile::Text);
1596     QTextStream out(&save_file);
1597     makeCsv(out);
1598 
1599     return true;
1600 }
1601 
1602 // IOGraph
1603 
IOGraph(QCustomPlot * parent)1604 IOGraph::IOGraph(QCustomPlot *parent) :
1605     parent_(parent),
1606     visible_(false),
1607     graph_(NULL),
1608     bars_(NULL),
1609     val_units_(IOG_ITEM_UNIT_FIRST),
1610     hf_index_(-1),
1611     cur_idx_(-1)
1612 {
1613     Q_ASSERT(parent_ != NULL);
1614     graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
1615     Q_ASSERT(graph_ != NULL);
1616 
1617     GString *error_string;
1618     error_string = register_tap_listener("frame",
1619                           this,
1620                           "",
1621                           TL_REQUIRES_PROTO_TREE,
1622                           tapReset,
1623                           tapPacket,
1624                           tapDraw,
1625                           NULL);
1626     if (error_string) {
1627 //        QMessageBox::critical(this, tr("%1 failed to register tap listener").arg(name_),
1628 //                             error_string->str);
1629 //        config_err_ = error_string->str;
1630         g_string_free(error_string, TRUE);
1631     }
1632 }
1633 
~IOGraph()1634 IOGraph::~IOGraph() {
1635     remove_tap_listener(this);
1636     if (graph_) {
1637         parent_->removeGraph(graph_);
1638     }
1639     if (bars_) {
1640         parent_->removePlottable(bars_);
1641     }
1642 }
1643 
1644 // Construct a full filter string from the display filter and value unit / Y axis.
1645 // Check for errors and sets config_err_ if any are found.
setFilter(const QString & filter)1646 void IOGraph::setFilter(const QString &filter)
1647 {
1648     GString *error_string;
1649     QString full_filter(filter.trimmed());
1650 
1651     config_err_.clear();
1652 
1653     // Make sure we have a good display filter
1654     if (!full_filter.isEmpty()) {
1655         dfilter_t *dfilter;
1656         bool status;
1657         gchar *err_msg;
1658         status = dfilter_compile(full_filter.toUtf8().constData(), &dfilter, &err_msg);
1659         dfilter_free(dfilter);
1660         if (!status) {
1661             config_err_ = QString::fromUtf8(err_msg);
1662             g_free(err_msg);
1663             filter_ = full_filter;
1664             return;
1665         }
1666     }
1667 
1668     // Check our value unit + field combo.
1669     error_string = check_field_unit(vu_field_.toUtf8().constData(), NULL, val_units_);
1670     if (error_string) {
1671         config_err_ = error_string->str;
1672         g_string_free(error_string, TRUE);
1673         return;
1674     }
1675 
1676     // Make sure vu_field_ survives edt tree pruning by adding it to our filter
1677     // expression.
1678     if (val_units_ >= IOG_ITEM_UNIT_CALC_SUM && !vu_field_.isEmpty() && hf_index_ >= 0) {
1679         if (full_filter.isEmpty()) {
1680             full_filter = vu_field_;
1681         } else {
1682             full_filter += QString(" && (%1)").arg(vu_field_);
1683         }
1684     }
1685 
1686     error_string = set_tap_dfilter(this, full_filter.toUtf8().constData());
1687     if (error_string) {
1688         config_err_ = error_string->str;
1689         g_string_free(error_string, TRUE);
1690         return;
1691     } else {
1692         if (filter_.compare(filter) && visible_) {
1693             emit requestRetap();
1694         }
1695         filter_ = filter;
1696     }
1697 }
1698 
applyCurrentColor()1699 void IOGraph::applyCurrentColor()
1700 {
1701     if (graph_) {
1702         graph_->setPen(QPen(color_, graph_line_width_));
1703     } else if (bars_) {
1704         bars_->setPen(QPen(QBrush(ColorUtils::graphColor(0)), graph_line_width_)); // ...or omit it altogether?
1705         bars_->setBrush(color_);
1706     }
1707 }
1708 
setVisible(bool visible)1709 void IOGraph::setVisible(bool visible)
1710 {
1711     bool old_visibility = visible_;
1712     visible_ = visible;
1713     if (graph_) {
1714         graph_->setVisible(visible_);
1715     }
1716     if (bars_) {
1717         bars_->setVisible(visible_);
1718     }
1719     if (old_visibility != visible_) {
1720         emit requestReplot();
1721     }
1722 }
1723 
setName(const QString & name)1724 void IOGraph::setName(const QString &name)
1725 {
1726     name_ = name;
1727     if (graph_) {
1728         graph_->setName(name_);
1729     }
1730     if (bars_) {
1731         bars_->setName(name_);
1732     }
1733 }
1734 
color()1735 QRgb IOGraph::color()
1736 {
1737     return color_.color().rgb();
1738 }
1739 
setColor(const QRgb color)1740 void IOGraph::setColor(const QRgb color)
1741 {
1742     color_ = QBrush(color);
1743     applyCurrentColor();
1744 }
1745 
setPlotStyle(int style)1746 void IOGraph::setPlotStyle(int style)
1747 {
1748     // Switch plottable if needed
1749     switch (style) {
1750     case psBar:
1751     case psStackedBar:
1752         if (graph_) {
1753             bars_ = new QCPBars(parent_->xAxis, parent_->yAxis);
1754             parent_->removeGraph(graph_);
1755             graph_ = NULL;
1756         }
1757         break;
1758     default:
1759         if (bars_) {
1760             graph_ = parent_->addGraph(parent_->xAxis, parent_->yAxis);
1761             parent_->removePlottable(bars_);
1762             bars_ = NULL;
1763         }
1764         break;
1765     }
1766     setValueUnits(val_units_);
1767 
1768     if (graph_) {
1769         graph_->setLineStyle(QCPGraph::lsNone);
1770         graph_->setScatterStyle(QCPScatterStyle::ssNone);
1771     }
1772     switch (style) {
1773     case psLine:
1774         if (graph_) {
1775             graph_->setLineStyle(QCPGraph::lsLine);
1776         }
1777         break;
1778     case psImpulse:
1779         if (graph_) {
1780             graph_->setLineStyle(QCPGraph::lsImpulse);
1781         }
1782         break;
1783     case psDot:
1784         if (graph_) {
1785             graph_->setScatterStyle(QCPScatterStyle::ssDisc);
1786         }
1787         break;
1788     case psSquare:
1789         if (graph_) {
1790             graph_->setScatterStyle(QCPScatterStyle::ssSquare);
1791         }
1792         break;
1793     case psDiamond:
1794         if (graph_) {
1795             graph_->setScatterStyle(QCPScatterStyle::ssDiamond);
1796         }
1797         break;
1798     case psCross:
1799         if (graph_) {
1800             graph_->setScatterStyle(QCPScatterStyle::ssCross);
1801         }
1802         break;
1803     case psPlus:
1804         if (graph_) {
1805             graph_->setScatterStyle(QCPScatterStyle::ssPlus);
1806         }
1807         break;
1808     case psCircle:
1809         if (graph_) {
1810             graph_->setScatterStyle(QCPScatterStyle::ssCircle);
1811         }
1812         break;
1813 
1814     case psBar:
1815     case IOGraph::psStackedBar:
1816         // Stacking set in scanGraphs
1817         bars_->moveBelow(NULL);
1818         break;
1819     }
1820 
1821     setName(name_);
1822     applyCurrentColor();
1823 }
1824 
valueUnitLabel()1825 const QString IOGraph::valueUnitLabel()
1826 {
1827     return val_to_str_const(val_units_, y_axis_vs, "Unknown");
1828 }
1829 
setValueUnits(int val_units)1830 void IOGraph::setValueUnits(int val_units)
1831 {
1832     if (val_units >= IOG_ITEM_UNIT_FIRST && val_units <= IOG_ITEM_UNIT_LAST) {
1833         int old_val_units = val_units_;
1834         val_units_ = (io_graph_item_unit_t)val_units;
1835 
1836         if (old_val_units != val_units) {
1837             setFilter(filter_); // Check config & prime vu field
1838             if (val_units < IOG_ITEM_UNIT_CALC_SUM) {
1839                 emit requestRecalc();
1840             }
1841         }
1842     }
1843 }
1844 
setValueUnitField(const QString & vu_field)1845 void IOGraph::setValueUnitField(const QString &vu_field)
1846 {
1847     int old_hf_index = hf_index_;
1848 
1849     vu_field_ = vu_field.trimmed();
1850     hf_index_ = -1;
1851 
1852     header_field_info *hfi = proto_registrar_get_byname(vu_field_.toUtf8().constData());
1853     if (hfi) {
1854         hf_index_ = hfi->id;
1855     }
1856 
1857     if (old_hf_index != hf_index_) {
1858         setFilter(filter_); // Check config & prime vu field
1859     }
1860 }
1861 
addToLegend()1862 bool IOGraph::addToLegend()
1863 {
1864     if (graph_) {
1865         return graph_->addToLegend();
1866     }
1867     if (bars_) {
1868         return bars_->addToLegend();
1869     }
1870     return false;
1871 }
1872 
removeFromLegend()1873 bool IOGraph::removeFromLegend()
1874 {
1875     if (graph_) {
1876         return graph_->removeFromLegend();
1877     }
1878     if (bars_) {
1879         return bars_->removeFromLegend();
1880     }
1881     return false;
1882 }
1883 
startOffset()1884 double IOGraph::startOffset()
1885 {
1886     if (graph_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(graph_->keyAxis()->ticker()) && graph_->data()->size() > 0) {
1887         return graph_->data()->at(0)->key;
1888     }
1889     if (bars_ && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(bars_->keyAxis()->ticker()) && bars_->data()->size() > 0) {
1890         return bars_->data()->at(0)->key;
1891     }
1892     return 0.0;
1893 }
1894 
packetFromTime(double ts)1895 int IOGraph::packetFromTime(double ts)
1896 {
1897     int idx = ts * 1000 / interval_;
1898     if (idx >= 0 && idx < (int) cur_idx_) {
1899         switch (val_units_) {
1900         case IOG_ITEM_UNIT_CALC_MAX:
1901         case IOG_ITEM_UNIT_CALC_MIN:
1902             return items_[idx].extreme_frame_in_invl;
1903         default:
1904             return items_[idx].last_frame_in_invl;
1905         }
1906     }
1907     return -1;
1908 }
1909 
clearAllData()1910 void IOGraph::clearAllData()
1911 {
1912     cur_idx_ = -1;
1913     reset_io_graph_items(items_, max_io_items_);
1914     if (graph_) {
1915         graph_->data()->clear();
1916     }
1917     if (bars_) {
1918         bars_->data()->clear();
1919     }
1920     start_time_ = 0.0;
1921 }
1922 
recalcGraphData(capture_file * cap_file,bool enable_scaling)1923 void IOGraph::recalcGraphData(capture_file *cap_file, bool enable_scaling)
1924 {
1925     /* Moving average variables */
1926     unsigned int mavg_in_average_count = 0, mavg_left = 0, mavg_right = 0;
1927     unsigned int mavg_to_remove = 0, mavg_to_add = 0;
1928     double mavg_cumulated = 0;
1929     QCPAxis *x_axis = nullptr;
1930 
1931     if (graph_) {
1932         graph_->data()->clear();
1933         x_axis = graph_->keyAxis();
1934     }
1935     if (bars_) {
1936         bars_->data()->clear();
1937         x_axis = bars_->keyAxis();
1938     }
1939 
1940     if (moving_avg_period_ > 0 && cur_idx_ >= 0) {
1941         /* "Warm-up phase" - calculate average on some data not displayed;
1942          * just to make sure average on leftmost and rightmost displayed
1943          * values is as reliable as possible
1944          */
1945         guint64 warmup_interval = 0;
1946 
1947 //        for (; warmup_interval < first_interval; warmup_interval += interval_) {
1948 //            mavg_cumulated += get_it_value(io, i, (int)warmup_interval/interval_);
1949 //            mavg_in_average_count++;
1950 //            mavg_left++;
1951 //        }
1952         mavg_cumulated += getItemValue((int)warmup_interval/interval_, cap_file);
1953         mavg_in_average_count++;
1954         for (warmup_interval = interval_;
1955             ((warmup_interval < (0 + (moving_avg_period_ / 2) * (guint64)interval_)) &&
1956              (warmup_interval <= (cur_idx_ * (guint64)interval_)));
1957              warmup_interval += interval_) {
1958 
1959             mavg_cumulated += getItemValue((int)warmup_interval / interval_, cap_file);
1960             mavg_in_average_count++;
1961             mavg_right++;
1962         }
1963         mavg_to_add = (unsigned int)warmup_interval;
1964     }
1965 
1966     for (int i = 0; i <= cur_idx_; i++) {
1967         double ts = (double) i * interval_ / 1000;
1968         if (x_axis && qSharedPointerDynamicCast<QCPAxisTickerDateTime>(x_axis->ticker())) {
1969             ts += start_time_;
1970         }
1971         double val = getItemValue(i, cap_file);
1972 
1973         if (moving_avg_period_ > 0) {
1974             if (i != 0) {
1975                 mavg_left++;
1976                 if (mavg_left > moving_avg_period_ / 2) {
1977                     mavg_left--;
1978                     mavg_in_average_count--;
1979                     mavg_cumulated -= getItemValue((int)mavg_to_remove / interval_, cap_file);
1980                     mavg_to_remove += interval_;
1981                 }
1982                 if (mavg_to_add <= (unsigned int) cur_idx_ * interval_) {
1983                     mavg_in_average_count++;
1984                     mavg_cumulated += getItemValue((int)mavg_to_add / interval_, cap_file);
1985                     mavg_to_add += interval_;
1986                 } else {
1987                     mavg_right--;
1988                 }
1989             }
1990             if (mavg_in_average_count > 0) {
1991                 val = mavg_cumulated / mavg_in_average_count;
1992             }
1993         }
1994 
1995         val *= y_axis_factor_;
1996 
1997         if (hasItemToShow(i, val))
1998         {
1999             if (graph_) {
2000                 graph_->addData(ts, val);
2001             }
2002             if (bars_) {
2003                 bars_->addData(ts, val);
2004             }
2005         }
2006 //        qDebug() << "=rgd i" << i << ts << val;
2007     }
2008 
2009     // attempt to rescale time values to specific units
2010     if (enable_scaling) {
2011         calculateScaledValueUnit();
2012     } else {
2013         scaled_value_unit_.clear();
2014     }
2015 
2016     emit requestReplot();
2017 }
2018 
calculateScaledValueUnit()2019 void IOGraph::calculateScaledValueUnit()
2020 {
2021     // Reset unit and recalculate if needed.
2022     scaled_value_unit_.clear();
2023 
2024     // If there is no field, scaling is not possible.
2025     if (hf_index_ < 0) {
2026         return;
2027     }
2028 
2029     switch (val_units_) {
2030     case IOG_ITEM_UNIT_CALC_SUM:
2031     case IOG_ITEM_UNIT_CALC_MAX:
2032     case IOG_ITEM_UNIT_CALC_MIN:
2033     case IOG_ITEM_UNIT_CALC_AVERAGE:
2034         // Unit is not yet known, continue detecting it.
2035         break;
2036     default:
2037         // Unit is Packets, Bytes, Bits, etc.
2038         return;
2039     }
2040 
2041     if (proto_registrar_get_ftype(hf_index_) == FT_RELATIVE_TIME) {
2042         // find maximum absolute value and scale accordingly
2043         double maxValue = 0;
2044         if (graph_) {
2045             maxValue = maxValueFromGraphData(*graph_->data());
2046         } else if (bars_) {
2047             maxValue = maxValueFromGraphData(*bars_->data());
2048         }
2049         // If the maximum value is zero, then either we have no data or
2050         // everything is zero, do not scale the unit in this case.
2051         if (maxValue == 0) {
2052             return;
2053         }
2054 
2055         // XXX GTK+ always uses "ms" for log scale, should we do that too?
2056         int value_multiplier;
2057         if (maxValue >= 1.0) {
2058             scaled_value_unit_ = "s";
2059             value_multiplier = 1;
2060         } else if (maxValue >= 0.001) {
2061             scaled_value_unit_ = "ms";
2062             value_multiplier = 1000;
2063         } else {
2064             scaled_value_unit_ = "us";
2065             value_multiplier = 1000000;
2066         }
2067 
2068         if (graph_) {
2069             scaleGraphData(*graph_->data(), value_multiplier);
2070         } else if (bars_) {
2071             scaleGraphData(*bars_->data(), value_multiplier);
2072         }
2073     }
2074 }
2075 
2076 template<class DataMap>
maxValueFromGraphData(const DataMap & map)2077 double IOGraph::maxValueFromGraphData(const DataMap &map)
2078 {
2079     double maxValue = 0;
2080     typename DataMap::const_iterator it = map.constBegin();
2081     while (it != map.constEnd()) {
2082         maxValue = MAX(fabs((*it).value), maxValue);
2083         ++it;
2084     }
2085     return maxValue;
2086 }
2087 
2088 template<class DataMap>
scaleGraphData(DataMap & map,int scalar)2089 void IOGraph::scaleGraphData(DataMap &map, int scalar)
2090 {
2091     if (scalar != 1) {
2092         typename DataMap::iterator it = map.begin();
2093         while (it != map.end()) {
2094             (*it).value *= scalar;
2095             ++it;
2096         }
2097     }
2098 }
2099 
captureEvent(CaptureEvent e)2100 void IOGraph::captureEvent(CaptureEvent e)
2101 {
2102     if ((e.captureContext() == CaptureEvent::File) &&
2103             (e.eventType() == CaptureEvent::Closing))
2104     {
2105          remove_tap_listener(this);
2106     }
2107 }
2108 
reloadValueUnitField()2109 void IOGraph::reloadValueUnitField()
2110 {
2111     if (vu_field_.length() > 0) {
2112         setValueUnitField(vu_field_);
2113     }
2114 }
2115 
2116 // Check if a packet is available at the given interval (idx).
hasItemToShow(int idx,double value) const2117 bool IOGraph::hasItemToShow(int idx, double value) const
2118 {
2119     ws_assert(idx < max_io_items_);
2120 
2121     bool result = false;
2122 
2123     const io_graph_item_t *item = &items_[idx];
2124 
2125     switch (val_units_) {
2126     case IOG_ITEM_UNIT_PACKETS:
2127     case IOG_ITEM_UNIT_BYTES:
2128     case IOG_ITEM_UNIT_BITS:
2129     case IOG_ITEM_UNIT_CALC_FRAMES:
2130     case IOG_ITEM_UNIT_CALC_FIELDS:
2131         if(value == 0.0 && (graph_ && graph_->scatterStyle().shape() != QCPScatterStyle::ssNone)) {
2132             result = false;
2133         }
2134         else {
2135             result = true;
2136         }
2137         break;
2138 
2139     case IOG_ITEM_UNIT_CALC_SUM:
2140     case IOG_ITEM_UNIT_CALC_MAX:
2141     case IOG_ITEM_UNIT_CALC_MIN:
2142     case IOG_ITEM_UNIT_CALC_AVERAGE:
2143     case IOG_ITEM_UNIT_CALC_LOAD:
2144         if (item->fields) {
2145             result = true;
2146         }
2147         break;
2148 
2149     default:
2150         result = true;
2151         break;
2152     }
2153 
2154     return result;
2155 }
2156 
setInterval(int interval)2157 void IOGraph::setInterval(int interval)
2158 {
2159     interval_ = interval;
2160 }
2161 
2162 // Get the value at the given interval (idx) for the current value unit.
getItemValue(int idx,const capture_file * cap_file) const2163 double IOGraph::getItemValue(int idx, const capture_file *cap_file) const
2164 {
2165     ws_assert(idx < max_io_items_);
2166 
2167     return get_io_graph_item(items_, val_units_, idx, hf_index_, cap_file, interval_, cur_idx_);
2168 }
2169 
2170 // "tap_reset" callback for register_tap_listener
tapReset(void * iog_ptr)2171 void IOGraph::tapReset(void *iog_ptr)
2172 {
2173     IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2174     if (!iog) return;
2175 
2176 //    qDebug() << "=tapReset" << iog->name_;
2177     iog->clearAllData();
2178 }
2179 
2180 // "tap_packet" callback for register_tap_listener
tapPacket(void * iog_ptr,packet_info * pinfo,epan_dissect_t * edt,const void *)2181 tap_packet_status IOGraph::tapPacket(void *iog_ptr, packet_info *pinfo, epan_dissect_t *edt, const void *)
2182 {
2183     IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2184     if (!pinfo || !iog) {
2185         return TAP_PACKET_DONT_REDRAW;
2186     }
2187 
2188     int idx = get_io_graph_index(pinfo, iog->interval_);
2189     bool recalc = false;
2190 
2191     /* some sanity checks */
2192     if ((idx < 0) || (idx >= max_io_items_)) {
2193         iog->cur_idx_ = max_io_items_ - 1;
2194         return TAP_PACKET_DONT_REDRAW;
2195     }
2196 
2197     /* update num_items */
2198     if (idx > iog->cur_idx_) {
2199         iog->cur_idx_ = (guint32) idx;
2200         recalc = true;
2201     }
2202 
2203     /* set start time */
2204     if (iog->start_time_ == 0.0) {
2205         nstime_t start_nstime;
2206         nstime_set_zero(&start_nstime);
2207         nstime_delta(&start_nstime, &pinfo->abs_ts, &pinfo->rel_ts);
2208         iog->start_time_ = nstime_to_sec(&start_nstime);
2209     }
2210 
2211     epan_dissect_t *adv_edt = NULL;
2212     /* For ADVANCED mode we need to keep track of some more stuff than just frame and byte counts */
2213     if (iog->val_units_ >= IOG_ITEM_UNIT_CALC_SUM) {
2214         adv_edt = edt;
2215     }
2216 
2217     if (!update_io_graph_item(iog->items_, idx, pinfo, adv_edt, iog->hf_index_, iog->val_units_, iog->interval_)) {
2218         return TAP_PACKET_DONT_REDRAW;
2219     }
2220 
2221 //    qDebug() << "=tapPacket" << iog->name_ << idx << iog->hf_index_ << iog->val_units_ << iog->num_items_;
2222 
2223     if (recalc) {
2224         emit iog->requestRecalc();
2225     }
2226     return TAP_PACKET_REDRAW;
2227 }
2228 
2229 // "tap_draw" callback for register_tap_listener
tapDraw(void * iog_ptr)2230 void IOGraph::tapDraw(void *iog_ptr)
2231 {
2232     IOGraph *iog = static_cast<IOGraph *>(iog_ptr);
2233     if (!iog) return;
2234     emit iog->requestRecalc();
2235 
2236     if (iog->graph_) {
2237 //        qDebug() << "=tapDraw g" << iog->name_ << iog->graph_->data()->keys().size();
2238     }
2239     if (iog->bars_) {
2240 //        qDebug() << "=tapDraw b" << iog->name_ << iog->bars_->data()->keys().size();
2241     }
2242 }
2243 
2244 // Stat command + args
2245 
2246 static void
io_graph_init(const char *,void *)2247 io_graph_init(const char *, void*) {
2248     wsApp->emitStatCommandSignal("IOGraph", NULL, NULL);
2249 }
2250 
2251 static stat_tap_ui io_stat_ui = {
2252     REGISTER_STAT_GROUP_GENERIC,
2253     NULL,
2254     "io,stat",
2255     io_graph_init,
2256     0,
2257     NULL
2258 };
2259 
2260 extern "C" {
2261 
2262 void register_tap_listener_qt_iostat(void);
2263 
2264 void
register_tap_listener_qt_iostat(void)2265 register_tap_listener_qt_iostat(void)
2266 {
2267     register_stat_tap_ui(&io_stat_ui, NULL);
2268 }
2269 
2270 }
2271