72d5cbecfb1f14c0cc5f54a1176c861a2b2d3ab3
[lxde/lxpanel.git] / src / input-button.c
1 /*
2 * Copyright (c) 2014 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include <gtk/gtk.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <glib/gi18n.h>
26
27 #include "plugin.h"
28 #include "gtk-compat.h"
29
30 #include <keybinder.h>
31
32 /* generated by glib-genmarshal for BOOL:STRING */
33 void _marshal_BOOLEAN__STRING (GClosure *closure,
34 GValue *return_value G_GNUC_UNUSED,
35 guint n_param_values,
36 const GValue *param_values,
37 gpointer invocation_hint G_GNUC_UNUSED,
38 gpointer marshal_data)
39 {
40 typedef gboolean (*GMarshalFunc_BOOLEAN__STRING) (gpointer data1,
41 gpointer arg_1,
42 gpointer data2);
43 register GMarshalFunc_BOOLEAN__STRING callback;
44 register GCClosure *cc = (GCClosure*) closure;
45 register gpointer data1, data2;
46 gboolean v_return;
47
48 g_return_if_fail (return_value != NULL);
49 g_return_if_fail (n_param_values == 2);
50
51 if (G_CCLOSURE_SWAP_DATA (closure))
52 {
53 data1 = closure->data;
54 data2 = g_value_peek_pointer (param_values + 0);
55 }
56 else
57 {
58 data1 = g_value_peek_pointer (param_values + 0);
59 data2 = closure->data;
60 }
61 callback = (GMarshalFunc_BOOLEAN__STRING) (marshal_data ? marshal_data : cc->callback);
62
63 v_return = callback (data1, (char*) g_value_get_string(param_values + 1), data2);
64
65 g_value_set_boolean (return_value, v_return);
66 }
67
68 #define PANEL_TYPE_CFG_INPUT_BUTTON (config_input_button_get_type())
69
70 typedef struct _PanelCfgInputButton PanelCfgInputButton;
71 typedef struct _PanelCfgInputButtonClass PanelCfgInputButtonClass;
72
73 struct _PanelCfgInputButton
74 {
75 GtkFrame parent;
76 GtkToggleButton *none;
77 GtkToggleButton *custom;
78 GtkButton *btn;
79 gboolean do_key;
80 gboolean do_click;
81 guint key;
82 GdkModifierType mods;
83 gboolean has_focus;
84 };
85
86 struct _PanelCfgInputButtonClass
87 {
88 GtkFrameClass parent_class;
89 gboolean (*changed)(PanelCfgInputButton *btn, char *accel);
90 };
91
92 enum
93 {
94 CHANGED,
95 N_SIGNALS
96 };
97
98 static guint signals[N_SIGNALS];
99
100
101 /* ---- Events on test button ---- */
102
103 static void on_focus_in_event(GtkButton *test, GdkEvent *event,
104 PanelCfgInputButton *btn)
105 {
106 /* toggle radiobuttons */
107 gtk_toggle_button_set_active(btn->custom, TRUE);
108 btn->has_focus = TRUE;
109 if (btn->do_key)
110 gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(test)),
111 TRUE, GDK_CURRENT_TIME);
112 }
113
114 static void on_focus_out_event(GtkButton *test, GdkEvent *event,
115 PanelCfgInputButton *btn)
116 {
117 /* stop accepting mouse clicks */
118 btn->has_focus = FALSE;
119 if (btn->do_key)
120 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
121 }
122
123 static void _button_set_click_label(GtkButton *btn, guint keyval, GdkModifierType state)
124 {
125 char *mod_text, *text;
126 const char *btn_text;
127 char buff[64];
128
129 mod_text = gtk_accelerator_get_label(0, state);
130 btn_text = gdk_keyval_name(keyval);
131 switch (btn_text[0])
132 {
133 case '1':
134 btn_text = _("LeftBtn");
135 break;
136 case '2':
137 btn_text = _("MiddleBtn");
138 break;
139 case '3':
140 btn_text = _("RightBtn");
141 break;
142 default:
143 snprintf(buff, sizeof(buff), _("Btn%s"), btn_text);
144 btn_text = buff;
145 }
146 text = g_strdup_printf("%s%s", mod_text, btn_text);
147 gtk_button_set_label(btn, text);
148 g_free(text);
149 g_free(mod_text);
150 }
151
152 static gboolean on_key_event(GtkButton *test, GdkEventKey *event,
153 PanelCfgInputButton *btn)
154 {
155 GdkModifierType state;
156 char *text;
157 gboolean ret = FALSE;
158
159 /* ignore Tab completely so user can leave focus */
160 if (event->keyval == GDK_KEY_Tab)
161 return FALSE;
162 /* request mods directly, event->state isn't updated yet */
163 gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(test)),
164 NULL, NULL, &state);
165 /* special support for Win key, it doesn't work sometimes */
166 if ((state & GDK_SUPER_MASK) == 0 && (state & GDK_MOD4_MASK) != 0)
167 state |= GDK_SUPER_MASK;
168 state &= gtk_accelerator_get_default_mod_mask();
169 /* if mod key event then update test label and go */
170 if (event->is_modifier)
171 {
172 if (state != 0)
173 text = gtk_accelerator_get_label(0, state);
174 /* if no modifiers currently then show original state */
175 else if (btn->do_key)
176 text = gtk_accelerator_get_label(btn->key, btn->mods);
177 else
178 {
179 _button_set_click_label(test, btn->key, btn->mods);
180 return FALSE;
181 }
182 gtk_button_set_label(test, text);
183 g_free(text);
184 return FALSE;
185 }
186 /* if not keypress query then ignore key press */
187 if (event->type != GDK_KEY_PRESS || !btn->do_key)
188 return FALSE;
189 /* if keypress is equal to previous then nothing to do */
190 if (state == btn->mods && event->keyval == btn->key)
191 {
192 text = gtk_accelerator_get_label(event->keyval, state);
193 gtk_button_set_label(test, text);
194 g_free(text);
195 return FALSE;
196 }
197 /* drop single printable and printable with single Shift, Ctrl, Alt */
198 if (event->length != 0 && (state == 0 || state == GDK_SHIFT_MASK ||
199 state == GDK_CONTROL_MASK || state == GDK_MOD1_MASK))
200 {
201 GtkWidget* dlg;
202 text = gtk_accelerator_get_label(event->keyval, state);
203 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
204 _("Key combination '%s' cannot be used as"
205 " a global hotkey, sorry."), text);
206 g_free(text);
207 gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
208 gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
209 gtk_dialog_run(GTK_DIALOG(dlg));
210 gtk_widget_destroy(dlg);
211 return FALSE;
212 }
213 /* send a signal that it's changed */
214 text = gtk_accelerator_name(event->keyval, state);
215 g_signal_emit(btn, signals[CHANGED], 0, text, &ret);
216 g_free(text);
217 if (ret)
218 {
219 btn->mods = state;
220 btn->key = event->keyval;
221 }
222 text = gtk_accelerator_get_label(btn->key, btn->mods);
223 gtk_button_set_label(test, text);
224 g_free(text);
225 return FALSE;
226 }
227
228 static gboolean on_button_press_event(GtkButton *test, GdkEventButton *event,
229 PanelCfgInputButton *btn)
230 {
231 GdkModifierType state;
232 char *text;
233 char digit[4];
234 guint keyval;
235 gboolean ret = FALSE;
236
237 if (!btn->do_click)
238 return FALSE;
239 /* if not focused yet then take facus and skip event */
240 if (!btn->has_focus)
241 {
242 btn->has_focus = TRUE;
243 return FALSE;
244 }
245 /* if simple right-click then just ignore it */
246 state = event->state & gtk_accelerator_get_default_mod_mask();
247 if (event->button == 3 && state == 0)
248 return FALSE;
249 /* FIXME: how else to represent buttons? */
250 snprintf(digit, sizeof(digit), "%u", event->button);
251 keyval = gdk_keyval_from_name(digit);
252 /* if click is equal to previous then nothing to do */
253 if (state == btn->mods && keyval == btn->key)
254 {
255 _button_set_click_label(test, keyval, state);
256 return FALSE;
257 }
258 /* send a signal that it's changed */
259 text = gtk_accelerator_name(keyval, state);
260 g_signal_emit(btn, signals[CHANGED], 0, text, &ret);
261 g_free(text);
262 if (ret)
263 {
264 btn->mods = state;
265 btn->key = keyval;
266 }
267 _button_set_click_label(test, btn->key, btn->mods);
268 return FALSE;
269 }
270
271 static void on_reset(GtkRadioButton *rb, PanelCfgInputButton *btn)
272 {
273 gboolean ret = FALSE;
274 if (!gtk_toggle_button_get_active(btn->none))
275 return;
276 btn->mods = 0;
277 btn->key = 0;
278 gtk_button_set_label(btn->btn, "");
279 g_signal_emit(btn, signals[CHANGED], 0, NULL, &ret);
280 }
281
282 /* ---- Class implementation ---- */
283
284 G_DEFINE_TYPE(PanelCfgInputButton, config_input_button, GTK_TYPE_FRAME)
285
286 static void config_input_button_class_init(PanelCfgInputButtonClass *klass)
287 {
288 signals[CHANGED] =
289 g_signal_new("changed",
290 G_TYPE_FROM_CLASS(klass),
291 G_SIGNAL_RUN_LAST,
292 G_STRUCT_OFFSET(PanelCfgInputButtonClass, changed),
293 g_signal_accumulator_true_handled, NULL,
294 _marshal_BOOLEAN__STRING,
295 G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
296 }
297
298 static void config_input_button_init(PanelCfgInputButton *self)
299 {
300 GtkWidget *w = gtk_hbox_new(FALSE, 6);
301 GtkBox *box = GTK_BOX(w);
302
303 /* GtkRadioButton "None" */
304 w = gtk_radio_button_new_with_label(NULL, _("None"));
305 gtk_box_pack_start(box, w, FALSE, FALSE, 6);
306 self->none = GTK_TOGGLE_BUTTON(w);
307 gtk_toggle_button_set_active(self->none, TRUE);
308 g_signal_connect(w, "toggled", G_CALLBACK(on_reset), self);
309 /* GtkRadioButton "Custom:" */
310 w = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(w),
311 _("Custom:"));
312 gtk_box_pack_start(box, w, FALSE, FALSE, 0);
313 gtk_widget_set_can_focus(w, FALSE);
314 self->custom = GTK_TOGGLE_BUTTON(w);
315 /* test GtkButton */
316 w = gtk_button_new_with_label(NULL);
317 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
318 self->btn = GTK_BUTTON(w);
319 gtk_button_set_label(self->btn, " "); /* set some minimum size */
320 g_signal_connect(w, "focus-in-event", G_CALLBACK(on_focus_in_event), self);
321 g_signal_connect(w, "focus-out-event", G_CALLBACK(on_focus_out_event), self);
322 g_signal_connect(w, "key-press-event", G_CALLBACK(on_key_event), self);
323 g_signal_connect(w, "key-release-event", G_CALLBACK(on_key_event), self);
324 g_signal_connect(w, "button-press-event", G_CALLBACK(on_button_press_event), self);
325 /* HBox */
326 w = (GtkWidget *)box;
327 gtk_widget_show_all(w);
328 gtk_container_add(GTK_CONTAINER(self), w);
329 }
330
331 static PanelCfgInputButton *_config_input_button_new(const char *label)
332 {
333 return g_object_new(PANEL_TYPE_CFG_INPUT_BUTTON,
334 "label", label, NULL);
335 }
336
337 GtkWidget *panel_config_hotkey_button_new(const char *label, const char *hotkey)
338 {
339 PanelCfgInputButton *btn = _config_input_button_new(label);
340 char *text;
341
342 btn->do_key = TRUE;
343 if (hotkey && *hotkey)
344 {
345 gtk_accelerator_parse(hotkey, &btn->key, &btn->mods);
346 text = gtk_accelerator_get_label(btn->key, btn->mods);
347 gtk_button_set_label(btn->btn, text);
348 g_free(text);
349 gtk_toggle_button_set_active(btn->custom, TRUE);
350 }
351 return GTK_WIDGET(btn);
352 }
353
354 GtkWidget *panel_config_click_button_new(const char *label, const char *click)
355 {
356 PanelCfgInputButton *btn = _config_input_button_new(label);
357
358 btn->do_click = TRUE;
359 if (click && *click)
360 {
361 gtk_accelerator_parse(click, &btn->key, &btn->mods);
362 _button_set_click_label(btn->btn, btn->key, btn->mods);
363 gtk_toggle_button_set_active(btn->custom, TRUE);
364 }
365 return GTK_WIDGET(btn);
366 }
367
368 gboolean lxpanel_apply_hotkey(char **hkptr, const char *keystring,
369 void (*handler)(const char *keystring, gpointer user_data),
370 gpointer user_data, gboolean show_error)
371 {
372 g_return_val_if_fail(hkptr != NULL, FALSE);
373 g_return_val_if_fail(handler != NULL, FALSE);
374
375 if (keystring != NULL && !keybinder_bind(keystring, handler, user_data))
376 {
377 if (show_error)
378 {
379 GtkWidget* dlg;
380
381 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
382 _("Cannot assign '%s' as a global hotkey:"
383 " it is already bound."), keystring);
384 gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
385 gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
386 gtk_dialog_run(GTK_DIALOG(dlg));
387 gtk_widget_destroy(dlg);
388 }
389 return FALSE;
390 }
391 if (*hkptr != NULL)
392 keybinder_unbind(*hkptr, handler);
393 g_free(*hkptr);
394 *hkptr = g_strdup(keystring);
395 return TRUE;
396 }
397
398 guint panel_config_click_parse(const char *keystring, GdkModifierType *mods)
399 {
400 guint key;
401 const char *name;
402
403 if (keystring == NULL)
404 return 0;
405 gtk_accelerator_parse(keystring, &key, mods);
406 name = gdk_keyval_name(key);
407 if (name[0] >= '1' && name[0] <= '9')
408 return (name[0] - '0');
409 return 0;
410 }
411 #if 0
412 // test code, can be used as an example until erased. :)
413 static void handler(const char *keystring, void *user_data)
414 {
415 }
416
417 static char *hotkey = NULL;
418
419 static gboolean cb(PanelCfgInputButton *btn, char *text, gpointer unused)
420 {
421 g_print("got keystring \"%s\"\n", text);
422
423 if (!btn->do_key)
424 return TRUE;
425 return lxpanel_apply_hotkey(&hotkey, text, &handler, NULL, TRUE);
426 }
427
428 int main(int argc, char **argv)
429 {
430 GtkWidget *dialog;
431 GtkWidget *btn;
432
433 gtk_init(&argc, &argv);
434 dialog = gtk_dialog_new();
435 lxpanel_apply_hotkey(&hotkey, "<Super>z", &handler, NULL, FALSE);
436 btn = panel_config_hotkey_button_new("test", hotkey);
437 // btn = panel_config_click_button_new("test", NULL);
438 gtk_widget_show(btn);
439 g_signal_connect(btn, "changed", G_CALLBACK(cb), NULL);
440 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), btn);
441 gtk_dialog_run(GTK_DIALOG(dialog));
442 return 0;
443 }
444 #endif