Use more user-friendly messages on button when configure click, like "Ctrl+MiddleBtn".
[lxde/lxpanel.git] / src / input-button.c
CommitLineData
8a4622e5
AG
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
f7a4447c
AG
30#include <keybinder.h>
31
8a4622e5
AG
32#define PANEL_TYPE_CFG_INPUT_BUTTON (config_input_button_get_type())
33
34typedef struct _PanelCfgInputButton PanelCfgInputButton;
35typedef struct _PanelCfgInputButtonClass PanelCfgInputButtonClass;
36
37struct _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
50struct _PanelCfgInputButtonClass
51{
52 GtkFrameClass parent_class;
53 void (*changed)(PanelCfgInputButton *btn, char *accel);
54};
55
56enum
57{
58 CHANGED,
59 N_SIGNALS
60};
61
62static guint signals[N_SIGNALS];
63
64
65/* ---- Events on test button ---- */
66
67static 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
78static 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
87static 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
18332c7f
AG
150static 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
8a4622e5
AG
179static 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? */
75d47917 200 snprintf(digit, sizeof(digit), "%u", event->button);
8a4622e5
AG
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 {
18332c7f 205 _button_set_click_label(test, keyval, state);
8a4622e5
AG
206 return FALSE;
207 }
208 /* send a signal that it's changed */
8a4622e5
AG
209 btn->mods = state;
210 btn->key = keyval;
18332c7f 211 _button_set_click_label(test, keyval, state);
8a4622e5
AG
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
218static 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
230G_DEFINE_TYPE(PanelCfgInputButton, config_input_button, GTK_TYPE_FRAME)
231
232static 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
244static 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
277static 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
283GtkWidget *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
300GtkWidget *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);
18332c7f 309 _button_set_click_label(btn->btn, btn->key, btn->mods);
8a4622e5
AG
310 gtk_toggle_button_set_active(btn->custom, TRUE);
311 }
312 return GTK_WIDGET(btn);
313}
f7a4447c
AG
314
315gboolean 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}
75d47917
AG
344
345guint 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}
8a4622e5
AG
358#if 0
359// test code, can be used as an example until erased. :)
8a4622e5
AG
360static void handler(const char *keystring, void *user_data)
361{
362}
363
364static char *hotkey = NULL;
365
366static 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;
f7a4447c 372 lxpanel_apply_hotkey(&hotkey, text, &handler, NULL, TRUE);
8a4622e5
AG
373}
374
375int main(int argc, char **argv)
376{
377 GtkWidget *dialog;
378 GtkWidget *btn;
379
380 gtk_init(&argc, &argv);
381 dialog = gtk_dialog_new();
f7a4447c 382 lxpanel_apply_hotkey(&hotkey, "<Super>z", &handler, NULL, FALSE);
8a4622e5
AG
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