GCC Code Coverage Report


Directory: ./
File: panels/keyboard/cc-keyboard-shortcut-group.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 159 0.0%
Functions: 0 18 0.0%
Branches: 0 80 0.0%

Line Branch Exec Source
1 /* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright (C) 2022 Purism SPC
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 * Author(s):
19 * Mohammed Sadiq <sadiq@sadiqpk.org>
20 *
21 * SPDX-License-Identifier: GPL-2.0-or-later
22 */
23
24 #undef G_LOG_DOMAIN
25 #define G_LOG_DOMAIN "cc-keyboard-shortcut-group"
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <glib/gi18n.h>
32
33 #include "cc-keyboard-item.h"
34 #include "cc-keyboard-shortcut-row.h"
35 #include "cc-keyboard-shortcut-group.h"
36
37 struct _CcKeyboardShortcutGroup
38 {
39 AdwPreferencesGroup parent_instance;
40
41 GtkListBox *shortcut_list_box;
42
43 GtkSizeGroup *accelerator_size_group;
44 CcKeyboardShortcutEditor *shortcut_editor;
45
46 GListModel *shortcut_items;
47 GtkFilterListModel *filtered_shortcuts;
48 GtkCustomFilter *custom_filter;
49
50 CcKeyboardManager *keyboard_manager;
51 char **search_terms;
52 char *section_id;
53 char *section_title;
54 /* The text representing the count of shortcuts changed */
55 char *modified_text;
56
57 gboolean is_empty;
58 };
59
60 G_DEFINE_TYPE (CcKeyboardShortcutGroup, cc_keyboard_shortcut_group, ADW_TYPE_PREFERENCES_GROUP)
61
62 enum {
63 PROP_0,
64 PROP_EMPTY,
65 PROP_MODIFIED_TEXT,
66 N_PROPS
67 };
68
69 static GParamSpec *properties[N_PROPS];
70
71 static void
72 shortcut_group_row_activated_cb (CcKeyboardShortcutGroup *self,
73 GtkListBoxRow *row)
74 {
75 g_assert (CC_IS_KEYBOARD_SHORTCUT_GROUP (self));
76 g_assert (GTK_IS_LIST_BOX_ROW (row));
77
78 if (CC_IS_KEYBOARD_SHORTCUT_ROW (row))
79 {
80 CcKeyboardItem *item;
81
82 item = cc_keyboard_shortcut_row_get_item (CC_KEYBOARD_SHORTCUT_ROW (row));
83 cc_keyboard_shortcut_editor_set_mode (self->shortcut_editor, CC_SHORTCUT_EDITOR_EDIT);
84 cc_keyboard_shortcut_editor_set_item (self->shortcut_editor, item);
85 }
86 else /* Add shortcut row */
87 {
88 cc_keyboard_shortcut_editor_set_mode (self->shortcut_editor, CC_SHORTCUT_EDITOR_CREATE);
89 cc_keyboard_shortcut_editor_set_item (self->shortcut_editor, NULL);
90 }
91
92 gtk_widget_set_visible (GTK_WIDGET (self->shortcut_editor), TRUE);
93 }
94
95 static void
96 group_shortcut_changed_cb (CcKeyboardShortcutGroup *self)
97 {
98 g_assert (CC_IS_KEYBOARD_SHORTCUT_GROUP (self));
99
100 /* Free the modified text, so that it will be regenerated when someone asks for one */
101 g_clear_pointer (&self->modified_text, g_free);
102 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODIFIED_TEXT]);
103 }
104
105 static void
106 shortcut_group_update_modified_text (CcKeyboardShortcutGroup *self)
107 {
108 guint n_items, n_modified = 0;
109
110 g_assert (CC_IS_KEYBOARD_SHORTCUT_GROUP (self));
111
112 if (self->modified_text)
113 return;
114
115 n_items = g_list_model_get_n_items (self->shortcut_items);
116
117 for (guint i = 0; i < n_items; i++)
118 {
119 g_autoptr(CcKeyboardItem) item = NULL;
120
121 item = g_list_model_get_item (self->shortcut_items, i);
122
123 if (!cc_keyboard_item_is_value_default (item))
124 n_modified++;
125 }
126
127 if (n_modified == 0)
128 self->modified_text = g_strdup ("");
129 else
130 self->modified_text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
131 "%d modified",
132 "%d modified",
133 n_modified),
134 n_modified);
135 }
136
137 static GtkWidget *
138 shortcut_group_row_new (gpointer item,
139 gpointer user_data)
140 {
141 CcKeyboardShortcutGroup *self = user_data;
142 GtkWidget *row;
143
144 /* Row to add custom shortcut */
145 if (GTK_IS_STRING_OBJECT (item))
146 {
147 GtkWidget *icon;
148
149 row = adw_preferences_row_new ();
150 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
151 gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
152 gtk_accessible_update_property (GTK_ACCESSIBLE (row),
153 GTK_ACCESSIBLE_PROPERTY_LABEL,
154 _("Add a Shortcut"),
155 -1);
156 icon = gtk_image_new_from_icon_name ("list-add-symbolic");
157 gtk_widget_set_margin_top (icon, 15);
158 gtk_widget_set_margin_bottom (icon, 15);
159 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), icon);
160
161 return row;
162
163 }
164
165 row = GTK_WIDGET (cc_keyboard_shortcut_row_new (item,
166 self->keyboard_manager,
167 self->shortcut_editor,
168 self->accelerator_size_group));
169
170 g_signal_connect_object (item, "notify::is-value-default",
171 G_CALLBACK (group_shortcut_changed_cb),
172 self,
173 G_CONNECT_SWAPPED);
174
175 return row;
176 }
177
178 static gboolean
179 shortcut_group_filter_cb (gpointer item,
180 gpointer user_data)
181 {
182 CcKeyboardShortcutGroup *self = user_data;
183
184 if (!self->search_terms)
185 return TRUE;
186
187 /* We don't want to show the "Add new shortcut" row in search results */
188 if (GTK_IS_STRING_OBJECT (item))
189 return FALSE;
190
191 return cc_keyboard_item_matches_string (item, self->search_terms);
192 }
193
194 static void
195 group_filter_list_changed_cb (CcKeyboardShortcutGroup *self)
196 {
197 guint n_items;
198 gboolean empty;
199
200 g_assert (CC_IS_KEYBOARD_SHORTCUT_GROUP (self));
201
202 n_items = g_list_model_get_n_items (G_LIST_MODEL (self->filtered_shortcuts));
203 empty = n_items == 0;
204
205 if (self->is_empty == empty)
206 return;
207
208 self->is_empty = empty;
209 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EMPTY]);
210 }
211
212 static void
213 shortcut_group_set_list_model (CcKeyboardShortcutGroup *self,
214 GListModel *shortcut_items)
215 {
216 GtkExpression *expression;
217 GtkSortListModel *sort_model;
218 GtkStringSorter *sorter;
219 GListModel *model = NULL;
220
221 g_assert (!self->shortcut_items);
222 self->shortcut_items = g_object_ref (shortcut_items);
223
224 /* Sort shortcuts by description */
225 expression = gtk_property_expression_new (CC_TYPE_KEYBOARD_ITEM, NULL, "description");
226 sorter = gtk_string_sorter_new (expression);
227 sort_model = gtk_sort_list_model_new (g_object_ref (shortcut_items), GTK_SORTER (sorter));
228
229 /*
230 * This is a workaround to add an additional item to the end
231 * of the shortcut list, which will be used to show "+" row
232 * to add more items. We do this way instead of appending a
233 * row to avoid some imperfections in the GUI.
234 */
235 if (g_strcmp0 (self->section_id, "custom") == 0)
236 {
237 g_autoptr(GListStore) add_shortcut = NULL;
238 g_autoptr(GtkStringObject) str = NULL;
239 GtkFlattenListModel *flat_model;
240 GListStore *shortcut_store;
241
242 shortcut_store = g_list_store_new (G_TYPE_LIST_MODEL);
243 add_shortcut = g_list_store_new (GTK_TYPE_STRING_OBJECT);
244
245 str = gtk_string_object_new ("add-shortcut");
246 g_list_store_append (add_shortcut, str);
247
248 g_list_store_append (shortcut_store, sort_model);
249 g_list_store_append (shortcut_store, add_shortcut);
250
251 flat_model = gtk_flatten_list_model_new (G_LIST_MODEL (shortcut_store));
252 model = G_LIST_MODEL (flat_model);
253 }
254
255 if (!model)
256 model = G_LIST_MODEL (sort_model);
257
258 self->custom_filter = gtk_custom_filter_new (shortcut_group_filter_cb, self, NULL);
259 self->filtered_shortcuts = gtk_filter_list_model_new (model, GTK_FILTER (self->custom_filter));
260
261 g_signal_connect_object (self->filtered_shortcuts, "items-changed",
262 G_CALLBACK (group_filter_list_changed_cb),
263 self, G_CONNECT_SWAPPED);
264 group_filter_list_changed_cb (self);
265 }
266
267 static void
268 shortcut_group_update_title (CcKeyboardShortcutGroup *self)
269 {
270 const char *title = NULL;
271 guint n_items;
272 gboolean show_title = TRUE;
273
274 if (!self->section_title)
275 return;
276
277 n_items = g_list_model_get_n_items (G_LIST_MODEL (self->filtered_shortcuts));
278
279 if (!self->search_terms || n_items == 0)
280 show_title = FALSE;
281
282 if (show_title)
283 title = _(self->section_title);
284
285 adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (self), title);
286 }
287
288 static void
289 cc_keyboard_shortcut_group_get_property (GObject *object,
290 guint prop_id,
291 GValue *value,
292 GParamSpec *pspec)
293 {
294 CcKeyboardShortcutGroup *self = (CcKeyboardShortcutGroup *)object;
295
296 switch (prop_id)
297 {
298 case PROP_EMPTY:
299 g_value_set_boolean (value, self->is_empty);
300 break;
301
302 case PROP_MODIFIED_TEXT:
303 shortcut_group_update_modified_text (self);
304 g_value_set_string (value, self->modified_text);
305 break;
306
307 default:
308 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309 }
310 }
311
312 static void
313 cc_keyboard_shortcut_group_finalize (GObject *object)
314 {
315 CcKeyboardShortcutGroup *self = (CcKeyboardShortcutGroup *)object;
316
317 g_clear_pointer (&self->section_id, g_free);
318 g_clear_pointer (&self->section_title, g_free);
319 g_clear_object (&self->shortcut_items);
320 g_clear_object (&self->filtered_shortcuts);
321
322 G_OBJECT_CLASS (cc_keyboard_shortcut_group_parent_class)->finalize (object);
323 }
324
325 static void
326 cc_keyboard_shortcut_group_class_init (CcKeyboardShortcutGroupClass *klass)
327 {
328 GObjectClass *object_class = G_OBJECT_CLASS (klass);
329
330 object_class->get_property = cc_keyboard_shortcut_group_get_property;
331 object_class->finalize = cc_keyboard_shortcut_group_finalize;
332
333 /**
334 * CcKeyboardShortcutGroup:empty:
335 *
336 * Whether the list of shortcuts is empty
337 */
338 properties[PROP_EMPTY] =
339 g_param_spec_boolean ("empty",
340 "Empty Shorcuts",
341 "Whether the group contain no shorcuts",
342 FALSE,
343 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
344
345 /**
346 * CcKeyboardShortcutGroup:modified_text:
347 *
348 * A string the represents the number of modified keys
349 * present in the group, translated to current locale.
350 * This is a string property so that it can be bound to
351 * UI label as such.
352 *
353 * Shall be any empty string if no shortcut is modified.
354 */
355 properties[PROP_MODIFIED_TEXT] =
356 g_param_spec_string ("modified-text",
357 "Modified Text",
358 "A string representing the number of modified shortcut items",
359 "",
360 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
361
362 g_object_class_install_properties (object_class, N_PROPS, properties);
363 }
364
365 static void
366 cc_keyboard_shortcut_group_init (CcKeyboardShortcutGroup *self)
367 {
368 self->shortcut_list_box = GTK_LIST_BOX (gtk_list_box_new ());
369 gtk_widget_add_css_class (GTK_WIDGET (self->shortcut_list_box), "boxed-list");
370 g_signal_connect_object (self->shortcut_list_box, "row-activated",
371 G_CALLBACK (shortcut_group_row_activated_cb),
372 self, G_CONNECT_SWAPPED);
373
374 adw_preferences_group_add (ADW_PREFERENCES_GROUP (self),
375 GTK_WIDGET (self->shortcut_list_box));
376 }
377
378 GtkWidget *
379 cc_keyboard_shortcut_group_new (GListModel *shortcut_items,
380 const char *section_id,
381 const char *section_title,
382 CcKeyboardManager *manager,
383 CcKeyboardShortcutEditor *shortcut_editor,
384 GtkSizeGroup *size_group)
385 {
386 CcKeyboardShortcutGroup *self;
387
388 g_return_val_if_fail (section_id && *section_id, NULL);
389 g_return_val_if_fail (G_IS_LIST_MODEL (shortcut_items), NULL);
390 g_return_val_if_fail (g_list_model_get_item_type (shortcut_items) == CC_TYPE_KEYBOARD_ITEM, NULL);
391
392 self = g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_GROUP, NULL);
393 self->section_title = g_strdup (section_title);
394 self->section_id = g_strdup (section_id);
395
396 self->keyboard_manager = manager;
397 self->shortcut_editor = shortcut_editor;
398 self->accelerator_size_group = size_group;
399
400 shortcut_group_set_list_model (self, shortcut_items);
401 shortcut_group_update_title (self);
402
403 g_object_bind_property (self, "empty",
404 self, "visible",
405 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
406
407 gtk_list_box_bind_model (self->shortcut_list_box,
408 G_LIST_MODEL (self->filtered_shortcuts),
409 shortcut_group_row_new,
410 self, NULL);
411
412 return GTK_WIDGET (self);
413 }
414
415 /**
416 * cc_keyboard_shortcut_group_get_model:
417 * self: A #CcKeyboardShortcutGroup
418 *
419 * Get the #GListModel used to create shortcut rows.
420 * The content of the model can be different from
421 * the #GListModel given to create @self if a search
422 * is in progress.
423 *
424 * Returns: (transfer none): A #GListModel
425 */
426 GListModel *
427 cc_keyboard_shortcut_group_get_model (CcKeyboardShortcutGroup *self)
428 {
429 g_return_val_if_fail (CC_IS_KEYBOARD_SHORTCUT_GROUP (self), NULL);
430
431 return G_LIST_MODEL (self->filtered_shortcuts);
432 }
433
434 void
435 cc_keyboard_shortcut_group_set_filter (CcKeyboardShortcutGroup *self,
436 GStrv search_terms)
437 {
438 g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_GROUP (self));
439
440 self->search_terms = search_terms;
441 gtk_filter_changed (GTK_FILTER (self->custom_filter), GTK_FILTER_CHANGE_DIFFERENT);
442 shortcut_group_update_title (self);
443 }
444