GCC Code Coverage Report


Directory: ./
File: panels/wacom/gsd-wacom-key-shortcut-button.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 183 0.0%
Functions: 0 18 0.0%
Branches: 0 65 0.0%

Line Branch Exec Source
1 /*
2 * gsd-wacom-key-shortcut-button.c
3 *
4 * Copyright © 2013 Red Hat, Inc.
5 *
6 * Author: Joaquim Rocha <jrocha@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23 #include <glib/gi18n-lib.h>
24
25 #include "gsd-wacom-key-shortcut-button.h"
26
27 /**
28 * SECTION:gsd-wacom-key-shortcut-button
29 * @short_description: A button which captures and displays a keyboard shortcut
30 * @title: GsdWacomKeyShortcutButton
31 *
32 * GsdWacomKeyShortcutButton is a button which, when clicked, captures a keyboard
33 * shortcut and displays it.
34 * It works in a similar way to #GtkCellRendererAccel but, being a #GtkWidget,
35 * can be added to e.g. containers.
36 */
37
38 #define DEFAULT_CANCEL_KEY GDK_KEY_Escape
39 #define DEFAULT_CLEAR_KEY GDK_KEY_BackSpace
40
41 enum {
42 KEY_SHORTCUT_EDITED,
43 KEY_SHORTCUT_CLEARED,
44 LAST_SIGNAL
45 };
46
47 enum {
48 PROP_0,
49 PROP_SHORTCUT_KEY_VAL,
50 PROP_SHORTCUT_KEY_MODS,
51 PROP_SHORTCUT_MODE,
52 PROP_SHORTCUT_CANCEL_KEY,
53 PROP_SHORTCUT_CLEAR_KEY,
54 N_PROPERTIES
55 };
56
57 struct _GsdWacomKeyShortcutButton
58 {
59 GtkButton parent_instance;
60
61 gboolean editing_mode;
62
63 guint keyval;
64 guint keycode;
65 GdkModifierType mods;
66
67 /* Temporary shortcut info used for allowing
68 * modifier-only shortcuts */
69 guint tmp_shortcut_keyval;
70 GdkModifierType tmp_shortcut_mods;
71 guint32 tmp_shortcut_time;
72
73 GsdWacomKeyShortcutButtonMode mode;
74
75 guint cancel_keyval;
76 guint clear_keyval;
77 };
78
79 G_DEFINE_TYPE (GsdWacomKeyShortcutButton, gsd_wacom_key_shortcut_button, GTK_TYPE_BUTTON);
80
81 static guint signals[LAST_SIGNAL] = { 0 };
82
83 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
84
85 static void gsd_wacom_key_shortcut_button_changed (GsdWacomKeyShortcutButton *self);
86
87 static void
88 gsd_wacom_key_shortcut_button_set_property (GObject *object,
89 guint property_id,
90 const GValue *value,
91 GParamSpec *pspec)
92 {
93 GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (object);
94 gboolean changed = FALSE;
95
96 switch (property_id)
97 {
98 case PROP_SHORTCUT_KEY_VAL:
99 self->keyval = g_value_get_uint (value);
100 changed = TRUE;
101 break;
102
103 case PROP_SHORTCUT_KEY_MODS:
104 self->mods = g_value_get_uint (value);
105 changed = TRUE;
106 break;
107
108 case PROP_SHORTCUT_MODE:
109 self->mode = g_value_get_enum (value);
110 break;
111
112 case PROP_SHORTCUT_CANCEL_KEY:
113 self->cancel_keyval = g_value_get_uint (value);
114 break;
115
116 case PROP_SHORTCUT_CLEAR_KEY:
117 self->clear_keyval = g_value_get_uint (value);
118 break;
119
120 default:
121 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
122 break;
123 }
124
125 if (changed)
126 gsd_wacom_key_shortcut_button_changed (self);
127 }
128
129 static void
130 gsd_wacom_key_shortcut_button_get_property (GObject *object,
131 guint property_id,
132 GValue *value,
133 GParamSpec *pspec)
134 {
135 GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (object);
136
137 switch (property_id)
138 {
139 case PROP_SHORTCUT_KEY_VAL:
140 g_value_set_uint (value, self->keyval);
141 break;
142
143 case PROP_SHORTCUT_KEY_MODS:
144 g_value_set_uint (value, self->mods);
145 break;
146
147 case PROP_SHORTCUT_MODE:
148 g_value_set_enum (value, self->mode);
149 break;
150
151 case PROP_SHORTCUT_CANCEL_KEY:
152 g_value_set_uint (value, self->cancel_keyval);
153 break;
154
155 case PROP_SHORTCUT_CLEAR_KEY:
156 g_value_set_uint (value, self->clear_keyval);
157 break;
158
159 default:
160 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
161 break;
162 }
163 }
164
165 static void
166 gsd_wacom_key_shortcut_set_editing_mode (GsdWacomKeyShortcutButton *self,
167 GdkEvent *event)
168 {
169 self->editing_mode = TRUE;
170 gsd_wacom_key_shortcut_button_changed (self);
171 gtk_widget_grab_focus (GTK_WIDGET (self));
172 }
173
174 static void
175 gsd_wacom_key_shortcut_remove_editing_mode (GsdWacomKeyShortcutButton *self)
176 {
177 self->editing_mode = FALSE;
178 self->tmp_shortcut_keyval = 0;
179 self->tmp_shortcut_mods = 0;
180 self->tmp_shortcut_time = 0;
181 }
182
183 static void
184 gsd_wacom_key_shortcut_button_changed (GsdWacomKeyShortcutButton *self)
185 {
186 g_autofree gchar *text = NULL;
187
188 if (self->editing_mode)
189 {
190 gtk_button_set_label (GTK_BUTTON (self), _("New shortcut…"));
191
192 gtk_widget_set_state_flags (GTK_WIDGET (self),
193 GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT,
194 FALSE);
195
196 return;
197 }
198
199 if (self->keyval == 0 && self->mods == 0)
200 {
201 gtk_button_set_label (GTK_BUTTON (self), "");
202 return;
203 }
204
205 text = gtk_accelerator_get_label (self->keyval, self->mods);
206 gtk_button_set_label (GTK_BUTTON (self), text);
207 }
208
209 static void
210 gsd_wacom_key_shortcut_button_activate (GtkButton *self)
211 {
212 gsd_wacom_key_shortcut_set_editing_mode (GSD_WACOM_KEY_SHORTCUT_BUTTON (self), NULL);
213
214 GTK_BUTTON_CLASS (gsd_wacom_key_shortcut_button_parent_class)->activate (self);
215 }
216
217 static void
218 key_shortcut_finished_editing (GsdWacomKeyShortcutButton *self,
219 guint32 time)
220 {
221 self->editing_mode = FALSE;
222
223 gsd_wacom_key_shortcut_remove_editing_mode (self);
224
225 gsd_wacom_key_shortcut_button_changed (self);
226 }
227
228 static gboolean
229 gsd_wacom_key_shortcut_button_key_released_cb (GsdWacomKeyShortcutButton *self,
230 guint keyval,
231 guint keycode,
232 GdkModifierType state)
233 {
234 if (self->tmp_shortcut_keyval == 0)
235 return FALSE;
236
237 self->keyval = self->tmp_shortcut_keyval;
238 self->mods = self->tmp_shortcut_mods;
239
240 key_shortcut_finished_editing (self, self->tmp_shortcut_time);
241
242 g_signal_emit (self, signals[KEY_SHORTCUT_EDITED], 0);
243
244 return TRUE;
245 }
246
247 static gboolean
248 gsd_wacom_key_shortcut_button_key_pressed_cb (GsdWacomKeyShortcutButton *self,
249 guint keyval,
250 guint keycode,
251 GdkModifierType state,
252 GtkEventController *controller)
253 {
254 /* This code is based on the gtk_cell_renderer_accel_start_editing */
255 GdkModifierType mods = 0;
256 GdkEvent *event;
257 guint shortcut_keyval;
258 gboolean edited;
259 gboolean cleared;
260
261 event = gtk_event_controller_get_current_event (controller);
262
263 /* GTK and OTHER modes don't allow modifier keyvals */
264 if (gdk_key_event_is_modifier (event) && self->mode != GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL)
265 return TRUE;
266
267 if (!self->editing_mode)
268 return FALSE;
269
270 edited = FALSE;
271 cleared = FALSE;
272
273 mods = state;
274
275 if (keyval == GDK_KEY_Sys_Req && (mods & GDK_ALT_MASK) != 0)
276 {
277 /* HACK: we don't want to use SysRq as a keybinding (but we do
278 * want Alt+Print), so we avoid translation from Alt+Print to SysRq
279 */
280 keyval = GDK_KEY_Print;
281 }
282
283 shortcut_keyval = gdk_keyval_to_lower (keyval);
284
285 if (shortcut_keyval == GDK_KEY_ISO_Left_Tab)
286 shortcut_keyval = GDK_KEY_Tab;
287
288 mods &= gtk_accelerator_get_default_mod_mask ();
289
290 /* Put shift back if it changed the case of the key, not otherwise.
291 */
292 if (shortcut_keyval != keyval)
293 mods |= GDK_SHIFT_MASK;
294
295 if (mods == 0)
296 {
297 if (keyval == self->cancel_keyval)
298 {
299 /* cancel the edition */
300 goto out;
301 }
302 else if (keyval == self->clear_keyval)
303 {
304 /* clear the current shortcut */
305 cleared = TRUE;
306 goto out;
307 }
308 }
309
310 self->tmp_shortcut_keyval = 0;
311 self->tmp_shortcut_mods = 0;
312 self->tmp_shortcut_time = 0;
313
314 if (gdk_key_event_is_modifier (event))
315 {
316 /* when the user presses a non-modifier key, it readily assigns the
317 * shortcut but since we also support modifiers-only shortcuts, we
318 * cannot assign the shortcut right when the user presses a modifier
319 * key because the user might assign e.g. Alt, Alt+Ctrl, Alt+Ctrl+Shift, etc.
320 * So, we keep track of the pressed shortcut's (keyval, mods and time) if
321 * it is a modifier shortcut and assign them when a key-release happens */
322 self->tmp_shortcut_keyval = shortcut_keyval;
323 self->tmp_shortcut_mods = mods;
324 self->tmp_shortcut_time = gtk_event_controller_get_current_event_time (controller);
325
326 return TRUE;
327 }
328
329 edited = TRUE;
330
331 out:
332
333 if (edited)
334 {
335 self->keyval = shortcut_keyval;
336 self->mods = mods;
337 }
338
339 if (cleared)
340 {
341 self->keyval = 0;
342 self->mods = 0;
343 }
344
345 key_shortcut_finished_editing (self, gtk_event_controller_get_current_event_time (controller));
346
347 if (edited)
348 g_signal_emit (self, signals[KEY_SHORTCUT_EDITED], 0);
349 else if (cleared)
350 g_signal_emit (self, signals[KEY_SHORTCUT_CLEARED], 0);
351
352 return TRUE;
353 }
354
355 static void
356 gsd_wacom_key_shortcut_button_button_pressed_cb (GsdWacomKeyShortcutButton *self,
357 gint n_press,
358 gdouble x,
359 gdouble y)
360 {
361 if (!self->editing_mode)
362 gsd_wacom_key_shortcut_set_editing_mode (self, NULL);
363 }
364
365 static void
366 gsd_wacom_key_shortcut_button_unrealize (GtkWidget *widget)
367 {
368 GsdWacomKeyShortcutButton *self;
369
370 self = GSD_WACOM_KEY_SHORTCUT_BUTTON (widget);
371
372 gsd_wacom_key_shortcut_remove_editing_mode (self);
373
374 GTK_WIDGET_CLASS (gsd_wacom_key_shortcut_button_parent_class)->unrealize (widget);
375 }
376
377 static void
378 gsd_wacom_key_shortcut_button_class_init (GsdWacomKeyShortcutButtonClass *klass)
379 {
380 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
381 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
382 GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
383
384 gobject_class->set_property = gsd_wacom_key_shortcut_button_set_property;
385 gobject_class->get_property = gsd_wacom_key_shortcut_button_get_property;
386
387 obj_properties[PROP_SHORTCUT_KEY_VAL] =
388 g_param_spec_uint ("key-value",
389 "The key value",
390 "The key value of the shortcut currently set",
391 0,
392 G_MAXUINT,
393 0,
394 G_PARAM_READWRITE |
395 G_PARAM_STATIC_STRINGS);
396
397 obj_properties[PROP_SHORTCUT_KEY_MODS] =
398 g_param_spec_uint ("key-mods",
399 "The key modifiers",
400 "The key modifiers of the shortcut currently set",
401 0,
402 G_MAXUINT,
403 0,
404 G_PARAM_READWRITE |
405 G_PARAM_STATIC_STRINGS);
406
407 obj_properties[PROP_SHORTCUT_CANCEL_KEY] =
408 g_param_spec_uint ("cancel-key",
409 "The cancel key",
410 "The key which cancels the edition of the shortcut",
411 0,
412 G_MAXUINT,
413 DEFAULT_CANCEL_KEY,
414 G_PARAM_READWRITE |
415 G_PARAM_STATIC_STRINGS);
416
417 obj_properties[PROP_SHORTCUT_CLEAR_KEY] =
418 g_param_spec_uint ("clear-key",
419 "The clear key",
420 "The key which clears the currently set shortcut",
421 0,
422 G_MAXUINT,
423 DEFAULT_CLEAR_KEY,
424 G_PARAM_READWRITE |
425 G_PARAM_STATIC_STRINGS);
426
427 /**
428 * GsdWacomKeyShortcutButton:mode:
429 *
430 * Determines which type of keys are allowed in the captured shortcuts.
431 * %GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL is the same as
432 * %GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER but allows shortcuts composed of
433 * only modifier keys.
434 */
435 obj_properties[PROP_SHORTCUT_MODE] =
436 g_param_spec_enum ("mode",
437 "The shortcut mode",
438 "The mode with which the shortcuts are captured",
439 GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON_MODE,
440 GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER,
441 G_PARAM_READWRITE |
442 G_PARAM_STATIC_STRINGS);
443
444 g_object_class_install_properties (gobject_class,
445 N_PROPERTIES,
446 obj_properties);
447
448 widget_class->unrealize = gsd_wacom_key_shortcut_button_unrealize;
449
450 button_class->activate = gsd_wacom_key_shortcut_button_activate;
451
452 /**
453 * GsdWacomKeyShortcutButton::key-shortcut-edited:
454 * @keyshortcutbutton: the #GsdWacomKeyShortcutButton
455 *
456 * Emitted when the key shortcut of the @keyshortcutbutton is edited.
457 *
458 * The new shortcut can be retrieved by using the #GsdWacomKeyShortcutButton:key-value
459 * and #GsdWacomKeyShortcutButton:key-mods properties.
460 */
461 signals[KEY_SHORTCUT_EDITED] = g_signal_new ("key-shortcut-edited",
462 GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON,
463 G_SIGNAL_RUN_LAST,
464 0,
465 NULL, NULL,
466 g_cclosure_marshal_VOID__VOID,
467 G_TYPE_NONE, 0);
468
469 /**
470 * GsdWacomKeyShortcutButton::key-shortcut-cleared:
471 * @keyshortcutbutton: the #GsdWacomKeyShortcutButton
472 *
473 * Emitted when the key shortcut of the @keyshortcutbutton is cleared.
474 */
475 signals[KEY_SHORTCUT_CLEARED] = g_signal_new ("key-shortcut-cleared",
476 GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON,
477 G_SIGNAL_RUN_LAST,
478 0,
479 NULL, NULL,
480 g_cclosure_marshal_VOID__VOID,
481 G_TYPE_NONE, 0);
482 }
483
484 static void
485 gsd_wacom_key_shortcut_button_init (GsdWacomKeyShortcutButton *self)
486 {
487 GtkEventController *controller;
488 GtkGesture *gesture;
489
490 self->cancel_keyval = DEFAULT_CANCEL_KEY;
491 self->clear_keyval = DEFAULT_CLEAR_KEY;
492
493 controller = gtk_event_controller_key_new ();
494 g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gsd_wacom_key_shortcut_button_key_pressed_cb), self);
495 g_signal_connect_swapped (controller, "key-released", G_CALLBACK (gsd_wacom_key_shortcut_button_key_released_cb), self);
496 gtk_widget_add_controller (GTK_WIDGET (self), controller);
497
498 gesture = gtk_gesture_click_new ();
499 g_signal_connect_swapped (gesture, "pressed", G_CALLBACK (gsd_wacom_key_shortcut_button_button_pressed_cb), self);
500 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
501 }
502
503 /**
504 * gsd_wacom_key_shortcut_button_new:
505 *
506 * Creates a new #GsdWacomKeyShortcutButton.
507 *
508 * Returns: a new #GsdWacomKeyShortcutButton object.
509 *
510 * Since: 3.10
511 */
512 GtkWidget *
513 gsd_wacom_key_shortcut_button_new (void)
514 {
515 return g_object_new (GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON, NULL);
516 }
517
518 GType
519 gsd_wacom_key_shortcut_button_mode_type (void)
520 {
521 static GType enum_type_id = 0;
522 if (G_UNLIKELY (!enum_type_id))
523 {
524 static const GEnumValue values[] =
525 {
526 { GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER, "OTHER", "other" },
527 { GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL, "ALL", "all" },
528 { 0, NULL, NULL }
529 };
530 enum_type_id = g_enum_register_static ("GsdWacomKeyShortcutButtonMode", values);
531 }
532 return enum_type_id;
533 }
534