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