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