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