1 /* cc-xkb-modifier-dialog.c
2 *
3 * Copyright 2019 Bastien Nocera <hadess@hadess.net>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-2.0-or-later
19 */
20
21 #include <glib/gi18n.h>
22 #define HANDY_USE_UNSTABLE_API
23 #include <handy.h>
24
25 #include "cc-xkb-modifier-dialog.h"
26 #include "list-box-helper.h"
27
28 struct _CcXkbModifierDialog
29 {
30 GtkDialog parent_instance;
31
32 GtkLabel *description_label;
33 GtkSwitch *enabled_switch;
34 GtkListBox *listbox;
35 GtkListBox *switch_listbox;
36 HdyActionRow *switch_row;
37
38 GSettings *input_source_settings;
39 const CcXkbModifier *modifier;
40 GSList *radio_group;
41 };
42
43 G_DEFINE_TYPE (CcXkbModifierDialog, cc_xkb_modifier_dialog, GTK_TYPE_DIALOG)
44
45 static const gchar *custom_css =
46 ".xkb-option-button {"
47 " padding: 12px"
48 "}";
49
50 static const CcXkbOption*
get_xkb_option_from_name(const CcXkbModifier * modifier,const gchar * name)51 get_xkb_option_from_name (const CcXkbModifier *modifier, const gchar* name)
52 {
53 const CcXkbOption *options = modifier->options;
54 int i;
55
56 for (i = 0; options[i].label && options[i].xkb_option; i++)
57 {
58 if (g_str_equal (name, options[i].xkb_option))
59 return &options[i];
60 }
61
62 return NULL;
63 }
64
65 static GtkRadioButton *
get_radio_button_from_xkb_option_name(CcXkbModifierDialog * self,const gchar * name)66 get_radio_button_from_xkb_option_name (CcXkbModifierDialog *self,
67 const gchar *name)
68 {
69 gchar *xkb_option;
70 GSList *l;
71
72 for (l = self->radio_group; l != NULL; l = l->next)
73 {
74 xkb_option = g_object_get_data (l->data, "xkb-option");
75 if (g_strcmp0 (xkb_option, name) == 0)
76 return l->data;
77 }
78
79 return NULL;
80 }
81
82 static void
update_active_radio(CcXkbModifierDialog * self)83 update_active_radio (CcXkbModifierDialog *self)
84 {
85 g_auto(GStrv) options = NULL;
86 GtkRadioButton *rightalt_radio;
87 const CcXkbOption *default_option;
88 guint i;
89
90 options = g_settings_get_strv (self->input_source_settings, "xkb-options");
91
92 for (i = 0; options != NULL && options[i] != NULL; i++)
93 {
94 GtkRadioButton *radio;
95
96 if (!g_str_has_prefix (options[i], self->modifier->prefix))
97 continue;
98
99 radio = get_radio_button_from_xkb_option_name (self, options[i]);
100
101 if (!radio)
102 continue;
103
104 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
105 gtk_switch_set_active (self->enabled_switch, TRUE);
106 return;
107 }
108
109 if (self->modifier->default_option != NULL)
110 {
111 default_option = get_xkb_option_from_name(self->modifier, self->modifier->default_option);
112 rightalt_radio = get_radio_button_from_xkb_option_name (self, default_option->xkb_option);
113 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rightalt_radio), TRUE);
114 gtk_switch_set_active (self->enabled_switch, TRUE);
115 }
116 else
117 {
118 gtk_switch_set_active (self->enabled_switch, FALSE);
119 }
120 }
121
122 static void
set_xkb_option(CcXkbModifierDialog * self,gchar * xkb_option)123 set_xkb_option (CcXkbModifierDialog *self,
124 gchar *xkb_option)
125 {
126 g_autoptr(GPtrArray) array = NULL;
127 g_auto(GStrv) options = NULL;
128 gboolean found;
129 guint i;
130
131 /* Either replace the existing "<modifier>:" option in the string
132 * array, or add the option at the end
133 */
134 array = g_ptr_array_new ();
135 options = g_settings_get_strv (self->input_source_settings, "xkb-options");
136 found = FALSE;
137
138 for (i = 0; options != NULL && options[i] != NULL; i++)
139 {
140 if (g_str_has_prefix (options[i], self->modifier->prefix))
141 {
142 if (!found && xkb_option != NULL)
143 g_ptr_array_add (array, xkb_option);
144 found = TRUE;
145 }
146 else
147 {
148 g_ptr_array_add (array, options[i]);
149 }
150 }
151
152 if (!found && xkb_option != NULL)
153 g_ptr_array_add (array, xkb_option);
154
155 g_ptr_array_add (array, NULL);
156
157 g_settings_set_strv (self->input_source_settings,
158 "xkb-options",
159 (const gchar * const *) array->pdata);
160 }
161
162 static void
on_active_radio_changed_cb(CcXkbModifierDialog * self,GtkRadioButton * radio)163 on_active_radio_changed_cb (CcXkbModifierDialog *self,
164 GtkRadioButton *radio)
165 {
166 gchar *xkb_option;
167
168 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio)))
169 return;
170
171 if (!gtk_switch_get_state (self->enabled_switch))
172 return;
173
174 xkb_option = (gchar *)g_object_get_data (G_OBJECT (radio), "xkb-option");
175 set_xkb_option (self, xkb_option);
176 }
177
178 static void
on_xkb_options_changed_cb(CcXkbModifierDialog * self)179 on_xkb_options_changed_cb (CcXkbModifierDialog *self)
180 {
181 if (self->modifier == NULL)
182 update_active_radio (self);
183 }
184
185 static gboolean
enable_switch_changed_cb(GtkSwitch * widget,gboolean state,gpointer user_data)186 enable_switch_changed_cb (GtkSwitch *widget,
187 gboolean state,
188 gpointer user_data)
189 {
190 CcXkbModifierDialog *self = user_data;
191 gchar *xkb_option;
192 GSList *l;
193
194 gtk_widget_set_sensitive (GTK_WIDGET (self->listbox), state);
195
196 if (state)
197 {
198 for (l = self->radio_group; l != NULL; l = l->next)
199 {
200 if (gtk_toggle_button_get_active (l->data))
201 {
202 xkb_option = (gchar *)g_object_get_data (l->data, "xkb-option");
203 set_xkb_option (self, xkb_option);
204 break;
205 }
206 }
207 }
208 else
209 {
210 set_xkb_option (self, NULL);
211 }
212
213 return FALSE;
214 }
215
216 static void
cc_xkb_modifier_dialog_finalize(GObject * object)217 cc_xkb_modifier_dialog_finalize (GObject *object)
218 {
219 CcXkbModifierDialog *self = (CcXkbModifierDialog *)object;
220
221 g_clear_object (&self->input_source_settings);
222
223 G_OBJECT_CLASS (cc_xkb_modifier_dialog_parent_class)->finalize (object);
224 }
225
226 static void
cc_xkb_modifier_dialog_class_init(CcXkbModifierDialogClass * klass)227 cc_xkb_modifier_dialog_class_init (CcXkbModifierDialogClass *klass)
228 {
229 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
230 GObjectClass *object_class = G_OBJECT_CLASS (klass);
231
232 object_class->finalize = cc_xkb_modifier_dialog_finalize;
233
234 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-xkb-modifier-dialog.ui");
235
236 gtk_widget_class_bind_template_child (widget_class, CcXkbModifierDialog, description_label);
237 gtk_widget_class_bind_template_child (widget_class, CcXkbModifierDialog, enabled_switch);
238 gtk_widget_class_bind_template_child (widget_class, CcXkbModifierDialog, listbox);
239 gtk_widget_class_bind_template_child (widget_class, CcXkbModifierDialog, switch_listbox);
240 gtk_widget_class_bind_template_child (widget_class, CcXkbModifierDialog, switch_row);
241
242 gtk_widget_class_bind_template_callback (widget_class, enable_switch_changed_cb);
243 }
244
245 static void
add_radio_buttons(CcXkbModifierDialog * self)246 add_radio_buttons (CcXkbModifierDialog *self)
247 {
248 GtkWidget *row, *radio_button, *label, *last_button = NULL;
249 CcXkbOption *options = self->modifier->options;
250 int i;
251
252 for (i = 0; options[i].label && options[i].xkb_option; i++)
253 {
254 row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
255 "visible", TRUE,
256 "selectable", FALSE,
257 NULL);
258 gtk_container_add (GTK_CONTAINER (self->listbox), row);
259
260 radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON,
261 "visible", TRUE,
262 "can_focus", TRUE,
263 "receives_default", FALSE,
264 "draw_indicator", TRUE,
265 NULL);
266 label = g_object_new (GTK_TYPE_LABEL,
267 "visible", TRUE,
268 "margin_left", 6,
269 "label", g_dpgettext2 (NULL, "keyboard key", options[i].label),
270 NULL);
271 gtk_container_add (GTK_CONTAINER (radio_button), label);
272 gtk_style_context_add_class (gtk_widget_get_style_context (radio_button), "xkb-option-button");
273 gtk_radio_button_join_group (GTK_RADIO_BUTTON (radio_button), GTK_RADIO_BUTTON (last_button));
274 g_object_set_data (G_OBJECT (radio_button), "xkb-option", options[i].xkb_option);
275 g_signal_connect_object (radio_button, "toggled", (GCallback)on_active_radio_changed_cb, self, G_CONNECT_SWAPPED);
276 gtk_container_add (GTK_CONTAINER (row), radio_button);
277
278 last_button = radio_button;
279 }
280
281 self->radio_group = NULL;
282 if (last_button != NULL)
283 self->radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (last_button));
284 }
285
286 static void
cc_xkb_modifier_dialog_init(CcXkbModifierDialog * self)287 cc_xkb_modifier_dialog_init (CcXkbModifierDialog *self)
288 {
289 g_autoptr(GtkCssProvider) provider = NULL;
290
291 gtk_widget_init_template (GTK_WIDGET (self));
292
293 provider = gtk_css_provider_new ();
294 gtk_css_provider_load_from_data (provider, custom_css, -1, NULL);
295
296 gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
297 GTK_STYLE_PROVIDER (provider),
298 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
299
300 self->modifier = NULL;
301
302 self->input_source_settings = g_settings_new ("org.gnome.desktop.input-sources");
303 g_signal_connect_object (self->input_source_settings,
304 "changed::xkb-options",
305 G_CALLBACK (on_xkb_options_changed_cb),
306 self, G_CONNECT_SWAPPED);
307 }
308
309 CcXkbModifierDialog *
cc_xkb_modifier_dialog_new(GSettings * input_settings,const CcXkbModifier * modifier)310 cc_xkb_modifier_dialog_new (GSettings *input_settings,
311 const CcXkbModifier *modifier)
312 {
313 CcXkbModifierDialog *self;
314
315 self = g_object_new (CC_TYPE_XKB_MODIFIER_DIALOG,
316 "use-header-bar", TRUE,
317 NULL);
318 self->input_source_settings = g_object_ref (input_settings);
319
320 self->modifier = modifier;
321 gtk_window_set_title (GTK_WINDOW (self), gettext (modifier->title));
322 hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (self->switch_row), gettext (modifier->title));
323 gtk_label_set_markup (self->description_label, gettext (modifier->description));
324 gtk_widget_set_visible (GTK_WIDGET (self->switch_listbox), modifier->default_option == NULL);
325 add_radio_buttons (self);
326 update_active_radio (self);
327 gtk_widget_set_sensitive (GTK_WIDGET (self->listbox), gtk_switch_get_state (self->enabled_switch));
328
329 return self;
330 }
331
332 gboolean
xcb_modifier_transform_binding_to_label(GValue * value,GVariant * variant,gpointer user_data)333 xcb_modifier_transform_binding_to_label (GValue *value,
334 GVariant *variant,
335 gpointer user_data)
336 {
337 const CcXkbModifier *modifier = user_data;
338 const CcXkbOption *entry = NULL;
339 const char **items;
340 guint i;
341
342 items = g_variant_get_strv (variant, NULL);
343
344 for (i = 0; items != NULL && items[i] != NULL; i++)
345 {
346 entry = get_xkb_option_from_name (modifier, items[i]);
347 if (entry != NULL)
348 break;
349 }
350
351 if (entry == NULL && modifier->default_option == NULL)
352 {
353 g_value_set_string (value, _("Disabled"));
354 return TRUE;
355 }
356 else if (entry == NULL)
357 {
358 entry = get_xkb_option_from_name(modifier, modifier->default_option);
359 }
360
361 g_value_set_string (value,
362 g_dpgettext2 (NULL, "keyboard key", entry->label));
363 return TRUE;
364 }
365