1 /*
2   Gpredict: Real-time satellite tracking and orbit prediction program
3 
4   Copyright (C)  2001-2017  Alexandru Csete, OZ9AEC
5   Copyright (C)       2017  Patrick Dohmen, DL4PD
6 
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 2 of the License, or
10   (at your option) any later version.
11 
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16 
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, visit http://www.fsf.org/
19 */
20 #ifdef HAVE_CONFIG_H
21 #include <build-config.h>
22 #endif
23 
24 #include <gtk/gtk.h>
25 #include <glib/gi18n.h>
26 #include <math.h>
27 #include "gtk-freq-knob.h"
28 
29 
30 G_LOCK_DEFINE_STATIC(updatelock);
31 
32 static GtkHBoxClass *parent_class = NULL;
33 
34 #define FMTSTR "<span size='xx-large'>%c</span>"
35 
36 /* x-index in table for buttons and labels */
37 static const guint idx[] = {
38     0,
39     2,
40     3,
41     4,
42     6,
43     7,
44     8,
45     10,
46     11,
47     12
48 };
49 
50 static guint    freq_changed_signal = 0;
51 
52 
gtk_freq_knob_destroy(GtkWidget * widget)53 static void gtk_freq_knob_destroy(GtkWidget * widget)
54 {
55     (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
56 }
57 
gtk_freq_knob_class_init(GtkFreqKnobClass * class)58 static void gtk_freq_knob_class_init(GtkFreqKnobClass * class)
59 {
60     GtkWidgetClass *widget_class;
61 
62     widget_class = (GtkWidgetClass *) class;
63     parent_class = g_type_class_peek_parent(class);
64     widget_class->destroy = gtk_freq_knob_destroy;
65 
66     /* create freq changed signal */
67     freq_changed_signal = g_signal_new("freq-changed",
68                                        G_TYPE_FROM_CLASS(class),
69                                        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
70                                        0,
71                                        NULL,
72                                        NULL,
73                                        g_cclosure_marshal_VOID__VOID,
74                                        G_TYPE_NONE, 0);
75 }
76 
gtk_freq_knob_init(GtkFreqKnob * knob)77 static void gtk_freq_knob_init(GtkFreqKnob * knob)
78 {
79     knob->min = 0.0;
80     knob->max = 9999999999.0;
81 }
82 
gtk_freq_knob_update(GtkFreqKnob * knob)83 static void    *gtk_freq_knob_update(GtkFreqKnob * knob)
84 {
85     gchar           b[11];
86     gchar          *buff;
87     guint           i;
88 
89     G_LOCK(updatelock);
90     g_ascii_formatd(b, 11, "%10.0f", fabs(knob->value));
91 
92     /* set label markups */
93     for (i = 0; i < 10; i++)
94     {
95         buff = g_strdup_printf(FMTSTR, b[i]);
96         gtk_label_set_markup(GTK_LABEL(knob->digits[i]), buff);
97         g_free(buff);
98     }
99 
100     G_UNLOCK(updatelock);
101 
102     return FALSE;
103 }
104 
button_clicked_cb(GtkWidget * button,gpointer data)105 static void button_clicked_cb(GtkWidget * button, gpointer data)
106 {
107     GtkFreqKnob    *knob = GTK_FREQ_KNOB(data);
108     gdouble         delta =
109         GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "delta"));
110 
111     if ((delta > 0.0) && ((knob->value + delta) <= knob->max))
112     {
113         knob->value += delta;
114     }
115     else if ((delta < 0.0) && ((knob->value + delta) >= knob->min))
116     {
117         knob->value += delta;
118     }
119 
120     g_idle_add((GSourceFunc) gtk_freq_knob_update, knob);
121 
122     /* emit "freq_changed" signal */
123     g_signal_emit(G_OBJECT(data), freq_changed_signal, 0);
124 }
125 
126 /*
127  * Manage button press events
128  *
129  * @param digit Pointer to the event box that received the event
130  * @param event Pointer to the GdkEventButton that contains details for te event
131  * @param data Pointer to the GtkFreqKnob widget (we need it to update the value)
132  *
133  * Always returns TRUE to prevent further propagation of the event
134  *
135  * This function is called when a mouse button is pressed on a digit. This is used
136  * to increment or decrement the value:
137  * - Left button: up
138  * - Right button: down
139  * - Middle button: set digit to 0 (TBC)
140  *
141  * Wheel up/down are managed in a separate callback since these are treated as
142  * scroll events rather than button press events (they used to be button press
143  * events though)
144  *
145  * The digit labels are stored in an array. To get the amount of change corresponding
146  * to the clicked label we can convert the index (attached to the evtbox):
147  * delta = 10^(9-index)
148  *
149  * Whether the delta is positive or negative depends on which mouse button triggered
150  * the event.
151  */
on_button_press(GtkWidget * evtbox,GdkEventButton * event,gpointer data)152 static gboolean on_button_press(GtkWidget * evtbox,
153                                 GdkEventButton * event, gpointer data)
154 {
155     GtkFreqKnob    *knob = GTK_FREQ_KNOB(data);
156     guint           idx =
157         GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(evtbox), "index"));
158     gdouble         delta = pow(10, 9 - idx);
159     gdouble         value;
160 
161 
162     if (delta < 1.0)            // no change
163         return TRUE;
164 
165     if (event->type != GDK_BUTTON_PRESS)
166         return TRUE;
167 
168     switch (event->button)
169     {
170         /* left button */
171     case 1:
172         value = gtk_freq_knob_get_value(knob) + delta;
173         gtk_freq_knob_set_value(knob, value);
174         g_signal_emit(G_OBJECT(data), freq_changed_signal, 0);
175         break;
176 
177         /* middle button */
178     case 2:
179         break;
180 
181         /* right button */
182     case 3:
183         value = gtk_freq_knob_get_value(knob) - delta;
184         gtk_freq_knob_set_value(knob, value);
185         g_signal_emit(G_OBJECT(data), freq_changed_signal, 0);
186         break;
187 
188     default:
189         break;
190     }
191 
192     return TRUE;
193 }
194 
195 /*
196  * Manage scroll wheel events
197  *
198  * @param digit Pointer to the event box that received the event
199  * @param event Pointer to the GdkEventScroll that contains details for te event
200  * @param data Pointer to the GtkFreqKnob widget (we need it to update the value)
201  *
202  * Always returns TRUE to prevent further propagation of the event
203  *
204  * This function is called when the mouse wheel is moved up or down. This is used
205  * to increment or decrement the value.
206  *
207  * Button presses are managed in a separate callback since these are treated as
208  * different events.
209  *
210  * The digit labels are stored in an array. To get the amount of change corresponding
211  * to the clicked label we can convert the index (attached to the evtbox):
212  * delta = 10^(9-index)
213  *
214  * Whether the delta is positive or negative depends on the scroll direction.
215  */
on_button_scroll(GtkWidget * evtbox,GdkEventScroll * event,gpointer data)216 static gboolean on_button_scroll(GtkWidget * evtbox,
217                                  GdkEventScroll * event, gpointer data)
218 {
219     GtkFreqKnob    *knob = GTK_FREQ_KNOB(data);
220     guint           idx =
221         GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(evtbox), "index"));
222     gdouble         delta = pow(10, 9 - idx);
223     gdouble         value;
224 
225 
226     if (delta < 0.01)
227     {
228         /* no change, user clicked on sign or decimal separator */
229         return TRUE;
230     }
231 
232     if (event->type != GDK_SCROLL)
233     {
234         /* wrong event (not possible?) */
235         return TRUE;
236     }
237 
238     switch (event->direction)
239     {
240 
241         /* decrease value by delta */
242     case GDK_SCROLL_DOWN:
243     case GDK_SCROLL_LEFT:
244         value = gtk_freq_knob_get_value(knob) - delta;
245         gtk_freq_knob_set_value(knob, value);
246         g_signal_emit(G_OBJECT(data), freq_changed_signal, 0);
247         break;
248 
249         /* increase value by delta */
250     case GDK_SCROLL_UP:
251     case GDK_SCROLL_RIGHT:
252         value = gtk_freq_knob_get_value(knob) + delta;
253         gtk_freq_knob_set_value(knob, value);
254         g_signal_emit(G_OBJECT(data), freq_changed_signal, 0);
255         break;
256 
257     default:
258         break;
259     }
260 
261     return TRUE;
262 }
263 
gtk_freq_knob_set_value(GtkFreqKnob * knob,gdouble val)264 void gtk_freq_knob_set_value(GtkFreqKnob * knob, gdouble val)
265 {
266     if ((val >= knob->min) && (val <= knob->max))
267     {
268         knob->value = val;
269         g_idle_add((GSourceFunc) gtk_freq_knob_update, knob);
270     }
271 }
272 
gtk_freq_knob_get_value(GtkFreqKnob * knob)273 gdouble gtk_freq_knob_get_value(GtkFreqKnob * knob)
274 {
275     return knob->value;
276 }
277 
gtk_freq_knob_get_type()278 GType gtk_freq_knob_get_type()
279 {
280     static GType    gtk_freq_knob_type = 0;
281 
282     if (!gtk_freq_knob_type)
283     {
284         static const GTypeInfo gtk_freq_knob_info = {
285             sizeof(GtkFreqKnobClass),
286             NULL,               /* base_init */
287             NULL,               /* base_finalize */
288             (GClassInitFunc) gtk_freq_knob_class_init,
289             NULL,               /* class_finalize */
290             NULL,               /* class_data */
291             sizeof(GtkFreqKnob),
292             5,                  /* n_preallocs */
293             (GInstanceInitFunc) gtk_freq_knob_init,
294             NULL,
295         };
296 
297         gtk_freq_knob_type = g_type_register_static(GTK_TYPE_BOX,
298                                                     "GtkFreqKnob",
299                                                     &gtk_freq_knob_info, 0);
300     }
301 
302     return gtk_freq_knob_type;
303 }
304 
gtk_freq_knob_new(gdouble val,gboolean buttons)305 GtkWidget      *gtk_freq_knob_new(gdouble val, gboolean buttons)
306 {
307     GtkFreqKnob    *knob;
308     GtkWidget      *widget;
309     GtkWidget      *table;
310     GtkWidget      *label;
311     guint           i;
312     gint            delta;
313 
314 
315     widget = g_object_new(GTK_TYPE_FREQ_KNOB, NULL);
316     knob = GTK_FREQ_KNOB(widget);
317     knob->value = val;
318 
319     table = gtk_grid_new();
320 
321     /* create buttons and labels */
322     for (i = 0; i < 10; i++)
323     {
324         /* labels */
325         knob->digits[i] = gtk_label_new(NULL);
326 
327         if (!buttons)
328         {
329             /* passive display widget without event boxes or buttons */
330             gtk_grid_attach(GTK_GRID(table), knob->digits[i], idx[i], 1, 1, 1);
331         }
332         else
333         {
334             /* active widget that allows changing the value */
335 
336             gtk_widget_set_tooltip_text(knob->digits[i],
337                                         _
338                                         ("Use mouse buttons and wheel to change value"));
339 
340             /* Event boxes for catching mouse evetns */
341             knob->evtbox[i] = gtk_event_box_new();
342             g_object_set_data(G_OBJECT(knob->evtbox[i]),
343                               "index", GUINT_TO_POINTER(i));
344             gtk_container_add(GTK_CONTAINER(knob->evtbox[i]), knob->digits[i]);
345             gtk_grid_attach(GTK_GRID(table), knob->evtbox[i], idx[i], 1, 1, 1);
346 
347             g_signal_connect(knob->evtbox[i],
348                              "button_press_event", (GCallback) on_button_press,
349                              widget);
350             g_signal_connect(knob->evtbox[i], "scroll_event",
351                              (GCallback) on_button_scroll, widget);
352 
353 
354             /* UP buttons */
355             knob->buttons[i] = gtk_button_new();
356 
357             label = gtk_label_new("\342\226\264");
358             gtk_container_add(GTK_CONTAINER(knob->buttons[i]), label);
359             gtk_button_set_relief(GTK_BUTTON(knob->buttons[i]),
360                                   GTK_RELIEF_NONE);
361             delta = (gint) pow(10, 9 - i);
362             g_object_set_data(G_OBJECT(knob->buttons[i]),
363                               "delta", GINT_TO_POINTER(delta));
364             gtk_grid_attach(GTK_GRID(table), knob->buttons[i], idx[i], 0, 1,
365                             1);
366             g_signal_connect(knob->buttons[i], "clicked",
367                              G_CALLBACK(button_clicked_cb), widget);
368 
369             /* DOWN buttons */
370             knob->buttons[i + 10] = gtk_button_new();
371 
372             label = gtk_label_new("\342\226\276");
373             gtk_container_add(GTK_CONTAINER(knob->buttons[i + 10]), label);
374             gtk_button_set_relief(GTK_BUTTON(knob->buttons[i + 10]),
375                                   GTK_RELIEF_NONE);
376             g_object_set_data(G_OBJECT(knob->buttons[i + 10]),
377                               "delta", GINT_TO_POINTER(-delta));
378             gtk_grid_attach(GTK_GRID(table), knob->buttons[i + 10],
379                             idx[i], 2, 1, 1);
380             g_signal_connect(knob->buttons[i + 10], "clicked",
381                              G_CALLBACK(button_clicked_cb), widget);
382         }
383     }
384 
385     /* Add misc labels */
386     label = gtk_label_new(NULL);
387     gtk_label_set_markup(GTK_LABEL(label), "<span size='xx-large'>.</span>");
388     gtk_grid_attach(GTK_GRID(table), label, 5, 1, 1, 1);
389     label = gtk_label_new(NULL);
390     gtk_label_set_markup(GTK_LABEL(label), "<span size='xx-large'>.</span>");
391     gtk_grid_attach(GTK_GRID(table), label, 9, 1, 1, 1);
392 
393     label = gtk_label_new(NULL);
394     gtk_label_set_markup(GTK_LABEL(label), "<span size='xx-large'> Hz</span>");
395     gtk_grid_attach(GTK_GRID(table), label, 13, 1, 1, 1);
396 
397     g_idle_add((GSourceFunc) gtk_freq_knob_update, knob);
398 
399     gtk_container_add(GTK_CONTAINER(widget), table);
400     gtk_widget_show_all(widget);
401 
402     return widget;
403 }
404