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