CEBL  2.1
PieMenu.cpp
Go to the documentation of this file.
1 /* PieMenu.cpp
2  * \author Jeshua Bratman
3  *
4  * Pie menu widget for GTK. Used by CEBL pie interfaces.
5  */
6 
7 #include "PieMenu.hpp"
8 
9 #include <math.h>
10 #include <gtk/gtksignal.h>
11 #include <gtk/gtkmain.h>
12 #include <cstring>
13 #include <iostream>
14 #include <string>
15 #include <vector>
16 using namespace std;
17 
18 G_DEFINE_TYPE(PieMenu, pie_menu, GTK_TYPE_DRAWING_AREA);
19 static gboolean pie_menu_expose (GtkWidget *pie, GdkEventExpose *event);
20 static void pie_menu_dispose (GObject *self);
21 
24  {
25  int selected;
26  int lit;
27  int nSegments;
29 
30  bool train_mode;
31 
32  double pulse_value;
34 
35  std::vector<std::string> labels;
36  std::vector<std::string> secondary_labels;
37  std::vector<double> bar_percentages;
38  unsigned int bg_red, bg_green, bg_blue;
39  };
40 
41 #define PIE_MENU_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), PIE_MENU_TYPE, PieMenuPrivate))
42 
43 static void pie_menu_class_init (PieMenuClass *cls)
44 {
45  GObjectClass *obj_class;
46  GtkWidgetClass *widget_class;
47 
48  obj_class = G_OBJECT_CLASS (cls);
49  widget_class = GTK_WIDGET_CLASS (cls);
50 
51  widget_class->expose_event = pie_menu_expose;
52  obj_class->dispose = pie_menu_dispose;
53 
54  g_type_class_add_private (obj_class, sizeof (PieMenuPrivate));
55 }
56 
57 
58 
59 GtkWidget *pie_menu_new ()
60 {
61 
62  return ((GtkWidget*)(g_object_new(PIE_MENU_TYPE,NULL)));
63 }
64 
65 
66 
67 /***************************************************
68  **Drawing
69  ***************************************************/
70 //draw pie
71 static void pie_draw (GtkWidget *pie, cairo_t *cr)
72 {
73  if(pie==NULL)
74  return;
75 
76 
77 
78  double width = pie->allocation.width;
79  double height = pie->allocation.height;
80  double center_x = width/2;
81  double center_y = height/2;
82  double radius_normal = MIN (width / 2,height / 2) * .9;
83  //double CURSOR_RADIUS = 7;
84 
85  //------------
86  //get private variables
87  PieMenuPrivate *vars;
88  vars = PIE_MENU_GET_PRIVATE (pie);
89 
90  //-------------
91  //pie slice color progression
92  double color_red = .5;
93  double color_green = .5;
94  double color_blue = 1;
95  //it is green's turn to change
96  double color_turn = 3;
97  double color_step;
98  switch(vars->nSegments)
99  {
100  case 2:
101  color_step = .5;
102  break;
103  case 3:
104  color_step = .4;
105  break;
106  case 4:
107  case 5:
108  case 6:
109  case 7:
110  case 8:
111  case 9:
112  color_step = .2;
113  break;
114  default:
115  color_step = .2 - (.01*(((vars->nSegments-1))%10));
116  break;
117  }
118  //-------------
119 
120  double inner_radius_normal = radius_normal/1.7;
121  double inner_radius = inner_radius_normal;
122  double radius = radius_normal;
123  double radius_selected = radius_normal*1.1;
124  double inner_radius_selected = radius_normal/2.5;
125 
126  int i;
127 
128 
129  int nSegments = vars->nSegments;
130  int selected = vars->selected;
131 
132  double bar_width = inner_radius/(15+nSegments);
133  double poly_radius = inner_radius/10;
134 
135  double theta = 0;
136  cairo_set_source_rgb(cr,
137  (double)vars->bg_red/65535,
138  (double)vars->bg_green/65535,
139  (double)vars->bg_blue/65535);
140 
141  cairo_move_to(cr,0,0);
142  cairo_line_to(cr,width,0);
143  cairo_line_to(cr,width,height);
144  cairo_line_to(cr,0,height);
145  cairo_line_to(cr,0,0);
146  cairo_fill_preserve (cr);
147  cairo_stroke(cr);
148 
149  for(i=1;i<=nSegments;i++)
150  {
151  double next_theta = 2*M_PI * (double)i/nSegments;
152  double mid_theta = (next_theta + theta)/2.0;
153  //set color
154  if(vars->lit == i-1)
155  {
156  cairo_set_source_rgba(cr,color_red * 2, color_green*2, color_blue*1,1);
157 
158  }
159  else
160  {
161  cairo_set_source_rgba(cr,color_red, color_green, color_blue, 1);
162  }
163  cairo_set_line_width(cr,1);
164 
165  if(i == selected+1)
166  {
167  radius = radius_selected;
168  inner_radius = inner_radius_selected;
169  }
170  cairo_move_to(cr,center_x+inner_radius*cos(theta),center_y+inner_radius*sin(theta));
171  cairo_line_to(cr,center_x + radius * cos(theta),center_y + radius*sin(theta));
172  cairo_line_to(cr,center_x + radius * cos(next_theta),center_y + radius*sin(next_theta));
173  cairo_line_to(cr,center_x+inner_radius*cos(next_theta),center_y+inner_radius*sin(next_theta));
174 
175  //cairo_line_to(cr,center_x + radius * cos(theta-step),center_y + radius*sin(theta-step));
176  cairo_fill_preserve (cr);
177  cairo_stroke(cr);
178 
179  //now put curved end on
180  cairo_arc (cr, center_x, center_y, radius, theta, next_theta);
181  cairo_fill_preserve (cr);
182  cairo_stroke (cr);
183 
184 
185 
186 
187  //----------------
188  //put circle inside
189 
190  cairo_set_source_rgb(cr,
191  (double)vars->bg_red/65535,
192  (double)vars->bg_green/65535,
193  (double)vars->bg_blue/65535);
194 
195 
196  cairo_move_to(cr,center_x,center_y);
197  cairo_line_to(cr,center_x + inner_radius * cos(theta), center_y + inner_radius*sin(theta));
198  cairo_line_to(cr,center_x + inner_radius * cos(next_theta),center_y + inner_radius*sin(next_theta));
199  cairo_line_to(cr,center_x,center_y);
200  cairo_fill_preserve (cr);
201  cairo_stroke(cr);
202 
203 
204  cairo_arc (cr, center_x, center_y, inner_radius, theta, next_theta);
205  cairo_fill_preserve (cr);
206  cairo_stroke (cr);
207 
208  //----------------------------------------
209  //bars
210 
211  if(vars->bars_visible)
212  {
213  double bar_height;
214  //if section is lit, extend bar to reach it
215  if(vars->lit==(i-1))
216  {
217  bar_height= inner_radius;
218 
219  }
220  else
221  bar_height= inner_radius * vars->bar_percentages[i-1];
222 
223  if(bar_height != 0)
224  {
225 
226  double costheta = cos(mid_theta);
227  double sintheta = sin(mid_theta);
228 
229  double origin1x = center_x - bar_width * sintheta;
230  double origin1y = center_y + bar_width * costheta;
231  double origin2x = center_x + bar_width * sintheta;
232  double origin2y = center_y - bar_width * costheta;
233 
234  cairo_set_source_rgba(cr,color_red, color_green, color_blue, 1);
235 
236  cairo_set_line_width(cr,1);
237  cairo_move_to(cr
238  , origin1x
239  , origin1y);
240  cairo_line_to(cr
241  , origin1x + bar_height * costheta
242  , origin1y + bar_height * sintheta);
243  cairo_line_to(cr
244  , origin2x + bar_height * costheta
245  , origin2y + bar_height * sintheta);
246  cairo_line_to(cr
247  , origin2x
248  , origin2y);
249  cairo_line_to(cr
250  , origin1x
251  , origin1y);
252 
253  cairo_fill_preserve (cr);
254  cairo_stroke(cr);
255  }
256  }
257 
258  //------------------------------
259  //update for next iteration
260  radius = radius_normal;
261  inner_radius = inner_radius_normal;
262  theta = next_theta;
263  //-------------------------------
264  //color progression
265  //red
266  if(color_turn == 1)
267  {
268  color_red = color_red - color_step;
269  color_green = color_green + color_step;
270  if(color_green >= .99)
271  {
272  color_turn = 2;
273  }
274  }
275  //green
276  else if(color_turn == 2)
277  {
278 
279  color_green = color_green - color_step;
280  color_blue = color_blue + color_step;
281  if(color_blue >= .99)
282  {
283  color_turn = 3;
284  }
285  }
286  //blue
287  else
288  {
289  color_blue = color_blue - color_step;
290  color_red = color_red + color_step;
291  if(color_red >= .99)
292  {
293  color_turn = 1;
294  }
295  }
296  }
297 
298 
299  //----------------------------------------
300  //draw a polygon in the center
301 
302  //only try to draw a triangle or bigger if not in train mode
303  if(nSegments >= 3 && !vars->train_mode)
304  {
305  bool moved = false;
306  theta = 0.0;
307  double x,y;
308  cairo_set_source_rgba(cr,0,0,0,1);
309  for(i=0;i<nSegments;i++)
310  {
311  x = center_x + cos(theta) * poly_radius;
312  y = center_y + sin(theta) * poly_radius;
313  if(!moved)
314  {
315  cairo_move_to(cr,x,y);
316  moved = true;
317  }
318  else
319  {
320  cairo_line_to(cr,x,y);
321  }
322  theta += 2.0 * M_PI / nSegments;
323  }
324  theta = 0.0;
325  x = center_x + cos(theta) * poly_radius;
326  y = center_y + sin(theta) * poly_radius;
327  cairo_line_to(cr,x,y);
328  cairo_fill_preserve (cr);
329  cairo_stroke(cr);
330  }
331  //-------------------------------------------------------
332  //text
333  char buffer[255];
334  for(i=0;i<nSegments;i++)
335  {
336 
337  theta = 2*M_PI * (double)i/nSegments;
338  double next_theta = 2*M_PI * (double)(i+1)/(nSegments);
339  double mid_theta = (next_theta + theta)/2.0;
340 
341  //draw label
342  double text_x = center_x + (radius*.7) * cos(mid_theta);
343  double text_y = center_y + (radius*.7) * sin(mid_theta);
344 
345 
346  //create label
347  cairo_text_extents_t ext;
348  double text_start_x, text_start_y;
349  if(vars->labels.size() <= unsigned(i))
350  {
351  sprintf(buffer,"Class %d",i);
352  }
353  else if(vars->secondary_labels.size() <=unsigned(i) || vars->train_mode)
354  {
355  strncpy(buffer, vars->labels[i].c_str(), 254);
356  }
357  else
358  {
359  string l = vars->labels[i] + " [" + vars->secondary_labels[i] + "]";
360  strncpy(buffer, l.c_str(), 254);
361  }
362  //determine a good size for the font
363  int font_size = int(18 / (vars->nSegments) + width/300);
364  int length_factor = 10-(strlen(buffer)/5);
365  if(length_factor >= 0)
366  font_size+= length_factor;
367  cairo_set_font_size(cr,font_size);
368 
369  cairo_text_extents(cr, buffer, &ext);
370  text_start_x = text_x-ext.width/2;
371  text_start_y = text_y;
372  double padding = 10;
373  double tl_x = text_start_x-padding;
374  double tl_y = text_start_y+padding;
375  double bl_x = tl_x;
376  double bl_y = tl_y - ext.height - padding*2;
377  double tr_x = tl_x + ext.width + padding*2;
378  double tr_y = tl_y;
379  double br_x = tr_x;
380  double br_y = bl_y;
381  cairo_new_path (cr);
382  cairo_move_to(cr,tl_x, tl_y);
383  cairo_line_to(cr,bl_x, bl_y);
384  cairo_line_to(cr,br_x, br_y);
385  cairo_line_to(cr,tr_x, tr_y);
386  cairo_line_to(cr,tl_x, tl_y);
387  cairo_set_source_rgba(cr,1,1,1,.7);
388  cairo_fill_preserve (cr);
389  cairo_set_source_rgb(cr,0,0,0);
390  cairo_stroke(cr);
391 
392  cairo_set_source_rgba(cr,0,0,0, 1);
393  cairo_move_to(cr,text_start_x, text_start_y);
394  cairo_show_text(cr,buffer);
395  }
396  //-------------------------------------------------------
397  //cursor
398  /*double cursor_x,cursor_y;
399  if(vars->cursor_visible)
400  {
401  //printf("!!cursor_x = %f\n",vars->cursor_x);
402  cursor_x = vars->cursor_x;//cos(vars->cursor_theta)*(vars->cursor_dist * radius_normal)+center_x;
403  cursor_y = vars->cursor_y; //sin(vars->cursor_theta)*(vars->cursor_dist * radius_normal)+center_y;
404  //printf("\nx = %f, y = %f",cursor_x,cursor_y);
405  cairo_move_to(cr,cursor_x+CURSOR_RADIUS, cursor_y);
406  cairo_arc(cr,cursor_x, cursor_y, CURSOR_RADIUS, 0, 2 * M_PI);
407  cairo_set_source_rgba(cr, .3,.9,.5,.7);
408  cairo_fill_preserve (cr);
409 
410  //cairo_move_to(cr,cursor_x+CURSOR_RADIUS,cursor_y);
411  cairo_set_source_rgb (cr, 0, 0, 0);
412  cairo_stroke (cr);
413  }*/
414 
415 
416  vars->lit = -1; //unlight segment
417 }
418 
419 //expose event
420 static gboolean pie_menu_expose (GtkWidget *pie, GdkEventExpose *event)
421 {
422  g_return_val_if_fail (pie != NULL, false);
423  g_return_val_if_fail (PIE_IS_PIE_MENU(pie), false);
424 
425 
426 
427  cairo_t *cr;
428  //get cairo_t
429  cr = gdk_cairo_create (pie->window);
430  //set clip region
431  cairo_rectangle (cr,
432  event->area.x, event->area.y,
433  event->area.width, event->area.height);
434  cairo_clip (cr);
435  //draw
436  pie_draw (pie, cr);
437  cairo_destroy (cr);
438  return FALSE;
439 }
440 
441 /***************************************************
442  **Animation/Redrawing
443  ***************************************************/
444  static void pie_menu_redraw_canvas(PieMenu *pie)
445  {
446 
447 
448 
449  g_return_if_fail (pie != NULL);
450 
451  g_return_if_fail (PIE_IS_PIE_MENU(pie));
452 
453  GtkWidget *widget;
454  GdkRegion *region;
455 
456  widget = GTK_WIDGET (pie);
457 
458  if (!widget->window) return;
459 
460  region = gdk_drawable_get_clip_region (widget->window);
461  gdk_window_invalidate_region (widget->window, region, TRUE);
462  gdk_window_process_updates (widget->window, TRUE);
463 
464  gdk_region_destroy (region);
465  }
466 
467 
468 static gboolean pie_menu_update(gpointer data)
469 {
470 
471  g_return_val_if_fail (data != NULL, false);
472  if(!PIE_IS_PIE_MENU(data))
473  {
474  return false;
475  }
476  PieMenu *pie;
477  pie = PIE_MENU(data);
478  if(pie==NULL)
479  return FALSE;
480  pie_menu_redraw_canvas (pie);
481  return TRUE; /* keep running this event */
482 }
483 
484 static void pie_menu_final(gpointer g_class, gpointer class_data)
485 {
486 }
487 static void pie_menu_dispose (GObject *self)
488 {
489  g_return_if_fail (self != NULL);
490  g_return_if_fail (PIE_IS_PIE_MENU(self));
491 
492  PieMenu *pie = PIE_MENU(self);
493  PieMenuPrivate *vars;
494  vars = PIE_MENU_GET_PRIVATE (pie);
495 }
496 
497 
498 static void pie_menu_init (PieMenu *pie)
499 {
500  if(pie==NULL)
501  return;
502 
503  //GtkWidget *widget = GTK_WIDGET (pie);
504 
505  //initialize all variables for pie
506  PieMenuPrivate * vars = PIE_MENU_GET_PRIVATE (pie);
507  vars->nSegments = 3;
508  vars->selected = -1;
509  //vars->cursor_x = 0;
510  //vars->cursor_y = 0.0;
511  //vars->cursor_visible = 0;
512  vars->bars_visible = false;
513 
514  vars->pulse_direction = 0;
515  vars->pulse_value = 0.0;
516  vars->lit = -1;
517 
518  vars->labels.resize(0);
519  vars->bar_percentages.resize(vars->nSegments);
520  for(int i=0;i<vars->nSegments;i++)
521  vars->bar_percentages[i] = 0;
522 
523  //update pie with these new values
524  pie_menu_update(pie);
525 
526  //add a timer to update widget every 200 milliseconds
527  g_timeout_add(200, pie_menu_update, pie);
528 }
529 /***************************************************
530  **mutators
531  ***************************************************/
532 
533 void pie_set_segments(GtkWidget *pie, int segments)
534 {
535  if(pie==NULL)
536  return;
537  //------------
538  //get private variables
539  PieMenuPrivate *vars;
540  vars = PIE_MENU_GET_PRIVATE (pie);
541  vars->nSegments = segments;
542  vars->bar_percentages.resize(segments);
543  for(int i=0;i<vars->nSegments;i++)
544  vars->bar_percentages[i] = 0;
545 
546 }
547 
548 void pie_set_bg(GtkWidget *pie, unsigned int red, unsigned int green, unsigned int blue)
549 {
550  if(pie==NULL)
551  return;
552  //------------
553  //get private variables
554  PieMenuPrivate *vars;
555  vars = PIE_MENU_GET_PRIVATE (pie);
556  vars->bg_red = red;
557  vars->bg_green = green;
558  vars->bg_blue = blue;
559 }
560 
561 void pie_set_selected(GtkWidget *pie, int segment)
562 {
563  // cout << "setting selected to " << segment << "\n";
564  if(pie==NULL)
565  return;
566  //------------
567  //get private variables
568  PieMenuPrivate *vars;
569  vars = PIE_MENU_GET_PRIVATE (pie);
570  if(vars->selected != segment)
571  {
572  vars->selected = segment;
573  pie_menu_update(pie);
574  }
575 }
576 
577 
578 
579 void pie_set_bars_visible(GtkWidget *pie, bool val)
580 {
581  if(pie==NULL)
582  return;
583  //------------
584  //get private variables
585  PieMenuPrivate *vars;
586  vars = PIE_MENU_GET_PRIVATE (pie);
587  vars->bars_visible=val;
588  if(!val)
589  for(int i=0;i<vars->nSegments;i++)
590  vars->bar_percentages[i] = 0;
591 }
592 
593 
594 void pie_set_labels(GtkWidget *pie, std::vector<std::string> labels)
595 {
596  if(pie==NULL)
597  return;
598  PieMenuPrivate *vars;
599  vars = PIE_MENU_GET_PRIVATE (pie);
600  vars->labels = labels;
601 }
602 
603 void pie_set_secondary_labels(GtkWidget *pie, std::vector<std::string> labels)
604 {
605  if(pie==NULL)
606  return;
607  PieMenuPrivate *vars;
608  vars = PIE_MENU_GET_PRIVATE (pie);
609  vars->secondary_labels = labels;
610 }
611 
612 
613 void pie_set_train_mode(GtkWidget *pie)
614 {
615  if(pie==NULL)
616  return;
617  PieMenuPrivate *vars;
618  vars = PIE_MENU_GET_PRIVATE(pie);
619 
620  if(!vars->train_mode)
621  {
622  vars->train_mode = true;
623  pie_menu_update(pie);
624  }
625 }
626 
627 void pie_set_use_mode(GtkWidget *pie)
628 {
629  if(pie==NULL)
630  return;
631  PieMenuPrivate *vars;
632  vars = PIE_MENU_GET_PRIVATE(pie);
633  if(vars->train_mode)
634  {
635  vars->train_mode = false;
636  pie_menu_update(pie);
637  }
638 }
639 
640 
641 
642 void pie_select_class(GtkWidget *pie, int cls)
643 {
644  if(pie==NULL)
645  {
646  cerr << "ERROR pie is NULL\n";
647  return;
648  }
649  PieMenuPrivate *vars;
650  vars = PIE_MENU_GET_PRIVATE (pie);
651 
652  for(int i=0;i<vars->nSegments;i++)
653  vars->bar_percentages[i] = 0;
654  vars->lit = cls;
655 }
656 
657 void pie_set_class_proportions(GtkWidget *pie, std::vector<double> proportions)
658 {
659  if(pie==NULL)
660  {
661  cerr << "ERROR pie is NULL\n";
662  return;
663  }
664 
665 
666  PieMenuPrivate *vars;
667  vars = PIE_MENU_GET_PRIVATE (pie);
668 
669  if(unsigned(vars->nSegments) > proportions.size())
670  {
671  cerr << "ERROR vector is of wrong size in pie_set_class_proportions\n";
672  return;
673  }
674 
675  for(int i=0;i<vars->nSegments;i++)
676  {
677  vars->bar_percentages[i] = proportions[i];
678  }
679 }