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