1 /*
2   Gpredict: Real-time satellite tracking and orbit prediction program
3 
4   Copyright (C)  2001-2017  Alexandru Csete, OZ9AEC.
5 
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2 of the License, or
9   (at your option) any later version.
10 
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15 
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, visit http://www.fsf.org/
18 */
19 
20 #include <gtk/gtk.h>
21 #include <glib/gi18n.h>
22 #include <math.h>
23 #ifdef HAVE_CONFIG_H
24 #include <build-config.h>
25 #endif
26 
27 #include "gtk-rot-knob.h"
28 
29 #define FMTSTR "<span size='xx-large'>%c</span>"
30 
31 
32 static GtkHBoxClass *parent_class = NULL;
33 
34 /* Convert digit index (in the digit array) to amount of change. */
35 const gdouble   INDEX_TO_DELTA[] = { 0.0, 100.0, 10.0, 1.0, 0.0, 0.1, 0.01 };
36 
37 
gtk_rot_knob_destroy(GtkWidget * widget)38 static void gtk_rot_knob_destroy(GtkWidget * widget)
39 {
40     (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
41 }
42 
gtk_rot_knob_class_init(GtkRotKnobClass * class)43 static void gtk_rot_knob_class_init(GtkRotKnobClass * class)
44 {
45     GtkWidgetClass *widget_class = (GtkWidgetClass *) class;
46 
47     widget_class->destroy = gtk_rot_knob_destroy;
48 
49     parent_class = g_type_class_peek_parent(class);
50 }
51 
gtk_rot_knob_init(GtkRotKnob * knob)52 static void gtk_rot_knob_init(GtkRotKnob * knob)
53 {
54     (void)knob;
55 }
56 
57 /*
58  * Update rotor display widget.
59  *
60  * @param knob The rotor control widget.
61  *
62  */
gtk_rot_knob_update(GtkRotKnob * knob)63 static void gtk_rot_knob_update(GtkRotKnob * knob)
64 {
65     gchar           b[7];
66     gchar          *buff;
67     guint           i;
68 
69     g_ascii_formatd(b, 8, "%6.2f", fabs(knob->value));
70 
71     /* set label markups */
72     for (i = 0; i < 6; i++)
73     {
74         buff = g_strdup_printf(FMTSTR, b[i]);
75         gtk_label_set_markup(GTK_LABEL(knob->digits[i + 1]), buff);
76         g_free(buff);
77     }
78 
79     if (knob->value < 0)
80         buff = g_strdup_printf(FMTSTR, '-');
81     else
82         buff = g_strdup_printf(FMTSTR, ' ');
83 
84     gtk_label_set_markup(GTK_LABEL(knob->digits[0]), buff);
85     g_free(buff);
86 }
87 
88 /*
89  * Button clicked event.
90  *
91  * @param button The button that was clicked.
92  * @param data Pointer to the GtkRotKnob widget.
93  *
94  */
button_clicked_cb(GtkWidget * button,gpointer data)95 static void button_clicked_cb(GtkWidget * button, gpointer data)
96 {
97     GtkRotKnob     *knob = GTK_ROT_KNOB(data);
98     gdouble         delta =
99         GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "delta")) / 100.0;
100 
101     if ((delta > 0.0) && ((knob->value + delta) <= knob->max + .005))
102     {
103         knob->value += delta;
104         if (knob->value > knob->max)
105         {
106             knob->value = knob->max;
107         }
108     }
109     else if ((delta < 0.0) && ((knob->value + delta) >= knob->min - .005))
110     {
111         knob->value += delta;
112         if (knob->value < knob->min)
113         {
114             knob->value = knob->min;
115         }
116     }
117 
118     gtk_rot_knob_update(knob);
119 }
120 
121 /*
122  * Manage button press events
123  *
124  * @param digit Pointer to the event box that received the event
125  * @param event Pointer to the GdkEventButton that contains details for te event
126  * @param data Pointer to the GtkRotKnob widget (we need it to update the value)
127  * @return Always TRUE to prevent further propagation of the event
128  *
129  * This function is called when a mouse button is pressed on a digit. This is used
130  * to increment or decrement the value:
131  * - Left button: up
132  * - Right button: down
133  * - Middle button: set digit to 0 (TBC)
134  *
135  * Wheel up/down are managed in a separate callback since these are treated as scroll events
136  * rather than button press events (they used to be button press events though)
137  *
138  * The digit labels are stored in an array that also contains the sign and the
139  * decimal separator. To get the amount of change corresponding to the clicked label we
140  * can use the INDEX_TO_DELTA[] array. The index to this table is attached to the evtbox.
141  * Whether the delta is positive or negative depends on which mouse button triggered the event.
142  */
on_button_press(GtkWidget * evtbox,GdkEventButton * event,gpointer data)143 static gboolean on_button_press(GtkWidget * evtbox,
144                                 GdkEventButton * event, gpointer data)
145 {
146     GtkRotKnob     *knob = GTK_ROT_KNOB(data);
147     guint           idx =
148         GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(evtbox), "index"));
149     gdouble         delta = INDEX_TO_DELTA[idx];
150     gdouble         value;
151 
152     if (delta < 0.01)
153     {
154         /* no change, user clicked on sign or decimal separator */
155         return TRUE;
156     }
157 
158     if (event->type != GDK_BUTTON_PRESS)
159     {
160         /* wrong event (not possible?) */
161         return TRUE;
162     }
163 
164     switch (event->button)
165     {
166         /* left button */
167     case 1:
168         value = gtk_rot_knob_get_value(knob) + delta;
169         gtk_rot_knob_set_value(knob, value);
170         break;
171 
172         /* middle button */
173     case 2:
174         break;
175 
176         /* right button */
177     case 3:
178         value = gtk_rot_knob_get_value(knob) - delta;
179         gtk_rot_knob_set_value(knob, value);
180         break;
181 
182     default:
183         break;
184     }
185 
186     return TRUE;
187 }
188 
189 /*
190  * Manage scroll wheel events
191  *
192  * @param digit Pointer to the event box that received the event
193  * @param event Pointer to the GdkEventScroll that contains details for te event
194  * @param data Pointer to the GtkRotKnob widget (we need it to update the value)
195  * @return Always TRUE to prevent further propagation of the event
196  *
197  * This function is called when the mouse wheel is moved up or down. This is used to increment
198  * or decrement the value.
199  *
200  * Button presses are managed in a separate callback since these are treated as different
201  * events.
202  *
203  * The digit labels are stored in an array that also contains the sign and the
204  * decimal separator. To get the amount of change corresponding to the clicked label we
205  * can use the INDEX_TO_DELTA[] array. The index is attached to the evtbox.
206  * Whether the delta is positive or negative depends on the scroll direction.
207  */
on_button_scroll(GtkWidget * evtbox,GdkEventScroll * event,gpointer data)208 static gboolean on_button_scroll(GtkWidget * evtbox,
209                                  GdkEventScroll * event, gpointer data)
210 {
211     GtkRotKnob     *knob = GTK_ROT_KNOB(data);
212     guint           idx =
213         GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(evtbox), "index"));
214     gdouble         delta = INDEX_TO_DELTA[idx];
215     gdouble         value;
216 
217     if (delta < 0.01)
218     {
219         /* no change, user clicked on sign or decimal separator */
220         return TRUE;
221     }
222 
223     if (event->type != GDK_SCROLL)
224     {
225         /* wrong event (not possible?) */
226         return TRUE;
227     }
228 
229     switch (event->direction)
230     {
231         /* decrease value by delta */
232     case GDK_SCROLL_DOWN:
233     case GDK_SCROLL_LEFT:
234         value = gtk_rot_knob_get_value(knob) - delta;
235         gtk_rot_knob_set_value(knob, value);
236         break;
237 
238         /* increase value by delta */
239     case GDK_SCROLL_UP:
240     case GDK_SCROLL_RIGHT:
241         value = gtk_rot_knob_get_value(knob) + delta;
242         gtk_rot_knob_set_value(knob, value);
243         break;
244 
245     default:
246         break;
247     }
248 
249     return TRUE;
250 }
251 
gtk_rot_knob_get_type()252 GType gtk_rot_knob_get_type()
253 {
254     static GType    gtk_rot_knob_type = 0;
255 
256     if (!gtk_rot_knob_type)
257     {
258         static const GTypeInfo gtk_rot_knob_info = {
259             sizeof(GtkRotKnobClass),
260             NULL,               /* base_init */
261             NULL,               /* base_finalize */
262             (GClassInitFunc) gtk_rot_knob_class_init,
263             NULL,               /* class_finalize */
264             NULL,               /* class_data */
265             sizeof(GtkRotKnob),
266             5,                  /* n_preallocs */
267             (GInstanceInitFunc) gtk_rot_knob_init,
268             NULL
269         };
270 
271         gtk_rot_knob_type = g_type_register_static(GTK_TYPE_BOX,
272                                                    "GtkRotKnob",
273                                                    &gtk_rot_knob_info, 0);
274     }
275 
276     return gtk_rot_knob_type;
277 }
278 
279 /*
280  * Set the range of the control widget
281  *
282  * @param knob The rotor control widget.
283  * @param min The new lower limit of the control widget.
284  * @param max The new upper limit of the control widget.
285  */
gtk_rot_knob_set_range(GtkRotKnob * knob,gdouble min,gdouble max)286 void gtk_rot_knob_set_range(GtkRotKnob * knob, gdouble min, gdouble max)
287 {
288     gtk_rot_knob_set_min(knob, min);
289     gtk_rot_knob_set_max(knob, max);
290 }
291 
292 /*
293  * Set the value of the rotor control widget.
294  *
295  * @param knob The rotor control widget.
296  * @param val The new value.
297  *
298  */
gtk_rot_knob_set_value(GtkRotKnob * knob,gdouble val)299 void gtk_rot_knob_set_value(GtkRotKnob * knob, gdouble val)
300 {
301     /* set the new value */
302     if (val <= knob->min)
303         knob->value = knob->min;
304     else if (val >= knob->max)
305         knob->value = knob->max;
306     else
307         knob->value = val;
308 
309     /* update the display */
310     gtk_rot_knob_update(knob);
311 }
312 
313 /*
314  * Get the current value of the rotor control widget.
315  *
316  * @param knob The rotor control widget.
317  * @return The current value.
318  *
319  * Hint: For reading the value you can also access knob->value.
320  *
321  */
gtk_rot_knob_get_value(GtkRotKnob * knob)322 gdouble gtk_rot_knob_get_value(GtkRotKnob * knob)
323 {
324     return knob->value;
325 }
326 
327 /*
328  * Get the upper limit of the control widget
329  *
330  * @param knob The rotor control widget.
331  * @return The upper limit of the control widget.
332  */
gtk_rot_knob_get_max(GtkRotKnob * knob)333 gdouble gtk_rot_knob_get_max(GtkRotKnob * knob)
334 {
335     return knob->max;
336 }
337 
338 /*
339  * Get the lower limit of the control widget
340  *
341  * @param knob The rotor control widget.
342  * @return The lower limit of the control widget.
343  */
gtk_rot_knob_get_min(GtkRotKnob * knob)344 gdouble gtk_rot_knob_get_min(GtkRotKnob * knob)
345 {
346     return knob->min;
347 }
348 
349 /*
350  * Set the lower limit of the control widget
351  *
352  * @param knob The rotor control widget.
353  * @param min The new lower limit of the control widget.
354  */
gtk_rot_knob_set_min(GtkRotKnob * knob,gdouble min)355 void gtk_rot_knob_set_min(GtkRotKnob * knob, gdouble min)
356 {
357     /* just som sanity check we have only 3 digits */
358     if (min < 1000)
359     {
360         knob->min = min;
361 
362         /* ensure that current value is within range */
363         if (knob->value < knob->min)
364         {
365             knob->value = knob->min;
366             gtk_rot_knob_update(knob);
367         }
368     }
369 }
370 
371 /*
372  * Set the upper limit of the control widget
373  *
374  * @param knob The rotor control widget.
375  * @param min The new upper limit of the control widget.
376  */
gtk_rot_knob_set_max(GtkRotKnob * knob,gdouble max)377 void gtk_rot_knob_set_max(GtkRotKnob * knob, gdouble max)
378 {
379     /* just som sanity check we have only 3 digits */
380     if (max < 1000)
381     {
382         knob->max = max;
383 
384         /* ensure that current value is within range */
385         if (knob->value > knob->max)
386         {
387             knob->value = knob->max;
388             gtk_rot_knob_update(knob);
389         }
390     }
391 }
392 
393 /*
394  * Create a new rotor control widget.
395  *
396  * @param min The lower limit in decimal degrees.
397  * @param max The upper limit in decimal degrees.
398  * @param val The initial value of the control.
399  * @return A new rotor control widget.
400  *
401  */
gtk_rot_knob_new(gdouble min,gdouble max,gdouble val)402 GtkWidget      *gtk_rot_knob_new(gdouble min, gdouble max, gdouble val)
403 {
404     GtkRotKnob     *knob;
405     GtkWidget      *widget;
406     GtkWidget      *table;
407     GtkWidget      *label;
408     guint           i;
409 
410     widget = g_object_new(GTK_TYPE_ROT_KNOB, NULL);
411     knob = GTK_ROT_KNOB(widget);
412 
413     knob->min = min;
414     knob->max = max;
415     knob->value = val;
416 
417     table = gtk_grid_new();
418 
419     /* +100 deg */
420     knob->buttons[0] = gtk_button_new_with_label("\342\226\264");
421     gtk_button_set_relief(GTK_BUTTON(knob->buttons[0]), GTK_RELIEF_NONE);
422     g_object_set_data(G_OBJECT(knob->buttons[0]),
423                       "delta", GINT_TO_POINTER(10000));
424     gtk_grid_attach(GTK_GRID(table), knob->buttons[0], 1, 0, 1, 1);
425     g_signal_connect(knob->buttons[0], "clicked",
426                      G_CALLBACK(button_clicked_cb), widget);
427 
428     /* +10 deg */
429     knob->buttons[1] = gtk_button_new_with_label("\342\226\264");
430     gtk_button_set_relief(GTK_BUTTON(knob->buttons[1]), GTK_RELIEF_NONE);
431     g_object_set_data(G_OBJECT(knob->buttons[1]),
432                       "delta", GINT_TO_POINTER(1000));
433     gtk_grid_attach(GTK_GRID(table), knob->buttons[1], 2, 0, 1, 1);
434     g_signal_connect(knob->buttons[1], "clicked",
435                      G_CALLBACK(button_clicked_cb), widget);
436 
437     /* +1 deg */
438     knob->buttons[2] = gtk_button_new_with_label("\342\226\264");
439     gtk_button_set_relief(GTK_BUTTON(knob->buttons[2]), GTK_RELIEF_NONE);
440     g_object_set_data(G_OBJECT(knob->buttons[2]),
441                       "delta", GINT_TO_POINTER(100));
442     gtk_grid_attach(GTK_GRID(table), knob->buttons[2], 3, 0, 1, 1);
443     g_signal_connect(knob->buttons[2], "clicked",
444                      G_CALLBACK(button_clicked_cb), widget);
445 
446     /* +0.1 deg */
447     knob->buttons[3] = gtk_button_new_with_label("\342\226\264");
448     gtk_button_set_relief(GTK_BUTTON(knob->buttons[3]), GTK_RELIEF_NONE);
449     g_object_set_data(G_OBJECT(knob->buttons[3]),
450                       "delta", GINT_TO_POINTER(10));
451     gtk_grid_attach(GTK_GRID(table), knob->buttons[3], 5, 0, 1, 1);
452     g_signal_connect(knob->buttons[3], "clicked",
453                      G_CALLBACK(button_clicked_cb), widget);
454 
455     /* +0.01 deg */
456     knob->buttons[4] = gtk_button_new_with_label("\342\226\264");
457     gtk_button_set_relief(GTK_BUTTON(knob->buttons[4]), GTK_RELIEF_NONE);
458     g_object_set_data(G_OBJECT(knob->buttons[4]), "delta", GINT_TO_POINTER(1));
459     gtk_grid_attach(GTK_GRID(table), knob->buttons[4], 6, 0, 1, 1);
460     g_signal_connect(knob->buttons[4], "clicked",
461                      G_CALLBACK(button_clicked_cb), widget);
462 
463     /* -100 deg */
464     knob->buttons[5] = gtk_button_new_with_label("\342\226\276");
465     gtk_button_set_relief(GTK_BUTTON(knob->buttons[5]), GTK_RELIEF_NONE);
466     g_object_set_data(G_OBJECT(knob->buttons[5]),
467                       "delta", GINT_TO_POINTER(-10000));
468     gtk_grid_attach(GTK_GRID(table), knob->buttons[5], 1, 2, 1, 1);
469     g_signal_connect(knob->buttons[5], "clicked",
470                      G_CALLBACK(button_clicked_cb), widget);
471 
472     /* -10 deg */
473     knob->buttons[6] = gtk_button_new_with_label("\342\226\276");
474     gtk_button_set_relief(GTK_BUTTON(knob->buttons[6]), GTK_RELIEF_NONE);
475     g_object_set_data(G_OBJECT(knob->buttons[6]),
476                       "delta", GINT_TO_POINTER(-1000));
477     gtk_grid_attach(GTK_GRID(table), knob->buttons[6], 2, 2, 1, 1);
478     g_signal_connect(knob->buttons[6], "clicked",
479                      G_CALLBACK(button_clicked_cb), widget);
480 
481     /* -1 deg */
482     knob->buttons[7] = gtk_button_new_with_label("\342\226\276");
483     gtk_button_set_relief(GTK_BUTTON(knob->buttons[7]), GTK_RELIEF_NONE);
484     g_object_set_data(G_OBJECT(knob->buttons[7]),
485                       "delta", GINT_TO_POINTER(-100));
486     gtk_grid_attach(GTK_GRID(table), knob->buttons[7], 3, 2, 1, 1);
487     g_signal_connect(knob->buttons[7], "clicked",
488                      G_CALLBACK(button_clicked_cb), widget);
489 
490     /* -0.1 deg */
491     knob->buttons[8] = gtk_button_new_with_label("\342\226\276");
492     gtk_button_set_relief(GTK_BUTTON(knob->buttons[8]), GTK_RELIEF_NONE);
493     g_object_set_data(G_OBJECT(knob->buttons[8]),
494                       "delta", GINT_TO_POINTER(-10));
495     gtk_grid_attach(GTK_GRID(table), knob->buttons[8], 5, 2, 1, 1);
496     g_signal_connect(knob->buttons[8], "clicked",
497                      G_CALLBACK(button_clicked_cb), widget);
498 
499     /* -0.01 deg */
500     knob->buttons[9] = gtk_button_new_with_label("\342\226\276");
501     gtk_button_set_relief(GTK_BUTTON(knob->buttons[9]), GTK_RELIEF_NONE);
502     g_object_set_data(G_OBJECT(knob->buttons[9]),
503                       "delta", GINT_TO_POINTER(-1));
504     gtk_grid_attach(GTK_GRID(table), knob->buttons[9], 6, 2, 1, 1);
505     g_signal_connect(knob->buttons[9], "clicked",
506                      G_CALLBACK(button_clicked_cb), widget);
507 
508     /* create labels */
509     for (i = 0; i < 7; i++)
510     {
511         /* labels showing the digits */
512         knob->digits[i] = gtk_label_new(NULL);
513         gtk_widget_set_tooltip_text(knob->digits[i],
514                                     _
515                                     ("Use mouse buttons and wheel to change value"));
516 
517         /* Event boxes for catching mouse evetns */
518         knob->evtbox[i] = gtk_event_box_new();
519         g_object_set_data(G_OBJECT(knob->evtbox[i]), "index",
520                           GUINT_TO_POINTER(i));
521         gtk_container_add(GTK_CONTAINER(knob->evtbox[i]), knob->digits[i]);
522         gtk_grid_attach(GTK_GRID(table), knob->evtbox[i], i, 1, 1, 1);
523 
524         g_signal_connect(knob->evtbox[i], "button_press_event",
525                          (GCallback) on_button_press, widget);
526         g_signal_connect(knob->evtbox[i], "scroll_event",
527                          (GCallback) on_button_scroll, widget);
528 
529     }
530 
531     /* degree sign */
532     label = gtk_label_new(NULL);
533     gtk_label_set_markup(GTK_LABEL(label),
534                          "<span size='xx-large'>\302\260</span>");
535     gtk_grid_attach(GTK_GRID(table), label, 7, 1, 1, 1);
536     gtk_rot_knob_update(knob);
537     gtk_container_add(GTK_CONTAINER(widget), table);
538     gtk_widget_show_all(widget);
539 
540     return widget;
541 }
542