1 #include <config.h>
2 #include <gdk/gdk.h>
3 #include <math.h>
4 #include <cairo-gobject.h>
5 
6 #include "xapp-enums.h"
7 #include "xapp-icon-chooser-dialog.h"
8 #include "xapp-stack-sidebar.h"
9 #include <glib/gi18n-lib.h>
10 #include <glib/gstdio.h>
11 
12 #define DEBUG_REFS 0
13 #define DEBUG_ICON_THEME 0
14 
15 /**
16  * SECTION:xapp-icon-chooser-dialog
17  * @Short_description: A dialog for selecting an icon
18  * @Title: XAppIconChooserDialog
19  *
20  * The XAppIconChooserDialog creates a dialog so that
21  * the user can select an icon. It provides the ability
22  * to browse by category, search by icon name, or select
23  * from a specific file.
24  */
25 
26 typedef struct
27 {
28     const gchar  *name; /* This is a translation which doesn't get freed */
29     GList        *icons;
30     GList        *iter;
31     GtkListStore *model;
32 } IconCategoryInfo;
33 
34 typedef struct
35 {
36     GtkResponseType  response;
37     XAppIconSize     icon_size;
38     GtkListStore    *search_icon_store;
39     GFileEnumerator *search_file_enumerator;
40     GCancellable    *cancellable;
41     GList           *full_icon_list;
42     GList           *search_iter;
43     GHashTable      *categories;
44     GHashTable      *surfaces_by_name;
45     GtkWidget       *search_bar;
46     GtkWidget       *icon_view;
47     GtkWidget       *list_box;
48     GtkWidget       *default_button;
49     GtkWidget       *select_button;
50     GtkWidget       *browse_button;
51     GtkWidget       *action_area;
52     GtkWidget       *loading_bar;
53     GtkCellArea     *ca_box;
54     gchar           *icon_string;
55     gchar           *current_text;
56     gulong           search_changed_id;
57     gboolean         allow_paths;
58     gchar           *default_icon;
59     IconCategoryInfo *current_category;
60 } XAppIconChooserDialogPrivate;
61 
62 struct _XAppIconChooserDialog
63 {
64     XAppGtkWindow parent_instance;
65 };
66 
67 typedef struct
68 {
69     XAppIconChooserDialog *dialog;
70     GtkListStore          *model;
71     IconCategoryInfo      *category_info;
72     GCancellable          *cancellable;
73     cairo_surface_t       *surface;
74     const gchar           *name;
75     gboolean               chunk_end;
76 } NamedIconInfoLoadCallbackInfo;
77 
78 typedef struct
79 {
80     XAppIconChooserDialog *dialog;
81     GtkListStore          *model;
82     GCancellable          *cancellable;
83     GFileEnumerator       *enumerator;
84     gchar                 *short_name;
85     gchar                 *long_name;
86     gboolean               chunk_end;
87 } FileIconInfoLoadCallbackInfo;
88 
89 typedef struct
90 {
91     const gchar  *name;
92     const gchar  *contexts[5];
93 } IconCategoryDefinition;
94 
95 static IconCategoryDefinition categories[] = {
96     //  Category name       context names
97     {
98         N_("Actions"),       { "Actions", NULL }
99     },
100     {
101         N_("Applications"),  { "Applications", "Apps", NULL }
102     },
103     {
104         N_("Categories"),    { "Categories", NULL }
105     },
106     {
107         N_("Devices"),       { "Devices", NULL }
108     },
109     {
110         N_("Emblems"),       { "Emblems", NULL }
111     },
112     {
113         N_("Emoji"),         { "Emotes", NULL }
114     },
115     {
116         N_("Mime types"),    { "MimeTypes", "Mimetypes", NULL }
117     },
118     {
119         N_("Places"),        { "Places", NULL }
120     },
121     {
122         N_("Status"),        { "Status", "Notifications", NULL }
123     },
124     {
125         N_("Other"),         { "Panel", NULL }
126     }
127 };
128 
129 enum
130 {
131     CLOSE,
132     SELECT,
133     LAST_SIGNAL
134 };
135 
136 enum
137 {
138     PROP_0,
139     PROP_ICON_SIZE,
140     PROP_ALLOW_PATHS,
141     PROP_DEFAULT_ICON,
142     N_PROPERTIES
143 };
144 
145 enum
146 {
147     COLUMN_DISPLAY_NAME,
148     COLUMN_FULL_NAME,
149     COLUMN_SURFACE,
150 };
151 
152 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
153 
154 static guint signals[LAST_SIGNAL] = {0, };
155 
156 G_DEFINE_TYPE_WITH_PRIVATE (XAppIconChooserDialog, xapp_icon_chooser_dialog, XAPP_TYPE_GTK_WINDOW)
157 
158 static void on_category_selected (GtkListBox            *list_box,
159                                  XAppIconChooserDialog *dialog);
160 
161 static void on_search_text_changed (GtkSearchEntry        *entry,
162                                     XAppIconChooserDialog *dialog);
163 
164 static void on_icon_view_selection_changed (GtkIconView *icon_view,
165                                             gpointer     user_data);
166 
167 static void on_icon_store_icons_added (GtkTreeModel *tree_model,
168                                        GtkTreePath  *path,
169                                        GtkTreeIter  *iter,
170                                        gpointer      user_data);
171 
172 static void on_browse_button_clicked (GtkButton *button,
173                                       gpointer   user_data);
174 
175 static void on_select_button_clicked (GtkButton *button,
176                                       gpointer   user_data);
177 
178 static void on_cancel_button_clicked (GtkButton *button,
179                                       gpointer   user_data);
180 
181 static void on_default_button_clicked (GtkButton *button,
182                                            gpointer   user_data);
183 
184 static gboolean on_search_bar_key_pressed (GtkWidget *widget,
185                                            GdkEvent  *event,
186                                            gpointer   user_data);
187 
188 static gboolean on_delete_event (GtkWidget   *widget,
189                                  GdkEventAny *event);
190 
191 static void on_icon_view_item_activated (GtkIconView *iconview,
192                                          GtkTreePath *path,
193                                          gpointer     user_data);
194 
195 static void load_categories (XAppIconChooserDialog *dialog);
196 
197 static void load_icons_for_category (XAppIconChooserDialog *dialog,
198                                      IconCategoryInfo      *category_info,
199                                      guint                  icon_size);
200 
201 static void search_path (XAppIconChooserDialog *dialog,
202                          const gchar           *path_string,
203                          GtkListStore          *icon_store);
204 
205 static void search_icon_name (XAppIconChooserDialog *dialog,
206                               const gchar           *name_string,
207                               GtkListStore          *icon_store);
208 
209 static gint list_box_sort (GtkListBoxRow *row1,
210                            GtkListBoxRow *row2,
211                            gpointer       user_data);
212 
213 static gint search_model_sort (GtkTreeModel *model,
214                                GtkTreeIter  *a,
215                                GtkTreeIter  *b,
216                                gpointer      user_data);
217 
218 static void
free_category_info(IconCategoryInfo * category_info)219 free_category_info (IconCategoryInfo *category_info)
220 {
221     g_list_free_full (category_info->icons, g_free);
222 
223     g_clear_object (&category_info->model);
224 
225     g_free (category_info);
226 }
227 
228 static void
free_file_info(FileIconInfoLoadCallbackInfo * file_info)229 free_file_info (FileIconInfoLoadCallbackInfo *file_info)
230 {
231     g_object_unref (file_info->cancellable);
232 
233     g_object_unref (file_info->enumerator);
234 
235     g_free (file_info->short_name);
236     g_free (file_info->long_name);
237 
238     g_free (file_info);
239 }
240 
241 static void
free_named_info(NamedIconInfoLoadCallbackInfo * named_info)242 free_named_info (NamedIconInfoLoadCallbackInfo *named_info)
243 {
244     g_object_unref (named_info->cancellable);
245 
246     g_clear_pointer (&named_info->surface, cairo_surface_destroy);
247 
248     g_free (named_info);
249 }
250 
251 #if DEBUG_REFS
252 static void
on_cancellable_finalize(gpointer data,GObject * object)253 on_cancellable_finalize (gpointer  data,
254                          GObject  *object)
255 {
256     g_printerr ("Cancellable Finalize: %p\n", object);
257 }
258 
259 static void
on_enumerator_finalize(gpointer data,GObject * object)260 on_enumerator_finalize (gpointer  data,
261                          GObject  *object)
262 {
263     g_printerr ("Enumerator Finalize: %p\n", object);
264 }
265 #endif
266 
267 static void
on_enumerator_toggle_ref_called(gpointer data,GObject * object,gboolean is_last_ref)268 on_enumerator_toggle_ref_called (gpointer data,
269                                  GObject *object,
270                                  gboolean is_last_ref)
271 {
272     XAppIconChooserDialog *dialog;
273     XAppIconChooserDialogPrivate *priv;
274 
275     dialog = XAPP_ICON_CHOOSER_DIALOG (data);
276     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
277 
278     if (is_last_ref)
279     {
280         g_object_remove_toggle_ref (object,
281                                     (GToggleNotify) on_enumerator_toggle_ref_called,
282                                     dialog);
283 
284         priv->search_file_enumerator = NULL;
285     }
286 }
287 
288 static void
clear_search_state(XAppIconChooserDialog * dialog)289 clear_search_state (XAppIconChooserDialog *dialog)
290 {
291     XAppIconChooserDialogPrivate *priv;
292 
293     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
294 
295     g_cancellable_cancel (priv->cancellable);
296     g_clear_object (&priv->cancellable);
297 
298     g_clear_object (&priv->search_file_enumerator);
299 
300     gtk_widget_hide (priv->loading_bar);
301     priv->search_iter = NULL;
302 }
303 
304 void
xapp_icon_chooser_dialog_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)305 xapp_icon_chooser_dialog_get_property (GObject    *object,
306                                        guint       prop_id,
307                                        GValue     *value,
308                                        GParamSpec *pspec)
309 {
310     XAppIconChooserDialog *dialog;
311     XAppIconChooserDialogPrivate *priv;
312 
313     dialog = XAPP_ICON_CHOOSER_DIALOG (object);
314     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
315 
316     switch (prop_id)
317     {
318         case PROP_ICON_SIZE:
319             g_value_set_enum (value, priv->icon_size);
320             break;
321         case PROP_ALLOW_PATHS:
322             g_value_set_boolean (value, priv->allow_paths);
323             break;
324         case PROP_DEFAULT_ICON:
325             g_value_set_string (value, xapp_icon_chooser_dialog_get_default_icon(dialog));
326             break;
327         default:
328             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
329             break;
330     }
331 }
332 
333 void
xapp_icon_chooser_dialog_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)334 xapp_icon_chooser_dialog_set_property (GObject      *object,
335                                        guint         prop_id,
336                                        const GValue *value,
337                                        GParamSpec   *pspec)
338 {
339     XAppIconChooserDialog        *dialog;
340     XAppIconChooserDialogPrivate *priv;
341 
342     dialog = XAPP_ICON_CHOOSER_DIALOG (object);
343     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
344 
345     switch (prop_id)
346     {
347         case PROP_ICON_SIZE:
348             priv->icon_size = g_value_get_enum (value);
349             break;
350         case PROP_ALLOW_PATHS:
351             priv->allow_paths = g_value_get_boolean (value);
352             if (priv->allow_paths)
353             {
354                 gtk_widget_show (priv->browse_button);
355                 gtk_widget_set_no_show_all (priv->browse_button, FALSE);
356             }
357             else
358             {
359                 gtk_widget_hide (priv->browse_button);
360                 gtk_widget_set_no_show_all (priv->browse_button, TRUE);
361             }
362             break;
363         case PROP_DEFAULT_ICON:
364             xapp_icon_chooser_dialog_set_default_icon (dialog, g_value_get_string (value));
365             break;
366         default:
367             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
368             break;
369     }
370 }
371 
372 static void
xapp_icon_chooser_dialog_dispose(GObject * object)373 xapp_icon_chooser_dialog_dispose (GObject *object)
374 {
375     XAppIconChooserDialog        *dialog;
376     XAppIconChooserDialogPrivate *priv;
377 
378     dialog = XAPP_ICON_CHOOSER_DIALOG (object);
379     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
380 
381     if (priv->categories != NULL)
382     {
383         g_hash_table_destroy (priv->categories);
384         priv->categories = NULL;
385     }
386 
387     if (priv->surfaces_by_name != NULL)
388     {
389         g_hash_table_destroy (priv->surfaces_by_name);
390         priv->surfaces_by_name = NULL;
391     }
392 
393     g_clear_pointer (&priv->icon_string, g_free);
394     g_clear_pointer (&priv->default_icon, g_free);
395     g_clear_pointer (&priv->current_text, g_free);
396     g_clear_object (&priv->cancellable);
397 
398     G_OBJECT_CLASS (xapp_icon_chooser_dialog_parent_class)->dispose (object);
399 }
400 
401 static void
xapp_icon_chooser_dialog_init(XAppIconChooserDialog * dialog)402 xapp_icon_chooser_dialog_init (XAppIconChooserDialog *dialog)
403 {
404     XAppIconChooserDialogPrivate *priv;
405     GtkWidget                    *main_box;
406     GtkWidget                    *secondary_box;
407     GtkWidget                    *toolbar;
408     GtkWidget                    *overlay;
409     GtkWidget                    *spinner;
410     GtkWidget                    *spinner_label;
411     GtkWidget                    *loading_bar_box;
412     GtkToolItem                  *tool_item;
413     GtkWidget                    *toolbar_box;
414     GtkWidget                    *right_box;
415     GtkStyleContext              *style;
416     GtkSizeGroup                 *button_size_group;
417     GtkWidget                    *cancel_button;
418     GtkWidget                    *button_area;
419     GtkWidget                    *scrolled_window;
420 
421     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
422 
423     priv->icon_size = XAPP_ICON_SIZE_32;
424     priv->categories = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) free_category_info);
425 
426     /* surfaces_by_name will save surfaces generated by icon name, so they can avoid creating
427      * them again when they're reloaded (like re-selecting a previously selected category) */
428     priv->surfaces_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cairo_surface_destroy);
429 
430     priv->response = GTK_RESPONSE_NONE;
431     priv->icon_string = NULL;
432     priv->current_text = NULL;
433     priv->cancellable = NULL;
434     priv->allow_paths = TRUE;
435     priv->full_icon_list = NULL;
436 
437     priv->search_icon_store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
438 
439     gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_icon_store),
440                                      COLUMN_DISPLAY_NAME,
441                                      search_model_sort,
442                                      priv,
443                                      NULL);
444     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->search_icon_store),
445                                           COLUMN_DISPLAY_NAME,
446                                           GTK_SORT_ASCENDING);
447 
448     g_signal_connect (priv->search_icon_store, "row-inserted",
449                       G_CALLBACK (on_icon_store_icons_added), dialog);
450 
451     gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 450);
452     gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
453     gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
454     gtk_window_set_title (GTK_WINDOW (dialog), _("Choose an icon"));
455 
456     main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
457     gtk_container_add (GTK_CONTAINER (dialog), main_box);
458 
459     // toolbar
460     toolbar = gtk_toolbar_new ();
461     gtk_box_pack_start (GTK_BOX (main_box), toolbar, FALSE, FALSE, 0);
462     style = gtk_widget_get_style_context (toolbar);
463     gtk_style_context_add_class (style, "primary-toolbar");
464 
465     tool_item = gtk_tool_item_new ();
466     gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, 0);
467     gtk_tool_item_set_expand (tool_item, TRUE);
468 
469     toolbar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
470     gtk_container_add (GTK_CONTAINER (tool_item), toolbar_box);
471     style = gtk_widget_get_style_context (GTK_WIDGET (toolbar_box));
472     gtk_style_context_add_class (style, "linked");
473 
474     priv->search_bar = gtk_search_entry_new ();
475     gtk_box_pack_start (GTK_BOX (toolbar_box), priv->search_bar, TRUE, TRUE, 0);
476     gtk_entry_set_placeholder_text (GTK_ENTRY (priv->search_bar), _("Search"));
477 
478     priv->search_changed_id = g_signal_connect (priv->search_bar, "search-changed",
479                                                 G_CALLBACK (on_search_text_changed), dialog);
480     g_signal_connect (priv->search_bar, "key-press-event",
481                       G_CALLBACK (on_search_bar_key_pressed), dialog);
482 
483     priv->browse_button = gtk_button_new_with_label (_("Browse"));
484     gtk_box_pack_start (GTK_BOX (toolbar_box), priv->browse_button, FALSE, FALSE, 0);
485 
486     g_signal_connect (priv->browse_button, "clicked",
487                       G_CALLBACK (on_browse_button_clicked), dialog);
488 
489     secondary_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
490     gtk_box_pack_start (GTK_BOX (main_box), secondary_box, TRUE, TRUE, 0);
491 
492     // context list
493     scrolled_window = gtk_scrolled_window_new (NULL, NULL);
494     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
495     gtk_box_pack_start (GTK_BOX (secondary_box), scrolled_window, FALSE, FALSE, 0);
496 
497     priv->list_box = gtk_list_box_new ();
498     gtk_container_add(GTK_CONTAINER (scrolled_window), GTK_WIDGET (priv->list_box));
499     gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box), list_box_sort, NULL, NULL);
500     g_signal_connect (priv->list_box, "selected-rows-changed",
501                       G_CALLBACK (on_category_selected), dialog);
502 
503     style = gtk_widget_get_style_context (GTK_WIDGET (scrolled_window));
504     gtk_style_context_add_class (style, "sidebar");
505 
506     right_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
507     gtk_box_pack_start (GTK_BOX (secondary_box), right_box, TRUE, TRUE, 0);
508 
509     overlay = gtk_overlay_new ();
510     gtk_box_pack_start (GTK_BOX (right_box), overlay, TRUE, TRUE, 0);
511 
512     // icon view
513     scrolled_window = gtk_scrolled_window_new (NULL, NULL);
514     gtk_overlay_add_overlay (GTK_OVERLAY (overlay), scrolled_window);
515     gtk_widget_set_halign (scrolled_window, GTK_ALIGN_FILL);
516     gtk_widget_set_valign (scrolled_window, GTK_ALIGN_FILL);
517 
518     priv->loading_bar = gtk_frame_new (NULL);
519     gtk_frame_set_shadow_type (GTK_FRAME (priv->loading_bar), GTK_SHADOW_NONE);
520 
521     gtk_style_context_add_class (gtk_widget_get_style_context (priv->loading_bar),
522                                  "background");
523 
524     gtk_overlay_add_overlay (GTK_OVERLAY (overlay), priv->loading_bar);
525     gtk_widget_set_halign (priv->loading_bar, GTK_ALIGN_START);
526     gtk_widget_set_valign (priv->loading_bar, GTK_ALIGN_END);
527     gtk_widget_set_no_show_all (priv->loading_bar, TRUE);
528 
529     loading_bar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
530     gtk_container_add (GTK_CONTAINER (priv->loading_bar), loading_bar_box);
531     g_object_set (loading_bar_box,
532                   "margin", 4,
533                   NULL);
534 
535     spinner = gtk_spinner_new ();
536     gtk_spinner_start (GTK_SPINNER (spinner));
537     gtk_box_pack_start (GTK_BOX (loading_bar_box), spinner, FALSE, FALSE, 4);
538 
539     spinner_label = gtk_label_new (_("Loading..."));
540     gtk_box_pack_start (GTK_BOX (loading_bar_box), spinner_label, FALSE, FALSE, 4);
541 
542     gtk_widget_show_all (loading_bar_box);
543 
544     GtkCellArea *ca_box = gtk_cell_area_box_new ();
545     gtk_orientable_set_orientation (GTK_ORIENTABLE (ca_box), GTK_ORIENTATION_VERTICAL);
546 
547     GtkCellRenderer *r;
548 
549     r = gtk_cell_renderer_pixbuf_new ();
550     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ca_box), r, FALSE);
551     gtk_cell_area_attribute_connect (GTK_CELL_AREA (ca_box), r,
552                                      "surface",
553                                      COLUMN_SURFACE);
554 
555     r = gtk_cell_renderer_text_new ();
556     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ca_box), r, FALSE);
557     gtk_cell_area_attribute_connect (GTK_CELL_AREA (ca_box), r,
558                                      "text",
559                                      COLUMN_DISPLAY_NAME);
560     g_object_set (G_OBJECT (r),
561                   "alignment", PANGO_ALIGN_CENTER,
562                   "xalign", 0.5,
563                   "width-chars", 20,
564                   "wrap-mode", PANGO_WRAP_WORD_CHAR,
565                   "wrap-width", 0,
566                   NULL);
567 
568     priv->ca_box = ca_box;
569     priv->icon_view = gtk_icon_view_new_with_area (GTK_CELL_AREA (ca_box));
570     gtk_container_add(GTK_CONTAINER (scrolled_window), GTK_WIDGET (priv->icon_view));
571 
572     gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (priv->icon_view), COLUMN_FULL_NAME);
573 
574     g_signal_connect (priv->icon_view, "selection-changed",
575                       G_CALLBACK (on_icon_view_selection_changed), dialog);
576     g_signal_connect (priv->icon_view, "item-activated",
577                       G_CALLBACK (on_icon_view_item_activated), dialog);
578 
579     // buttons
580     button_area = gtk_action_bar_new ();
581     priv->action_area = button_area;
582     gtk_box_pack_start (GTK_BOX (main_box), button_area, FALSE, FALSE, 0);
583 
584     button_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
585 
586     priv->default_button = gtk_button_new_with_label (_("Default"));
587     gtk_widget_set_no_show_all (priv->default_button, TRUE);
588     style = gtk_widget_get_style_context (GTK_WIDGET (priv->default_button));
589     gtk_style_context_add_class (style, "text-button");
590     gtk_size_group_add_widget (button_size_group, priv->default_button);
591     gtk_action_bar_pack_start (GTK_ACTION_BAR (button_area), priv->default_button);
592 
593     g_signal_connect (priv->default_button, "clicked",
594                       G_CALLBACK (on_default_button_clicked), dialog);
595 
596     priv->select_button = gtk_button_new_with_label (_("Select"));
597     style = gtk_widget_get_style_context (GTK_WIDGET (priv->select_button));
598     gtk_style_context_add_class (style, "text-button");
599     gtk_size_group_add_widget (button_size_group, priv->select_button);
600     gtk_action_bar_pack_end (GTK_ACTION_BAR (button_area), priv->select_button);
601 
602     g_signal_connect (priv->select_button, "clicked",
603                       G_CALLBACK (on_select_button_clicked), dialog);
604 
605     cancel_button = gtk_button_new_with_label (_("Cancel"));
606     style = gtk_widget_get_style_context (GTK_WIDGET (cancel_button));
607     gtk_style_context_add_class (style, "text-button");
608     gtk_size_group_add_widget (button_size_group, cancel_button);
609     gtk_action_bar_pack_end (GTK_ACTION_BAR (button_area), cancel_button);
610 
611     g_signal_connect (cancel_button, "clicked",
612                       G_CALLBACK (on_cancel_button_clicked), dialog);
613 
614     load_categories (dialog);
615 }
616 
617 static void
xapp_icon_chooser_dialog_class_init(XAppIconChooserDialogClass * klass)618 xapp_icon_chooser_dialog_class_init (XAppIconChooserDialogClass *klass)
619 {
620     GtkBindingSet *binding_set;
621     GObjectClass *object_class = G_OBJECT_CLASS (klass);
622     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
623 
624     object_class->get_property = xapp_icon_chooser_dialog_get_property;
625     object_class->set_property = xapp_icon_chooser_dialog_set_property;
626     object_class->dispose = xapp_icon_chooser_dialog_dispose;
627 
628     widget_class->delete_event = on_delete_event;
629 
630     /**
631      * XAppIconChooserDialog:icon-size:
632      *
633      * The preferred size to use when looking up icons. This only works with icon names.
634      * Additionally, there is no guarantee that a selected icon name will exist in a
635      * particular size.
636      */
637     obj_properties[PROP_ICON_SIZE] =
638         g_param_spec_enum ("icon-size",
639                            _("Icon size"),
640                            _("The preferred icon size."),
641                            XAPP_TYPE_ICON_SIZE,
642                            XAPP_ICON_SIZE_32,
643                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
644 
645     /**
646      * XAppIconChooserDialog:allow-paths:
647      *
648      * Whether to allow paths to be searched and selected or only icon names.
649      */
650     obj_properties[PROP_ALLOW_PATHS] =
651         g_param_spec_boolean ("allow-paths",
652                               _("Allow Paths"),
653                               _("Whether to allow paths."),
654                               TRUE,
655                               G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
656 
657     /**
658      * XAppIconChooserDialog:default-icon:
659      *
660      * The icon to use by default.
661      */
662     obj_properties[PROP_DEFAULT_ICON] =
663         g_param_spec_string ("default-icon",
664                              _("Default Icon"),
665                              _("The icon to use by default"),
666                              NULL,
667                              G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
668 
669     g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
670 
671     // keybinding signals
672     signals[CLOSE] =
673         g_signal_new ("close",
674                       G_TYPE_FROM_CLASS (klass),
675                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
676                       G_STRUCT_OFFSET (GtkWidgetClass, delete_event),
677                       NULL, NULL, NULL,
678                       G_TYPE_NONE, 0);
679 
680     signals[SELECT] =
681         g_signal_new ("select",
682                       G_TYPE_FROM_CLASS (klass),
683                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
684                       0,
685                       NULL, NULL, NULL,
686                       G_TYPE_NONE, 0);
687 
688     binding_set = gtk_binding_set_by_class (klass);
689     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
690     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "select", 0);
691     gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "select", 0);
692 
693     gtk_widget_class_set_css_name (widget_class, "stacksidebar");
694 }
695 
696 /**
697  * xapp_icon_chooser_dialog_new:
698  *
699  * Creates a new #XAppIconChooserDialog.
700  *
701  * Returns: a newly created #XAppIconChooserDialog
702  */
703 XAppIconChooserDialog *
xapp_icon_chooser_dialog_new(void)704 xapp_icon_chooser_dialog_new (void)
705 {
706     return g_object_new (XAPP_TYPE_ICON_CHOOSER_DIALOG, NULL);
707 }
708 
709 /**
710  * xapp_icon_chooser_dialog_run:
711  * @dialog: a #XAppIconChooserDialog
712  *
713  * Shows the dialog and enters a separate main loop until an icon is chosen or the action is canceled.
714  *
715  * xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
716  * xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
717  * applications which use this dialog multiple times, as it may improve performance for subsequent
718  * calls.
719  *
720  * Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
721  */
722 gint
xapp_icon_chooser_dialog_run(XAppIconChooserDialog * dialog)723 xapp_icon_chooser_dialog_run (XAppIconChooserDialog *dialog)
724 {
725     XAppIconChooserDialogPrivate *priv;
726 
727     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
728 
729     gtk_widget_show_all (GTK_WIDGET (dialog));
730     gtk_widget_grab_focus (priv->search_bar);
731 
732     gtk_main ();
733 
734     return priv->response;
735 }
736 
737 /**
738  * xapp_icon_chooser_dialog_run_with_icon:
739  * @dialog: a #XAppIconChooserDialog
740  * @icon: a string representing the icon that should be selected
741  *
742  * Like xapp_icon_chooser_dialog_run but selects the icon specified by @icon. This can be either an
743  * icon name or a path. Passing an icon string or path that doesn't exist is accepted, but it may show
744  * multiple results, or none at all. This behavior is useful if, for example, you wish to have the
745  * user select an image file from a particular directory.
746  *
747  * If the property allow_paths is FALSE, setting a path will yield no results when the dialog is opened.
748  *
749  * xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
750  * xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
751  * applications which use this dialog multiple times, as it may improve performance for subsequent
752  * calls.
753  *
754  * Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
755  */
756 gint
xapp_icon_chooser_dialog_run_with_icon(XAppIconChooserDialog * dialog,gchar * icon)757 xapp_icon_chooser_dialog_run_with_icon (XAppIconChooserDialog *dialog,
758                                         gchar                 *icon)
759 {
760     XAppIconChooserDialogPrivate *priv;
761 
762     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
763 
764     gtk_widget_show_all (GTK_WIDGET (dialog));
765     gtk_entry_set_text (GTK_ENTRY (priv->search_bar), icon);
766     gtk_widget_grab_focus (priv->search_bar);
767 
768     gtk_main ();
769 
770     return priv->response;
771 }
772 
773 /**
774  * xapp_icon_chooser_dialog_run_with_category:
775  * @dialog: a #XAppIconChooserDialog
776  *
777  * Like xapp_icon_chooser_dialog_run but selects a particular category specified by @category.
778  * This is used when there is a particular category of icon that is more appropriate than the
779  * others. If the category does not exist, the first category in the list will be selected. To
780  * get a list of possible categories, use gtk_icon_theme_list_contexts ().
781  *
782  * xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
783  * xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
784  * applications which use this dialog multiple times, as it may improve performance for subsequent
785  * calls.
786  *
787  * Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
788  */
789 gint
xapp_icon_chooser_dialog_run_with_category(XAppIconChooserDialog * dialog,gchar * category)790 xapp_icon_chooser_dialog_run_with_category (XAppIconChooserDialog *dialog,
791                                             gchar                 *category)
792 {
793     XAppIconChooserDialogPrivate *priv;
794     GList                        *children;
795 
796     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
797 
798     gtk_widget_show_all (GTK_WIDGET (dialog));
799     gtk_widget_grab_focus (priv->search_bar);
800 
801     children = gtk_container_get_children (GTK_CONTAINER (priv->list_box));
802     for ( ; children; children = children->next)
803     {
804         GtkWidget    *row;
805         GtkWidget    *child;
806         const gchar  *context;
807 
808         row = children->data;
809         child = gtk_bin_get_child (GTK_BIN (row));
810         context = gtk_label_get_text (GTK_LABEL (child));
811         if (g_strcmp0 (context, category) == 0)
812         {
813             gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), GTK_LIST_BOX_ROW (row));
814             break;
815         }
816     }
817 
818     gtk_main ();
819 
820     return priv->response;
821 }
822 
823 /**
824  * xapp_icon_chooser_dialog_get_default_icon:
825  *
826  * Returns the default icon (if set).
827  *
828  * Returns: (transfer full): the default icon, or NULL if none is set
829  */
830 gchar *
xapp_icon_chooser_dialog_get_default_icon(XAppIconChooserDialog * dialog)831 xapp_icon_chooser_dialog_get_default_icon (XAppIconChooserDialog *dialog)
832 {
833     XAppIconChooserDialogPrivate *priv;
834 
835     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
836 
837     return g_strdup(priv->default_icon);
838 }
839 
840 /**
841  * xapp_icon_chooser_dialog_set_default_icon:
842  * @icon: the default icon, or NULL to unset
843  *
844  * Sets the default icon. If @icon is not NULL, a button will be shown that
845  * will reset the dialog to it's default value.
846  */
847 void
xapp_icon_chooser_dialog_set_default_icon(XAppIconChooserDialog * dialog,const gchar * icon)848 xapp_icon_chooser_dialog_set_default_icon (XAppIconChooserDialog *dialog,
849                                            const gchar           *icon)
850 {
851     XAppIconChooserDialogPrivate *priv;
852 
853     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
854 
855     priv->default_icon = g_strdup (icon);
856     if (icon == NULL)
857     {
858         gtk_widget_hide (priv->default_button);
859     }
860     else
861     {
862         gtk_widget_show (priv->default_button);
863     }
864 }
865 
866 /**
867  * xapp_icon_chooser_dialog_add_custom_category:
868  * @dialog: a #XAppIconChooserDialog
869  * @name: the name of the category as it will be displayed in the category list
870  * @icons: (transfer full) (element-type utf8): a list of icon names to add to the new category
871  *
872  * Adds a custom category to the dialog.
873  */
874 void
xapp_icon_chooser_dialog_add_custom_category(XAppIconChooserDialog * dialog,const gchar * name,GList * icons)875 xapp_icon_chooser_dialog_add_custom_category   (XAppIconChooserDialog *dialog,
876                                                 const gchar           *name,
877                                                 GList                 *icons)
878 {
879     XAppIconChooserDialogPrivate *priv;
880     IconCategoryInfo        *category_info;
881     GtkWidget               *row;
882     GtkWidget               *label;
883 
884     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
885 
886     category_info = g_new0 (IconCategoryInfo, 1);
887     category_info->name = name;
888     category_info->icons = icons;
889 
890     category_info->model = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
891     g_signal_connect (category_info->model, "row-inserted",
892                       G_CALLBACK (on_icon_store_icons_added), dialog);
893 
894     category_info->icons = g_list_sort (category_info->icons, (GCompareFunc) g_utf8_collate);
895 
896     row = gtk_list_box_row_new ();
897     label = gtk_label_new (category_info->name);
898     gtk_label_set_xalign (GTK_LABEL (label), 0.0);
899     gtk_widget_set_margin_start (GTK_WIDGET (label), 6);
900     gtk_widget_set_margin_end (GTK_WIDGET (label), 6);
901 
902     gtk_container_add (GTK_CONTAINER (row), label);
903     gtk_container_add (GTK_CONTAINER (priv->list_box), row);
904 
905     g_hash_table_insert (priv->categories, row, category_info);
906 }
907 
908 /**
909  * xapp_icon_chooser_dialog_get_icon_string:
910  * @dialog: a #XAppIconChooserDialog
911  *
912  * Gets the currently selected icon from the dialog. If allow-paths is TRUE, this function may return
913  * either an icon name or a path depending on what the user selects. Otherwise it will only return an
914  * icon name.
915  *
916  * Returns: (transfer full): the string representation of the currently selected icon or NULL
917  * if no icon is selected.
918  */
919 gchar *
xapp_icon_chooser_dialog_get_icon_string(XAppIconChooserDialog * dialog)920 xapp_icon_chooser_dialog_get_icon_string (XAppIconChooserDialog *dialog)
921 {
922     XAppIconChooserDialogPrivate *priv;
923 
924     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
925 
926     return g_strdup (priv->icon_string);
927 }
928 
929 static void
xapp_icon_chooser_dialog_close(XAppIconChooserDialog * dialog,GtkResponseType response)930 xapp_icon_chooser_dialog_close (XAppIconChooserDialog *dialog,
931                                 GtkResponseType        response)
932 {
933     XAppIconChooserDialogPrivate *priv;
934 
935     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
936 
937     priv->response = response;
938     gtk_widget_hide (GTK_WIDGET (dialog));
939 
940     gtk_main_quit ();
941 }
942 
943 static void
on_custom_button_clicked(GtkButton * button,gpointer user_data)944 on_custom_button_clicked (GtkButton *button,
945                           gpointer   user_data)
946 {
947     GtkResponseType response_id;
948 
949     response_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "response-id"));
950 
951     xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), response_id);
952 }
953 
954 /**
955  * xapp_icon_chooser_dialog_add_button:
956  * @dialog: an #XAppIconChooserDialog
957  * @button: a #GtkButton to add
958  * @packing: the #GtkPackType to specify start or end packing to the action bar
959  * @response_id: the dialog response id to return when this button is clicked.
960  *
961  * Allows a button to be added to the #GtkActionBar of the dialog with a custom
962  * response id.
963  */
964 void
xapp_icon_chooser_dialog_add_button(XAppIconChooserDialog * dialog,GtkWidget * button,GtkPackType packing,GtkResponseType response_id)965 xapp_icon_chooser_dialog_add_button (XAppIconChooserDialog *dialog,
966                                      GtkWidget             *button,
967                                      GtkPackType            packing,
968                                      GtkResponseType        response_id)
969 {
970     XAppIconChooserDialogPrivate *priv;
971 
972     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
973 
974     g_signal_connect (button,
975                       "clicked",
976                       G_CALLBACK (on_custom_button_clicked),
977                       dialog);
978 
979     /* This saves having to use a custom container for callback data. */
980     g_object_set_data (G_OBJECT (button),
981                        "response-id", GINT_TO_POINTER (response_id));
982 
983     if (packing == GTK_PACK_START)
984     {
985         gtk_action_bar_pack_start (GTK_ACTION_BAR (priv->action_area), button);
986     }
987     else
988     {
989         gtk_action_bar_pack_end (GTK_ACTION_BAR (priv->action_area), button);
990     }
991 }
992 
993 static gint
list_box_sort(GtkListBoxRow * row1,GtkListBoxRow * row2,gpointer user_data)994 list_box_sort (GtkListBoxRow *row1,
995                GtkListBoxRow *row2,
996                gpointer       user_data)
997 {
998     GtkWidget   *item;
999     const gchar *label1;
1000     const gchar *label2;
1001 
1002     item = gtk_bin_get_child (GTK_BIN (row1));
1003     label1 = gtk_label_get_text (GTK_LABEL (item));
1004 
1005     item = gtk_bin_get_child (GTK_BIN (row2));
1006     label2 = gtk_label_get_text (GTK_LABEL (item));
1007 
1008     return g_strcmp0 (label1, label2);
1009 }
1010 
1011 static gint
search_model_sort(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)1012 search_model_sort (GtkTreeModel *model,
1013                    GtkTreeIter  *a,
1014                    GtkTreeIter  *b,
1015                    gpointer      user_data)
1016 {
1017     gchar       *a_value;
1018     gchar       *b_value;
1019     gchar       *search_str;
1020     gboolean     a_starts_with;
1021     gboolean     b_starts_with;
1022     gint ret;
1023 
1024     XAppIconChooserDialogPrivate *priv = (XAppIconChooserDialogPrivate *) user_data;
1025 
1026     search_str = priv->current_text;
1027 
1028     gtk_tree_model_get (model, a, COLUMN_DISPLAY_NAME, &a_value, -1);
1029     gtk_tree_model_get (model, b, COLUMN_DISPLAY_NAME, &b_value, -1);
1030 
1031     ret = g_strcmp0 (a_value, b_value);
1032 
1033     if (search_str == NULL)
1034     {
1035     }
1036     else
1037     if (g_strcmp0 (a_value, search_str) == 0)
1038     {
1039         ret = -1;
1040     }
1041     else
1042     if (g_strcmp0 (b_value, search_str) == 0)
1043     {
1044         ret = 1;
1045     }
1046     else
1047     {
1048         a_starts_with = g_str_has_prefix (a_value, search_str);
1049         b_starts_with = g_str_has_prefix (b_value, search_str);
1050 
1051         if (a_starts_with && !b_starts_with)
1052         {
1053             ret = -1;
1054         }
1055 
1056         if (!a_starts_with && b_starts_with)
1057         {
1058             ret = 1;
1059         }
1060     }
1061 
1062     g_free (a_value);
1063     g_free (b_value);
1064 
1065     return ret;
1066 }
1067 
1068 static void
load_categories(XAppIconChooserDialog * dialog)1069 load_categories (XAppIconChooserDialog *dialog)
1070 {
1071     XAppIconChooserDialogPrivate *priv;
1072     GtkListBoxRow                *row;
1073     GtkIconTheme                 *theme;
1074     GList                        *contexts, *l;
1075     gint                          i;
1076     gint                          j;
1077 
1078     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1079 
1080     theme = gtk_icon_theme_get_default ();
1081     contexts = gtk_icon_theme_list_contexts (theme);
1082 
1083     for (i = 0; i < G_N_ELEMENTS (categories); i++)
1084     {
1085         IconCategoryDefinition  *category;
1086         IconCategoryInfo        *category_info;
1087         GtkWidget               *row;
1088         GtkWidget               *label;
1089         GList                   *context_icons;
1090 
1091         category = &categories[i];
1092 
1093         category_info = g_new0 (IconCategoryInfo, 1);
1094 
1095         category_info->name = _(category->name);
1096 
1097         for (j = 0; category->contexts[j] != NULL; j++)
1098         {
1099             GList *match;
1100 
1101             context_icons = gtk_icon_theme_list_icons (theme, category->contexts[j]);
1102             category_info->icons = g_list_concat (category_info->icons, context_icons);
1103 
1104             match = g_list_find_custom (contexts, category->contexts[j], (GCompareFunc) g_strcmp0);
1105 
1106             if (match)
1107             {
1108                 contexts = g_list_remove_link (contexts, match);
1109                 g_free (match->data);
1110                 g_list_free (match);
1111             }
1112         }
1113 
1114         /* Any contexts not consumed by categories should be added to the 'other' category */
1115         if (i == (G_N_ELEMENTS (categories) - 1) && g_list_length (contexts) > 0)
1116         {
1117             for (l = contexts; l != NULL; l = l->next)
1118             {
1119 
1120 #if DEBUG_ICON_THEME
1121                 g_message ("Adding unused category to Other category: '%s'", (gchar *) l->data);
1122 #endif
1123                 context_icons = gtk_icon_theme_list_icons (theme, (gchar *) l->data);
1124 
1125                 category_info->icons = g_list_concat (category_info->icons, context_icons);
1126             }
1127         }
1128 
1129         if (g_list_length (category_info->icons) == 0)
1130         {
1131             free_category_info (category_info);
1132 
1133             continue;
1134         }
1135 
1136         /* Add the list of icons for this category into our master search list */
1137         priv->full_icon_list = g_list_concat (priv->full_icon_list,
1138                                               g_list_copy (category_info->icons));
1139 
1140         category_info->model = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
1141         g_signal_connect (category_info->model, "row-inserted",
1142                           G_CALLBACK (on_icon_store_icons_added), dialog);
1143 
1144         category_info->icons = g_list_sort (category_info->icons, (GCompareFunc) g_utf8_collate);
1145 
1146         row = gtk_list_box_row_new ();
1147         label = gtk_label_new (category_info->name);
1148         gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1149         gtk_widget_set_margin_start (GTK_WIDGET (label), 6);
1150         gtk_widget_set_margin_end (GTK_WIDGET (label), 6);
1151 
1152         gtk_container_add (GTK_CONTAINER (row), label);
1153         gtk_container_add (GTK_CONTAINER (priv->list_box), row);
1154 
1155         g_hash_table_insert (priv->categories, row, category_info);
1156     }
1157 
1158     g_list_free_full (contexts, g_free);
1159 
1160     priv->full_icon_list = g_list_sort (priv->full_icon_list, (GCompareFunc) g_utf8_collate);
1161 
1162     row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->list_box), 0);
1163     gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), row);
1164 }
1165 
1166 static gboolean
load_next_file_search_chunk(gpointer user_data)1167 load_next_file_search_chunk (gpointer user_data)
1168 {
1169     XAppIconChooserDialogPrivate *priv;
1170     FileIconInfoLoadCallbackInfo *callback_info;
1171 
1172     callback_info = (FileIconInfoLoadCallbackInfo*) user_data;
1173     priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
1174 
1175     if (g_cancellable_is_cancelled (callback_info->cancellable))
1176     {
1177         free_file_info (callback_info);
1178 
1179         return G_SOURCE_REMOVE;
1180     }
1181 
1182     search_path (callback_info->dialog,
1183                  priv->current_text,
1184                  priv->search_icon_store);
1185 
1186     free_file_info (callback_info);
1187 
1188     return G_SOURCE_REMOVE;
1189 }
1190 
1191 static gboolean
load_next_category_chunk(gpointer user_data)1192 load_next_category_chunk (gpointer user_data)
1193 {
1194     XAppIconChooserDialogPrivate *priv;
1195     NamedIconInfoLoadCallbackInfo *callback_info;
1196 
1197     callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
1198     priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
1199 
1200     if (g_cancellable_is_cancelled (callback_info->cancellable))
1201     {
1202         free_named_info (callback_info);
1203 
1204         return G_SOURCE_REMOVE;
1205     }
1206 
1207     load_icons_for_category (callback_info->dialog,
1208                              callback_info->category_info,
1209                              priv->icon_size);
1210 
1211     free_named_info (callback_info);
1212 
1213     return G_SOURCE_REMOVE;
1214 }
1215 
1216 static gboolean
load_next_name_search_chunk(gpointer user_data)1217 load_next_name_search_chunk (gpointer user_data)
1218 {
1219     XAppIconChooserDialogPrivate *priv;
1220     NamedIconInfoLoadCallbackInfo *callback_info;
1221 
1222     callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
1223     priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
1224 
1225     if (g_cancellable_is_cancelled (callback_info->cancellable))
1226     {
1227         free_named_info (callback_info);
1228 
1229         return G_SOURCE_REMOVE;
1230     }
1231 
1232     search_icon_name (callback_info->dialog,
1233                       priv->current_text,
1234                       priv->search_icon_store);
1235 
1236     free_named_info (callback_info);
1237 
1238     return G_SOURCE_REMOVE;
1239 }
1240 
1241 static void
finish_pixbuf_load_from_file(GObject * stream,GAsyncResult * res,gpointer * user_data)1242 finish_pixbuf_load_from_file (GObject      *stream,
1243                               GAsyncResult *res,
1244                               gpointer     *user_data)
1245 {
1246     FileIconInfoLoadCallbackInfo *callback_info;
1247     GdkPixbuf                *pixbuf;
1248     GError                   *error = NULL;
1249 
1250     callback_info = (FileIconInfoLoadCallbackInfo *) user_data;
1251 
1252     pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
1253 
1254     g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
1255     g_object_unref (stream);
1256 
1257     if (g_cancellable_is_cancelled (callback_info->cancellable))
1258     {
1259         g_clear_object (&pixbuf);
1260         free_file_info (callback_info);
1261 
1262         return;
1263     }
1264 
1265     if (pixbuf == NULL)
1266     {
1267         if (error && (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED))
1268         {
1269             g_message ("%s\n", error->message);
1270         }
1271 
1272         g_clear_error (&error);
1273     }
1274 
1275     if (pixbuf)
1276     {
1277         GtkTreeIter iter;
1278         cairo_surface_t *surface;
1279 
1280         surface = gdk_cairo_surface_create_from_pixbuf (pixbuf,
1281                                                         0,
1282                                                         gtk_widget_get_window (GTK_WIDGET (callback_info->dialog)));
1283 
1284         gtk_list_store_append (callback_info->model, &iter);
1285         gtk_list_store_set (callback_info->model, &iter,
1286                             COLUMN_DISPLAY_NAME, callback_info->short_name,
1287                             COLUMN_FULL_NAME, callback_info->long_name,
1288                             COLUMN_SURFACE, surface,
1289                             -1);
1290 
1291         cairo_surface_destroy (surface);
1292         g_object_unref (pixbuf);
1293     }
1294 
1295     if (callback_info->chunk_end)
1296     {
1297         g_idle_add ((GSourceFunc) load_next_file_search_chunk, callback_info);
1298     }
1299     else
1300     {
1301         free_file_info (callback_info);
1302     }
1303 
1304 }
1305 
1306 static void
add_named_entry(NamedIconInfoLoadCallbackInfo * callback_info,cairo_surface_t * surface)1307 add_named_entry (NamedIconInfoLoadCallbackInfo *callback_info,
1308                  cairo_surface_t               *surface)
1309 {
1310     GtkTreeIter iter;
1311 
1312     gtk_list_store_append (callback_info->model, &iter);
1313     gtk_list_store_set (callback_info->model, &iter,
1314                         COLUMN_DISPLAY_NAME, callback_info->name,
1315                         COLUMN_FULL_NAME, callback_info->name,
1316                         COLUMN_SURFACE, surface,
1317                         -1);
1318 }
1319 
1320 static gboolean
add_named_entry_with_existing_surface(gpointer user_data)1321 add_named_entry_with_existing_surface (gpointer user_data)
1322 {
1323     NamedIconInfoLoadCallbackInfo *callback_info;
1324 
1325     callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
1326 
1327     if (g_cancellable_is_cancelled (callback_info->cancellable))
1328     {
1329         free_named_info (callback_info);
1330 
1331         return G_SOURCE_REMOVE;
1332     }
1333 
1334     /* Category results have a category_info attached. */
1335     if (callback_info->category_info)
1336     {
1337         add_named_entry (callback_info, callback_info->surface);
1338 
1339         if (callback_info->chunk_end)
1340         {
1341             g_idle_add ((GSourceFunc) load_next_category_chunk, callback_info);
1342 
1343             return G_SOURCE_REMOVE;
1344         }
1345     }
1346     /* Otherwise, it's a search result set */
1347     else
1348     {
1349         add_named_entry (callback_info, callback_info->surface);
1350 
1351         if (callback_info->chunk_end)
1352         {
1353             g_idle_add ((GSourceFunc) load_next_name_search_chunk, callback_info);
1354 
1355             return G_SOURCE_REMOVE;
1356         }
1357     }
1358 
1359     free_named_info (callback_info);
1360 
1361     return G_SOURCE_REMOVE;
1362 }
1363 
1364 static void
finish_pixbuf_load_from_name(GObject * info,GAsyncResult * res,gpointer * user_data)1365 finish_pixbuf_load_from_name (GObject      *info,
1366                               GAsyncResult *res,
1367                               gpointer     *user_data)
1368 {
1369     XAppIconChooserDialogPrivate *priv;
1370     NamedIconInfoLoadCallbackInfo *callback_info;
1371     GdkPixbuf                *pixbuf;
1372     cairo_surface_t          *surface;
1373     GError                   *error = NULL;
1374 
1375     callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
1376     priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
1377 
1378     pixbuf = gtk_icon_info_load_symbolic_for_context_finish (GTK_ICON_INFO (info), res, NULL, &error);
1379     g_object_unref (info);
1380 
1381     if (g_cancellable_is_cancelled (callback_info->cancellable))
1382     {
1383         g_clear_object (&pixbuf);
1384 
1385         free_named_info (callback_info);
1386 
1387         return;
1388     }
1389 
1390     if (pixbuf == NULL)
1391     {
1392         if (error && (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED))
1393         {
1394             g_message ("%s\n", error->message);
1395         }
1396 
1397         free_named_info (callback_info);
1398 
1399         g_clear_error (&error);
1400 
1401         return;
1402     }
1403 
1404     surface = gdk_cairo_surface_create_from_pixbuf (pixbuf,
1405                                                     0,
1406                                                     gtk_widget_get_window (GTK_WIDGET (callback_info->dialog)));
1407     g_object_unref (pixbuf);
1408 
1409     /* Hash table 'takes' reference, we don't have to free surface.
1410        callback_info->name is already owned by priv->full_icon_list so
1411        it needs to be copied */
1412     g_hash_table_insert (priv->surfaces_by_name,
1413                          g_strdup (callback_info->name),
1414                          (gpointer) surface);
1415 
1416     /* If there's a category_info, this is a category selection. */
1417     if (callback_info->category_info)
1418     {
1419         add_named_entry (callback_info, surface);
1420 
1421         if (callback_info->chunk_end)
1422         {
1423             g_idle_add ((GSourceFunc) load_next_category_chunk, callback_info);
1424 
1425             return;
1426         }
1427     }
1428     /* Otherwise, it's a search result set */
1429     else
1430     {
1431         add_named_entry (callback_info, surface);
1432 
1433         if (callback_info->chunk_end)
1434         {
1435             g_idle_add ((GSourceFunc) load_next_name_search_chunk, callback_info);
1436 
1437             return;
1438         }
1439     }
1440 
1441     free_named_info (callback_info);
1442 }
1443 
1444 #define CATEGORY_CHUNK_SIZE 500
1445 
1446 static void
load_icons_for_category(XAppIconChooserDialog * dialog,IconCategoryInfo * category_info,guint icon_size)1447 load_icons_for_category (XAppIconChooserDialog *dialog,
1448                          IconCategoryInfo      *category_info,
1449                          guint                  icon_size)
1450 {
1451     XAppIconChooserDialogPrivate *priv;
1452     GtkIconTheme *theme;
1453     gint chunk_count;
1454     gint scale;
1455 
1456     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1457     theme = gtk_icon_theme_get_default ();
1458 
1459     scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
1460 
1461     for (chunk_count = 0; chunk_count < CATEGORY_CHUNK_SIZE; chunk_count++)
1462     {
1463         if (category_info->iter == NULL)
1464         {
1465             category_info->iter = category_info->icons;
1466         }
1467         else
1468         {
1469             category_info->iter = category_info->iter->next;
1470         }
1471 
1472         if (category_info->iter)
1473         {
1474             NamedIconInfoLoadCallbackInfo *callback_info;
1475             const gchar *name = category_info->iter->data;
1476             cairo_surface_t *surface;
1477 
1478             callback_info = g_new0 (NamedIconInfoLoadCallbackInfo, 1);
1479             callback_info->dialog = dialog;
1480             callback_info->category_info = category_info;
1481             callback_info->model = category_info->model;
1482             callback_info->cancellable = g_object_ref (priv->cancellable);
1483             callback_info->name = name;
1484             callback_info->chunk_end = (chunk_count == CATEGORY_CHUNK_SIZE - 1);
1485 
1486             surface = g_hash_table_lookup (priv->surfaces_by_name, name);
1487 
1488             if (surface != NULL)
1489             {
1490                 callback_info->surface = cairo_surface_reference (surface);
1491                 g_idle_add ((GSourceFunc) add_named_entry_with_existing_surface, callback_info);
1492             }
1493             else
1494             {
1495                 GtkIconInfo *info;
1496                 GtkStyleContext *context;
1497 
1498                 info = gtk_icon_theme_lookup_icon_for_scale (theme, name, icon_size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
1499                 if (info == NULL)
1500                 {
1501                     // this shouldn't be the case most of the time, but if a custom category is defined it's possible
1502                     // the icon doesn't exist. In that case it's best to just skip over it since trying to load it will
1503                     // lead to a segfault.
1504                     continue;
1505                 }
1506 
1507                 context = gtk_widget_get_style_context (priv->icon_view);
1508                 gtk_icon_info_load_symbolic_for_context_async (info, context, NULL, (GAsyncReadyCallback) (finish_pixbuf_load_from_name), callback_info);
1509             }
1510         }
1511         else
1512         {
1513             gtk_widget_hide (priv->loading_bar);
1514             break; // Quit the count early, we're out of data
1515         }
1516     }
1517 }
1518 
1519 static void
on_category_selected(GtkListBox * list_box,XAppIconChooserDialog * dialog)1520 on_category_selected (GtkListBox            *list_box,
1521                       XAppIconChooserDialog *dialog)
1522 {
1523     XAppIconChooserDialogPrivate *priv;
1524     GList                        *selection;
1525     GtkWidget                    *selected;
1526     IconCategoryInfo             *category_info;
1527 
1528     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1529 
1530     clear_search_state (dialog);
1531 
1532     selection = gtk_list_box_get_selected_rows (GTK_LIST_BOX (priv->list_box));
1533 
1534     if (!selection)
1535     {
1536         return;
1537     }
1538 
1539     gtk_widget_show (priv->loading_bar);
1540 
1541     g_signal_handler_block (priv->search_bar, priv->search_changed_id);
1542     gtk_entry_set_text (GTK_ENTRY (priv->search_bar), "");
1543     g_signal_handler_unblock (priv->search_bar, priv->search_changed_id);
1544 
1545     selected = selection->data;
1546     category_info = g_hash_table_lookup (priv->categories, selected);
1547 
1548     priv->cancellable = g_cancellable_new ();
1549 
1550 #if DEBUG_REFS
1551     g_object_weak_ref (G_OBJECT (priv->cancellable), (GWeakNotify) on_cancellable_finalize, priv);
1552 #endif
1553 
1554     priv->current_category = category_info;
1555 
1556     gtk_list_store_clear (GTK_LIST_STORE (category_info->model));
1557     gtk_icon_view_set_model (GTK_ICON_VIEW (priv->icon_view), GTK_TREE_MODEL (category_info->model));
1558 
1559     load_icons_for_category (dialog,
1560                              category_info,
1561                              priv->icon_size);
1562 
1563     gtk_adjustment_set_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->icon_view)), 0.0);
1564 
1565     g_list_free (selection);
1566 }
1567 
1568 #define SEARCH_CHUNK_SIZE 2
1569 
1570 static void
search_path(XAppIconChooserDialog * dialog,const gchar * path_string,GtkListStore * icon_store)1571 search_path (XAppIconChooserDialog *dialog,
1572              const gchar           *path_string,
1573              GtkListStore          *icon_store)
1574 {
1575     XAppIconChooserDialogPrivate *priv;
1576     gchar            *search_str = NULL;
1577     GFile            *dir;
1578     GFileInfo        *child_info = NULL;;
1579     gint chunk_count;
1580 
1581     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1582 
1583     if (g_file_test (path_string, G_FILE_TEST_IS_DIR))
1584     {
1585         dir = g_file_new_for_path (path_string);
1586     }
1587     else
1588     {
1589         GFile *file;
1590 
1591         file = g_file_new_for_path (path_string);
1592         dir = g_file_get_parent (file);
1593         search_str = g_file_get_basename (file);
1594         g_object_unref (file);
1595     }
1596 
1597     if (!g_file_query_exists (dir, NULL) ||
1598         g_file_query_file_type (dir, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
1599     {
1600         g_free (search_str);
1601         g_object_unref (dir);
1602         return;
1603     }
1604 
1605     if (!priv->search_file_enumerator)
1606     {
1607         priv->search_file_enumerator  = g_file_enumerate_children (dir,
1608                                                                    G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1609                                                                    G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
1610                                                                    G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1611                                                                    G_FILE_ATTRIBUTE_STANDARD_NAME,
1612                                                                    G_FILE_QUERY_INFO_NONE, NULL, NULL);
1613 
1614         g_object_add_toggle_ref (G_OBJECT (priv->search_file_enumerator),
1615                                  (GToggleNotify) on_enumerator_toggle_ref_called,
1616                                  dialog);
1617 
1618 #if DEBUG_REFS
1619         g_object_weak_ref (G_OBJECT (priv->search_file_enumerator), (GWeakNotify) on_enumerator_finalize, dialog);
1620 #endif
1621     }
1622 
1623     chunk_count = 0;
1624 
1625     while (chunk_count < SEARCH_CHUNK_SIZE)
1626     {
1627         const gchar  *child_name;
1628         gchar        *child_path;
1629         GFile        *child;
1630         GError       *error = NULL;
1631 
1632         g_clear_object (&child_info);
1633         child_info = g_file_enumerator_next_file (priv->search_file_enumerator, NULL, NULL);
1634 
1635         if (!child_info)
1636         {
1637             break;
1638         }
1639 
1640         child_name = g_file_info_get_name (child_info);
1641         child = g_file_enumerator_get_child (priv->search_file_enumerator, child_info);
1642         child_path = g_file_get_path (child);
1643 
1644         if (search_str == NULL || g_str_has_prefix (child_name, search_str))
1645         {
1646             priv->current_category = NULL;
1647 
1648             gchar *content_type;
1649             gboolean uncertain;
1650 
1651             content_type = g_content_type_guess (child_name, NULL, 0, &uncertain);
1652 
1653             if (content_type && g_str_has_prefix (content_type, "image") && !uncertain)
1654             {
1655                 GFileInputStream         *stream;
1656 
1657                 stream = g_file_read (child, NULL, &error);
1658 
1659                 if (stream != NULL)
1660                 {
1661                     FileIconInfoLoadCallbackInfo *callback_info;
1662                     gint scale;
1663 
1664                     callback_info = g_new0 (FileIconInfoLoadCallbackInfo, 1);
1665                     callback_info->dialog = dialog;
1666                     callback_info->model = icon_store;
1667                     callback_info->cancellable = g_object_ref (priv->cancellable);
1668                     callback_info->enumerator = g_object_ref (priv->search_file_enumerator);
1669                     callback_info->short_name = g_strdup (child_name);
1670                     callback_info->long_name = g_strdup (child_path);
1671                     callback_info->chunk_end = (chunk_count == SEARCH_CHUNK_SIZE - 1);
1672 
1673 
1674                     scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
1675 
1676                     gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream),
1677                                                                -1,
1678                                                                priv->icon_size * scale,
1679                                                                TRUE,
1680                                                                NULL,
1681                                                                (GAsyncReadyCallback) finish_pixbuf_load_from_file,
1682                                                                callback_info);
1683 
1684                     chunk_count ++;
1685                 }
1686                 else
1687                 {
1688                     if (error)
1689                     {
1690                         g_message ("%s\n", error->message);
1691                         g_error_free (error);
1692                     }
1693                 }
1694             }
1695 
1696             g_free (content_type);
1697         }
1698 
1699         g_free (child_path);
1700         g_object_unref (child);
1701     }
1702 
1703     if (!child_info)
1704     {
1705         if (priv->search_file_enumerator)
1706         {
1707             g_object_unref (priv->search_file_enumerator);
1708         }
1709 
1710         gtk_widget_hide (priv->loading_bar);
1711     }
1712     else
1713     {
1714         g_clear_object (&child_info);
1715     }
1716 
1717     g_free (search_str);
1718     g_object_unref (dir);
1719 }
1720 
1721 static void
search_icon_name(XAppIconChooserDialog * dialog,const gchar * name_string,GtkListStore * icon_store)1722 search_icon_name (XAppIconChooserDialog *dialog,
1723                   const gchar           *name_string,
1724                   GtkListStore          *icon_store)
1725 {
1726     XAppIconChooserDialogPrivate *priv;
1727     GtkIconTheme                 *theme;
1728     GList                        *icons;
1729     gint chunk_count;
1730     gint scale;
1731 
1732     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1733 
1734     theme = gtk_icon_theme_get_default ();
1735     scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
1736 
1737     icons = priv->full_icon_list;
1738 
1739     chunk_count = 0;
1740 
1741     while (chunk_count < SEARCH_CHUNK_SIZE)
1742     {
1743         if (priv->search_iter == NULL)
1744         {
1745             priv->search_iter = icons;
1746         }
1747         else
1748         {
1749             priv->search_iter = priv->search_iter->next;
1750         }
1751 
1752         if (priv->search_iter)
1753         {
1754             priv->current_category = NULL;
1755 
1756             if (g_strrstr (priv->search_iter->data, name_string))
1757             {
1758                 NamedIconInfoLoadCallbackInfo *callback_info;
1759                 cairo_surface_t *surface;
1760                 const gchar *name = priv->search_iter->data;
1761 
1762                 callback_info = g_new0 (NamedIconInfoLoadCallbackInfo, 1);
1763                 callback_info->dialog = dialog;
1764                 callback_info->model = priv->search_icon_store;
1765                 callback_info->cancellable = g_object_ref (priv->cancellable);
1766                 callback_info->category_info = NULL;
1767                 callback_info->name = name;
1768                 callback_info->chunk_end = (chunk_count == SEARCH_CHUNK_SIZE - 1);
1769 
1770                 surface = g_hash_table_lookup (priv->surfaces_by_name, name);
1771 
1772                 if (surface != NULL)
1773                 {
1774                     callback_info->surface = cairo_surface_reference (surface);
1775                     g_idle_add ((GSourceFunc) add_named_entry_with_existing_surface, callback_info);
1776                 }
1777                 else
1778                 {
1779                     GtkIconInfo *info;
1780                     GtkStyleContext *context;
1781 
1782                     info = gtk_icon_theme_lookup_icon_for_scale (theme, name, priv->icon_size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
1783                     context = gtk_widget_get_style_context (priv->icon_view);
1784                     gtk_icon_info_load_symbolic_for_context_async (info, context, NULL, (GAsyncReadyCallback) (finish_pixbuf_load_from_name), callback_info);
1785                 }
1786 
1787                 chunk_count++;
1788             }
1789         }
1790         else
1791         {
1792             gtk_widget_hide (priv->loading_bar);
1793 
1794             break; // Quit the count early, we're out of data
1795         }
1796     }
1797 }
1798 
1799 static void
on_search_text_changed(GtkSearchEntry * entry,XAppIconChooserDialog * dialog)1800 on_search_text_changed (GtkSearchEntry        *entry,
1801                         XAppIconChooserDialog *dialog)
1802 {
1803     XAppIconChooserDialogPrivate *priv;
1804     const gchar                  *search_text;
1805 
1806     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1807 
1808     /* The search cancellable is carried in search callback data.  If the
1809      * text changes, we cancel it here, our load callbacks will check the
1810      * state, and react appropriately (like not adding results to the model). */
1811     clear_search_state (dialog);
1812 
1813     gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), NULL);
1814 
1815     priv->cancellable = g_cancellable_new ();
1816 
1817 #if DEBUG_REFS
1818     g_object_weak_ref (G_OBJECT (priv->cancellable), (GWeakNotify) on_cancellable_finalize, dialog);
1819 #endif
1820 
1821     search_text = gtk_entry_get_text (GTK_ENTRY (entry));
1822 
1823     if (g_strcmp0 (search_text, "") == 0)
1824     {
1825         g_clear_pointer (&priv->current_text, g_free);
1826         g_clear_pointer (&priv->icon_string, g_free);
1827 
1828         gtk_widget_hide (priv->loading_bar);
1829 
1830         gtk_list_store_clear (GTK_LIST_STORE (priv->search_icon_store));
1831     }
1832     else
1833     if (strlen (search_text) < 2)
1834     {
1835         return;
1836     }
1837     else
1838     {
1839         g_free (priv->current_text);
1840         priv->current_text = g_strdup (search_text);
1841 
1842         gtk_widget_show (priv->loading_bar);
1843 
1844         gtk_list_store_clear (GTK_LIST_STORE (priv->search_icon_store));
1845         gtk_icon_view_set_model (GTK_ICON_VIEW (priv->icon_view), GTK_TREE_MODEL (priv->search_icon_store));
1846         if (g_strrstr (search_text, "/"))
1847         {
1848             if (priv->allow_paths)
1849             {
1850                 search_path (dialog, search_text, priv->search_icon_store);
1851             }
1852         }
1853         else
1854         {
1855             search_icon_name (dialog, search_text, priv->search_icon_store);
1856         }
1857     }
1858 }
1859 
1860 static void
on_icon_view_selection_changed(GtkIconView * icon_view,gpointer user_data)1861 on_icon_view_selection_changed (GtkIconView *icon_view,
1862                                 gpointer     user_data)
1863 {
1864     XAppIconChooserDialogPrivate *priv;
1865     GList                        *selected_items;
1866     gchar                        *icon_string = NULL;
1867 
1868     priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
1869 
1870     selected_items = gtk_icon_view_get_selected_items (icon_view);
1871     if (selected_items == NULL)
1872     {
1873         gtk_widget_set_sensitive (GTK_WIDGET (priv->select_button), FALSE);
1874     }
1875     else
1876     {
1877         GtkTreePath  *tree_path;
1878         GtkTreeModel *model;
1879         GtkTreeIter   iter;
1880 
1881         gtk_widget_set_sensitive (GTK_WIDGET (priv->select_button), TRUE);
1882 
1883         tree_path = selected_items->data;
1884         model = gtk_icon_view_get_model (icon_view);
1885         gtk_tree_model_get_iter (model, &iter, tree_path);
1886         gtk_tree_model_get (model, &iter, COLUMN_FULL_NAME, &icon_string, -1);
1887     }
1888 
1889     if (priv->icon_string != NULL)
1890     {
1891         g_free (priv->icon_string);
1892     }
1893 
1894     priv->icon_string = icon_string;
1895 
1896     g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free);
1897 }
1898 
1899 static void
on_icon_store_icons_added(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)1900 on_icon_store_icons_added (GtkTreeModel *tree_model,
1901                            GtkTreePath  *path,
1902                            GtkTreeIter  *iter,
1903                            gpointer      user_data)
1904 {
1905     XAppIconChooserDialogPrivate *priv;
1906     GtkTreePath                  *new_path;
1907 
1908     priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
1909 
1910     if (tree_model != gtk_icon_view_get_model (GTK_ICON_VIEW (priv->icon_view))) {
1911         return;
1912     }
1913 
1914     new_path = gtk_tree_path_new_first ();
1915     gtk_icon_view_select_path (GTK_ICON_VIEW (priv->icon_view), new_path);
1916 
1917     gtk_tree_path_free (new_path);
1918 }
1919 
1920 static void
on_browse_button_clicked(GtkButton * button,gpointer user_data)1921 on_browse_button_clicked (GtkButton *button,
1922                           gpointer   user_data)
1923 {
1924     XAppIconChooserDialog        *dialog = user_data;
1925     XAppIconChooserDialogPrivate *priv;
1926     GtkWidget                    *file_dialog;
1927     const gchar                  *search_text;
1928     GtkFileFilter                *file_filter;
1929     gint                          response;
1930 
1931     priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
1932 
1933     file_dialog = gtk_file_chooser_dialog_new (_("Select image file"),
1934                                                GTK_WINDOW (dialog),
1935                                                GTK_FILE_CHOOSER_ACTION_OPEN,
1936                                                _("Cancel"),
1937                                                GTK_RESPONSE_CANCEL,
1938                                                _("Open"),
1939                                                GTK_RESPONSE_ACCEPT,
1940                                                NULL);
1941 
1942     search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_bar));
1943     if (g_strrstr (search_text, "/"))
1944     {
1945         gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (file_dialog), search_text);
1946     }
1947     else
1948     {
1949         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_dialog), "/usr/share/icons/");
1950     }
1951 
1952     file_filter = gtk_file_filter_new ();
1953     gtk_file_filter_set_name (file_filter, _("Image"));
1954     gtk_file_filter_add_pixbuf_formats (file_filter);
1955     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
1956 
1957     response = gtk_dialog_run (GTK_DIALOG (file_dialog));
1958     if (response == GTK_RESPONSE_ACCEPT)
1959     {
1960         gchar *filename;
1961 
1962         filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_dialog));
1963         gtk_entry_set_text (GTK_ENTRY (priv->search_bar), filename);
1964         g_free (filename);
1965     }
1966 
1967     gtk_widget_destroy (file_dialog);
1968 }
1969 
1970 static void
on_select_button_clicked(GtkButton * button,gpointer user_data)1971 on_select_button_clicked (GtkButton *button,
1972                           gpointer   user_data)
1973 {
1974     xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
1975 }
1976 
1977 static void
on_cancel_button_clicked(GtkButton * button,gpointer user_data)1978 on_cancel_button_clicked (GtkButton *button,
1979                           gpointer   user_data)
1980 {
1981     xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_CANCEL);
1982 }
1983 
1984 static gboolean
on_delete_event(GtkWidget * widget,GdkEventAny * event)1985 on_delete_event (GtkWidget   *widget,
1986                  GdkEventAny *event)
1987 {
1988     xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (widget), GTK_RESPONSE_CANCEL);
1989 
1990     return TRUE;
1991 }
1992 
1993 static void
on_icon_view_item_activated(GtkIconView * iconview,GtkTreePath * path,gpointer user_data)1994 on_icon_view_item_activated (GtkIconView *iconview,
1995                              GtkTreePath *path,
1996                              gpointer     user_data)
1997 {
1998     xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
1999 }
2000 
2001 static gboolean
on_search_bar_key_pressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)2002 on_search_bar_key_pressed (GtkWidget *widget,
2003                            GdkEvent  *event,
2004                            gpointer   user_data)
2005 {
2006     XAppIconChooserDialogPrivate *priv;
2007     guint                         keyval;
2008 
2009     priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
2010 
2011     gdk_event_get_keyval (event, &keyval);
2012     switch (keyval)
2013     {
2014         case GDK_KEY_Escape:
2015             xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_CANCEL);
2016             return TRUE;
2017         case GDK_KEY_Return:
2018         case GDK_KEY_KP_Enter:
2019             if (priv->icon_string != NULL)
2020             {
2021                 xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
2022             }
2023             return TRUE;
2024     }
2025 
2026     return FALSE;
2027 }
2028 
2029 static void
on_default_button_clicked(GtkButton * button,gpointer user_data)2030 on_default_button_clicked (GtkButton *button,
2031                            gpointer   user_data)
2032 {
2033     XAppIconChooserDialogPrivate *priv;
2034 
2035     priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
2036 
2037     gtk_entry_set_text (GTK_ENTRY (priv->search_bar), priv->default_icon);
2038 }
2039