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