1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2019 The Free Software Foundation, Inc.
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 <gtk/gtk.h>
24 #include "dlg-preferences-shortcuts.h"
25 #include "glib-utils.h"
26 #include "gth-accel-dialog.h"
27 #include "gth-browser.h"
28 #include "gth-main.h"
29 #include "gth-preferences.h"
30 #include "gtk-utils.h"
31 #include "main.h"
32
33
34 #define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
35 #define BROWSER_DATA_KEY "shortcuts-preference-data"
36 #define GTH_SHORTCUT_CATEGORY_ALL "all"
37 #define GTH_SHORTCUT_CATEGORY_MODIFIED "modified"
38
39
40 enum {
41 ACTION_NAME_COLUMN,
42 DESCRIPTION_COLUMN,
43 ACCELERATOR_LABEL_COLUMN
44 };
45
46
47 typedef struct {
48 GthBrowser *browser;
49 GtkBuilder *builder;
50 GtkWidget *preferences_dialog;
51 GPtrArray *rows;
52 char *show_category;
53 } BrowserData;
54
55
56 static void
browser_data_free(BrowserData * data)57 browser_data_free (BrowserData *data)
58 {
59 g_free (data->show_category);
60 g_ptr_array_unref (data->rows);
61 g_object_unref (data->builder);
62 g_free (data);
63 }
64
65
66 typedef struct {
67 BrowserData *browser_data;
68 GthShortcut *shortcut;
69 GtkWidget *accel_label;
70 GtkWidget *revert_button;
71 gboolean modified;
72 } RowData;
73
74
75 static RowData *
row_data_new(BrowserData * data,GthShortcut * shortcut)76 row_data_new (BrowserData *data,
77 GthShortcut *shortcut)
78 {
79 RowData *row_data;
80
81 row_data = g_new0 (RowData, 1);
82 row_data->browser_data = data;
83 row_data->shortcut = shortcut;
84 row_data->accel_label = NULL;
85
86 g_ptr_array_add (row_data->browser_data->rows, row_data);
87
88 return row_data;
89 }
90
91
92 static void
row_data_free(RowData * row_data)93 row_data_free (RowData *row_data)
94 {
95 if (row_data == NULL)
96 return;
97 g_free (row_data);
98 }
99
100
101 static void
row_data_update_accel_label(RowData * row_data)102 row_data_update_accel_label (RowData *row_data)
103 {
104 if (row_data == NULL)
105 return;
106
107 row_data->modified = g_strcmp0 (row_data->shortcut->default_accelerator, row_data->shortcut->accelerator) != 0;
108 if (row_data->modified) {
109 char *esc_text;
110 char *markup_text;
111
112 esc_text = g_markup_escape_text (row_data->shortcut->label, -1);
113 markup_text = g_strdup_printf ("<b>%s</b>", esc_text);
114 gtk_label_set_markup (GTK_LABEL (row_data->accel_label), markup_text);
115
116 g_free (markup_text);
117 g_free (esc_text);
118 }
119 else
120 gtk_label_set_text (GTK_LABEL (row_data->accel_label), row_data->shortcut->label);
121
122 gtk_widget_set_sensitive (row_data->revert_button, row_data->modified);
123 gtk_widget_set_child_visible (row_data->revert_button, row_data->modified);
124 }
125
126
127 static RowData *
find_row_by_shortcut(BrowserData * browser_data,GthShortcut * shortcut)128 find_row_by_shortcut (BrowserData *browser_data,
129 GthShortcut *shortcut)
130 {
131 int i;
132
133 for (i = 0; i < browser_data->rows->len; i++) {
134 RowData *row_data = g_ptr_array_index (browser_data->rows, i);
135 if (g_strcmp0 (row_data->shortcut->detailed_action, shortcut->detailed_action) == 0)
136 return row_data;
137 }
138
139 return NULL;
140 }
141
142
143 static void
update_filter_if_required(BrowserData * data)144 update_filter_if_required (BrowserData *data)
145 {
146 if (g_strcmp0 (data->show_category, GTH_SHORTCUT_CATEGORY_MODIFIED) == 0)
147 gtk_list_box_invalidate_filter (GTK_LIST_BOX (_gtk_builder_get_widget (data->builder, "shortcuts_list")));
148 }
149
150
151 static gboolean
row_data_update_shortcut(RowData * row_data,guint keycode,GdkModifierType modifiers,GtkWindow * parent)152 row_data_update_shortcut (RowData *row_data,
153 guint keycode,
154 GdkModifierType modifiers,
155 GtkWindow *parent)
156 {
157 gboolean change;
158
159 change = gth_window_can_change_shortcut (GTH_WINDOW (row_data->browser_data->browser),
160 row_data->shortcut->detailed_action,
161 row_data->shortcut->context,
162 keycode,
163 modifiers,
164 parent);
165
166 if (change) {
167 GPtrArray *shortcuts_v;
168 GthShortcut *shortcut;
169
170 shortcuts_v = gth_window_get_shortcuts (GTH_WINDOW (row_data->browser_data->browser));
171 shortcut = gth_shortcut_array_find (shortcuts_v,
172 row_data->shortcut->context,
173 keycode,
174 modifiers);
175 if (shortcut != NULL) {
176 gth_shortcut_set_key (shortcut, 0, 0);
177 row_data_update_accel_label (find_row_by_shortcut (row_data->browser_data, shortcut));
178 }
179
180 gth_shortcut_set_key (row_data->shortcut, keycode, modifiers);
181 row_data_update_accel_label (row_data);
182 update_filter_if_required (row_data->browser_data);
183
184 gth_main_shortcuts_changed (gth_window_get_shortcuts (GTH_WINDOW (row_data->browser_data->browser)));
185 }
186
187 return change;
188 }
189
190
191 static void
accel_dialog_response_cb(GtkDialog * dialog,gint response_id,gpointer user_data)192 accel_dialog_response_cb (GtkDialog *dialog,
193 gint response_id,
194 gpointer user_data)
195 {
196 RowData *row_data = user_data;
197 guint keycode;
198 GdkModifierType modifiers;
199
200 switch (response_id) {
201 case GTK_RESPONSE_OK:
202 if (gth_accel_dialog_get_accel (GTH_ACCEL_DIALOG (dialog), &keycode, &modifiers))
203 if (row_data_update_shortcut (row_data, keycode, modifiers, GTK_WINDOW (dialog)))
204 gtk_widget_destroy (GTK_WIDGET (dialog));
205 break;
206
207 case GTK_RESPONSE_CANCEL:
208 gtk_widget_destroy (GTK_WIDGET (dialog));
209 break;
210
211 case GTH_ACCEL_BUTTON_RESPONSE_DELETE:
212 row_data_update_shortcut (row_data, 0, 0, GTK_WINDOW (dialog));
213 gtk_widget_destroy (GTK_WIDGET (dialog));
214 break;
215 }
216 }
217
218
219 static void
shortcuts_list_row_activated_cb(GtkListBox * box,GtkListBoxRow * row,gpointer user_data)220 shortcuts_list_row_activated_cb (GtkListBox *box,
221 GtkListBoxRow *row,
222 gpointer user_data)
223 {
224 RowData *row_data;
225 GtkWidget *dialog;
226
227 row_data = g_object_get_data (G_OBJECT (row), "shortcut-row-data");
228
229 dialog = gth_accel_dialog_new (_("Shortcut"),
230 _gtk_widget_get_toplevel_if_window (GTK_WIDGET (box)),
231 row_data->shortcut->keyval,
232 row_data->shortcut->modifiers);
233 g_signal_connect (dialog,
234 "response",
235 G_CALLBACK (accel_dialog_response_cb),
236 row_data);
237 gtk_widget_show (dialog);
238 }
239
240
241 static void
revert_button_clicked_cb(GtkButton * button,gpointer user_data)242 revert_button_clicked_cb (GtkButton *button,
243 gpointer user_data)
244 {
245 RowData *row_data = user_data;
246 guint keycode;
247 GdkModifierType modifiers;
248
249 gtk_accelerator_parse (row_data->shortcut->default_accelerator, &keycode, &modifiers);
250 row_data_update_shortcut (row_data, keycode, modifiers, GTK_WINDOW (row_data->browser_data->preferences_dialog));
251 }
252
253
254 static GtkWidget *
_new_shortcut_row(GthShortcut * shortcut,BrowserData * data)255 _new_shortcut_row (GthShortcut *shortcut,
256 BrowserData *data)
257 {
258 GtkWidget *row;
259 RowData *row_data;
260 GtkWidget *box;
261 GtkWidget *label;
262 GtkWidget *button_box;
263 GtkWidget *button;
264
265 row = gtk_list_box_row_new ();
266 row_data = row_data_new (data, shortcut);
267 g_object_set_data_full (G_OBJECT (row), "shortcut-row-data", row_data, (GDestroyNotify) row_data_free);
268
269 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
270 gtk_container_set_border_width (GTK_CONTAINER (box), 5);
271 gtk_container_add (GTK_CONTAINER (row), box);
272
273 label = gtk_label_new (_(shortcut->description));
274 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
275 gtk_widget_set_margin_end (label, 12);
276 gtk_size_group_add_widget (GTK_SIZE_GROUP (gtk_builder_get_object (data->builder, "column1_size_group")), label);
277 gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
278
279 row_data->accel_label = label = gtk_label_new ("");
280 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
281 gtk_widget_set_margin_end (label, 12);
282 gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL);
283 gtk_size_group_add_widget (GTK_SIZE_GROUP (gtk_builder_get_object (data->builder, "column2_size_group")), label);
284 gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
285
286 button_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
287 gtk_widget_set_margin_start (button_box, 12);
288 gtk_widget_set_margin_end (button_box, 12);
289 gtk_size_group_add_widget (GTK_SIZE_GROUP (gtk_builder_get_object (data->builder, "column3_size_group")), button_box);
290 gtk_box_pack_start (GTK_BOX (box), button_box, FALSE, FALSE, 0);
291
292 row_data->revert_button = button = gtk_button_new_from_icon_name ("edit-clear-symbolic", GTK_ICON_SIZE_MENU);
293 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
294 gtk_widget_set_tooltip_text (button, _("Revert"));
295 gtk_style_context_add_class (gtk_widget_get_style_context (button), "circular");
296 gtk_style_context_add_class (gtk_widget_get_style_context (button), "revert-shortcut-button");
297 gtk_box_pack_start (GTK_BOX (button_box), button, FALSE, FALSE, 0);
298
299 gtk_widget_show_all (row);
300 row_data_update_accel_label (row_data);
301
302 g_signal_connect (row_data->revert_button,
303 "clicked",
304 G_CALLBACK (revert_button_clicked_cb),
305 row_data);
306
307 return row;
308 }
309
310
311 static GtkWidget *
_new_shortcut_category_row(const char * category_id,int n_category)312 _new_shortcut_category_row (const char *category_id,
313 int n_category)
314 {
315 GtkWidget *row;
316 GtkWidget *box;
317 GthShortcutCategory *category;
318 const char *text;
319 char *esc_text;
320 char *markup_text;
321 GtkWidget *label;
322
323 row = gtk_list_box_row_new ();
324 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
325 gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
326
327 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
328 if (n_category > 1)
329 gtk_widget_set_margin_top (box, 15);
330 gtk_container_add (GTK_CONTAINER (row), box);
331
332 category = gth_main_get_shortcut_category (category_id);
333 text = (category != NULL) ? _(category->display_name) : _("Other");
334 esc_text = g_markup_escape_text (text, -1);
335 markup_text = g_strdup_printf ("<b>%s</b>", esc_text);
336
337 label = gtk_label_new (NULL);
338 gtk_label_set_markup (GTK_LABEL (label), markup_text);
339 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
340 gtk_widget_set_margin_start (label, 5);
341 gtk_widget_set_margin_end (label, 5);
342 gtk_widget_set_margin_top (label, 5);
343 gtk_widget_set_margin_bottom (label, 5);
344 gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
345 gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL);
346 gtk_box_pack_start (GTK_BOX (box), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, 0);
347
348 gtk_widget_show_all (row);
349
350 g_free (markup_text);
351 g_free (esc_text);
352
353 return row;
354 }
355
356
357 static void
restore_all_button_clicked_cb(GtkButton * button,gpointer user_data)358 restore_all_button_clicked_cb (GtkButton *button,
359 gpointer user_data)
360 {
361 BrowserData *data = user_data;
362 GtkWidget *dialog;
363 gboolean reassign;
364
365 dialog = _gtk_yesno_dialog_new (GTK_WINDOW (data->preferences_dialog),
366 GTK_DIALOG_MODAL,
367 _("Do you want to revert all the changes and use the default shortcuts?"),
368 _GTK_LABEL_CANCEL,
369 _("Revert"));
370 _gtk_dialog_add_class_to_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES, GTK_STYLE_CLASS_DESTRUCTIVE_ACTION);
371
372 reassign = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES;
373 gtk_widget_destroy (GTK_WIDGET (dialog));
374
375 if (reassign) {
376 int i;
377
378 for (i = 0; i < data->rows->len; i++) {
379 RowData *row_data = g_ptr_array_index (data->rows, i);
380
381 gth_shortcut_set_accelerator (row_data->shortcut, row_data->shortcut->default_accelerator);
382 row_data_update_accel_label (row_data);
383 }
384
385 gth_main_shortcuts_changed (gth_window_get_shortcuts (GTH_WINDOW (data->browser)));
386 }
387 }
388
389
390 static int
cmp_category(gconstpointer a,gconstpointer b)391 cmp_category (gconstpointer a,
392 gconstpointer b)
393 {
394 GthShortcutCategory *cat_a = * (GthShortcutCategory **) a;
395 GthShortcutCategory *cat_b = * (GthShortcutCategory **) b;
396
397 if (cat_a->sort_order == cat_b->sort_order)
398 return 0;
399 else if (cat_a->sort_order > cat_b->sort_order)
400 return 1;
401 else
402 return -1;
403 }
404
405
406 static void
category_combo_box_changed_cb(GtkComboBox * combo_box,gpointer user_data)407 category_combo_box_changed_cb (GtkComboBox *combo_box,
408 gpointer user_data)
409 {
410 BrowserData *data = user_data;
411 GtkTreeIter iter;
412
413 if (gtk_combo_box_get_active_iter (combo_box, &iter)) {
414 char *category_id;
415
416 gtk_tree_model_get (GTK_TREE_MODEL (gtk_builder_get_object (data->builder, "category_liststore")),
417 &iter,
418 0, &category_id,
419 -1);
420
421 g_free (data->show_category);
422 data->show_category = g_strdup (category_id);
423
424 gtk_list_box_invalidate_filter (GTK_LIST_BOX (_gtk_builder_get_widget (data->builder, "shortcuts_list")));
425
426 g_free (category_id);
427 }
428 }
429
430
431 static gboolean
shortcut_filter_func(GtkListBoxRow * row,gpointer user_data)432 shortcut_filter_func (GtkListBoxRow *row,
433 gpointer user_data)
434 {
435 BrowserData *data = user_data;
436 RowData *row_data;
437
438 if (g_strcmp0 (data->show_category, GTH_SHORTCUT_CATEGORY_ALL) == 0)
439 return TRUE;
440
441 row_data = g_object_get_data (G_OBJECT (row), "shortcut-row-data");
442 if ((row_data == NULL) || (row_data->shortcut == NULL))
443 return FALSE;
444
445 if (g_strcmp0 (data->show_category, GTH_SHORTCUT_CATEGORY_MODIFIED) == 0)
446 return row_data->modified;
447
448 return g_strcmp0 (data->show_category, row_data->shortcut->category) == 0;
449 }
450
451
452 void
shortcuts__dlg_preferences_construct_cb(GtkWidget * dialog,GthBrowser * browser,GtkBuilder * dialog_builder)453 shortcuts__dlg_preferences_construct_cb (GtkWidget *dialog,
454 GthBrowser *browser,
455 GtkBuilder *dialog_builder)
456 {
457 BrowserData *data;
458 GHashTable *visible_categories;
459 GtkWidget *shortcuts_list;
460 GPtrArray *shortcuts_v;
461 const char *last_category;
462 int n_category;
463 int i;
464 GtkWidget *label;
465 GtkWidget *page;
466
467 data = g_new0 (BrowserData, 1);
468 data->browser = browser;
469 data->builder = _gtk_builder_new_from_file ("shortcuts-preferences.ui", NULL);
470 data->preferences_dialog = dialog;
471 data->rows = g_ptr_array_new ();
472 data->show_category = g_strdup (GTH_SHORTCUT_CATEGORY_ALL);
473
474 g_object_set_data_full (G_OBJECT (dialog), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
475
476 /* shortcut list */
477
478 visible_categories = g_hash_table_new (g_str_hash, g_str_equal);
479 shortcuts_list = _gtk_builder_get_widget (data->builder, "shortcuts_list");
480 shortcuts_v = gth_window_get_shortcuts_by_category (GTH_WINDOW (browser));
481 last_category = NULL;
482 n_category = 0;
483 for (i = 0; i < shortcuts_v->len; i++) {
484 GthShortcut *shortcut = g_ptr_array_index (shortcuts_v, i);
485
486 if (g_strcmp0 (shortcut->category, GTH_SHORTCUT_CATEGORY_HIDDEN) == 0)
487 continue;
488
489 if ((shortcut->context & GTH_SHORTCUT_CONTEXT_INTERNAL) != 0)
490 continue;
491
492 if ((shortcut->context & GTH_SHORTCUT_CONTEXT_DOC) != 0)
493 continue;
494
495 if ((shortcut->context & GTH_SHORTCUT_CONTEXT_FIXED) != 0)
496 continue;
497
498 if (g_strcmp0 (shortcut->category,last_category) != 0) {
499 last_category = shortcut->category;
500 n_category++;
501 g_hash_table_add (visible_categories, shortcut->category);
502 gtk_list_box_insert (GTK_LIST_BOX (shortcuts_list),
503 _new_shortcut_category_row (shortcut->category, n_category),
504 -1);
505 }
506 gtk_list_box_insert (GTK_LIST_BOX (shortcuts_list),
507 _new_shortcut_row (shortcut, data),
508 -1);
509 }
510 gtk_list_box_set_filter_func (GTK_LIST_BOX (shortcuts_list),
511 shortcut_filter_func,
512 data,
513 NULL);
514
515 g_signal_connect (shortcuts_list,
516 "row-activated",
517 G_CALLBACK (shortcuts_list_row_activated_cb),
518 data);
519 g_signal_connect (_gtk_builder_get_widget (data->builder, "restore_all_button"),
520 "clicked",
521 G_CALLBACK (restore_all_button_clicked_cb),
522 data);
523
524 /* shortcut categories */
525
526 {
527 GPtrArray *category_v;
528 GtkListStore *list_store;
529 GtkTreeIter iter;
530 int i;
531
532 category_v = _g_ptr_array_dup (gth_main_get_shortcut_categories (), NULL, NULL);
533 g_ptr_array_sort (category_v, cmp_category);
534
535 list_store = (GtkListStore *) gtk_builder_get_object (data->builder, "category_liststore");
536
537 gtk_list_store_append (list_store, &iter);
538 gtk_list_store_set (list_store, &iter,
539 0, GTH_SHORTCUT_CATEGORY_ALL,
540 1, C_("Shortcuts", "All"),
541 -1);
542
543 gtk_list_store_append (list_store, &iter);
544 gtk_list_store_set (list_store, &iter,
545 0, GTH_SHORTCUT_CATEGORY_MODIFIED,
546 1, C_("Shortcuts", "Modified"),
547 -1);
548
549 for (i = 0; i < category_v->len; i++) {
550 GthShortcutCategory *category = g_ptr_array_index (category_v, i);
551
552 if (! g_hash_table_contains (visible_categories, category->id))
553 continue;
554
555 gtk_list_store_append (list_store, &iter);
556 gtk_list_store_set (list_store, &iter,
557 0, category->id,
558 1, _(category->display_name),
559 -1);
560 }
561
562 gtk_combo_box_set_active (GTK_COMBO_BOX (_gtk_builder_get_widget (data->builder, "category_combobox")), 0);
563 g_signal_connect (_gtk_builder_get_widget (data->builder, "category_combobox"),
564 "changed",
565 G_CALLBACK (category_combo_box_changed_cb),
566 data);
567
568 g_ptr_array_unref (category_v);
569 }
570
571 g_hash_table_unref (visible_categories);
572
573 /* add the page to the preferences dialog */
574
575 label = gtk_label_new (_("Shortcuts"));
576 gtk_widget_show (label);
577
578 page = _gtk_builder_get_widget (data->builder, "preferences_page");
579 gtk_widget_show (page);
580 gtk_notebook_append_page (GTK_NOTEBOOK (_gtk_builder_get_widget (dialog_builder, "notebook")), page, label);
581 }
582