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