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 >k_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