Witness: enum witness_notifyResponse_type
[wireshark-wip.git] / ui / gtk / rlc_lte_graph.c
blob53177639a309016b265f7625c8a0a5af5a1b7d77
1 /* rlc_lte_graph.c
2 * By Martin Mathieson
3 * Based upon tcp_graph.c
5 * $Id$
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "config.h"
28 #include <stdlib.h>
29 #include <math.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #if GTK_CHECK_VERSION(3,0,0)
34 # include <gdk/gdkkeysyms-compat.h>
35 #endif
37 #include <epan/packet.h>
38 #include <epan/epan_dissect.h>
39 #include <epan/dissectors/packet-rlc-lte.h>
40 #include <epan/tap.h>
42 #include "../globals.h"
43 #include "../frame_tvbuff.h"
44 #include "ui/simple_dialog.h"
45 #include "../stat_menu.h"
47 #include "ui/gtk/gui_utils.h"
48 #include "ui/gtk/dlg_utils.h"
49 #include "ui/gtk/gui_stat_menu.h"
50 #include "ui/gtk/tap_param_dlg.h"
52 #include "ui/gtk/old-gtk-compat.h"
54 #define AXIS_HORIZONTAL 0
55 #define AXIS_VERTICAL 1
57 #define WINDOW_TITLE_LENGTH 256
59 #define MOUSE_BUTTON_LEFT 1
60 #define MOUSE_BUTTON_MIDDLE 2
61 #define MOUSE_BUTTON_RIGHT 3
63 #define MAX_PIXELS_PER_SN 90
64 #define MAX_PIXELS_PER_SECOND 50000
66 extern int proto_rlc_lte;
68 struct segment {
69 struct segment *next;
70 guint32 num; /* framenum */
71 guint32 rel_secs;
72 guint32 rel_usecs;
73 guint32 abs_secs;
74 guint32 abs_usecs;
76 gboolean isControlPDU;
77 guint16 SN;
78 guint16 isResegmented;
79 guint16 ACKNo;
80 #define MAX_NACKs 128
81 guint16 noOfNACKs;
82 guint16 NACKs[MAX_NACKs];
84 guint16 ueid;
85 guint16 channelType;
86 guint16 channelId;
87 guint8 rlcMode;
88 guint8 direction;
91 struct line {
92 double x1, y1, x2, y2;
95 struct irect {
96 int x, y, width, height;
99 struct zoomfactor {
100 double x, y;
103 typedef enum {
104 ELMT_NONE = 0,
105 ELMT_LINE = 1,
106 ELMT_ELLIPSE = 2
107 } ElementType;
109 struct line_params {
110 struct line dim;
113 struct rect {
114 double x, y, width, height;
117 struct ellipse_params {
118 struct rect dim;
121 struct element {
122 ElementType type;
123 GdkRGBA *elment_color_p;
124 struct segment *parent;
125 union {
126 struct line_params line;
127 struct ellipse_params ellipse;
128 } p;
131 struct element_list {
132 struct element_list *next;
133 struct element *elements;
136 struct axis {
137 struct graph *g; /* which graph we belong to */
138 GtkWidget *drawing_area;
139 /* Double-buffering to avoid flicker */
140 #if GTK_CHECK_VERSION(2,22,0)
141 cairo_surface_t *surface[2];
142 #else
143 GdkPixmap *pixmap[2];
144 #endif
145 /* Which of the 2 buffers we are currently showing */
146 int displayed;
147 #define AXIS_ORIENTATION 1 << 0
148 int flags;
149 /* dim and orig (relative to origin of window) of axis' pixmap */
150 struct irect p;
151 /* dim and orig (relative to origin of axis' pixmap) of scale itself */
152 struct irect s;
153 gdouble min, max;
154 gdouble major, minor; /* major and minor ticks */
155 const char **label;
158 #define HAXIS_INIT_HEIGHT 70
159 #define VAXIS_INIT_WIDTH 100
160 #define TITLEBAR_HEIGHT 50
161 #define RMARGIN_WIDTH 30
163 struct style_rlc_lte {
164 GdkRGBA seq_color;
165 GdkRGBA seq_resegmented_color;
166 GdkRGBA ack_color[2];
167 int flags;
170 /* style flags */
171 #define TIME_ORIGIN 0x10
172 /* show time from beginning of capture as opposed to time from beginning
173 * of the connection */
174 #define TIME_ORIGIN_CAP 0x10
175 #define TIME_ORIGIN_CONN 0x00
177 struct cross {
178 int x, y;
179 int draw; /* indicates whether we should draw cross at all */
180 int erase_needed; /* indicates whether currently drawn at recorded position */
183 struct bounds {
184 double x0, y0, width, height;
187 struct zoom {
188 double x, y;
191 struct zooms {
192 double x, y;
193 double step_x, step_y;
194 struct zoom initial;
195 #define ZOOM_OUT (1 << 0)
196 int flags;
199 struct grab {
200 int grabbed;
201 int x, y;
205 struct graph {
206 #define GRAPH_DESTROYED (1 << 0)
207 int flags;
208 GtkWidget *toplevel; /* keypress handler needs this */
209 GtkWidget *drawing_area;
210 PangoFontDescription *font; /* font used for annotations etc. */
212 /* Double-buffering */
213 #if GTK_CHECK_VERSION(2,22,0)
214 cairo_surface_t *title_surface;
215 cairo_surface_t *surface[2];
216 #else
217 GdkPixmap *title_pixmap;
218 GdkPixmap *pixmap[2];
219 #endif
220 int displayed; /* which of both pixmaps is on screen right now */
222 /* Next 4 attribs describe the graph in natural units, before any scaling.
223 * For example, if we want to display graph of TCP conversation that
224 * started 112.309845 s after beginning of the capture and ran until
225 * 479.093582 s, 237019 B went through the connection (in one direction)
226 * starting with isn 31934022, then (bounds.x0, bounds.y0)=(112.309845,
227 * 31934022) and (bounds.width, bounds.height)=(366.783737, 237019). */
228 struct bounds bounds;
229 /* dimensions and position of the graph, both expressed already in pixels.
230 * x and y give the position of upper left corner of the graph relative
231 * to origin of the graph window, size is basically bounds*zoom */
232 struct irect geom;
233 /* viewport (=graph window area which is reserved for graph itself), its
234 * size and position relative to origin of the graph window */
235 struct irect wp;
236 struct grab grab;
237 /* If we need to display 237019 sequence numbers (=bytes) onto say 500
238 * pixels, we have to scale the graph down by factor of 0.002109. This
239 * number would be zoom.y. Obviously, both directions have separate zooms.*/
240 struct zooms zoom;
241 gboolean zoomrect_erase_needed;
242 struct cross cross;
243 struct axis *x_axis, *y_axis;
245 /* List of segments to show */
246 struct segment *segments;
248 /* These are filled in with the channel/direction this graph is showing */
249 guint16 ueid;
250 guint16 channelType;
251 guint16 channelId;
252 guint8 rlcMode;
253 guint8 direction;
255 /* Lists of elements to draw */
256 struct element_list *elists; /* element lists */
258 /* Colours, etc to be used in drawing */
259 struct style_rlc_lte style;
262 static int refnum = 0;
264 #define debug(section) if (debugging & section)
265 /* print function entry points */
266 #define DBS_FENTRY (1 << 0)
267 #define DBS_AXES_TICKS (1 << 1)
268 #define DBS_AXES_DRAWING (1 << 2)
269 #define DBS_GRAPH_DRAWING (1 << 3)
270 #define DBS_TPUT_ELMTS (1 << 4)
271 /*static int debugging = DBS_FENTRY;*/
272 /*static int debugging = DBS_AXES_TICKS;*/
273 /*static int debugging = DBS_AXES_DRAWING;*/
274 /*static int debugging = DBS_GRAPH_DRAWING;*/
275 /*static int debugging = DBS_TPUT_ELMTS;*/
276 static int debugging = 0;
278 static void create_gui(struct graph * );
279 static void create_drawing_area(struct graph * );
280 static void callback_toplevel_destroy(GtkWidget * , gpointer );
281 static void callback_create_help(GtkWidget * , gpointer );
282 static void get_mouse_position(GtkWidget *, int *pointer_x, int *pointer_y, GdkModifierType *mask);
283 static rlc_lte_tap_info *select_rlc_lte_session(capture_file *, struct segment * );
284 static int compare_headers(guint16 ueid1, guint16 channelType1, guint16 channelId1, guint8 rlcMode1, guint8 direction1,
285 guint16 ueid2, guint16 channelType2, guint16 channelId2, guint8 rlcMode2, guint8 direction2,
286 gboolean isControlFrame);
287 static void get_data_control_counts(struct graph *g, int *data, int *acks, int *nacks);
289 static struct graph *graph_new(void);
290 static void graph_destroy(struct graph * );
291 static void graph_initialize_values(struct graph * );
292 static void graph_init_sequence(struct graph * );
293 static void draw_element_line(struct graph * , struct element * , cairo_t * , GdkRGBA *new_color);
294 static void draw_element_ellipse(struct graph * , struct element * , cairo_t *cr , GdkRGBA *new_color);
295 static void graph_display(struct graph * );
296 static void graph_pixmaps_create(struct graph * );
297 static void graph_pixmaps_switch(struct graph * );
298 static void graph_pixmap_draw(struct graph * );
299 static void graph_pixmap_display(struct graph * );
300 static void graph_element_lists_make(struct graph * );
301 static void graph_element_lists_free(struct graph * );
302 static void graph_element_lists_initialize(struct graph * );
303 static void graph_title_pixmap_create(struct graph * );
304 static void graph_title_pixmap_draw(struct graph * );
305 static void graph_title_pixmap_display(struct graph * );
306 static void graph_segment_list_get(struct graph *, gboolean channel_known );
307 static void graph_segment_list_free(struct graph * );
308 static void graph_select_segment(struct graph * , int , int );
309 static int line_detect_collision(struct element * , int , int );
310 static int ellipse_detect_collision(struct element *e, int x, int y);
311 static void axis_pixmaps_create(struct axis * );
312 static void axis_pixmaps_switch(struct axis * );
313 static void axis_display(struct axis * );
314 static void v_axis_pixmap_draw(struct axis * );
315 static void h_axis_pixmap_draw(struct axis * );
316 static void axis_pixmap_display(struct axis * );
317 static void axis_compute_ticks(struct axis * , double , double , int );
318 static double axis_zoom_get(struct axis * , int );
319 static void axis_ticks_up(int * , int * );
320 static void axis_ticks_down(int * , int * );
321 static void axis_destroy(struct axis * );
322 static int get_label_dim(struct axis * , int , double );
324 static void toggle_crosshairs(struct graph *);
325 static void cross_draw(struct graph * , int x, int y);
326 static void cross_erase(struct graph * );
327 static void zoomrect_draw(struct graph * , int , int );
328 static void zoomrect_erase(struct graph * );
329 static gboolean motion_notify_event(GtkWidget * , GdkEventMotion * , gpointer );
331 static void toggle_time_origin(struct graph * );
332 static void restore_initial_graph_view(struct graph *g);
333 static gboolean configure_event(GtkWidget * , GdkEventConfigure * , gpointer );
334 #if GTK_CHECK_VERSION(3,0,0)
335 static gboolean draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data);
336 #else
337 static gboolean expose_event(GtkWidget * , GdkEventExpose * , gpointer );
338 #endif
339 static gboolean button_press_event(GtkWidget * , GdkEventButton * , gpointer );
340 static gboolean button_release_event(GtkWidget * , GdkEventButton * , gpointer );
341 static gboolean key_press_event(GtkWidget * , GdkEventKey * , gpointer );
342 static void graph_initialize(struct graph *);
343 static void graph_get_bounds(struct graph *);
344 static void graph_read_config(struct graph *);
345 static void rlc_lte_make_elmtlist(struct graph *);
347 #if defined(_WIN32) && !defined(__MINGW32__) && (_MSC_VER < 1800)
348 /* Starting VS2013, rint already defined in math.h. No need to redefine */
349 static int rint(double ); /* compiler template for Windows */
350 #endif
352 /* This should arguably be part of the graph, but in practice you can
353 only click on one graph at a time, so this is probably OK */
354 static struct irect zoomrect;
357 /* XXX - what about OS X? */
358 static char helptext[] =
359 "Here's what you can do:\n"
360 "\n"
361 " Left Mouse Button selects segment under cursor in Wireshark's packet list\n"
362 " can also drag to zoom in on a rectangular region\n"
363 " Middle Mouse Button zooms in (towards area under cursor)\n"
364 " Right Mouse Button moves the graph (if zoomed in)\n"
365 "\n"
366 " <Space bar> toggles crosshairs on/off\n"
367 "\n"
368 " 'i' or '+' zoom in (towards area under mouse pointer)\n"
369 " 'o' or '-' zoom out\n"
370 " (add shift to lock Y axis, control to lock X axis)\n"
371 " 'r' or <Home> restore graph to initial state (zoom out max)\n"
372 " 't' toggle time axis to being at zero, or to use time in capture\n"
373 "\n"
374 " <Left> move view left by 100 pixels (if zoomed in)\n"
375 " <Right> move view right 100 pixels (if zoomed in)\n"
376 " <Up> move view up by 100 pixels (if zoomed in)\n"
377 " <Down> move view down by 100 pixels (if zoomed in)\n"
378 "\n"
379 " <Shift><Left> move view left by 10 pixels (if zoomed in)\n"
380 " <Shift><Right> move view right 10 pixels (if zoomed in)\n"
381 " <Shift><Up> move view up by 10 pixels (if zoomed in)\n"
382 " <Shift><Down> move view down by 10 pixels (if zoomed in)\n"
383 "\n"
384 " <Ctrl><Left> move view left by 1 pixel (if zoomed in)\n"
385 " <Ctrl><Right> move view right 1 pixel (if zoomed in)\n"
386 " <Ctrl><Up> move view up by 1 pixel (if zoomed in)\n"
387 " <Ctrl><Down> move view down by 1 pixel (if zoomed in)\n"
388 "\n"
389 " <Page_Up> move up by a large number of pixels (if zoomed in)\n"
390 " <Page_Down> move down by a large number of pixels (if zoomed in)\n"
393 static void set_busy_cursor(GdkWindow *w)
395 GdkCursor* cursor = gdk_cursor_new(GDK_WATCH);
396 gdk_window_set_cursor(w, cursor);
397 gdk_flush();
398 #if GTK_CHECK_VERSION(3,0,0)
399 g_object_unref(cursor);
400 #else
401 gdk_cursor_unref(cursor);
402 #endif
405 static void unset_busy_cursor(GdkWindow *w)
407 gdk_window_set_cursor(w, NULL);
408 gdk_flush();
411 void rlc_lte_graph_cb(GtkAction *action _U_, gpointer user_data _U_)
413 struct segment current;
414 struct graph *g;
416 debug(DBS_FENTRY) puts("rlc_lte_graph_cb()");
418 /* Can we choose an RLC channel from the selected frame? */
419 if (!select_rlc_lte_session(&cfile, &current)) {
420 return;
423 if (!(g = graph_new())) {
424 return;
427 refnum++;
428 graph_initialize_values(g);
430 /* Get our list of segments from the packet list */
431 graph_segment_list_get(g, FALSE);
433 create_gui(g);
434 graph_init_sequence(g);
437 void rlc_lte_graph_known_channel_launch(guint16 ueid, guint8 rlcMode,
438 guint16 channelType, guint16 channelId,
439 guint8 direction)
441 struct graph *g;
443 debug(DBS_FENTRY) puts("rlc_lte_graph_known_channel()");
445 if (!(g = graph_new())) {
446 return;
449 refnum++;
450 graph_initialize_values(g);
452 /* Can set channel info for graph now */
453 g->ueid = ueid;
454 g->rlcMode = rlcMode;
455 g->channelType = channelType;
456 g->channelId = channelId;
457 g->direction = direction;
459 /* Get our list of segments from the packet list */
460 graph_segment_list_get(g, TRUE);
462 create_gui(g);
463 graph_init_sequence(g);
467 static void create_gui(struct graph *g)
469 debug(DBS_FENTRY) puts("create_gui()");
470 /* create_text_widget(g); */
471 create_drawing_area(g);
474 static void create_drawing_area(struct graph *g)
476 #if GTK_CHECK_VERSION(3,0,0)
477 GtkStyleContext *context;
478 #endif
479 char *display_name;
480 char window_title[WINDOW_TITLE_LENGTH];
481 GtkAllocation widget_alloc;
483 debug(DBS_FENTRY) puts("create_drawing_area()");
484 display_name = cf_get_display_name(&cfile);
485 /* Set channel details in title */
486 g_snprintf(window_title, WINDOW_TITLE_LENGTH, "LTE RLC Graph %d: %s (UE-%u, chan=%s%u %s - %s)",
487 refnum, display_name,
488 g->ueid, (g->channelType == CHANNEL_TYPE_SRB) ? "SRB" : "DRB",
489 g->channelId,
490 (g->direction == DIRECTION_UPLINK) ? "UL" : "DL",
491 (g->rlcMode == RLC_UM_MODE) ? "UM" : "AM");
492 g_free(display_name);
493 g->toplevel = dlg_window_new("RLC Graph");
494 gtk_window_set_title(GTK_WINDOW(g->toplevel), window_title);
495 gtk_widget_set_name(g->toplevel, "Test Graph");
497 /* Create the drawing area */
498 g->drawing_area = gtk_drawing_area_new();
499 g->x_axis->drawing_area = g->y_axis->drawing_area = g->drawing_area;
500 gtk_widget_set_size_request(g->drawing_area,
501 g->wp.width + g->wp.x + RMARGIN_WIDTH,
502 g->wp.height + g->wp.y + g->x_axis->s.height);
503 gtk_widget_show(g->drawing_area);
505 #if GTK_CHECK_VERSION(3,0,0)
506 g_signal_connect(g->drawing_area, "draw", G_CALLBACK(draw_event), g);
507 #else
508 g_signal_connect(g->drawing_area, "expose_event", G_CALLBACK(expose_event), g);
509 #endif
511 g_signal_connect(g->drawing_area, "button_press_event",
512 G_CALLBACK(button_press_event), g);
513 g_signal_connect(g->drawing_area, "button_release_event",
514 G_CALLBACK(button_release_event), g);
515 g_signal_connect(g->toplevel, "destroy", G_CALLBACK(callback_toplevel_destroy), g);
516 g_signal_connect(g->drawing_area, "motion_notify_event",
517 G_CALLBACK(motion_notify_event), g);
519 /* why doesn't drawing area send key_press_signals? */
520 g_signal_connect(g->toplevel, "key_press_event", G_CALLBACK(key_press_event), g);
521 gtk_widget_set_events(g->toplevel,
522 GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK);
524 gtk_widget_set_events(g->drawing_area,
525 GDK_EXPOSURE_MASK
526 | GDK_LEAVE_NOTIFY_MASK
527 | GDK_ENTER_NOTIFY_MASK
528 | GDK_BUTTON_PRESS_MASK
529 | GDK_BUTTON_RELEASE_MASK
530 | GDK_POINTER_MOTION_MASK
531 | GDK_POINTER_MOTION_HINT_MASK);
533 gtk_container_add(GTK_CONTAINER(g->toplevel), g->drawing_area);
534 gtk_widget_show(g->toplevel);
536 /* In case we didn't get what we asked for */
537 gtk_widget_get_allocation(GTK_WIDGET(g->drawing_area), &widget_alloc);
538 g->wp.width = widget_alloc.width - g->wp.x - RMARGIN_WIDTH;
539 g->wp.height = widget_alloc.height - g->wp.y - g->x_axis->s.height;
541 #if GTK_CHECK_VERSION(3,0,0)
542 context = gtk_widget_get_style_context(g->drawing_area);
543 gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL,
544 GTK_STYLE_PROPERTY_FONT, &g->font,
545 NULL);
546 #else
547 g->font = gtk_widget_get_style(g->drawing_area)->font_desc;
549 #endif
551 g_signal_connect(g->drawing_area, "configure_event", G_CALLBACK(configure_event), g);
554 static void callback_toplevel_destroy(GtkWidget *widget _U_, gpointer data)
556 struct graph *g = (struct graph * )data;
558 if (!(g->flags & GRAPH_DESTROYED)) {
559 g->flags |= GRAPH_DESTROYED;
560 graph_destroy((struct graph * )data);
564 static void callback_create_help(GtkWidget *widget _U_, gpointer data _U_)
566 GtkWidget *toplevel, *vbox, *text, *scroll, *bbox, *close_bt;
567 GtkTextBuffer *buf;
569 toplevel = dlg_window_new("Help for LTE RLC graphing");
570 gtk_window_set_default_size(GTK_WINDOW(toplevel), 540, 540);
572 vbox = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0, FALSE);
573 gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
574 gtk_container_add(GTK_CONTAINER(toplevel), vbox);
576 scroll = scrolled_window_new(NULL, NULL);
577 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
578 GTK_SHADOW_IN);
579 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
580 text = gtk_text_view_new();
581 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
582 buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
583 gtk_text_buffer_set_text(buf, helptext, -1);
584 gtk_container_add(GTK_CONTAINER(scroll), text);
586 /* Button row. */
587 bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL);
588 gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
589 gtk_widget_show(bbox);
591 close_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CLOSE);
592 window_set_cancel_button(toplevel, close_bt, window_cancel_button_cb);
594 g_signal_connect(toplevel, "delete_event", G_CALLBACK(window_delete_event_cb), NULL);
596 gtk_widget_show_all(toplevel);
597 window_present(toplevel);
600 static void get_mouse_position(GtkWidget *widget, int *pointer_x, int *pointer_y, GdkModifierType *mask)
602 #if GTK_CHECK_VERSION(3,0,0)
603 gdk_window_get_device_position (gtk_widget_get_window(widget),
604 gdk_device_manager_get_client_pointer(
605 gdk_display_get_device_manager(
606 gtk_widget_get_display(GTK_WIDGET(widget)))),
607 pointer_x, pointer_y, mask);
609 #else
610 gdk_window_get_pointer (gtk_widget_get_window(widget), pointer_x, pointer_y, mask);
611 #endif
614 static struct graph *graph_new(void)
616 struct graph *g;
618 g = (struct graph * )g_malloc0(sizeof(struct graph));
619 graph_element_lists_initialize(g);
621 g->x_axis = (struct axis * )g_malloc0(sizeof(struct axis));
622 g->y_axis = (struct axis * )g_malloc0(sizeof(struct axis));
624 g->x_axis->g = g;
625 g->x_axis->flags = 0;
626 g->x_axis->flags |= AXIS_ORIENTATION;
627 g->x_axis->s.x = g->x_axis->s.y = 0;
628 g->x_axis->s.height = HAXIS_INIT_HEIGHT;
629 g->x_axis->p.x = VAXIS_INIT_WIDTH;
630 g->x_axis->p.height = HAXIS_INIT_HEIGHT;
632 g->y_axis->g = g;
633 g->y_axis->flags = 0;
634 g->y_axis->flags &= ~AXIS_ORIENTATION;
635 g->y_axis->p.x = g->y_axis->p.y = 0;
636 g->y_axis->p.width = VAXIS_INIT_WIDTH;
637 g->y_axis->s.x = 0;
638 g->y_axis->s.y = TITLEBAR_HEIGHT;
639 g->y_axis->s.width = VAXIS_INIT_WIDTH;
641 return g;
644 static void graph_initialize_values(struct graph *g)
646 g->geom.width = g->wp.width = 750;
647 g->geom.height = g->wp.height = 550;
648 g->geom.x = g->wp.x = VAXIS_INIT_WIDTH;
649 g->geom.y = g->wp.y = TITLEBAR_HEIGHT;
650 g->flags = 0;
651 g->zoom.x = g->zoom.y = 1.0;
653 /* Zooming in step - set same for both dimensions */
654 g->zoom.step_x = g->zoom.step_y = 1.15;
655 g->zoom.flags = 0;
657 g->cross.draw = g->cross.erase_needed = FALSE;
658 g->zoomrect_erase_needed = FALSE;
659 g->grab.grabbed = 0;
662 static void graph_init_sequence(struct graph *g)
664 debug(DBS_FENTRY) puts("graph_init_sequence()");
666 graph_initialize(g);
667 g->zoom.initial.x = g->zoom.x;
668 g->zoom.initial.y = g->zoom.y;
669 graph_element_lists_make(g);
670 g->x_axis->s.width = g->wp.width;
671 g->x_axis->p.width = g->x_axis->s.width + RMARGIN_WIDTH;
672 g->x_axis->p.y = TITLEBAR_HEIGHT + g->wp.height;
673 g->x_axis->s.height = g->x_axis->p.height = HAXIS_INIT_HEIGHT;
674 g->y_axis->s.height = g->wp.height;
675 g->y_axis->p.height = g->wp.height + TITLEBAR_HEIGHT;
676 graph_pixmaps_create(g);
677 axis_pixmaps_create(g->y_axis);
678 axis_pixmaps_create(g->x_axis);
679 graph_title_pixmap_create(g);
680 graph_title_pixmap_draw(g);
681 graph_title_pixmap_display(g);
682 graph_display(g);
683 axis_display(g->y_axis);
684 axis_display(g->x_axis);
687 static void graph_initialize(struct graph *g)
689 debug(DBS_FENTRY) puts("graph_initialize()");
690 graph_get_bounds(g);
692 /* Want to start with absolute times, rather than being relative to 0 */
693 g->x_axis->min = g->bounds.x0;
694 g->y_axis->min = 0;
696 graph_read_config(g);
699 static void graph_destroy(struct graph *g)
701 debug(DBS_FENTRY) puts("graph_destroy()");
703 axis_destroy(g->x_axis);
704 axis_destroy(g->y_axis);
705 /* window_destroy(g->drawing_area); */
706 window_destroy(g->toplevel);
707 /* window_destroy(g->text); */
708 #if GTK_CHECK_VERSION(2,22,0)
709 if (g->title_surface){
710 cairo_surface_destroy(g->title_surface);
712 if (g->surface[0]){
713 cairo_surface_destroy(g->surface[0]);
715 if (g->surface[1]){
716 cairo_surface_destroy(g->surface[1]);
718 #else
719 g_object_unref(g->pixmap[0]);
720 g_object_unref(g->pixmap[1]);
721 #endif /* GTK_CHECK_VERSION(2,22,0) */
722 g_free(g->x_axis);
723 g_free(g->y_axis);
724 graph_segment_list_free(g);
725 graph_element_lists_free(g);
727 g_free(g);
731 typedef struct rlc_scan_t {
732 struct graph *g;
733 struct segment *last;
734 } rlc_scan_t;
737 static int
738 tapall_rlc_lte_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
740 rlc_scan_t *ts = (rlc_scan_t *)pct;
741 struct graph *g = ts->g;
742 const rlc_lte_tap_info *rlchdr = (const rlc_lte_tap_info*)vip;
744 /* See if this one matches current channel */
745 if (compare_headers(g->ueid, g->channelType, g->channelId, g->rlcMode, g->direction,
746 rlchdr->ueid, rlchdr->channelType, rlchdr->channelId, rlchdr->rlcMode, rlchdr->direction,
747 rlchdr->isControlPDU)) {
749 struct segment *segment = (struct segment *)g_malloc(sizeof(struct segment));
751 /* It matches. Add to end of segment list */
752 segment->next = NULL;
753 segment->num = pinfo->fd->num;
754 segment->rel_secs = (guint32) pinfo->rel_ts.secs;
755 segment->rel_usecs = pinfo->rel_ts.nsecs/1000;
756 segment->abs_secs = (guint32) pinfo->fd->abs_ts.secs;
757 segment->abs_usecs = pinfo->fd->abs_ts.nsecs/1000;
759 segment->ueid = rlchdr->ueid;
760 segment->channelType = rlchdr->channelType;
761 segment->channelId = rlchdr->channelId;
762 segment->direction = rlchdr->direction;
764 segment->isControlPDU = rlchdr->isControlPDU;
766 if (!rlchdr->isControlPDU) {
767 /* Data */
768 segment->SN = rlchdr->sequenceNumber;
769 segment->isResegmented = rlchdr->isResegmented;
771 else {
772 /* Status PDU */
773 gint n;
774 segment->ACKNo = rlchdr->ACKNo;
775 segment->noOfNACKs = rlchdr->noOfNACKs;
776 for (n=0; n < rlchdr->noOfNACKs; n++) {
777 segment->NACKs[n] = rlchdr->NACKs[n];
781 /* Add to list */
782 if (ts->g->segments) {
783 /* Add to end of existing last element */
784 ts->last->next = segment;
785 } else {
786 /* Make this the first (only) segment */
787 ts->g->segments = segment;
790 /* This one is now the last one */
791 ts->last = segment;
794 return 0;
798 /* Here we collect all the external data we will ever need */
799 static void graph_segment_list_get(struct graph *g, gboolean channel_known)
801 struct segment current;
802 GString *error_string;
803 rlc_scan_t ts;
805 debug(DBS_FENTRY) puts("graph_segment_list_get()");
807 if (!channel_known) {
808 select_rlc_lte_session(&cfile, &current);
810 g->ueid = current.ueid;
811 g->rlcMode = current.rlcMode;
812 g->channelType = current.channelType;
813 g->channelId = current.channelId;
814 g->direction = (!current.isControlPDU) ? current.direction : !current.direction;
817 /* rescan all the packets and pick up all frames for this channel.
818 * we only filter for LTE RLC here for speed and do the actual compare
819 * in the tap listener
822 ts.g = g;
823 ts.last = NULL;
824 error_string = register_tap_listener("rlc-lte", &ts, "rlc-lte", 0, NULL, tapall_rlc_lte_packet, NULL);
825 if (error_string){
826 fprintf(stderr, "wireshark: Couldn't register rlc_lte_graph tap: %s\n",
827 error_string->str);
828 g_string_free(error_string, TRUE);
829 exit(1);
831 cf_retap_packets(&cfile);
832 remove_tap_listener(&ts);
836 typedef struct _th_t {
837 int num_hdrs;
838 #define MAX_SUPPORTED_CHANNELS 8
839 rlc_lte_tap_info *rlchdrs[MAX_SUPPORTED_CHANNELS];
840 } th_t;
843 static int
844 tap_lte_rlc_packet(void *pct, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *vip)
846 int n;
847 gboolean is_unique = TRUE;
848 th_t *th = (th_t *)pct;
849 const rlc_lte_tap_info *header = (const rlc_lte_tap_info*)vip;
851 /* Check new header details against any/all stored ones */
852 for (n=0; n < th->num_hdrs; n++) {
853 rlc_lte_tap_info *stored = th->rlchdrs[n];
855 if (compare_headers(stored->ueid, stored->channelType, stored->channelId, stored->rlcMode, stored->direction,
856 header->ueid, header->channelType, header->channelId, header->rlcMode, header->direction,
857 header->isControlPDU)) {
858 is_unique = FALSE;
859 break;
863 /* Add address if unique and have space for it */
864 if (is_unique && (th->num_hdrs < MAX_SUPPORTED_CHANNELS)) {
865 /* Copy the tap stuct in as next header */
866 /* Need to take a deep copy of the tap struct, it may not be valid
867 to read after this function returns? */
868 th->rlchdrs[th->num_hdrs] = g_new(rlc_lte_tap_info,1);
869 *(th->rlchdrs[th->num_hdrs]) = *header;
871 /* Store in direction of data though... */
872 if (th->rlchdrs[th->num_hdrs]->isControlPDU) {
873 th->rlchdrs[th->num_hdrs]->direction = !th->rlchdrs[th->num_hdrs]->direction;
875 th->num_hdrs++;
878 return 0;
882 /* XXX should be enhanced so that if we have multiple RLC channels in the same MAC frame
883 * then present the user with a dialog where the user can select WHICH RLC
884 * channel to graph.
886 static rlc_lte_tap_info *select_rlc_lte_session(capture_file *cf, struct segment *hdrs)
888 frame_data *fdata;
889 epan_dissect_t edt;
890 dfilter_t *sfcode;
891 GString *error_string;
892 nstime_t rel_ts;
893 th_t th = {0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
895 if (cf->state == FILE_CLOSED) {
896 return NULL;
899 fdata = cf->current_frame;
901 /* no real filter yet */
902 if (!dfilter_compile("rlc-lte", &sfcode)) {
903 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", dfilter_error_msg);
904 return NULL;
907 /* dissect the current frame */
908 if (!cf_read_frame(cf, fdata)) {
909 return NULL; /* error reading the frame */
912 error_string = register_tap_listener("rlc-lte", &th, NULL, 0, NULL, tap_lte_rlc_packet, NULL);
913 if (error_string){
914 fprintf(stderr, "wireshark: Couldn't register rlc_lte_graph tap: %s\n",
915 error_string->str);
916 g_string_free(error_string, TRUE);
917 exit(1);
920 epan_dissect_init(&edt, cf->epan, TRUE, FALSE);
921 epan_dissect_prime_dfilter(&edt, sfcode);
922 epan_dissect_run_with_taps(&edt, &cf->phdr, frame_tvbuff_new_buffer(fdata, &cf->buf), fdata, NULL);
923 rel_ts = edt.pi.rel_ts;
924 epan_dissect_cleanup(&edt);
925 remove_tap_listener(&th);
927 if (th.num_hdrs == 0){
928 /* This "shouldn't happen", as our menu items shouldn't
929 * even be enabled if the selected packet isn't an RLC PDU
930 * as rlc_lte_graph_selected_packet_enabled() is used
931 * to determine whether to enable any of our menu items. */
932 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
933 "Selected packet doesn't have an RLC PDU");
934 return NULL;
936 /* XXX fix this later, we should show a dialog allowing the user
937 to select which session he wants here
939 if (th.num_hdrs>1){
940 /* can only handle a single RLC channel yet */
941 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
942 "The selected packet has more than one LTE RLC channel "
943 "in it.");
944 return NULL;
947 /* For now, still always choose the first/only one */
948 hdrs->num = fdata->num;
949 hdrs->rel_secs = (guint32) rel_ts.secs;
950 hdrs->rel_usecs = rel_ts.nsecs/1000;
951 hdrs->abs_secs = (guint32) fdata->abs_ts.secs;
952 hdrs->abs_usecs = fdata->abs_ts.nsecs/1000;
954 hdrs->ueid = th.rlchdrs[0]->ueid;
955 hdrs->channelType = th.rlchdrs[0]->channelType;
956 hdrs->channelId = th.rlchdrs[0]->channelId;
957 hdrs->rlcMode = th.rlchdrs[0]->rlcMode;
958 hdrs->isControlPDU = th.rlchdrs[0]->isControlPDU;
959 hdrs->direction = !hdrs->isControlPDU ? th.rlchdrs[0]->direction : !th.rlchdrs[0]->direction;
961 return th.rlchdrs[0];
964 static int compare_headers(guint16 ueid1, guint16 channelType1, guint16 channelId1, guint8 rlcMode1, guint8 direction1,
965 guint16 ueid2, guint16 channelType2, guint16 channelId2, guint8 rlcMode2, guint8 direction2,
966 gboolean frameIsControl)
968 /* Same direction, data - OK. */
969 if (!frameIsControl) {
970 return (direction1 == direction2) &&
971 (ueid1 == ueid2) &&
972 (channelType1 == channelType2) &&
973 (channelId1 == channelId2) &&
974 (rlcMode1 == rlcMode2);
976 else {
977 if (frameIsControl && (rlcMode1 == RLC_AM_MODE) && (rlcMode2 == RLC_AM_MODE)) {
978 return ((direction1 != direction2) &&
979 (ueid1 == ueid2) &&
980 (channelType1 == channelType2) &&
981 (channelId1 == channelId2));
983 else {
984 return FALSE;
989 /* Free all segments in the graph */
990 static void graph_segment_list_free(struct graph *g)
992 struct segment *segment;
994 while (g->segments) {
995 segment = g->segments->next;
996 g_free(g->segments);
997 g->segments = segment;
999 g->segments = NULL;
1002 static void graph_element_lists_initialize(struct graph *g)
1004 g->elists = (struct element_list *)g_malloc0(sizeof(struct element_list));
1005 g->elists->elements = NULL;
1006 g->elists->next = NULL;
1009 static void graph_element_lists_make(struct graph *g)
1011 debug(DBS_FENTRY) puts("graph_element_lists_make()");
1012 rlc_lte_make_elmtlist(g);
1015 static void graph_element_lists_free(struct graph *g)
1017 struct element_list *list, *next_list;
1019 for (list=g->elists; list; list=next_list) {
1020 g_free(list->elements);
1021 next_list = list->next;
1022 g_free(list);
1024 g->elists = NULL; /* just to make debugging easier */
1027 static void graph_title_pixmap_create(struct graph *g)
1029 #if GTK_CHECK_VERSION(2,22,0)
1030 if (g->title_surface){
1031 cairo_surface_destroy(g->title_surface);
1032 g->title_surface = NULL;
1035 g->title_surface = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area),
1036 CAIRO_CONTENT_COLOR,
1037 g->x_axis->p.width,
1038 g->wp.y);
1040 #else
1041 if (g->title_pixmap)
1042 g_object_unref(g->title_pixmap);
1044 g->title_pixmap = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area),
1045 g->x_axis->p.width, g->wp.y, -1);
1046 #endif
1049 static void graph_title_pixmap_draw(struct graph *g)
1051 gint w, h;
1052 PangoLayout *layout;
1053 cairo_t *cr;
1055 #if GTK_CHECK_VERSION(2,22,0)
1056 cr = cairo_create(g->title_surface);
1057 #else
1058 cr = gdk_cairo_create(g->title_pixmap);
1059 #endif
1060 cairo_set_source_rgb(cr, 1, 1, 1);
1061 cairo_rectangle(cr, 0, 0, g->x_axis->p.width, g->wp.y);
1062 cairo_fill(cr);
1064 layout = gtk_widget_create_pango_layout(g->drawing_area, "");
1065 pango_layout_get_pixel_size(layout, &w, &h);
1066 cairo_move_to(cr, g->wp.width/2 - w/2, 20);
1067 pango_cairo_show_layout(cr, layout);
1068 g_object_unref(G_OBJECT(layout));
1070 cairo_destroy(cr);
1073 static void graph_title_pixmap_display(struct graph *g)
1075 cairo_t *cr;
1077 cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area));
1078 #if GTK_CHECK_VERSION(2,22,0)
1079 cairo_set_source_surface(cr, g->title_surface, g->wp.x, 0);
1080 #else
1081 gdk_cairo_set_source_pixmap(cr, g->title_pixmap, g->wp.x, 0);
1082 #endif
1083 cairo_rectangle(cr, g->wp.x, 0, g->x_axis->p.width, g->wp.y);
1084 cairo_fill(cr);
1085 cairo_destroy(cr);
1088 static void graph_pixmaps_create(struct graph *g)
1090 debug(DBS_FENTRY) puts("graph_pixmaps_create()");
1091 #if GTK_CHECK_VERSION(2,22,0)
1092 if (g->surface[0]){
1093 cairo_surface_destroy(g->surface[0]);
1094 g->surface[0] = NULL;
1097 if (g->surface[1]){
1098 cairo_surface_destroy(g->surface[1]);
1099 g->surface[1] = NULL;
1102 g->surface[0] = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area),
1103 CAIRO_CONTENT_COLOR,
1104 g->wp.width,
1105 g->wp.height);
1107 g->surface[1] = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area),
1108 CAIRO_CONTENT_COLOR,
1109 g->wp.width,
1110 g->wp.height);
1112 g->displayed = 0;
1113 #else
1114 if (g->pixmap[0])
1115 g_object_unref(g->pixmap[0]);
1116 if (g->pixmap[1])
1117 g_object_unref(g->pixmap[1]);
1119 g->pixmap[0] = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area),
1120 g->wp.width, g->wp.height, -1);
1121 g->pixmap[1] = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area),
1122 g->wp.width, g->wp.height, -1);
1124 g->displayed = 0;
1125 #endif /* GTK_CHECK_VERSION(2,22,0) */
1129 static void graph_display(struct graph *g)
1131 set_busy_cursor(gtk_widget_get_window(g->drawing_area));
1132 graph_pixmap_draw(g);
1133 unset_busy_cursor(gtk_widget_get_window(g->drawing_area));
1134 graph_pixmaps_switch(g);
1135 graph_pixmap_display(g);
1138 static void graph_pixmap_display(struct graph *g)
1140 cairo_t *cr;
1142 cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area));
1143 #if GTK_CHECK_VERSION(2,22,0)
1144 cairo_set_source_surface(cr, g->surface[g->displayed], g->wp.x, g->wp.y);
1145 #else
1146 gdk_cairo_set_source_pixmap(cr, g->pixmap[g->displayed], g->wp.x, g->wp.y);
1147 #endif /* GTK_CHECK_VERSION(2,22,0) */
1148 cairo_rectangle(cr, g->wp.x, g->wp.y, g->wp.width, g->wp.height);
1149 cairo_fill(cr);
1150 cairo_destroy(cr);
1153 static void graph_pixmaps_switch(struct graph *g)
1155 g->displayed = 1 ^ g->displayed;
1158 static void graph_pixmap_draw(struct graph *g)
1160 struct element_list *list;
1161 struct element *e;
1162 int not_disp;
1163 cairo_t *cr;
1164 cairo_t *cr_elements;
1165 GdkRGBA *current_color = NULL;
1166 GdkRGBA *color_to_set = NULL;
1167 gboolean line_stroked = TRUE;
1169 debug(DBS_FENTRY) puts("graph_display()");
1170 not_disp = 1 ^ g->displayed;
1172 #if GTK_CHECK_VERSION(2,22,0)
1173 cr = cairo_create(g->surface[not_disp]);
1174 #else
1175 cr = gdk_cairo_create(g->pixmap[not_disp]);
1176 #endif /* GTK_CHECK_VERSION(2,22,0) */
1177 cairo_set_source_rgb(cr, 1, 1, 1);
1178 cairo_rectangle(cr, 0, 0, g->wp.width, g->wp.height);
1179 cairo_fill(cr);
1180 cairo_destroy(cr);
1182 /* Create one cairo_t for use with all of the lines, rather than continually
1183 creating and destroying one for each line */
1184 #if GTK_CHECK_VERSION(2,22,0)
1185 cr_elements = cairo_create(g->surface[not_disp]);
1186 #else
1187 cr_elements = gdk_cairo_create(g->pixmap[not_disp]);
1188 #endif
1190 /* N.B. This makes drawing circles take half the time of the default setting.
1191 Changing from the default fill rule didn't make any noticeable difference
1192 though */
1193 cairo_set_tolerance(cr_elements, 1.0);
1195 /* Line width is always 1 pixel */
1196 cairo_set_line_width(cr_elements, 1.0);
1198 /* Draw all elements */
1199 for (list=g->elists; list; list=list->next) {
1200 for (e=list->elements; e->type != ELMT_NONE; e++) {
1202 /* Work out if we need to change colour */
1203 if (current_color == e->elment_color_p) {
1204 /* No change needed */
1205 color_to_set = NULL;
1207 else {
1208 /* Changing colour */
1209 current_color = color_to_set = e->elment_color_p;
1210 cairo_stroke(cr_elements);
1213 switch (e->type) {
1214 case ELMT_LINE:
1215 /* Draw the line */
1216 draw_element_line(g, e, cr_elements, color_to_set);
1217 line_stroked = FALSE;
1218 break;
1220 case ELMT_ELLIPSE:
1221 if (!line_stroked) {
1222 cairo_stroke(cr_elements);
1223 line_stroked = TRUE;
1225 /* Draw the ellipse */
1226 draw_element_ellipse(g, e, cr_elements, color_to_set);
1227 break;
1229 default:
1230 /* No other element types supported at the moment */
1231 break;
1235 /* Make sure any remaining lines get drawn */
1236 if (!line_stroked) {
1237 cairo_stroke(cr_elements);
1241 cairo_destroy(cr_elements);
1244 static void draw_element_line(struct graph *g, struct element *e, cairo_t *cr,
1245 GdkRGBA *new_color)
1247 int xx1, xx2, yy1, yy2;
1249 debug(DBS_GRAPH_DRAWING)
1250 printf("\nline element: (%.2f,%.2f)->(%.2f,%.2f), seg %d ...\n",
1251 e->p.line.dim.x1, e->p.line.dim.y1,
1252 e->p.line.dim.x2, e->p.line.dim.y2, e->parent->num);
1254 /* Set our new colour (if changed) */
1255 if (new_color != NULL) {
1256 gdk_cairo_set_source_rgba(cr, new_color);
1259 /* Map point into graph area, and round to nearest int */
1260 xx1 = (int)rint(e->p.line.dim.x1 + g->geom.x - g->wp.x);
1261 xx2 = (int)rint(e->p.line.dim.x2 + g->geom.x - g->wp.x);
1262 yy1 = (int)rint((g->geom.height-1-e->p.line.dim.y1) + g->geom.y-g->wp.y);
1263 yy2 = (int)rint((g->geom.height-1-e->p.line.dim.y2) + g->geom.y-g->wp.y);
1265 /* If line completely out of the area, we won't show it */
1266 if (((xx1 < 0) && (xx2 < 0)) || ((xx1 >= g->wp.width) && (xx2 >= g->wp.width)) ||
1267 ((yy1 < 0) && (yy2 < 0)) || ((yy1 >= g->wp.height) && (yy2 >= g->wp.height))) {
1268 debug(DBS_GRAPH_DRAWING) printf(" refusing: (%d,%d)->(%d,%d)\n", xx1, yy1, xx2, yy2);
1269 return;
1272 /* If one end of the line is out of bounds, don't worry. Cairo will
1273 clip the line to the outside of g->wp at the correct angle! */
1275 debug(DBS_GRAPH_DRAWING) printf("line: (%d,%d)->(%d,%d)\n", xx1, yy1, xx2, yy2);
1277 /* Draw from first position to second */
1278 cairo_move_to(cr, xx1+0.5, yy1+0.5);
1279 cairo_line_to(cr, xx2+0.5, yy2+0.5);
1282 static void draw_element_ellipse(struct graph *g, struct element *e, cairo_t *cr,
1283 GdkRGBA *new_color)
1285 gdouble w = e->p.ellipse.dim.width;
1286 gdouble h = e->p.ellipse.dim.height;
1287 gdouble x = e->p.ellipse.dim.x + g->geom.x - g->wp.x;
1288 gdouble y = g->geom.height-1 - e->p.ellipse.dim.y + g->geom.y - g->wp.y;
1290 debug(DBS_GRAPH_DRAWING) printf ("ellipse: (x, y) -> (w, h): (%f, %f) -> (%f, %f)\n", x, y, w, h);
1292 /* Set our new colour (if changed) */
1293 if (new_color != NULL) {
1294 gdk_cairo_set_source_rgba(cr, new_color);
1297 cairo_save(cr);
1298 cairo_translate(cr, x + w/2.0, y + h/2.0);
1299 cairo_scale(cr, w/2.0, h/2.0);
1300 cairo_arc(cr, 0.0, 0.0, 1.0, 0.0, 2*G_PI);
1301 cairo_fill(cr);
1302 cairo_restore(cr);
1306 static void axis_pixmaps_create(struct axis *axis)
1308 debug(DBS_FENTRY) puts("axis_pixmaps_create()");
1309 #if GTK_CHECK_VERSION(2,22,0)
1310 if (axis->surface[0]){
1311 cairo_surface_destroy(axis->surface[0]);
1312 axis->surface[0] = NULL;
1314 if (axis->surface[1]){
1315 cairo_surface_destroy(axis->surface[1]);
1316 axis->surface[1] = NULL;
1318 axis->surface[0] = gdk_window_create_similar_surface(gtk_widget_get_window(axis->drawing_area),
1319 CAIRO_CONTENT_COLOR,
1320 axis->p.width,
1321 axis->p.height);
1323 axis->surface[1] = gdk_window_create_similar_surface(gtk_widget_get_window(axis->drawing_area),
1324 CAIRO_CONTENT_COLOR,
1325 axis->p.width,
1326 axis->p.height);
1328 axis->displayed = 0;
1329 #else
1330 if (axis->pixmap[0])
1331 g_object_unref(axis->pixmap[0]);
1332 if (axis->pixmap[1])
1333 g_object_unref(axis->pixmap[1]);
1335 axis->pixmap[0] = gdk_pixmap_new(gtk_widget_get_window(axis->drawing_area),
1336 axis->p.width, axis->p.height, -1);
1337 axis->pixmap[1] = gdk_pixmap_new(gtk_widget_get_window(axis->drawing_area),
1338 axis->p.width, axis->p.height, -1);
1340 axis->displayed = 0;
1341 #endif
1344 static void axis_destroy(struct axis *axis)
1346 #if GTK_CHECK_VERSION(2,22,0)
1347 if (axis->surface[0]){
1348 cairo_surface_destroy(axis->surface[0]);
1349 axis->surface[0] = NULL;
1351 if (axis->surface[1]){
1352 cairo_surface_destroy(axis->surface[1]);
1353 axis->surface[1] = NULL;
1355 #else
1356 g_object_unref(axis->pixmap[0]);
1357 g_object_unref(axis->pixmap[1]);
1358 #endif
1359 g_free((gpointer)(axis->label) );
1362 static void axis_display(struct axis *axis)
1364 if (axis->flags & AXIS_ORIENTATION)
1365 h_axis_pixmap_draw(axis);
1366 else
1367 v_axis_pixmap_draw(axis);
1369 axis_pixmaps_switch(axis);
1370 axis_pixmap_display(axis);
1373 /* These show sequence numbers. Avoid subdividing whole numbers. */
1374 static void v_axis_pixmap_draw(struct axis *axis)
1376 struct graph *g = axis->g;
1377 int i;
1378 double major_tick;
1379 int not_disp, offset, imin, imax;
1380 double bottom, top, fl, corr;
1381 PangoLayout *layout;
1382 cairo_t *cr;
1384 debug(DBS_FENTRY) puts("v_axis_pixmap_draw()");
1386 /* Work out extent of axis */
1387 bottom = (g->geom.height - (g->wp.height + g->wp.y + (-g->geom.y))) /
1388 (double )g->geom.height * g->bounds.height;
1389 bottom += axis->min;
1390 top = (g->geom.height - (g->wp.y + (-g->geom.y))) /
1391 (double )g->geom.height * g->bounds.height;
1392 top += axis->min;
1393 axis_compute_ticks(axis, bottom, top, AXIS_VERTICAL);
1395 not_disp = 1 ^ axis->displayed;
1397 #if GTK_CHECK_VERSION(2,22,0)
1398 cr = cairo_create(axis->surface[not_disp]);
1399 #else
1400 cr = gdk_cairo_create(axis->pixmap[not_disp]);
1401 #endif
1402 cairo_set_source_rgb(cr, 1, 1, 1);
1403 cairo_rectangle(cr, 0, 0, axis->p.width, axis->p.height);
1404 cairo_fill(cr);
1406 /* axis */
1407 cairo_set_source_rgb(cr, 0, 0, 0);
1408 cairo_set_line_width(cr, 1.0);
1409 cairo_move_to(cr, axis->p.width - 1.5, (axis->p.height-axis->s.height)/2.0);
1410 cairo_line_to(cr, axis->s.width - 1.5, axis->p.height);
1412 offset = g->wp.y + (-g->geom.y);
1413 fl = floor(axis->min / axis->major) * axis->major;
1414 corr = rint((axis->min - fl) * g->zoom.y);
1416 /* major ticks */
1417 major_tick = axis->major * g->zoom.y;
1418 imin = (int) ((g->geom.height - offset + corr - g->wp.height) / major_tick + 1);
1419 imax = (int) ((g->geom.height - offset + corr) / major_tick);
1420 for (i=imin; i <= imax; i++) {
1421 gint w, h;
1422 char desc[32];
1423 int y = (int) (g->geom.height-1 - (int )rint(i * major_tick) -
1424 offset + corr + axis->s.y);
1426 debug(DBS_AXES_DRAWING) printf("%f @ %d\n",
1427 i*axis->major + fl, y);
1428 if ((y < 0) || (y > axis->p.height))
1429 continue;
1431 cairo_move_to(cr, axis->p.width - 15, y+0.5);
1432 cairo_line_to(cr, axis->s.width - 1, y+0.5);
1434 /* Won't be showing any decimal places here... */
1435 g_snprintf(desc, sizeof(desc), "%u", (unsigned int)(i*axis->major + fl));
1436 layout = gtk_widget_create_pango_layout(g->drawing_area, desc);
1437 pango_layout_get_pixel_size(layout, &w, &h);
1438 cairo_move_to(cr, axis->s.width-14-4-w, y - h/2);
1439 pango_cairo_show_layout(cr, layout);
1440 g_object_unref(G_OBJECT(layout));
1442 /* minor ticks */
1443 if (axis->minor) {
1444 double minor_tick = axis->minor * g->zoom.y;
1445 imin = (int) ((g->geom.height - offset + corr - g->wp.height)/minor_tick + 1);
1446 imax = (int) ((g->geom.height - offset + corr) / minor_tick);
1447 for (i=imin; i <= imax; i++) {
1448 int y = (int) (g->geom.height-1 - (int )rint(i*minor_tick) -
1449 offset + corr + axis->s.y);
1451 if ((y > 0) && (y < axis->p.height)) {
1452 cairo_set_line_width(cr, 1.0);
1453 cairo_move_to(cr, axis->s.width - 8, y+0.5);
1454 cairo_line_to(cr, axis->s.width - 1, y+0.5);
1458 for (i=0; axis->label[i]; i++) {
1459 gint w, h;
1460 layout = gtk_widget_create_pango_layout(g->drawing_area,
1461 axis->label[i]);
1462 pango_layout_get_pixel_size(layout, &w, &h);
1463 cairo_move_to(cr, (axis->p.width - w)/2, TITLEBAR_HEIGHT-10 - i*(h+3) - h);
1464 pango_cairo_show_layout(cr, layout);
1465 g_object_unref(G_OBJECT(layout));
1467 cairo_stroke(cr);
1468 cairo_destroy(cr);
1472 /* TODO: natural time units are subframes (ms), so might be good to always
1473 show 3 decimal places? */
1474 static void h_axis_pixmap_draw(struct axis *axis)
1476 struct graph *g = axis->g;
1477 int i;
1478 double major_tick, minor_tick;
1479 int not_disp, rdigits, offset, imin, imax;
1480 double left, right, j, fl, corr;
1481 PangoLayout *layout;
1482 cairo_t *cr;
1484 debug(DBS_FENTRY) puts("h_axis_pixmap_draw()");
1485 left = (g->wp.x-g->geom.x) / (double)g->geom.width * g->bounds.width;
1486 left += axis->min;
1487 right = (g->wp.x-g->geom.x+g->wp.width) / (double)g->geom.width * g->bounds.width;
1488 right += axis->min;
1489 axis_compute_ticks(axis, left, right, AXIS_HORIZONTAL);
1491 /* Work out how many decimal places should be shown */
1492 j = axis->major - floor(axis->major);
1493 for (rdigits=0; rdigits <= 6; rdigits++) {
1494 j *= 10;
1495 if (j <= 0.000001)
1496 break;
1497 j = j - floor(j);
1500 not_disp = 1 ^ axis->displayed;
1502 #if GTK_CHECK_VERSION(2,22,0)
1503 cr = cairo_create(axis->surface[not_disp]);
1504 #else
1505 cr = gdk_cairo_create(axis->pixmap[not_disp]);
1506 #endif
1507 cairo_set_source_rgb(cr, 1, 1, 1);
1508 cairo_rectangle(cr, 0, 0, axis->p.width, axis->p.height);
1509 cairo_fill(cr);
1511 /* axis */
1512 cairo_set_source_rgb(cr, 0, 0, 0);
1513 cairo_set_line_width(cr, 1.0);
1514 cairo_move_to(cr, 0, 0.5);
1515 cairo_line_to(cr, axis->s.width + (axis->p.width-axis->s.width)/2.0, 0.5);
1517 offset = g->wp.x - g->geom.x;
1519 fl = floor(axis->min / axis->major) * axis->major;
1520 corr = rint((axis->min - fl) * g->zoom.x);
1522 /* major ticks */
1523 major_tick = axis->major*g->zoom.x;
1524 imin = (int) ((offset + corr) / major_tick + 1);
1525 imax = (int) ((offset + corr + axis->s.width) / major_tick);
1526 for (i=imin; i <= imax; i++) {
1527 char desc[32];
1528 int w, h;
1529 int x = (int) (rint(i * major_tick) - offset - corr);
1531 /* printf("%f @ %d\n", i*axis->major + fl, x); */
1532 if ((x < 0) || (x > axis->s.width))
1533 continue;
1534 cairo_move_to(cr, x+0.5, 0);
1535 cairo_line_to(cr, x+0.5, 15);
1537 g_snprintf(desc, sizeof(desc), "%.*f", rdigits, i*axis->major + fl);
1538 layout = gtk_widget_create_pango_layout(g->drawing_area, desc);
1539 pango_layout_get_pixel_size(layout, &w, &h);
1540 cairo_move_to(cr, x - w/2, 15+4);
1541 pango_cairo_show_layout(cr, layout);
1542 g_object_unref(G_OBJECT(layout));
1544 if (axis->minor > 0) {
1545 /* minor ticks */
1546 minor_tick = axis->minor*g->zoom.x;
1547 imin = (int) ((offset + corr) / minor_tick + 1);
1548 imax = (int) ((offset + corr + g->wp.width) / minor_tick);
1549 for (i=imin; i <= imax; i++) {
1550 int x = (int) (rint(i * minor_tick) - offset - corr);
1551 if ((x > 0) && (x < axis->s.width)){
1552 cairo_move_to(cr, x+0.5, 0);
1553 cairo_line_to(cr, x+0.5, 8);
1557 for (i=0; axis->label[i]; i++) {
1558 gint w, h;
1559 layout = gtk_widget_create_pango_layout(g->drawing_area,
1560 axis->label[i]);
1561 pango_layout_get_pixel_size(layout, &w, &h);
1562 cairo_move_to(cr, axis->s.width - w - 50, 15+h+15 + i*(h+3));
1563 pango_cairo_show_layout(cr, layout);
1564 g_object_unref(G_OBJECT(layout));
1567 cairo_stroke(cr);
1568 cairo_destroy(cr);
1571 static void axis_pixmaps_switch(struct axis *axis)
1573 axis->displayed = 1 ^ axis->displayed;
1576 static void axis_pixmap_display(struct axis *axis)
1578 cairo_t *cr;
1580 cr = gdk_cairo_create(gtk_widget_get_window(axis->drawing_area));
1581 #if GTK_CHECK_VERSION(2,22,0)
1582 cairo_set_source_surface(cr, axis->surface[axis->displayed], axis->p.x, axis->p.y);
1583 #else
1584 gdk_cairo_set_source_pixmap(cr, axis->pixmap[axis->displayed], axis->p.x, axis->p.y);
1585 #endif
1586 cairo_rectangle(cr, axis->p.x, axis->p.y, axis->p.width, axis->p.height);
1587 cairo_fill(cr);
1588 cairo_destroy(cr);
1591 static void axis_compute_ticks(struct axis *axis, double x0, double xmax, int dir)
1593 int i, j, ii, jj, ms;
1594 double zoom, x, steps[3] = { 0.1, 0.5 };
1595 int dim, check_needed, diminished;
1596 double majthresh[2] = {2.0, 3.0};
1598 debug((DBS_FENTRY | DBS_AXES_TICKS)) puts("axis_compute_ticks()");
1599 debug(DBS_AXES_TICKS)
1600 printf("x0=%f xmax=%f dir=%s\n", x0,xmax, dir? "VERTICAL" : "HORIZONTAL");
1602 zoom = axis_zoom_get(axis, dir);
1603 x = xmax-x0;
1604 for (i=-9; i <= 12; i++) {
1605 if (x / pow(10, i) < 1)
1606 break;
1608 --i;
1609 ms = (int )(x / pow(10, i));
1611 if (ms > 5) {
1612 j = 0;
1613 ++i;
1614 } else if (ms > 2)
1615 j = 1;
1616 else
1617 j = 0;
1619 axis->major = steps[j] * pow(10, i);
1620 if (dir == AXIS_VERTICAL) {
1621 /* But don't divide further than whole sequence numbers */
1622 axis->major = MAX(axis->major, 1.0);
1625 debug(DBS_AXES_TICKS) printf("zoom=%.1f, x=%f -> i=%d -> ms=%d -> j=%d ->"
1626 " axis->major=%f\n", zoom, x, i, ms, j, axis->major);
1628 /* Compute minor ticks */
1629 jj = j;
1630 ii = i;
1631 axis_ticks_down(&ii, &jj);
1633 if ((dir == AXIS_VERTICAL) && (axis->major <= 1)) {
1634 /* Don't subdivide whole sequence numbers */
1635 axis->minor = 0;
1637 else {
1638 axis->minor = steps[jj] * pow(10, ii);
1639 /* We don't want minors if they would be less than 10 pixels apart */
1640 if (axis->minor*zoom < 10) {
1641 debug(DBS_AXES_TICKS) printf("refusing axis->minor of %f: "
1642 "axis->minor*zoom == %f\n",
1643 axis->minor, axis->minor*zoom);
1644 axis->minor = 0;
1648 check_needed = TRUE;
1649 diminished = FALSE;
1650 while (check_needed) {
1651 check_needed = FALSE;
1652 dim = get_label_dim(axis, dir, xmax);
1653 debug(DBS_AXES_TICKS) printf("axis->major==%.1f, axis->minor==%.1f =>"
1654 " axis->major*zoom/dim==%f, axis->minor*zoom/dim==%f\n",
1655 axis->major, axis->minor, axis->major*zoom/dim,
1656 axis->minor*zoom/dim);
1658 /* corrections: if majors are less than majthresh[dir] times label
1659 * dimension apart, we need to use bigger ones */
1660 if (axis->major*zoom / dim < majthresh[dir]) {
1661 axis_ticks_up(&ii, &jj);
1662 axis->minor = axis->major;
1663 axis_ticks_up(&i, &j);
1664 axis->major = steps[j] * pow(10, i);
1665 check_needed = TRUE;
1666 debug(DBS_AXES_TICKS) printf("axis->major enlarged to %.1f\n",
1667 axis->major);
1669 /* if minor ticks are bigger than majthresh[dir] times label dimension,
1670 * we could promote them to majors as well */
1671 if ((axis->minor*zoom / dim > majthresh[dir]) && !diminished) {
1672 axis_ticks_down(&i, &j);
1673 axis->major = axis->minor;
1674 axis_ticks_down(&ii, &jj);
1675 axis->minor = steps[jj] * pow(10, ii);
1676 check_needed = TRUE;
1677 diminished = TRUE;
1679 debug(DBS_AXES_TICKS) printf("axis->minor diminished to %.1f\n",
1680 axis->minor);
1682 if (axis->minor*zoom < 10) {
1683 debug(DBS_AXES_TICKS) printf("refusing axis->minor of %f: "
1684 "axis->minor*zoom == %f\n", axis->minor, axis->minor*zoom);
1685 axis->minor = 0;
1690 debug(DBS_AXES_TICKS) printf("corrected: axis->major == %.1f -> "
1691 "axis->minor == %.1f\n", axis->major, axis->minor);
1694 static void axis_ticks_up(int *i, int *j)
1696 (*j)++;
1697 if (*j>1) {
1698 (*i)++;
1699 *j = 0;
1703 static void axis_ticks_down(int *i, int *j)
1705 (*j)--;
1706 if (*j < 0) {
1707 (*i)--;
1708 *j = 1;
1712 static int get_label_dim(struct axis *axis, int dir, double label)
1714 double y;
1715 char str[32];
1716 int rdigits, dim;
1717 PangoLayout *layout;
1719 /* First, let's compute how many digits to the right of radix
1720 * we need to print */
1721 y = axis->major - floor(axis->major);
1722 for (rdigits=0; rdigits <= 6; rdigits++) {
1723 y *= 10;
1724 if (y <= 0.000001)
1725 break;
1726 y = y - floor(y);
1728 g_snprintf(str, sizeof(str), "%.*f", rdigits, label);
1729 switch (dir) {
1730 case AXIS_HORIZONTAL:
1731 layout = gtk_widget_create_pango_layout(axis->g->drawing_area, str);
1732 pango_layout_get_pixel_size(layout, &dim, NULL);
1733 g_object_unref(G_OBJECT(layout));
1734 break;
1735 case AXIS_VERTICAL:
1736 layout = gtk_widget_create_pango_layout(axis->g->drawing_area, str);
1737 pango_layout_get_pixel_size(layout, NULL, &dim);
1738 g_object_unref(G_OBJECT(layout));
1739 break;
1740 default:
1741 puts("initialize axis: an axis must be either horizontal or vertical");
1742 return -1;
1744 return dim;
1747 static double axis_zoom_get(struct axis *axis, int dir)
1749 switch (dir) {
1750 case AXIS_HORIZONTAL:
1751 return axis->g->zoom.x;
1752 case AXIS_VERTICAL:
1753 return axis->g->zoom.y;
1754 default:
1755 return -1;
1759 static void graph_select_segment(struct graph *g, int x, int y)
1761 struct element_list *list;
1762 struct element *e;
1763 guint num = 0;
1765 debug(DBS_FENTRY) puts("graph_select_segment()");
1767 x -= g->geom.x;
1768 y = g->geom.height-1 - (y - g->geom.y);
1770 set_busy_cursor(gtk_widget_get_window(g->drawing_area));
1772 for (list=g->elists; list; list=list->next) {
1773 for (e=list->elements; e->type != ELMT_NONE; e++) {
1774 switch (e->type) {
1775 case ELMT_LINE:
1776 if (line_detect_collision(e, x, y)) {
1777 num = e->parent->num;
1779 break;
1780 case ELMT_ELLIPSE:
1781 if (ellipse_detect_collision(e, x, y)) {
1782 num = e->parent->num;
1784 break;
1786 default:
1787 break;
1793 if (num) {
1794 cf_goto_frame(&cfile, num);
1798 static int line_detect_collision(struct element *e, int x, int y)
1800 int xx1, yy1, xx2, yy2;
1802 /* Get sorted x, y co-ordinates for line */
1803 if (e->p.line.dim.x1 < e->p.line.dim.x2) {
1804 xx1 = (int)rint(e->p.line.dim.x1);
1805 xx2 = (int)rint(e->p.line.dim.x2);
1806 } else {
1807 xx1 = (int)rint(e->p.line.dim.x2);
1808 xx2 = (int)rint(e->p.line.dim.x1);
1810 if (e->p.line.dim.y1 < e->p.line.dim.y2) {
1811 yy1 = (int)rint(e->p.line.dim.y1);
1812 yy2 = (int)rint(e->p.line.dim.y2);
1813 } else {
1814 yy1 = (int)rint(e->p.line.dim.y2);
1815 yy2 = (int)rint(e->p.line.dim.y1);
1818 printf("line: (%d,%d)->(%d,%d), clicked: (%d,%d)\n", xx1, yy1, xx2, yy2, x, y);
1821 /* N.B. won't match with diagonal lines... */
1822 if (((xx1 == x) && (xx2 == x) && (yy1 <= y) && (y <= yy2) )| /* lies along vertical line */
1823 ((yy1 == y) && (yy2 == y) && (xx1 <= x) && (x <= xx2))) { /* lies along horizontal line */
1824 return TRUE;
1826 else {
1827 return FALSE;
1831 static int ellipse_detect_collision(struct element *e, int x, int y)
1833 int xx1, yy1, xx2, yy2;
1835 xx1 = (int )rint (e->p.ellipse.dim.x);
1836 xx2 = (int )rint (e->p.ellipse.dim.x + e->p.ellipse.dim.width);
1837 yy1 = (int )rint (e->p.ellipse.dim.y - e->p.ellipse.dim.height);
1838 yy2 = (int )rint (e->p.ellipse.dim.y);
1840 printf ("ellipse: (%d,%d)->(%d,%d), clicked: (%d,%d)\n", xx1, yy1, xx2, yy2, x, y);
1842 if ((xx1 <= x) && (x <= xx2) && (yy1 <= y) && (y <= yy2)) {
1843 return TRUE;
1845 else {
1846 return FALSE;
1852 static gboolean configure_event(GtkWidget *widget _U_, GdkEventConfigure *event, gpointer user_data)
1854 struct graph *g = (struct graph *)user_data;
1855 struct zoom new_zoom;
1856 int cur_g_width, cur_g_height;
1857 int cur_wp_width, cur_wp_height;
1859 debug(DBS_FENTRY) puts("configure_event()");
1861 cur_wp_width = g->wp.width;
1862 cur_wp_height = g->wp.height;
1863 g->wp.width = event->width - g->y_axis->p.width - RMARGIN_WIDTH;
1864 g->wp.height = event->height - g->x_axis->p.height - g->wp.y;
1865 g->x_axis->s.width = g->wp.width;
1866 g->x_axis->p.width = g->wp.width + RMARGIN_WIDTH;
1867 g->y_axis->p.height = g->wp.height + g->wp.y;
1868 g->y_axis->s.height = g->wp.height;
1869 g->x_axis->p.y = g->y_axis->p.height;
1870 new_zoom.x = (double)g->wp.width / cur_wp_width;
1871 new_zoom.y = (double)g->wp.height / cur_wp_height;
1872 cur_g_width = g->geom.width;
1873 cur_g_height = g->geom.height;
1874 g->geom.width = (int)rint(g->geom.width * new_zoom.x);
1875 g->geom.height = (int)rint(g->geom.height * new_zoom.y);
1876 g->zoom.x = (double)(g->geom.width - 1) / g->bounds.width;
1877 g->zoom.y = (double)(g->geom.height -1) / g->bounds.height;
1879 g->geom.x = (int)(g->wp.x - (double)g->geom.width/cur_g_width * (g->wp.x - g->geom.x));
1880 g->geom.y = (int)(g->wp.y - (double)g->geom.height/cur_g_height * (g->wp.y - g->geom.y));
1881 #if 0
1882 printf("configure: graph: (%d,%d), (%d,%d); viewport: (%d,%d), (%d,%d); "
1883 "zooms: (%f,%f)\n", g->geom.x, g->geom.y, g->geom.width,
1884 g->geom.height, g->wp.x, g->wp.y, g->wp.width, g->wp.height,
1885 g->zoom.x, g->zoom.y);
1886 #endif
1888 graph_element_lists_make(g);
1889 graph_pixmaps_create(g);
1890 graph_title_pixmap_create(g);
1891 axis_pixmaps_create(g->y_axis);
1892 axis_pixmaps_create(g->x_axis);
1893 /* we don't do actual drawing here; we leave it to expose handler */
1894 graph_pixmap_draw(g);
1895 graph_pixmaps_switch(g);
1896 graph_title_pixmap_draw(g);
1897 h_axis_pixmap_draw(g->x_axis);
1898 axis_pixmaps_switch(g->x_axis);
1899 v_axis_pixmap_draw(g->y_axis);
1900 axis_pixmaps_switch(g->y_axis);
1902 return TRUE;
1906 #if GTK_CHECK_VERSION(3,0,0)
1907 static gboolean
1908 draw_event(GtkWidget *widget _U_, cairo_t *cr, gpointer user_data)
1910 struct graph *g = (struct graph *)user_data;
1912 debug(DBS_FENTRY) puts("draw_event()");
1914 /* lower left corner */
1915 cairo_set_source_rgb(cr, 1, 1, 1);
1916 cairo_rectangle(cr, 0, g->wp.y + g->wp.height, g->y_axis->p.width, g->x_axis->p.height);
1917 cairo_fill(cr);
1919 /* right margin */
1920 cairo_rectangle(cr, g->wp.x + g->wp.width, g->wp.y, RMARGIN_WIDTH, g->wp.height);
1921 cairo_fill(cr);
1923 /* Should these routines be copied here, or be given the cairo_t ?? */
1924 graph_pixmap_display(g);
1925 graph_title_pixmap_display(g);
1926 axis_pixmap_display(g->x_axis);
1927 axis_pixmap_display(g->y_axis);
1929 return TRUE;
1931 #else
1932 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
1934 struct graph *g = (struct graph *)user_data;
1935 cairo_t *cr;
1937 debug(DBS_FENTRY) puts("expose_event()");
1939 if (event->count)
1940 return TRUE;
1942 /* lower left corner */
1943 cr = gdk_cairo_create(gtk_widget_get_window(widget));
1944 cairo_set_source_rgb(cr, 1, 1, 1);
1945 cairo_rectangle(cr, 0, g->wp.y + g->wp.height, g->y_axis->p.width, g->x_axis->p.height);
1946 cairo_fill(cr);
1947 cairo_destroy(cr);
1948 cr = NULL;
1950 /* right margin */
1951 cr = gdk_cairo_create(gtk_widget_get_window(widget));
1952 cairo_set_source_rgb(cr, 1, 1, 1);
1953 cairo_rectangle(cr, g->wp.x + g->wp.width, g->wp.y, RMARGIN_WIDTH, g->wp.height);
1954 cairo_fill(cr);
1955 cairo_destroy(cr);
1956 cr = NULL;
1958 graph_pixmap_display(g);
1959 graph_title_pixmap_display(g);
1960 axis_pixmap_display(g->x_axis);
1961 axis_pixmap_display(g->y_axis);
1963 return TRUE;
1965 #endif
1968 static void
1969 perform_zoom(struct graph *g, struct zoomfactor *zf,
1970 int origin_x, int origin_y)
1972 int cur_width = g->geom.width, cur_height = g->geom.height;
1974 /* Multiply by x and y factors */
1975 g->geom.width = (int )rint(g->geom.width * zf->x);
1976 g->geom.height = (int )rint(g->geom.height * zf->y);
1978 /* If already fully-zoomed out, don't waste time re-drawing */
1979 if ((g->geom.width <= g->wp.width) &&
1980 (g->geom.height <= g->wp.height)) {
1981 return;
1984 /* Don't go out of bounds */
1985 if (g->geom.width < g->wp.width) {
1986 g->geom.width = g->wp.width;
1988 if (g->geom.height < g->wp.height) {
1989 g->geom.height = g->wp.height;
1992 /* Divide to work out new zoom */
1993 g->zoom.x = (g->geom.width - 1) / g->bounds.width;
1994 g->zoom.y = (g->geom.height- 1) / g->bounds.height;
1996 /* Move origin to keep mouse position at centre of view */
1997 g->geom.x -= (int)rint((g->geom.width - cur_width) *
1998 ((origin_x - g->geom.x)/(double )cur_width));
1999 g->geom.y -= (int)rint((g->geom.height - cur_height) *
2000 ((origin_y - g->geom.y)/(double )cur_height));
2002 /* Again, don't go out of bounds */
2003 if (g->geom.x > g->wp.x)
2004 g->geom.x = g->wp.x;
2005 if (g->geom.y > g->wp.y)
2006 g->geom.y = g->wp.y;
2007 if (g->wp.x + g->wp.width > g->geom.x + g->geom.width)
2008 g->geom.x = g->wp.width + g->wp.x - g->geom.width;
2009 if (g->wp.y + g->wp.height > g->geom.y + g->geom.height)
2010 g->geom.y = g->wp.height + g->wp.y - g->geom.height;
2013 static void
2014 do_zoom_rectangle(struct graph *g, struct irect lcl_zoomrect)
2016 int cur_width = g->wp.width, cur_height = g->wp.height;
2017 /* Make copy of geom1 before working out zoom */
2018 struct irect geom1 = g->geom;
2019 struct zoomfactor factor;
2021 /* Left hand too much to the right */
2022 if (lcl_zoomrect.x > g->wp.x + g->wp.width)
2023 return;
2024 /* Right hand not far enough */
2025 if (lcl_zoomrect.x + lcl_zoomrect.width < g->wp.x)
2026 return;
2027 /* Left hand too much to the left */
2028 if (lcl_zoomrect.x < g->wp.x) {
2029 int dx = g->wp.x - lcl_zoomrect.x;
2030 lcl_zoomrect.x += dx;
2031 lcl_zoomrect.width -= dx;
2033 /* Right hand too much to the right */
2034 if (lcl_zoomrect.x + lcl_zoomrect.width > g->wp.x + g->wp.width) {
2035 int dx = lcl_zoomrect.width + lcl_zoomrect.x - g->wp.x - g->wp.width;
2036 lcl_zoomrect.width -= dx;
2039 /* Top too low */
2040 if (lcl_zoomrect.y > g->wp.y + g->wp.height)
2041 return;
2042 /* Bottom too high */
2043 if (lcl_zoomrect.y + lcl_zoomrect.height < g->wp.y)
2044 return;
2045 /* Top too high */
2046 if (lcl_zoomrect.y < g->wp.y) {
2047 int dy = g->wp.y - lcl_zoomrect.y;
2048 lcl_zoomrect.y += dy;
2049 lcl_zoomrect.height -= dy;
2051 /* Bottom too low */
2052 if (lcl_zoomrect.y + lcl_zoomrect.height > g->wp.y + g->wp.height) {
2053 int dy = lcl_zoomrect.height + lcl_zoomrect.y - g->wp.y - g->wp.height;
2054 lcl_zoomrect.height -= dy;
2058 printf("before:\n"
2059 "\tgeom: (%d, %d)+(%d x %d)\n"
2062 factor.x = (double)cur_width / lcl_zoomrect.width;
2063 factor.y = (double)cur_height / lcl_zoomrect.height;
2066 printf("Zoomfactor: %f x %f\n", factor.x, factor.y);
2068 /* Work out new geom settings and zoom factor */
2069 perform_zoom(g, &factor,
2070 lcl_zoomrect.x, lcl_zoomrect.y);
2073 printf("middle:\n"
2074 "\tgeom: (%d, %d)+(%d x %d)\n"
2075 "\twp: (%d, %d)+(%d x %d)\n"
2076 "\tzoomrect: (%d, %d)+(%d x %d)\n",
2077 g->geom.x, g->geom.y,
2078 g->geom.width, g->geom.height,
2079 g->wp.x, g->wp.y, g->wp.width, g->wp.height,
2080 lcl_zoomrect.x, lcl_zoomrect.y, lcl_zoomrect.width, lcl_zoomrect.height);
2083 /* Final geom settings are in terms of old geom, zoomreect and zoom factor */
2084 g->geom.x = (int)(geom1.x * (1 + factor.x) -
2085 lcl_zoomrect.x * factor.x - (geom1.x - g->wp.x));
2086 g->geom.y = (int)(geom1.y * (1 + factor.y) -
2087 lcl_zoomrect.y * factor.y - (geom1.y - g->wp.y));
2090 printf("after:\n"
2091 "\tgeom: (%d, %d)+(%d x %d)\n"
2092 "\twp: (%d, %d)+(%d x %d)\n"
2093 "\tzoomrect: (%d, %d)+(%d x %d)\n",
2094 g->geom.x, g->geom.y,
2095 g->geom.width, g->geom.height,
2096 g->wp.x, g->wp.y, g->wp.width, g->wp.height,
2097 lcl_zoomrect.x, lcl_zoomrect.y, lcl_zoomrect.width, lcl_zoomrect.height);
2100 /* Redraw */
2101 graph_element_lists_make(g);
2102 g->cross.erase_needed = FALSE;
2103 graph_display(g);
2104 axis_display(g->y_axis);
2105 axis_display(g->x_axis);
2109 /* Zoom because of keyboard or mouse press */
2110 static void do_zoom_common(struct graph *g, GdkEventButton *event,
2111 gboolean lock_vertical, gboolean lock_horizontal)
2113 int cur_width = g->geom.width, cur_height = g->geom.height;
2114 struct { double x, y; } factor;
2115 int pointer_x, pointer_y;
2117 /* Get mouse position */
2118 if (event == NULL) {
2119 /* Keyboard - query it */
2120 get_mouse_position(g->drawing_area, &pointer_x, &pointer_y, NULL);
2122 else {
2123 /* Mouse - just read it from event */
2124 pointer_x = (int)event->x;
2125 pointer_y = (int)event->y;
2129 if (g->zoom.flags & ZOOM_OUT) {
2131 /* If can't zoom out anymore so don't waste time redrawing the whole graph! */
2132 if ((g->geom.height <= g->wp.height) &&
2133 (g->geom.width <= g->wp.width)) {
2134 return;
2137 /* Zoom out */
2138 if (lock_horizontal) {
2139 factor.x = 1.0;
2141 else {
2142 factor.x = 1 / g->zoom.step_x;
2145 if (lock_vertical) {
2146 factor.y = 1.0;
2148 else {
2149 factor.y = 1 / g->zoom.step_y;
2151 } else {
2152 /* Zoom in */
2153 if ((lock_horizontal) || (g->geom.width >= (g->bounds.width * MAX_PIXELS_PER_SECOND))) {
2154 factor.x = 1.0;
2156 else {
2157 factor.x = g->zoom.step_x;
2160 /* Don't zoom in too far vertically */
2161 if (lock_vertical || (g->geom.height >= (g->bounds.height * MAX_PIXELS_PER_SN))) {
2162 factor.y = 1.0;
2164 else {
2165 factor.y = g->zoom.step_y;
2169 /* Multiply by x and y factors */
2170 g->geom.width = (int )rint(g->geom.width * factor.x);
2171 g->geom.height = (int )rint(g->geom.height * factor.y);
2173 /* Clip to space if necessary */
2174 if (g->geom.width < g->wp.width)
2175 g->geom.width = g->wp.width;
2176 if (g->geom.height < g->wp.height)
2177 g->geom.height = g->wp.height;
2179 /* Work out new zoom */
2180 g->zoom.x = (g->geom.width - 1) / g->bounds.width;
2181 g->zoom.y = (g->geom.height- 1) / g->bounds.height;
2183 /* Move origin to keep mouse position at centre of view */
2184 g->geom.x -= (int )rint((g->geom.width - cur_width) *
2185 ((pointer_x - g->geom.x)/(double)cur_width));
2186 g->geom.y -= (int )rint((g->geom.height - cur_height) *
2187 ((pointer_y - g->geom.y)/(double)cur_height));
2189 /* Make sure we haven't moved outside the whole graph */
2190 if (g->geom.x > g->wp.x)
2191 g->geom.x = g->wp.x;
2192 if (g->geom.y > g->wp.y)
2193 g->geom.y = g->wp.y;
2194 if (g->wp.x + g->wp.width > g->geom.x + g->geom.width)
2195 g->geom.x = g->wp.width + g->wp.x - g->geom.width;
2196 if (g->wp.y + g->wp.height > g->geom.y + g->geom.height)
2197 g->geom.y = g->wp.height + g->wp.y - g->geom.height;
2198 #if 0
2199 printf("%s press: graph: (%d,%d), (%d,%d); viewport: (%d,%d), "
2200 "(%d,%d); zooms: (%f,%f)\n",
2201 (event != NULL) ? "mouse" : "key", g->geom.x, g->geom.y,
2202 g->geom.width, g->geom.height, g->wp.x, g->wp.y, g->wp.width,
2203 g->wp.height, g->zoom.x, g->zoom.y);
2204 #endif
2206 graph_element_lists_make(g);
2207 graph_display(g);
2208 axis_display(g->y_axis);
2209 axis_display(g->x_axis);
2211 if (g->cross.draw) {
2212 g->cross.erase_needed = FALSE;
2213 cross_draw(g, pointer_x, pointer_y);
2218 static void do_zoom_keyboard(struct graph *g,
2219 gboolean lock_vertical,
2220 gboolean lock_horizontal)
2222 do_zoom_common(g, NULL, lock_vertical, lock_horizontal);
2225 static void do_zoom_mouse(struct graph *g, GdkEventButton *event)
2227 do_zoom_common(g, event,
2228 event->state & GDK_SHIFT_MASK,
2229 event->state & GDK_CONTROL_MASK);
2232 static void do_zoom_in_keyboard(struct graph *g,
2233 gboolean lock_vertical,
2234 gboolean lock_horizontal)
2236 g->zoom.flags &= ~ZOOM_OUT;
2237 do_zoom_keyboard(g, lock_vertical, lock_horizontal);
2240 static void do_zoom_out_keyboard(struct graph *g,
2241 gboolean lock_vertical,
2242 gboolean lock_horizontal)
2244 g->zoom.flags |= ZOOM_OUT;
2245 do_zoom_keyboard(g, lock_vertical, lock_horizontal);
2248 static void do_key_motion(struct graph *g)
2250 if (g->geom.x > g->wp.x) {
2251 g->geom.x = g->wp.x;
2253 if (g->geom.y > g->wp.y) {
2254 g->geom.y = g->wp.y;
2256 if ((g->wp.x + g->wp.width) > (g->geom.x + g->geom.width)) {
2257 g->geom.x = g->wp.width + g->wp.x - g->geom.width;
2259 if ((g->wp.y + g->wp.height) > (g->geom.y + g->geom.height)) {
2260 g->geom.y = g->wp.height + g->wp.y - g->geom.height;
2263 graph_display(g);
2264 axis_display(g->y_axis);
2265 axis_display(g->x_axis);
2267 if (g->cross.draw) {
2268 int pointer_x, pointer_y;
2269 get_mouse_position(g->drawing_area, &pointer_x, &pointer_y, NULL);
2270 g->cross.erase_needed = FALSE;
2271 cross_draw (g, pointer_x, pointer_y);
2275 static void do_key_motion_up(struct graph *g, int step)
2277 g->geom.y += step;
2278 do_key_motion(g);
2281 static void do_key_motion_down(struct graph *g, int step)
2283 g->geom.y -= step;
2284 do_key_motion(g);
2287 static void do_key_motion_left(struct graph *g, int step)
2289 g->geom.x += step;
2290 do_key_motion(g);
2293 static void do_key_motion_right(struct graph *g, int step)
2295 g->geom.x -= step;
2296 do_key_motion(g);
2299 static gboolean button_press_event(GtkWidget *widget _U_, GdkEventButton *event, gpointer user_data)
2301 struct graph *g = (struct graph *)user_data;
2303 debug(DBS_FENTRY) puts("button_press_event()");
2305 if (event->button == MOUSE_BUTTON_RIGHT) {
2306 /* Turn on grab. N.B. using (maybe) approx mouse position from event... */
2307 g->grab.x = (int )rint (event->x) - g->geom.x;
2308 g->grab.y = (int )rint (event->y) - g->geom.y;
2309 g->grab.grabbed = TRUE;
2310 } else if (event->button == MOUSE_BUTTON_MIDDLE) {
2311 do_zoom_mouse(g, event);
2312 } else if (event->button == MOUSE_BUTTON_LEFT) {
2313 /* See if we're on an element that links to a frame */
2314 graph_select_segment(g, (int)event->x, (int)event->y);
2316 /* Set origin of rect, even if outside graph area */
2317 zoomrect.x = (int)event->x;
2318 zoomrect.y = (int)event->y;
2321 unset_busy_cursor(gtk_widget_get_window(g->drawing_area));
2322 return TRUE;
2325 static gboolean button_release_event(GtkWidget *widget _U_, GdkEventButton *event _U_, gpointer user_data)
2327 struct graph *g = (struct graph *)user_data;
2329 /* Turn off grab if right button released */
2330 if (event->button == MOUSE_BUTTON_RIGHT) {
2331 g->grab.grabbed = FALSE;
2333 else if (event->button == MOUSE_BUTTON_LEFT) {
2334 int xx1 = zoomrect.x;
2335 int xx2 = (int)event->x;
2336 int yy1 = zoomrect.y;
2337 int yy2 = (int)event->y;
2338 zoomrect.x = MIN(xx1, xx2);
2339 zoomrect.width = abs(xx1 - xx2);
2340 zoomrect.y = MIN(yy1, yy2);
2341 zoomrect.height = abs(yy1 - yy2);
2343 /* Finish selecting a region to zoom in on.
2344 Take care not to choose a too-small area (by accident?) */
2345 if ((zoomrect.width > 3) && (zoomrect.height > 3)) {
2346 int oldflags = g->zoom.flags;
2348 debug(DBS_GRAPH_DRAWING) printf("Zoom in from (%d, %d) - (%d, %d)\n",
2349 zoomrect.x, zoomrect.y,
2350 zoomrect.width, zoomrect.height);
2352 g->zoom.flags &= ~ZOOM_OUT;
2353 do_zoom_rectangle(g, zoomrect);
2354 g->zoom.flags = oldflags;
2358 return TRUE;
2361 static gboolean motion_notify_event(GtkWidget *widget _U_, GdkEventMotion *event, gpointer user_data)
2363 struct graph *g = (struct graph *)user_data;
2364 int x, y;
2365 GdkModifierType state;
2367 /* debug(DBS_FENTRY) puts ("motion_notify_event()"); */
2369 /* Make sure we have accurate mouse position */
2370 if (event->is_hint)
2371 get_mouse_position(g->drawing_area, &x, &y, &state);
2372 else {
2373 x = (int) event->x;
2374 y = (int) event->y;
2375 state = (GdkModifierType)event->state;
2378 if (state & GDK_BUTTON3_MASK) {
2379 if (g->grab.grabbed) {
2380 /* Move view by difference between where we grabbed and where we are now */
2381 g->geom.x = x-g->grab.x;
2382 g->geom.y = y-g->grab.y;
2384 /* Limit to outer bounds of graph */
2385 if (g->geom.x > g->wp.x)
2386 g->geom.x = g->wp.x;
2387 if (g->geom.y > g->wp.y)
2388 g->geom.y = g->wp.y;
2389 if (g->wp.x + g->wp.width > g->geom.x + g->geom.width)
2390 g->geom.x = g->wp.width + g->wp.x - g->geom.width;
2391 if (g->wp.y + g->wp.height > g->geom.y + g->geom.height)
2392 g->geom.y = g->wp.height + g->wp.y - g->geom.height;
2394 /* Redraw everything */
2395 g->cross.erase_needed = 0;
2396 graph_display(g);
2397 axis_display(g->y_axis);
2398 axis_display(g->x_axis);
2399 if (g->cross.draw) {
2400 cross_draw(g, x, y);
2404 else if (state & GDK_BUTTON1_MASK) {
2406 /* Draw bounded box for zoomrect being chosen! */
2407 if (g->zoomrect_erase_needed) {
2408 zoomrect_erase(g);
2410 zoomrect_draw(g, x, y);
2413 /* No button currently pressed */
2414 else {
2415 /* Update the cross if it's being shown */
2416 if (g->cross.erase_needed)
2417 cross_erase(g);
2418 if (g->cross.draw) {
2419 cross_draw(g, x, y);
2423 return TRUE;
2426 static gboolean key_press_event(GtkWidget *widget _U_, GdkEventKey *event, gpointer user_data)
2428 struct graph *g = (struct graph *)user_data;
2429 int step;
2431 debug(DBS_FENTRY) puts("key_press_event()");
2433 /* Holding down these keys can affect the step used for moving */
2434 if ((event->state & GDK_CONTROL_MASK) && (event->state & GDK_SHIFT_MASK)) {
2435 step = 0;
2437 else if (event->state & GDK_CONTROL_MASK) {
2438 step = 1;
2440 else if (event->state & GDK_SHIFT_MASK) {
2441 step = 10;
2443 else {
2444 step = 100;
2447 switch (event->keyval) {
2448 case ' ':
2449 toggle_crosshairs(g);
2450 break;
2451 case 't':
2452 /* Toggle betwee showing the time starting at 0, or time in capture */
2453 toggle_time_origin(g);
2454 break;
2455 case 'r':
2456 case GDK_Home:
2457 /* Go back to original view, all zoomed out */
2458 restore_initial_graph_view(g);
2459 break;
2461 /* Zooming in */
2462 case 'i':
2463 case 'I':
2464 do_zoom_in_keyboard(g,
2465 event->state & GDK_SHIFT_MASK,
2466 event->state & GDK_CONTROL_MASK);
2467 break;
2468 case '+':
2469 do_zoom_in_keyboard(g,
2470 FALSE,
2471 event->state & GDK_CONTROL_MASK);
2472 break;
2474 /* Zooming out */
2475 case 'o':
2476 case 'O':
2477 do_zoom_out_keyboard(g,
2478 event->state & GDK_SHIFT_MASK,
2479 event->state & GDK_CONTROL_MASK);
2480 break;
2481 case '-':
2482 do_zoom_out_keyboard(g,
2483 FALSE,
2484 event->state & GDK_CONTROL_MASK);
2486 /* Direction keys */
2487 case GDK_Left:
2488 do_key_motion_left(g, step);
2489 break;
2490 case GDK_Up:
2491 do_key_motion_up(g, step);
2492 break;
2493 case GDK_Right:
2494 do_key_motion_right(g, step);
2495 break;
2496 case GDK_Down:
2497 do_key_motion_down(g, step);
2498 break;
2500 case GDK_Page_Up:
2501 do_key_motion_up(g, 2000);
2502 break;
2503 case GDK_Page_Down:
2504 do_key_motion_down(g, 2000);
2505 break;
2507 /* Help */
2508 case GDK_F1:
2509 callback_create_help(NULL, NULL);
2510 break;
2512 default:
2513 break;
2515 return TRUE;
2518 static void toggle_crosshairs(struct graph *g)
2520 /* Toggle state */
2521 g->cross.draw ^= 1;
2523 /* Draw or erase as needed */
2524 if (g->cross.draw) {
2525 int x, y;
2526 get_mouse_position(g->drawing_area, &x, &y, NULL);
2527 cross_draw(g, x, y);
2528 } else if (g->cross.erase_needed) {
2529 cross_erase(g);
2533 static void cross_draw(struct graph *g, int x, int y)
2535 /* Shouldn't draw twice onto the same position if haven't erased in the
2536 meantime! */
2537 if (g->cross.erase_needed && (g->cross.x == x) && (g->cross.y == y)) {
2538 return;
2541 /* Draw the cross */
2542 if ((x > g->wp.x) && (x < g->wp.x+g->wp.width) &&
2543 (y > g->wp.y) && (y < g->wp.y+g->wp.height)) {
2545 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area));
2546 gdk_cairo_set_source_rgba(cr, &g->style.seq_color);
2547 cairo_set_line_width(cr, 1.0);
2549 /* Horizonal line */
2550 cairo_move_to(cr, g->wp.x, y);
2551 cairo_line_to(cr, g->wp.x + g->wp.width, y);
2553 /* Vertical line */
2554 cairo_move_to(cr, x, g->wp.y);
2555 cairo_line_to(cr, x, g->wp.y + g->wp.height);
2556 cairo_stroke(cr);
2557 cairo_destroy(cr);
2560 /* Update state */
2561 g->cross.x = x;
2562 g->cross.y = y;
2563 g->cross.erase_needed = TRUE;
2566 static void cross_erase(struct graph *g)
2568 int x = g->cross.x;
2569 int y = g->cross.y;
2571 if ((x > g->wp.x) && (x < g->wp.x+g->wp.width) &&
2572 (y >= g->wp.y) && (y < g->wp.y+g->wp.height)) {
2574 /* Just redraw what is in the pixmap buffer */
2575 graph_pixmap_display(g);
2578 g->cross.erase_needed = FALSE;
2581 static void zoomrect_draw(struct graph *g, int x, int y)
2583 if ((zoomrect.x > g->wp.x) && (zoomrect.x < g->wp.x + g->wp.width) &&
2584 (zoomrect.y > g->wp.y) && (zoomrect.y < g->wp.y + g->wp.height) &&
2585 (x > g->wp.x + 0.5) && (x < g->wp.x+g->wp.width) &&
2586 (y > g->wp.y) && (y < g->wp.y+g->wp.height)) {
2588 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area));
2589 gdk_cairo_set_source_rgba(cr, &g->style.seq_color);
2590 cairo_set_line_width(cr, 1.0);
2592 /* Do outline of rect */
2593 cairo_rectangle(cr, zoomrect.x, zoomrect.y, x-zoomrect.x, y-zoomrect.y);
2594 cairo_stroke(cr);
2595 cairo_destroy(cr);
2598 g->zoomrect_erase_needed = TRUE;
2601 static void zoomrect_erase(struct graph *g)
2603 /* Just redraw what is in the pixmap buffer */
2604 graph_pixmap_display(g);
2605 g->zoomrect_erase_needed = FALSE;
2609 /* Toggle between showing the time starting at 0, or time in capture */
2610 static void toggle_time_origin(struct graph *g)
2612 g->style.flags ^= TIME_ORIGIN;
2614 if ((g->style.flags & TIME_ORIGIN) == TIME_ORIGIN_CAP) {
2615 g->x_axis->min = g->bounds.x0;
2617 else {
2618 g->x_axis->min = 0;
2621 /* Redraw the axis */
2622 axis_display(g->x_axis);
2625 static void restore_initial_graph_view(struct graph *g)
2627 g->geom.width = g->wp.width;
2628 g->geom.height = g->wp.height;
2629 g->geom.x = g->wp.x;
2630 g->geom.y = g->wp.y;
2631 graph_init_sequence(g);
2633 /* Set flags so that mouse zoom will zoom in (zooming out is not possible!) */
2634 g->zoom.flags &= ~ZOOM_OUT;
2636 if (g->cross.draw) {
2637 g->cross.erase_needed = FALSE;
2641 /* Walk the segment list, totalling up data PDUs, status ACKs and NACKs */
2642 static void get_data_control_counts(struct graph *g, int *data, int *acks, int *nacks)
2644 struct segment *tmp;
2645 *data = 0;
2646 *acks = 0;
2647 *nacks = 0;
2649 for (tmp=g->segments; tmp; tmp=tmp->next) {
2650 if (tmp->isControlPDU) {
2651 (*acks)++;
2652 (*nacks) += tmp->noOfNACKs;
2654 else {
2655 (*data)++;
2660 /* Determine "bounds"
2661 * Essentially: look for lowest/highest time and seq in the list of segments
2662 * Not currently trying to work out the upper bound of the window, as we
2663 * don't reliably know the RLC channel state variables...
2665 static void graph_get_bounds(struct graph *g)
2667 struct segment *tmp;
2668 double tim;
2669 gboolean data_frame_seen = FALSE;
2670 double data_tim_low = 0;
2671 double data_tim_high = 0;
2672 guint32 data_seq_cur;
2673 guint32 data_seq_low = 0;
2674 guint32 data_seq_high = 0;
2675 gboolean ack_frame_seen = FALSE;
2677 double ack_tim_low = 0;
2678 double ack_tim_high = 0;
2679 guint32 ack_seq_cur;
2680 guint32 ack_seq_low = 0;
2681 guint32 ack_seq_high = 0;
2683 /* Go through all segments to determine "bounds" */
2684 for (tmp=g->segments; tmp; tmp=tmp->next) {
2685 if (!tmp->isControlPDU) {
2687 /* DATA frame */
2688 tim = tmp->rel_secs + tmp->rel_usecs / 1000000.0;
2689 data_seq_cur = tmp->SN;
2691 /* Want to include a little beyond end, so that cross on last SN
2692 will (more likely) fit within bounds */
2693 #define A_FEW_SUBFRAMES 0.005
2695 /* Initialise if first time seen */
2696 if (!data_frame_seen) {
2697 data_tim_low = tim;
2698 data_tim_high = tim + A_FEW_SUBFRAMES;
2699 data_seq_low = data_seq_cur;
2700 data_seq_high = data_seq_cur+1;
2701 data_frame_seen = TRUE;
2704 /* Update bounds after this frame */
2705 if (tim < data_tim_low) data_tim_low = tim;
2706 if (tim+0.02 > data_tim_high) data_tim_high = tim + A_FEW_SUBFRAMES;
2707 if (data_seq_cur < data_seq_low) data_seq_low = data_seq_cur;
2708 if (data_seq_cur+1 > data_seq_high) data_seq_high = data_seq_cur+1;
2710 else {
2712 /* STATUS PDU */
2713 int n;
2714 guint32 nack_seq_cur;
2716 tim = tmp->rel_secs + tmp->rel_usecs / 1000000.0;
2717 ack_seq_cur = tmp->ACKNo;
2719 /* Initialise if first status PDU seen */
2720 if (!ack_frame_seen) {
2721 ack_tim_low = ack_tim_high = tim;
2722 ack_seq_low = ack_seq_cur;
2723 ack_seq_high = ack_seq_cur;
2724 ack_frame_seen = TRUE;
2727 /* Update bounds after this frame */
2728 if (tim < ack_tim_low) ack_tim_low = tim;
2729 if (tim > ack_tim_high) ack_tim_high = tim;
2730 if (ack_seq_cur < ack_seq_low) ack_seq_low = ack_seq_cur;
2731 if (ack_seq_cur > ack_seq_high) ack_seq_high = ack_seq_cur;
2733 /* Also run through any/all NACKs to see if ack_seq_low/ack_seq_high
2734 should be extended */
2735 for (n=0; n < tmp->noOfNACKs; n++) {
2736 nack_seq_cur = tmp->NACKs[n];
2738 if (nack_seq_cur < ack_seq_low) ack_seq_low = nack_seq_cur;
2739 if (nack_seq_cur > ack_seq_high) ack_seq_high = nack_seq_cur;
2744 g->bounds.x0 = (((data_tim_low <= ack_tim_low) && data_frame_seen) || (!ack_frame_seen)) ? data_tim_low : ack_tim_low;
2745 g->bounds.width = ((((data_tim_high >= ack_tim_high) && data_frame_seen) || (!ack_frame_seen)) ? data_tim_high : ack_tim_high) - g->bounds.x0;
2746 g->bounds.y0 = 0; /* We always want the overal bounds to go back down to SN=0 */
2747 g->bounds.height = (((data_seq_high >= ack_seq_high) && data_frame_seen) || (!ack_frame_seen)) ? data_seq_high : ack_seq_high;
2749 g->zoom.x = (g->geom.width - 1) / g->bounds.width;
2750 g->zoom.y = (g->geom.height -1) / g->bounds.height;
2753 static void graph_read_config(struct graph *g)
2755 /* Black for PDUs */
2756 g->style.seq_color.red = (double)0 / 65535.0;
2757 g->style.seq_color.green = (double)0 / 65535.0;
2758 g->style.seq_color.blue = (double)0 / 65535.0;
2759 g->style.seq_color.alpha = 1.0;
2761 /* Grey for resegmentations */
2762 g->style.seq_resegmented_color.red = (double)0x7777 / 65535.0;
2763 g->style.seq_resegmented_color.green = (double)0x7777 / 65535.0;
2764 g->style.seq_resegmented_color.blue = (double)0x7777 / 65535.0;
2765 g->style.seq_resegmented_color.alpha = 1.0;
2767 /* Blueish */
2768 g->style.ack_color[0].red = (double)0x2222 / 65535.0;
2769 g->style.ack_color[0].green = (double)0x2222 / 65535.0;
2770 g->style.ack_color[0].blue = (double)0xaaaa / 65535.0;
2771 g->style.ack_color[0].alpha = 1.0;
2773 /* Reddish */
2774 g->style.ack_color[1].red = (double)0xaaaa / 65535.0;
2775 g->style.ack_color[1].green = (double)0x2222 / 65535.0;
2776 g->style.ack_color[1].blue = (double)0x2222 / 65535.0;
2777 g->style.ack_color[1].alpha = 1.0;
2779 /* Time origin should be shown as time in capture by default */
2780 g->style.flags = TIME_ORIGIN_CAP;
2782 g->y_axis->label = (const char ** )g_malloc(3 * sizeof(char * ));
2783 g->y_axis->label[0] = "Number";
2784 g->y_axis->label[1] = "Sequence";
2785 g->y_axis->label[2] = NULL;
2786 g->x_axis->label = (const char ** )g_malloc(2 * sizeof(char * ));
2787 g->x_axis->label[0] = "Time[s]";
2788 g->x_axis->label[1] = NULL;
2791 static void rlc_lte_make_elmtlist(struct graph *g)
2793 struct segment *tmp;
2794 struct element *elements0, *e0; /* list of elmts showing control */
2795 struct element *elements1, *e1; /* list of elmts showing data */
2796 struct segment *last_status_segment = NULL;
2797 double xx0, yy0;
2798 gboolean ack_seen = FALSE;
2799 guint32 seq_base;
2800 guint32 seq_cur;
2801 int n, data, acks, nacks;
2803 double previous_status_x = 0.0, previous_status_y = 0.0;
2805 debug(DBS_FENTRY) puts("rlc_lte_make_elmtlist()");
2807 /* Allocate all needed elements up-front */
2808 if (g->elists->elements == NULL) {
2809 get_data_control_counts(g, &data, &acks, &nacks);
2811 /* Allocate elements for data */
2812 n = data + 1;
2813 e0 = elements0 = (struct element *)g_malloc(n*sizeof(struct element));
2815 /* Allocate elements for status */
2816 n = (2*acks) + (4*nacks) + 2;
2817 e1 = elements1 = (struct element *)g_malloc(n*sizeof(struct element));
2819 /* Allocate container for 2nd list of elements */
2820 g->elists->next = (struct element_list *)g_malloc0(sizeof(struct element_list));
2822 } else {
2823 e0 = elements0 = g->elists->elements;
2824 e1 = elements1 = g->elists->next->elements;
2827 xx0 = g->bounds.x0;
2828 yy0 = g->bounds.y0;
2829 seq_base = (guint32) yy0;
2831 for (tmp=g->segments; tmp; tmp=tmp->next) {
2832 double secs;
2833 double x, y;
2835 /****************************************/
2836 /* X axis is time, Y is sequence number */
2837 /****************************************/
2839 secs = tmp->rel_secs + (tmp->rel_usecs / 1000000.0);
2840 x = secs - xx0;
2841 x *= g->zoom.x;
2843 if (!tmp->isControlPDU) {
2845 /* DATA */
2847 /* seq_cur is SN */
2848 seq_cur = tmp->SN - seq_base;
2850 /* Work out positions around this SN */
2851 #define DATA_BLOB_SIZE 4
2852 y = (g->zoom.y * seq_cur);
2854 /* Circle for data point */
2855 e0->type = ELMT_ELLIPSE;
2856 e0->parent = tmp;
2857 if (!tmp->isResegmented) {
2858 e0->elment_color_p = &g->style.seq_color;
2860 else {
2861 e0->elment_color_p = &g->style.seq_resegmented_color;
2864 e0->p.ellipse.dim.width = DATA_BLOB_SIZE;
2865 e0->p.ellipse.dim.height = DATA_BLOB_SIZE;
2866 e0->p.ellipse.dim.x = x;
2867 e0->p.ellipse.dim.y = y;
2868 e0++;
2869 } else {
2871 /* Remember the last status segment */
2872 last_status_segment = tmp;
2874 /* -1 so ACK lines up with last data, rather than showing above it... */
2875 seq_cur = tmp->ACKNo - seq_base - 1;
2877 /* Work out positions around this SN */
2878 y = (g->zoom.y * seq_cur);
2880 if (ack_seen) {
2882 if (y > previous_status_y) {
2883 /* Draw from previous ACK point horizontally to this time */
2884 e1->type = ELMT_LINE;
2885 e1->parent = tmp;
2886 e1->elment_color_p = &g->style.ack_color[0];
2887 e1->p.line.dim.x1 = previous_status_x;
2888 e1->p.line.dim.y1 = previous_status_y;
2889 e1->p.line.dim.x2 = x;
2890 e1->p.line.dim.y2 = previous_status_y;
2891 e1++;
2893 /* Now draw up to current ACK */
2894 e1->type = ELMT_LINE;
2895 e1->parent = tmp;
2896 e1->elment_color_p = &g->style.ack_color[0];
2897 e1->p.line.dim.x1 = x;
2898 e1->p.line.dim.y1 = previous_status_y;
2899 e1->p.line.dim.x2 = x;
2900 e1->p.line.dim.y2 = y;
2901 e1++;
2903 else {
2904 /* Want to go down, then along in this case... */
2905 e1->type = ELMT_LINE;
2906 e1->parent = tmp;
2907 e1->elment_color_p = &g->style.ack_color[0];
2908 e1->p.line.dim.x1 = previous_status_x;
2909 e1->p.line.dim.y1 = previous_status_y;
2910 e1->p.line.dim.x2 = previous_status_x;
2911 e1->p.line.dim.y2 = y;
2912 e1++;
2914 /* Now draw up to current ACK */
2915 e1->type = ELMT_LINE;
2916 e1->parent = tmp;
2917 e1->elment_color_p = &g->style.ack_color[0];
2918 e1->p.line.dim.x1 = previous_status_x;
2919 e1->p.line.dim.y1 = y;
2920 e1->p.line.dim.x2 = x;
2921 e1->p.line.dim.y2 = y;
2922 e1++;
2926 if (tmp->noOfNACKs > 0) {
2927 for (n=0; n < tmp->noOfNACKs; n++) {
2928 double nack_y = (g->zoom.y * tmp->NACKs[n]);
2930 /* A red cross to show where the NACK is reported */
2931 #define NACK_CROSS_SIZE 3
2932 e1->type = ELMT_LINE;
2933 e1->parent = tmp;
2934 e1->elment_color_p = &g->style.ack_color[1];
2935 e1->p.line.dim.x1 = x -NACK_CROSS_SIZE;
2936 e1->p.line.dim.y1 = nack_y - NACK_CROSS_SIZE;
2937 e1->p.line.dim.x2 = x + NACK_CROSS_SIZE;
2938 e1->p.line.dim.y2 = nack_y + NACK_CROSS_SIZE;
2939 e1++;
2941 e1->type = ELMT_LINE;
2942 e1->parent = tmp;
2943 e1->elment_color_p = &g->style.ack_color[1];
2944 e1->p.line.dim.x1 = x - NACK_CROSS_SIZE;
2945 e1->p.line.dim.y1 = nack_y + NACK_CROSS_SIZE;
2946 e1->p.line.dim.x2 = x + NACK_CROSS_SIZE;
2947 e1->p.line.dim.y2 = nack_y - NACK_CROSS_SIZE;
2948 e1++;
2950 e1->type = ELMT_LINE;
2951 e1->parent = tmp;
2952 e1->elment_color_p = &g->style.ack_color[1];
2953 e1->p.line.dim.x1 = x;
2954 e1->p.line.dim.y1 = nack_y + NACK_CROSS_SIZE;
2955 e1->p.line.dim.x2 = x;
2956 e1->p.line.dim.y2 = nack_y - NACK_CROSS_SIZE;
2957 e1++;
2959 e1->type = ELMT_LINE;
2960 e1->parent = tmp;
2961 e1->elment_color_p = &g->style.ack_color[1];
2962 e1->p.line.dim.x1 = x - NACK_CROSS_SIZE;
2963 e1->p.line.dim.y1 = nack_y;
2964 e1->p.line.dim.x2 = x + NACK_CROSS_SIZE;
2965 e1->p.line.dim.y2 = nack_y;
2966 e1++;
2970 ack_seen = TRUE;
2972 previous_status_x = x;
2973 previous_status_y = y;
2977 if (ack_seen) {
2978 /* Add one more line for status, from the last PDU -> rhs of graph */
2979 e1->type = ELMT_LINE;
2980 e1->parent = last_status_segment;
2981 e1->elment_color_p = &g->style.ack_color[0];
2982 e1->p.line.dim.x1 = previous_status_x;
2983 e1->p.line.dim.y1 = previous_status_y;
2984 e1->p.line.dim.x2 = g->bounds.width * g->zoom.x; /* right edge of graph area */
2985 e1->p.line.dim.y2 = previous_status_y;
2986 e1++;
2989 /* Complete both element lists */
2990 e0->type = ELMT_NONE;
2991 e1->type = ELMT_NONE;
2992 g->elists->elements = elements0;
2993 g->elists->next->elements = elements1;
2996 #if defined(_WIN32) && !defined(__MINGW32__) && (_MSC_VER < 1800)
2997 /* Starting VS2013, rint already defined in math.h. N o need to redefine */
2998 /* replacement of Unix rint() for Windows */
2999 static int rint(double x)
3001 char *buf;
3002 int i,dec,sig;
3004 buf = _fcvt(x, 0, &dec, &sig);
3005 i = atoi(buf);
3006 if (sig == 1) {
3007 i = i * -1;
3009 return i;
3011 #endif