Use more user-friendly messages on button when configure click, like "Ctrl+MiddleBtn".
[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 void _button_set_click_label(GtkButton *btn, guint keyval, GdkModifierType state)
151 {
152 char *mod_text, *text;
153 const char *btn_text;
154 char buff[64];
155
156 mod_text = gtk_accelerator_get_label(0, state);
157 btn_text = gdk_keyval_name(keyval);
158 switch (btn_text[0])
159 {
160 case '1':
161 btn_text = _("LeftBtn");
162 break;
163 case '2':
164 btn_text = _("MiddleBtn");
165 break;
166 case '3':
167 btn_text = _("RightBtn");
168 break;
169 default:
170 snprintf(buff, sizeof(buff), _("Btn%s"), btn_text);
171 btn_text = buff;
172 }
173 text = g_strdup_printf("%s%s", mod_text, btn_text);
174 gtk_button_set_label(btn, text);
175 g_free(text);
176 g_free(mod_text);
177 }
178
179 static gboolean on_button_press_event(GtkButton *test, GdkEventButton *event,
180 PanelCfgInputButton *btn)
181 {
182 GdkModifierType state;
183 char *text;
184 char digit[4];
185 guint keyval;
186
187 if (!btn->do_click)
188 return FALSE;
189 /* if not focused yet then take facus and skip event */
190 if (!btn->has_focus)
191 {
192 btn->has_focus = TRUE;
193 return FALSE;
194 }
195 /* if simple right-click then just ignore it */
196 state = event->state & gtk_accelerator_get_default_mod_mask();
197 if (event->button == 3 && state == 0)
198 return FALSE;
199 /* FIXME: how else to represent buttons? */
200 snprintf(digit, sizeof(digit), "%u", event->button);
201 keyval = gdk_keyval_from_name(digit);
202 /* if click is equal to previous then nothing to do */
203 if (state == btn->mods && keyval == btn->key)
204 {
205 _button_set_click_label(test, keyval, state);
206 return FALSE;
207 }
208 /* send a signal that it's changed */
209 btn->mods = state;
210 btn->key = keyval;
211 _button_set_click_label(test, keyval, state);
212 text = gtk_accelerator_name(keyval, state);
213 g_signal_emit(btn, signals[CHANGED], 0, text);
214 g_free(text);
215 return FALSE;
216 }
217
218 static void on_reset(GtkRadioButton *rb, PanelCfgInputButton *btn)
219 {
220 if (!gtk_toggle_button_get_active(btn->none))
221 return;
222 btn->mods = 0;
223 btn->key = 0;
224 gtk_button_set_label(btn->btn, "");
225 g_signal_emit(btn, signals[CHANGED], 0, NULL);
226 }
227
228 /* ---- Class implementation ---- */
229
230 G_DEFINE_TYPE(PanelCfgInputButton, config_input_button, GTK_TYPE_FRAME)
231
232 static void config_input_button_class_init(PanelCfgInputButtonClass *klass)
233 {
234 signals[CHANGED] =
235 g_signal_new("changed",
236 G_TYPE_FROM_CLASS(klass),
237 G_SIGNAL_RUN_FIRST,
238 G_STRUCT_OFFSET(PanelCfgInputButtonClass, changed),
239 NULL, NULL,
240 g_cclosure_marshal_VOID__STRING,
241 G_TYPE_NONE, 1, G_TYPE_STRING);
242 }
243
244 static void config_input_button_init(PanelCfgInputButton *self)
245 {
246 GtkWidget *w = gtk_hbox_new(FALSE, 6);
247 GtkBox *box = GTK_BOX(w);
248
249 /* GtkRadioButton "None" */
250 w = gtk_radio_button_new_with_label(NULL, _("None"));
251 gtk_box_pack_start(box, w, FALSE, FALSE, 6);
252 self->none = GTK_TOGGLE_BUTTON(w);
253 gtk_toggle_button_set_active(self->none, TRUE);
254 g_signal_connect(w, "toggled", G_CALLBACK(on_reset), self);
255 /* GtkRadioButton "Custom:" */
256 w = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(w),
257 _("Custom:"));
258 gtk_box_pack_start(box, w, FALSE, FALSE, 0);
259 gtk_widget_set_can_focus(w, FALSE);
260 self->custom = GTK_TOGGLE_BUTTON(w);
261 /* test GtkButton */
262 w = gtk_button_new_with_label(NULL);
263 gtk_box_pack_start(box, w, TRUE, TRUE, 0);
264 self->btn = GTK_BUTTON(w);
265 gtk_button_set_label(self->btn, " "); /* set some minimum size */
266 g_signal_connect(w, "focus-in-event", G_CALLBACK(on_focus_in_event), self);
267 g_signal_connect(w, "focus-out-event", G_CALLBACK(on_focus_out_event), self);
268 g_signal_connect(w, "key-press-event", G_CALLBACK(on_key_event), self);
269 g_signal_connect(w, "key-release-event", G_CALLBACK(on_key_event), self);
270 g_signal_connect(w, "button-press-event", G_CALLBACK(on_button_press_event), self);
271 /* HBox */
272 w = (GtkWidget *)box;
273 gtk_widget_show_all(w);
274 gtk_container_add(GTK_CONTAINER(self), w);
275 }
276
277 static PanelCfgInputButton *_config_input_button_new(const char *label)
278 {
279 return g_object_new(PANEL_TYPE_CFG_INPUT_BUTTON,
280 "label", label, NULL);
281 }
282
283 GtkWidget *panel_config_hotkey_button_new(const char *label, const char *hotkey)
284 {
285 PanelCfgInputButton *btn = _config_input_button_new(label);
286 char *text;
287
288 btn->do_key = TRUE;
289 if (hotkey && *hotkey)
290 {
291 gtk_accelerator_parse(hotkey, &btn->key, &btn->mods);
292 text = gtk_accelerator_get_label(btn->key, btn->mods);
293 gtk_button_set_label(btn->btn, text);
294 g_free(text);
295 gtk_toggle_button_set_active(btn->custom, TRUE);
296 }
297 return GTK_WIDGET(btn);
298 }
299
300 GtkWidget *panel_config_click_button_new(const char *label, const char *click)
301 {
302 PanelCfgInputButton *btn = _config_input_button_new(label);
303 char *text;
304
305 btn->do_click = TRUE;
306 if (click && *click)
307 {
308 gtk_accelerator_parse(click, &btn->key, &btn->mods);
309 _button_set_click_label(btn->btn, btn->key, btn->mods);
310 gtk_toggle_button_set_active(btn->custom, TRUE);
311 }
312 return GTK_WIDGET(btn);
313 }
314
315 gboolean lxpanel_apply_hotkey(char **hkptr, const char *keystring,
316 void (*handler)(const char *keystring, gpointer user_data),
317 gpointer user_data, gboolean show_error)
318 {
319 g_return_val_if_fail(hkptr != NULL, FALSE);
320 g_return_val_if_fail(handler != NULL, FALSE);
321
322 if (keystring != NULL && !keybinder_bind(keystring, handler, user_data))
323 {
324 if (show_error)
325 {
326 GtkWidget* dlg;
327
328 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
329 _("Cannot assign '%s' as a global hotkey:"
330 " it is already bound."), keystring);
331 gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
332 gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
333 gtk_dialog_run(GTK_DIALOG(dlg));
334 gtk_widget_destroy(dlg);
335 }
336 return FALSE;
337 }
338 if (*hkptr != NULL)
339 keybinder_unbind(*hkptr, handler);
340 g_free(*hkptr);
341 *hkptr = g_strdup(keystring);
342 return TRUE;
343 }
344
345 guint panel_config_click_parse(const char *keystring, GdkModifierType *mods)
346 {
347 guint key;
348 const char *name;
349
350 if (keystring == NULL)
351 return 0;
352 gtk_accelerator_parse(keystring, &key, mods);
353 name = gdk_keyval_name(key);
354 if (name[0] >= '1' && name[0] <= '9')
355 return (name[0] - '0');
356 return 0;
357 }
358 #if 0
359 // test code, can be used as an example until erased. :)
360 static void handler(const char *keystring, void *user_data)
361 {
362 }
363
364 static char *hotkey = NULL;
365
366 static void cb(PanelCfgInputButton *btn, char *text, gpointer unused)
367 {
368 g_print("got keystring \"%s\"\n", text);
369
370 if (!btn->do_key)
371 return;
372 lxpanel_apply_hotkey(&hotkey, text, &handler, NULL, TRUE);
373 }
374
375 int main(int argc, char **argv)
376 {
377 GtkWidget *dialog;
378 GtkWidget *btn;
379
380 gtk_init(&argc, &argv);
381 dialog = gtk_dialog_new();
382 lxpanel_apply_hotkey(&hotkey, "<Super>z", &handler, NULL, FALSE);
383 btn = panel_config_hotkey_button_new("test", hotkey);
384 // btn = panel_config_click_button_new("test", NULL);
385 gtk_widget_show(btn);
386 g_signal_connect(btn, "changed", G_CALLBACK(cb), NULL);
387 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), btn);
388 gtk_dialog_run(GTK_DIALOG(dialog));
389 return 0;
390 }
391 #endif