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