#include <config.h>
#include <gdk/gdk.h>
#include <math.h>
#include <cairo-gobject.h>
#include "xapp-enums.h"
#include "xapp-icon-chooser-dialog.h"
#include "xapp-stack-sidebar.h"
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#define DEBUG_REFS 0
#define DEBUG_ICON_THEME 0
/**
* SECTION:xapp-icon-chooser-dialog
* @Short_description: A dialog for selecting an icon
* @Title: XAppIconChooserDialog
*
* The XAppIconChooserDialog creates a dialog so that
* the user can select an icon. It provides the ability
* to browse by category, search by icon name, or select
* from a specific file.
*/
typedef struct
{
const gchar *name; /* This is a translation which doesn't get freed */
GList *icons;
GList *iter;
GtkListStore *model;
} IconCategoryInfo;
typedef struct
{
GtkResponseType response;
XAppIconSize icon_size;
GtkListStore *search_icon_store;
GFileEnumerator *search_file_enumerator;
GCancellable *cancellable;
GList *full_icon_list;
GList *search_iter;
GHashTable *categories;
GHashTable *surfaces_by_name;
GtkWidget *search_bar;
GtkWidget *icon_view;
GtkWidget *list_box;
GtkWidget *default_button;
GtkWidget *select_button;
GtkWidget *browse_button;
GtkWidget *action_area;
GtkWidget *loading_bar;
GtkCellArea *ca_box;
gchar *icon_string;
gchar *current_text;
gulong search_changed_id;
gboolean allow_paths;
gchar *default_icon;
IconCategoryInfo *current_category;
} XAppIconChooserDialogPrivate;
struct _XAppIconChooserDialog
{
XAppGtkWindow parent_instance;
};
typedef struct
{
XAppIconChooserDialog *dialog;
GtkListStore *model;
IconCategoryInfo *category_info;
GCancellable *cancellable;
cairo_surface_t *surface;
const gchar *name;
gboolean chunk_end;
} NamedIconInfoLoadCallbackInfo;
typedef struct
{
XAppIconChooserDialog *dialog;
GtkListStore *model;
GCancellable *cancellable;
GFileEnumerator *enumerator;
gchar *short_name;
gchar *long_name;
gboolean chunk_end;
} FileIconInfoLoadCallbackInfo;
typedef struct
{
const gchar *name;
const gchar *contexts[5];
} IconCategoryDefinition;
static IconCategoryDefinition categories[] = {
// Category name context names
{
N_("Actions"), { "Actions", NULL }
},
{
N_("Applications"), { "Applications", "Apps", NULL }
},
{
N_("Categories"), { "Categories", NULL }
},
{
N_("Devices"), { "Devices", NULL }
},
{
N_("Emblems"), { "Emblems", NULL }
},
{
N_("Emoji"), { "Emotes", NULL }
},
{
N_("Mime types"), { "MimeTypes", "Mimetypes", NULL }
},
{
N_("Places"), { "Places", NULL }
},
{
N_("Status"), { "Status", "Notifications", NULL }
},
{
N_("Other"), { "Panel", NULL }
}
};
enum
{
CLOSE,
SELECT,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_ICON_SIZE,
PROP_ALLOW_PATHS,
PROP_DEFAULT_ICON,
N_PROPERTIES
};
enum
{
COLUMN_DISPLAY_NAME,
COLUMN_FULL_NAME,
COLUMN_SURFACE,
};
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
static guint signals[LAST_SIGNAL] = {0, };
G_DEFINE_TYPE_WITH_PRIVATE (XAppIconChooserDialog, xapp_icon_chooser_dialog, XAPP_TYPE_GTK_WINDOW)
static void on_category_selected (GtkListBox *list_box,
XAppIconChooserDialog *dialog);
static void on_search_text_changed (GtkSearchEntry *entry,
XAppIconChooserDialog *dialog);
static void on_icon_view_selection_changed (GtkIconView *icon_view,
gpointer user_data);
static void on_icon_store_icons_added (GtkTreeModel *tree_model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer user_data);
static void on_browse_button_clicked (GtkButton *button,
gpointer user_data);
static void on_select_button_clicked (GtkButton *button,
gpointer user_data);
static void on_cancel_button_clicked (GtkButton *button,
gpointer user_data);
static void on_default_button_clicked (GtkButton *button,
gpointer user_data);
static gboolean on_search_bar_key_pressed (GtkWidget *widget,
GdkEvent *event,
gpointer user_data);
static gboolean on_delete_event (GtkWidget *widget,
GdkEventAny *event);
static void on_icon_view_item_activated (GtkIconView *iconview,
GtkTreePath *path,
gpointer user_data);
static void load_categories (XAppIconChooserDialog *dialog);
static void load_icons_for_category (XAppIconChooserDialog *dialog,
IconCategoryInfo *category_info,
guint icon_size);
static void search_path (XAppIconChooserDialog *dialog,
const gchar *path_string,
GtkListStore *icon_store);
static void search_icon_name (XAppIconChooserDialog *dialog,
const gchar *name_string,
GtkListStore *icon_store);
static gint list_box_sort (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data);
static gint search_model_sort (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer user_data);
static void
free_category_info (IconCategoryInfo *category_info)
{
g_list_free_full (category_info->icons, g_free);
g_clear_object (&category_info->model);
g_free (category_info);
}
static void
free_file_info (FileIconInfoLoadCallbackInfo *file_info)
{
g_object_unref (file_info->cancellable);
g_object_unref (file_info->enumerator);
g_free (file_info->short_name);
g_free (file_info->long_name);
g_free (file_info);
}
static void
free_named_info (NamedIconInfoLoadCallbackInfo *named_info)
{
g_object_unref (named_info->cancellable);
g_clear_pointer (&named_info->surface, cairo_surface_destroy);
g_free (named_info);
}
#if DEBUG_REFS
static void
on_cancellable_finalize (gpointer data,
GObject *object)
{
g_printerr ("Cancellable Finalize: %p\n", object);
}
static void
on_enumerator_finalize (gpointer data,
GObject *object)
{
g_printerr ("Enumerator Finalize: %p\n", object);
}
#endif
static void
on_enumerator_toggle_ref_called (gpointer data,
GObject *object,
gboolean is_last_ref)
{
XAppIconChooserDialog *dialog;
XAppIconChooserDialogPrivate *priv;
dialog = XAPP_ICON_CHOOSER_DIALOG (data);
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
if (is_last_ref)
{
g_object_remove_toggle_ref (object,
(GToggleNotify) on_enumerator_toggle_ref_called,
dialog);
priv->search_file_enumerator = NULL;
}
}
static void
clear_search_state (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
g_cancellable_cancel (priv->cancellable);
g_clear_object (&priv->cancellable);
g_clear_object (&priv->search_file_enumerator);
gtk_widget_hide (priv->loading_bar);
priv->search_iter = NULL;
}
void
xapp_icon_chooser_dialog_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
XAppIconChooserDialog *dialog;
XAppIconChooserDialogPrivate *priv;
dialog = XAPP_ICON_CHOOSER_DIALOG (object);
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
switch (prop_id)
{
case PROP_ICON_SIZE:
g_value_set_enum (value, priv->icon_size);
break;
case PROP_ALLOW_PATHS:
g_value_set_boolean (value, priv->allow_paths);
break;
case PROP_DEFAULT_ICON:
g_value_set_string (value, xapp_icon_chooser_dialog_get_default_icon(dialog));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
void
xapp_icon_chooser_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
XAppIconChooserDialog *dialog;
XAppIconChooserDialogPrivate *priv;
dialog = XAPP_ICON_CHOOSER_DIALOG (object);
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
switch (prop_id)
{
case PROP_ICON_SIZE:
priv->icon_size = g_value_get_enum (value);
break;
case PROP_ALLOW_PATHS:
priv->allow_paths = g_value_get_boolean (value);
if (priv->allow_paths)
{
gtk_widget_show (priv->browse_button);
gtk_widget_set_no_show_all (priv->browse_button, FALSE);
}
else
{
gtk_widget_hide (priv->browse_button);
gtk_widget_set_no_show_all (priv->browse_button, TRUE);
}
break;
case PROP_DEFAULT_ICON:
xapp_icon_chooser_dialog_set_default_icon (dialog, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
xapp_icon_chooser_dialog_dispose (GObject *object)
{
XAppIconChooserDialog *dialog;
XAppIconChooserDialogPrivate *priv;
dialog = XAPP_ICON_CHOOSER_DIALOG (object);
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
if (priv->categories != NULL)
{
g_hash_table_destroy (priv->categories);
priv->categories = NULL;
}
if (priv->surfaces_by_name != NULL)
{
g_hash_table_destroy (priv->surfaces_by_name);
priv->surfaces_by_name = NULL;
}
g_clear_pointer (&priv->icon_string, g_free);
g_clear_pointer (&priv->default_icon, g_free);
g_clear_pointer (&priv->current_text, g_free);
g_clear_object (&priv->cancellable);
G_OBJECT_CLASS (xapp_icon_chooser_dialog_parent_class)->dispose (object);
}
static void
xapp_icon_chooser_dialog_init (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
GtkWidget *main_box;
GtkWidget *secondary_box;
GtkWidget *toolbar;
GtkWidget *overlay;
GtkWidget *spinner;
GtkWidget *spinner_label;
GtkWidget *loading_bar_box;
GtkToolItem *tool_item;
GtkWidget *toolbar_box;
GtkWidget *right_box;
GtkStyleContext *style;
GtkSizeGroup *button_size_group;
GtkWidget *cancel_button;
GtkWidget *button_area;
GtkWidget *scrolled_window;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
priv->icon_size = XAPP_ICON_SIZE_32;
priv->categories = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) free_category_info);
/* surfaces_by_name will save surfaces generated by icon name, so they can avoid creating
* them again when they're reloaded (like re-selecting a previously selected category) */
priv->surfaces_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cairo_surface_destroy);
priv->response = GTK_RESPONSE_NONE;
priv->icon_string = NULL;
priv->current_text = NULL;
priv->cancellable = NULL;
priv->allow_paths = TRUE;
priv->full_icon_list = NULL;
priv->search_icon_store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->search_icon_store),
COLUMN_DISPLAY_NAME,
search_model_sort,
priv,
NULL);
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->search_icon_store),
COLUMN_DISPLAY_NAME,
GTK_SORT_ASCENDING);
g_signal_connect (priv->search_icon_store, "row-inserted",
G_CALLBACK (on_icon_store_icons_added), dialog);
gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 450);
gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
gtk_window_set_title (GTK_WINDOW (dialog), _("Choose an icon"));
main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (dialog), main_box);
// toolbar
toolbar = gtk_toolbar_new ();
gtk_box_pack_start (GTK_BOX (main_box), toolbar, FALSE, FALSE, 0);
style = gtk_widget_get_style_context (toolbar);
gtk_style_context_add_class (style, "primary-toolbar");
tool_item = gtk_tool_item_new ();
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, 0);
gtk_tool_item_set_expand (tool_item, TRUE);
toolbar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add (GTK_CONTAINER (tool_item), toolbar_box);
style = gtk_widget_get_style_context (GTK_WIDGET (toolbar_box));
gtk_style_context_add_class (style, "linked");
priv->search_bar = gtk_search_entry_new ();
gtk_box_pack_start (GTK_BOX (toolbar_box), priv->search_bar, TRUE, TRUE, 0);
gtk_entry_set_placeholder_text (GTK_ENTRY (priv->search_bar), _("Search"));
priv->search_changed_id = g_signal_connect (priv->search_bar, "search-changed",
G_CALLBACK (on_search_text_changed), dialog);
g_signal_connect (priv->search_bar, "key-press-event",
G_CALLBACK (on_search_bar_key_pressed), dialog);
priv->browse_button = gtk_button_new_with_label (_("Browse"));
gtk_box_pack_start (GTK_BOX (toolbar_box), priv->browse_button, FALSE, FALSE, 0);
g_signal_connect (priv->browse_button, "clicked",
G_CALLBACK (on_browse_button_clicked), dialog);
secondary_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (main_box), secondary_box, TRUE, TRUE, 0);
// context list
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start (GTK_BOX (secondary_box), scrolled_window, FALSE, FALSE, 0);
priv->list_box = gtk_list_box_new ();
gtk_container_add(GTK_CONTAINER (scrolled_window), GTK_WIDGET (priv->list_box));
gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box), list_box_sort, NULL, NULL);
g_signal_connect (priv->list_box, "selected-rows-changed",
G_CALLBACK (on_category_selected), dialog);
style = gtk_widget_get_style_context (GTK_WIDGET (scrolled_window));
gtk_style_context_add_class (style, "sidebar");
right_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX (secondary_box), right_box, TRUE, TRUE, 0);
overlay = gtk_overlay_new ();
gtk_box_pack_start (GTK_BOX (right_box), overlay, TRUE, TRUE, 0);
// icon view
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), scrolled_window);
gtk_widget_set_halign (scrolled_window, GTK_ALIGN_FILL);
gtk_widget_set_valign (scrolled_window, GTK_ALIGN_FILL);
priv->loading_bar = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (priv->loading_bar), GTK_SHADOW_NONE);
gtk_style_context_add_class (gtk_widget_get_style_context (priv->loading_bar),
"background");
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), priv->loading_bar);
gtk_widget_set_halign (priv->loading_bar, GTK_ALIGN_START);
gtk_widget_set_valign (priv->loading_bar, GTK_ALIGN_END);
gtk_widget_set_no_show_all (priv->loading_bar, TRUE);
loading_bar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add (GTK_CONTAINER (priv->loading_bar), loading_bar_box);
g_object_set (loading_bar_box,
"margin", 4,
NULL);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_box_pack_start (GTK_BOX (loading_bar_box), spinner, FALSE, FALSE, 4);
spinner_label = gtk_label_new (_("Loading..."));
gtk_box_pack_start (GTK_BOX (loading_bar_box), spinner_label, FALSE, FALSE, 4);
gtk_widget_show_all (loading_bar_box);
GtkCellArea *ca_box = gtk_cell_area_box_new ();
gtk_orientable_set_orientation (GTK_ORIENTABLE (ca_box), GTK_ORIENTATION_VERTICAL);
GtkCellRenderer *r;
r = gtk_cell_renderer_pixbuf_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ca_box), r, FALSE);
gtk_cell_area_attribute_connect (GTK_CELL_AREA (ca_box), r,
"surface",
COLUMN_SURFACE);
r = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ca_box), r, FALSE);
gtk_cell_area_attribute_connect (GTK_CELL_AREA (ca_box), r,
"text",
COLUMN_DISPLAY_NAME);
g_object_set (G_OBJECT (r),
"alignment", PANGO_ALIGN_CENTER,
"xalign", 0.5,
"width-chars", 20,
"wrap-mode", PANGO_WRAP_WORD_CHAR,
"wrap-width", 0,
NULL);
priv->ca_box = ca_box;
priv->icon_view = gtk_icon_view_new_with_area (GTK_CELL_AREA (ca_box));
gtk_container_add(GTK_CONTAINER (scrolled_window), GTK_WIDGET (priv->icon_view));
gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (priv->icon_view), COLUMN_FULL_NAME);
g_signal_connect (priv->icon_view, "selection-changed",
G_CALLBACK (on_icon_view_selection_changed), dialog);
g_signal_connect (priv->icon_view, "item-activated",
G_CALLBACK (on_icon_view_item_activated), dialog);
// buttons
button_area = gtk_action_bar_new ();
priv->action_area = button_area;
gtk_box_pack_start (GTK_BOX (main_box), button_area, FALSE, FALSE, 0);
button_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
priv->default_button = gtk_button_new_with_label (_("Default"));
gtk_widget_set_no_show_all (priv->default_button, TRUE);
style = gtk_widget_get_style_context (GTK_WIDGET (priv->default_button));
gtk_style_context_add_class (style, "text-button");
gtk_size_group_add_widget (button_size_group, priv->default_button);
gtk_action_bar_pack_start (GTK_ACTION_BAR (button_area), priv->default_button);
g_signal_connect (priv->default_button, "clicked",
G_CALLBACK (on_default_button_clicked), dialog);
priv->select_button = gtk_button_new_with_label (_("Select"));
style = gtk_widget_get_style_context (GTK_WIDGET (priv->select_button));
gtk_style_context_add_class (style, "text-button");
gtk_size_group_add_widget (button_size_group, priv->select_button);
gtk_action_bar_pack_end (GTK_ACTION_BAR (button_area), priv->select_button);
g_signal_connect (priv->select_button, "clicked",
G_CALLBACK (on_select_button_clicked), dialog);
cancel_button = gtk_button_new_with_label (_("Cancel"));
style = gtk_widget_get_style_context (GTK_WIDGET (cancel_button));
gtk_style_context_add_class (style, "text-button");
gtk_size_group_add_widget (button_size_group, cancel_button);
gtk_action_bar_pack_end (GTK_ACTION_BAR (button_area), cancel_button);
g_signal_connect (cancel_button, "clicked",
G_CALLBACK (on_cancel_button_clicked), dialog);
load_categories (dialog);
}
static void
xapp_icon_chooser_dialog_class_init (XAppIconChooserDialogClass *klass)
{
GtkBindingSet *binding_set;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->get_property = xapp_icon_chooser_dialog_get_property;
object_class->set_property = xapp_icon_chooser_dialog_set_property;
object_class->dispose = xapp_icon_chooser_dialog_dispose;
widget_class->delete_event = on_delete_event;
/**
* XAppIconChooserDialog:icon-size:
*
* The preferred size to use when looking up icons. This only works with icon names.
* Additionally, there is no guarantee that a selected icon name will exist in a
* particular size.
*/
obj_properties[PROP_ICON_SIZE] =
g_param_spec_enum ("icon-size",
_("Icon size"),
_("The preferred icon size."),
XAPP_TYPE_ICON_SIZE,
XAPP_ICON_SIZE_32,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* XAppIconChooserDialog:allow-paths:
*
* Whether to allow paths to be searched and selected or only icon names.
*/
obj_properties[PROP_ALLOW_PATHS] =
g_param_spec_boolean ("allow-paths",
_("Allow Paths"),
_("Whether to allow paths."),
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* XAppIconChooserDialog:default-icon:
*
* The icon to use by default.
*/
obj_properties[PROP_DEFAULT_ICON] =
g_param_spec_string ("default-icon",
_("Default Icon"),
_("The icon to use by default"),
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
// keybinding signals
signals[CLOSE] =
g_signal_new ("close",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkWidgetClass, delete_event),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[SELECT] =
g_signal_new ("select",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "select", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "select", 0);
gtk_widget_class_set_css_name (widget_class, "stacksidebar");
}
/**
* xapp_icon_chooser_dialog_new:
*
* Creates a new #XAppIconChooserDialog.
*
* Returns: a newly created #XAppIconChooserDialog
*/
XAppIconChooserDialog *
xapp_icon_chooser_dialog_new (void)
{
return g_object_new (XAPP_TYPE_ICON_CHOOSER_DIALOG, NULL);
}
/**
* xapp_icon_chooser_dialog_run:
* @dialog: a #XAppIconChooserDialog
*
* Shows the dialog and enters a separate main loop until an icon is chosen or the action is canceled.
*
* xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
* xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
* applications which use this dialog multiple times, as it may improve performance for subsequent
* calls.
*
* Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
*/
gint
xapp_icon_chooser_dialog_run (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
gtk_widget_show_all (GTK_WIDGET (dialog));
gtk_widget_grab_focus (priv->search_bar);
gtk_main ();
return priv->response;
}
/**
* xapp_icon_chooser_dialog_run_with_icon:
* @dialog: a #XAppIconChooserDialog
* @icon: a string representing the icon that should be selected
*
* Like xapp_icon_chooser_dialog_run but selects the icon specified by @icon. This can be either an
* icon name or a path. Passing an icon string or path that doesn't exist is accepted, but it may show
* multiple results, or none at all. This behavior is useful if, for example, you wish to have the
* user select an image file from a particular directory.
*
* If the property allow_paths is FALSE, setting a path will yield no results when the dialog is opened.
*
* xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
* xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
* applications which use this dialog multiple times, as it may improve performance for subsequent
* calls.
*
* Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
*/
gint
xapp_icon_chooser_dialog_run_with_icon (XAppIconChooserDialog *dialog,
gchar *icon)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
gtk_widget_show_all (GTK_WIDGET (dialog));
gtk_entry_set_text (GTK_ENTRY (priv->search_bar), icon);
gtk_widget_grab_focus (priv->search_bar);
gtk_main ();
return priv->response;
}
/**
* xapp_icon_chooser_dialog_run_with_category:
* @dialog: a #XAppIconChooserDialog
*
* Like xapp_icon_chooser_dialog_run but selects a particular category specified by @category.
* This is used when there is a particular category of icon that is more appropriate than the
* others. If the category does not exist, the first category in the list will be selected. To
* get a list of possible categories, use gtk_icon_theme_list_contexts ().
*
* xapp_icon_chooser_dialog_run (), xapp_icon_chooser_dialog_run_with_icon(), and
* xapp_icon_chooser_dialog_run_with_category () may all be called multiple times. This is useful for
* applications which use this dialog multiple times, as it may improve performance for subsequent
* calls.
*
* Returns: GTK_RESPONSE_OK if the user selected an icon, or GTK_RESPONSE_CANCEL otherwise
*/
gint
xapp_icon_chooser_dialog_run_with_category (XAppIconChooserDialog *dialog,
gchar *category)
{
XAppIconChooserDialogPrivate *priv;
GList *children;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
gtk_widget_show_all (GTK_WIDGET (dialog));
gtk_widget_grab_focus (priv->search_bar);
children = gtk_container_get_children (GTK_CONTAINER (priv->list_box));
for ( ; children; children = children->next)
{
GtkWidget *row;
GtkWidget *child;
const gchar *context;
row = children->data;
child = gtk_bin_get_child (GTK_BIN (row));
context = gtk_label_get_text (GTK_LABEL (child));
if (g_strcmp0 (context, category) == 0)
{
gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), GTK_LIST_BOX_ROW (row));
break;
}
}
gtk_main ();
return priv->response;
}
/**
* xapp_icon_chooser_dialog_get_default_icon:
*
* Returns the default icon (if set).
*
* Returns: (transfer full): the default icon, or NULL if none is set
*/
gchar *
xapp_icon_chooser_dialog_get_default_icon (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
return g_strdup(priv->default_icon);
}
/**
* xapp_icon_chooser_dialog_set_default_icon:
* @icon: the default icon, or NULL to unset
*
* Sets the default icon. If @icon is not NULL, a button will be shown that
* will reset the dialog to it's default value.
*/
void
xapp_icon_chooser_dialog_set_default_icon (XAppIconChooserDialog *dialog,
const gchar *icon)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
priv->default_icon = g_strdup (icon);
if (icon == NULL)
{
gtk_widget_hide (priv->default_button);
}
else
{
gtk_widget_show (priv->default_button);
}
}
/**
* xapp_icon_chooser_dialog_add_custom_category:
* @dialog: a #XAppIconChooserDialog
* @name: the name of the category as it will be displayed in the category list
* @icons: (transfer full) (element-type utf8): a list of icon names to add to the new category
*
* Adds a custom category to the dialog.
*/
void
xapp_icon_chooser_dialog_add_custom_category (XAppIconChooserDialog *dialog,
const gchar *name,
GList *icons)
{
XAppIconChooserDialogPrivate *priv;
IconCategoryInfo *category_info;
GtkWidget *row;
GtkWidget *label;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
category_info = g_new0 (IconCategoryInfo, 1);
category_info->name = name;
category_info->icons = icons;
category_info->model = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
g_signal_connect (category_info->model, "row-inserted",
G_CALLBACK (on_icon_store_icons_added), dialog);
category_info->icons = g_list_sort (category_info->icons, (GCompareFunc) g_utf8_collate);
row = gtk_list_box_row_new ();
label = gtk_label_new (category_info->name);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_set_margin_start (GTK_WIDGET (label), 6);
gtk_widget_set_margin_end (GTK_WIDGET (label), 6);
gtk_container_add (GTK_CONTAINER (row), label);
gtk_container_add (GTK_CONTAINER (priv->list_box), row);
g_hash_table_insert (priv->categories, row, category_info);
}
/**
* xapp_icon_chooser_dialog_get_icon_string:
* @dialog: a #XAppIconChooserDialog
*
* Gets the currently selected icon from the dialog. If allow-paths is TRUE, this function may return
* either an icon name or a path depending on what the user selects. Otherwise it will only return an
* icon name.
*
* Returns: (transfer full): the string representation of the currently selected icon or NULL
* if no icon is selected.
*/
gchar *
xapp_icon_chooser_dialog_get_icon_string (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
return g_strdup (priv->icon_string);
}
static void
xapp_icon_chooser_dialog_close (XAppIconChooserDialog *dialog,
GtkResponseType response)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
priv->response = response;
gtk_widget_hide (GTK_WIDGET (dialog));
gtk_main_quit ();
}
static void
on_custom_button_clicked (GtkButton *button,
gpointer user_data)
{
GtkResponseType response_id;
response_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "response-id"));
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), response_id);
}
/**
* xapp_icon_chooser_dialog_add_button:
* @dialog: an #XAppIconChooserDialog
* @button: a #GtkButton to add
* @packing: the #GtkPackType to specify start or end packing to the action bar
* @response_id: the dialog response id to return when this button is clicked.
*
* Allows a button to be added to the #GtkActionBar of the dialog with a custom
* response id.
*/
void
xapp_icon_chooser_dialog_add_button (XAppIconChooserDialog *dialog,
GtkWidget *button,
GtkPackType packing,
GtkResponseType response_id)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
g_signal_connect (button,
"clicked",
G_CALLBACK (on_custom_button_clicked),
dialog);
/* This saves having to use a custom container for callback data. */
g_object_set_data (G_OBJECT (button),
"response-id", GINT_TO_POINTER (response_id));
if (packing == GTK_PACK_START)
{
gtk_action_bar_pack_start (GTK_ACTION_BAR (priv->action_area), button);
}
else
{
gtk_action_bar_pack_end (GTK_ACTION_BAR (priv->action_area), button);
}
}
static gint
list_box_sort (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
GtkWidget *item;
const gchar *label1;
const gchar *label2;
item = gtk_bin_get_child (GTK_BIN (row1));
label1 = gtk_label_get_text (GTK_LABEL (item));
item = gtk_bin_get_child (GTK_BIN (row2));
label2 = gtk_label_get_text (GTK_LABEL (item));
return g_strcmp0 (label1, label2);
}
static gint
search_model_sort (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer user_data)
{
gchar *a_value;
gchar *b_value;
gchar *search_str;
gboolean a_starts_with;
gboolean b_starts_with;
gint ret;
XAppIconChooserDialogPrivate *priv = (XAppIconChooserDialogPrivate *) user_data;
search_str = priv->current_text;
gtk_tree_model_get (model, a, COLUMN_DISPLAY_NAME, &a_value, -1);
gtk_tree_model_get (model, b, COLUMN_DISPLAY_NAME, &b_value, -1);
ret = g_strcmp0 (a_value, b_value);
if (search_str == NULL)
{
}
else
if (g_strcmp0 (a_value, search_str) == 0)
{
ret = -1;
}
else
if (g_strcmp0 (b_value, search_str) == 0)
{
ret = 1;
}
else
{
a_starts_with = g_str_has_prefix (a_value, search_str);
b_starts_with = g_str_has_prefix (b_value, search_str);
if (a_starts_with && !b_starts_with)
{
ret = -1;
}
if (!a_starts_with && b_starts_with)
{
ret = 1;
}
}
g_free (a_value);
g_free (b_value);
return ret;
}
static void
load_categories (XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
GtkListBoxRow *row;
GtkIconTheme *theme;
GList *contexts, *l;
gint i;
gint j;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
theme = gtk_icon_theme_get_default ();
contexts = gtk_icon_theme_list_contexts (theme);
for (i = 0; i < G_N_ELEMENTS (categories); i++)
{
IconCategoryDefinition *category;
IconCategoryInfo *category_info;
GtkWidget *row;
GtkWidget *label;
GList *context_icons;
category = &categories[i];
category_info = g_new0 (IconCategoryInfo, 1);
category_info->name = _(category->name);
for (j = 0; category->contexts[j] != NULL; j++)
{
GList *match;
context_icons = gtk_icon_theme_list_icons (theme, category->contexts[j]);
category_info->icons = g_list_concat (category_info->icons, context_icons);
match = g_list_find_custom (contexts, category->contexts[j], (GCompareFunc) g_strcmp0);
if (match)
{
contexts = g_list_remove_link (contexts, match);
g_free (match->data);
g_list_free (match);
}
}
/* Any contexts not consumed by categories should be added to the 'other' category */
if (i == (G_N_ELEMENTS (categories) - 1) && g_list_length (contexts) > 0)
{
for (l = contexts; l != NULL; l = l->next)
{
#if DEBUG_ICON_THEME
g_message ("Adding unused category to Other category: '%s'", (gchar *) l->data);
#endif
context_icons = gtk_icon_theme_list_icons (theme, (gchar *) l->data);
category_info->icons = g_list_concat (category_info->icons, context_icons);
}
}
if (g_list_length (category_info->icons) == 0)
{
free_category_info (category_info);
continue;
}
/* Add the list of icons for this category into our master search list */
priv->full_icon_list = g_list_concat (priv->full_icon_list,
g_list_copy (category_info->icons));
category_info->model = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, CAIRO_GOBJECT_TYPE_SURFACE);
g_signal_connect (category_info->model, "row-inserted",
G_CALLBACK (on_icon_store_icons_added), dialog);
category_info->icons = g_list_sort (category_info->icons, (GCompareFunc) g_utf8_collate);
row = gtk_list_box_row_new ();
label = gtk_label_new (category_info->name);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_set_margin_start (GTK_WIDGET (label), 6);
gtk_widget_set_margin_end (GTK_WIDGET (label), 6);
gtk_container_add (GTK_CONTAINER (row), label);
gtk_container_add (GTK_CONTAINER (priv->list_box), row);
g_hash_table_insert (priv->categories, row, category_info);
}
g_list_free_full (contexts, g_free);
priv->full_icon_list = g_list_sort (priv->full_icon_list, (GCompareFunc) g_utf8_collate);
row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (priv->list_box), 0);
gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), row);
}
static gboolean
load_next_file_search_chunk (gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
FileIconInfoLoadCallbackInfo *callback_info;
callback_info = (FileIconInfoLoadCallbackInfo*) user_data;
priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
free_file_info (callback_info);
return G_SOURCE_REMOVE;
}
search_path (callback_info->dialog,
priv->current_text,
priv->search_icon_store);
free_file_info (callback_info);
return G_SOURCE_REMOVE;
}
static gboolean
load_next_category_chunk (gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
NamedIconInfoLoadCallbackInfo *callback_info;
callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
load_icons_for_category (callback_info->dialog,
callback_info->category_info,
priv->icon_size);
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
static gboolean
load_next_name_search_chunk (gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
NamedIconInfoLoadCallbackInfo *callback_info;
callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
search_icon_name (callback_info->dialog,
priv->current_text,
priv->search_icon_store);
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
static void
finish_pixbuf_load_from_file (GObject *stream,
GAsyncResult *res,
gpointer *user_data)
{
FileIconInfoLoadCallbackInfo *callback_info;
GdkPixbuf *pixbuf;
GError *error = NULL;
callback_info = (FileIconInfoLoadCallbackInfo *) user_data;
pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
g_object_unref (stream);
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
g_clear_object (&pixbuf);
free_file_info (callback_info);
return;
}
if (pixbuf == NULL)
{
if (error && (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED))
{
g_message ("%s\n", error->message);
}
g_clear_error (&error);
}
if (pixbuf)
{
GtkTreeIter iter;
cairo_surface_t *surface;
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf,
0,
gtk_widget_get_window (GTK_WIDGET (callback_info->dialog)));
gtk_list_store_append (callback_info->model, &iter);
gtk_list_store_set (callback_info->model, &iter,
COLUMN_DISPLAY_NAME, callback_info->short_name,
COLUMN_FULL_NAME, callback_info->long_name,
COLUMN_SURFACE, surface,
-1);
cairo_surface_destroy (surface);
g_object_unref (pixbuf);
}
if (callback_info->chunk_end)
{
g_idle_add ((GSourceFunc) load_next_file_search_chunk, callback_info);
}
else
{
free_file_info (callback_info);
}
}
static void
add_named_entry (NamedIconInfoLoadCallbackInfo *callback_info,
cairo_surface_t *surface)
{
GtkTreeIter iter;
gtk_list_store_append (callback_info->model, &iter);
gtk_list_store_set (callback_info->model, &iter,
COLUMN_DISPLAY_NAME, callback_info->name,
COLUMN_FULL_NAME, callback_info->name,
COLUMN_SURFACE, surface,
-1);
}
static gboolean
add_named_entry_with_existing_surface (gpointer user_data)
{
NamedIconInfoLoadCallbackInfo *callback_info;
callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
/* Category results have a category_info attached. */
if (callback_info->category_info)
{
add_named_entry (callback_info, callback_info->surface);
if (callback_info->chunk_end)
{
g_idle_add ((GSourceFunc) load_next_category_chunk, callback_info);
return G_SOURCE_REMOVE;
}
}
/* Otherwise, it's a search result set */
else
{
add_named_entry (callback_info, callback_info->surface);
if (callback_info->chunk_end)
{
g_idle_add ((GSourceFunc) load_next_name_search_chunk, callback_info);
return G_SOURCE_REMOVE;
}
}
free_named_info (callback_info);
return G_SOURCE_REMOVE;
}
static void
finish_pixbuf_load_from_name (GObject *info,
GAsyncResult *res,
gpointer *user_data)
{
XAppIconChooserDialogPrivate *priv;
NamedIconInfoLoadCallbackInfo *callback_info;
GdkPixbuf *pixbuf;
cairo_surface_t *surface;
GError *error = NULL;
callback_info = (NamedIconInfoLoadCallbackInfo*) user_data;
priv = xapp_icon_chooser_dialog_get_instance_private (callback_info->dialog);
pixbuf = gtk_icon_info_load_symbolic_for_context_finish (GTK_ICON_INFO (info), res, NULL, &error);
g_object_unref (info);
if (g_cancellable_is_cancelled (callback_info->cancellable))
{
g_clear_object (&pixbuf);
free_named_info (callback_info);
return;
}
if (pixbuf == NULL)
{
if (error && (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED))
{
g_message ("%s\n", error->message);
}
free_named_info (callback_info);
g_clear_error (&error);
return;
}
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf,
0,
gtk_widget_get_window (GTK_WIDGET (callback_info->dialog)));
g_object_unref (pixbuf);
/* Hash table 'takes' reference, we don't have to free surface.
callback_info->name is already owned by priv->full_icon_list so
it needs to be copied */
g_hash_table_insert (priv->surfaces_by_name,
g_strdup (callback_info->name),
(gpointer) surface);
/* If there's a category_info, this is a category selection. */
if (callback_info->category_info)
{
add_named_entry (callback_info, surface);
if (callback_info->chunk_end)
{
g_idle_add ((GSourceFunc) load_next_category_chunk, callback_info);
return;
}
}
/* Otherwise, it's a search result set */
else
{
add_named_entry (callback_info, surface);
if (callback_info->chunk_end)
{
g_idle_add ((GSourceFunc) load_next_name_search_chunk, callback_info);
return;
}
}
free_named_info (callback_info);
}
#define CATEGORY_CHUNK_SIZE 500
static void
load_icons_for_category (XAppIconChooserDialog *dialog,
IconCategoryInfo *category_info,
guint icon_size)
{
XAppIconChooserDialogPrivate *priv;
GtkIconTheme *theme;
gint chunk_count;
gint scale;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
theme = gtk_icon_theme_get_default ();
scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
for (chunk_count = 0; chunk_count < CATEGORY_CHUNK_SIZE; chunk_count++)
{
if (category_info->iter == NULL)
{
category_info->iter = category_info->icons;
}
else
{
category_info->iter = category_info->iter->next;
}
if (category_info->iter)
{
NamedIconInfoLoadCallbackInfo *callback_info;
const gchar *name = category_info->iter->data;
cairo_surface_t *surface;
callback_info = g_new0 (NamedIconInfoLoadCallbackInfo, 1);
callback_info->dialog = dialog;
callback_info->category_info = category_info;
callback_info->model = category_info->model;
callback_info->cancellable = g_object_ref (priv->cancellable);
callback_info->name = name;
callback_info->chunk_end = (chunk_count == CATEGORY_CHUNK_SIZE - 1);
surface = g_hash_table_lookup (priv->surfaces_by_name, name);
if (surface != NULL)
{
callback_info->surface = cairo_surface_reference (surface);
g_idle_add ((GSourceFunc) add_named_entry_with_existing_surface, callback_info);
}
else
{
GtkIconInfo *info;
GtkStyleContext *context;
info = gtk_icon_theme_lookup_icon_for_scale (theme, name, icon_size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
if (info == NULL)
{
// this shouldn't be the case most of the time, but if a custom category is defined it's possible
// the icon doesn't exist. In that case it's best to just skip over it since trying to load it will
// lead to a segfault.
continue;
}
context = gtk_widget_get_style_context (priv->icon_view);
gtk_icon_info_load_symbolic_for_context_async (info, context, NULL, (GAsyncReadyCallback) (finish_pixbuf_load_from_name), callback_info);
}
}
else
{
gtk_widget_hide (priv->loading_bar);
break; // Quit the count early, we're out of data
}
}
}
static void
on_category_selected (GtkListBox *list_box,
XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
GList *selection;
GtkWidget *selected;
IconCategoryInfo *category_info;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
clear_search_state (dialog);
selection = gtk_list_box_get_selected_rows (GTK_LIST_BOX (priv->list_box));
if (!selection)
{
return;
}
gtk_widget_show (priv->loading_bar);
g_signal_handler_block (priv->search_bar, priv->search_changed_id);
gtk_entry_set_text (GTK_ENTRY (priv->search_bar), "");
g_signal_handler_unblock (priv->search_bar, priv->search_changed_id);
selected = selection->data;
category_info = g_hash_table_lookup (priv->categories, selected);
priv->cancellable = g_cancellable_new ();
#if DEBUG_REFS
g_object_weak_ref (G_OBJECT (priv->cancellable), (GWeakNotify) on_cancellable_finalize, priv);
#endif
priv->current_category = category_info;
gtk_list_store_clear (GTK_LIST_STORE (category_info->model));
gtk_icon_view_set_model (GTK_ICON_VIEW (priv->icon_view), GTK_TREE_MODEL (category_info->model));
load_icons_for_category (dialog,
category_info,
priv->icon_size);
gtk_adjustment_set_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->icon_view)), 0.0);
g_list_free (selection);
}
#define SEARCH_CHUNK_SIZE 2
static void
search_path (XAppIconChooserDialog *dialog,
const gchar *path_string,
GtkListStore *icon_store)
{
XAppIconChooserDialogPrivate *priv;
gchar *search_str = NULL;
GFile *dir;
GFileInfo *child_info = NULL;;
gint chunk_count;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
if (g_file_test (path_string, G_FILE_TEST_IS_DIR))
{
dir = g_file_new_for_path (path_string);
}
else
{
GFile *file;
file = g_file_new_for_path (path_string);
dir = g_file_get_parent (file);
search_str = g_file_get_basename (file);
g_object_unref (file);
}
if (!g_file_query_exists (dir, NULL) ||
g_file_query_file_type (dir, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
{
g_free (search_str);
g_object_unref (dir);
return;
}
if (!priv->search_file_enumerator)
{
priv->search_file_enumerator = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_SIZE ","
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_add_toggle_ref (G_OBJECT (priv->search_file_enumerator),
(GToggleNotify) on_enumerator_toggle_ref_called,
dialog);
#if DEBUG_REFS
g_object_weak_ref (G_OBJECT (priv->search_file_enumerator), (GWeakNotify) on_enumerator_finalize, dialog);
#endif
}
chunk_count = 0;
while (chunk_count < SEARCH_CHUNK_SIZE)
{
const gchar *child_name;
gchar *child_path;
GFile *child;
GError *error = NULL;
g_clear_object (&child_info);
child_info = g_file_enumerator_next_file (priv->search_file_enumerator, NULL, NULL);
if (!child_info)
{
break;
}
child_name = g_file_info_get_name (child_info);
child = g_file_enumerator_get_child (priv->search_file_enumerator, child_info);
child_path = g_file_get_path (child);
if (search_str == NULL || g_str_has_prefix (child_name, search_str))
{
priv->current_category = NULL;
gchar *content_type;
gboolean uncertain;
content_type = g_content_type_guess (child_name, NULL, 0, &uncertain);
if (content_type && g_str_has_prefix (content_type, "image") && !uncertain)
{
GFileInputStream *stream;
stream = g_file_read (child, NULL, &error);
if (stream != NULL)
{
FileIconInfoLoadCallbackInfo *callback_info;
gint scale;
callback_info = g_new0 (FileIconInfoLoadCallbackInfo, 1);
callback_info->dialog = dialog;
callback_info->model = icon_store;
callback_info->cancellable = g_object_ref (priv->cancellable);
callback_info->enumerator = g_object_ref (priv->search_file_enumerator);
callback_info->short_name = g_strdup (child_name);
callback_info->long_name = g_strdup (child_path);
callback_info->chunk_end = (chunk_count == SEARCH_CHUNK_SIZE - 1);
scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream),
-1,
priv->icon_size * scale,
TRUE,
NULL,
(GAsyncReadyCallback) finish_pixbuf_load_from_file,
callback_info);
chunk_count ++;
}
else
{
if (error)
{
g_message ("%s\n", error->message);
g_error_free (error);
}
}
}
g_free (content_type);
}
g_free (child_path);
g_object_unref (child);
}
if (!child_info)
{
if (priv->search_file_enumerator)
{
g_object_unref (priv->search_file_enumerator);
}
gtk_widget_hide (priv->loading_bar);
}
else
{
g_clear_object (&child_info);
}
g_free (search_str);
g_object_unref (dir);
}
static void
search_icon_name (XAppIconChooserDialog *dialog,
const gchar *name_string,
GtkListStore *icon_store)
{
XAppIconChooserDialogPrivate *priv;
GtkIconTheme *theme;
GList *icons;
gint chunk_count;
gint scale;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
theme = gtk_icon_theme_get_default ();
scale = gtk_widget_get_scale_factor (GTK_WIDGET (dialog));
icons = priv->full_icon_list;
chunk_count = 0;
while (chunk_count < SEARCH_CHUNK_SIZE)
{
if (priv->search_iter == NULL)
{
priv->search_iter = icons;
}
else
{
priv->search_iter = priv->search_iter->next;
}
if (priv->search_iter)
{
priv->current_category = NULL;
if (g_strrstr (priv->search_iter->data, name_string))
{
NamedIconInfoLoadCallbackInfo *callback_info;
cairo_surface_t *surface;
const gchar *name = priv->search_iter->data;
callback_info = g_new0 (NamedIconInfoLoadCallbackInfo, 1);
callback_info->dialog = dialog;
callback_info->model = priv->search_icon_store;
callback_info->cancellable = g_object_ref (priv->cancellable);
callback_info->category_info = NULL;
callback_info->name = name;
callback_info->chunk_end = (chunk_count == SEARCH_CHUNK_SIZE - 1);
surface = g_hash_table_lookup (priv->surfaces_by_name, name);
if (surface != NULL)
{
callback_info->surface = cairo_surface_reference (surface);
g_idle_add ((GSourceFunc) add_named_entry_with_existing_surface, callback_info);
}
else
{
GtkIconInfo *info;
GtkStyleContext *context;
info = gtk_icon_theme_lookup_icon_for_scale (theme, name, priv->icon_size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
context = gtk_widget_get_style_context (priv->icon_view);
gtk_icon_info_load_symbolic_for_context_async (info, context, NULL, (GAsyncReadyCallback) (finish_pixbuf_load_from_name), callback_info);
}
chunk_count++;
}
}
else
{
gtk_widget_hide (priv->loading_bar);
break; // Quit the count early, we're out of data
}
}
}
static void
on_search_text_changed (GtkSearchEntry *entry,
XAppIconChooserDialog *dialog)
{
XAppIconChooserDialogPrivate *priv;
const gchar *search_text;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
/* The search cancellable is carried in search callback data. If the
* text changes, we cancel it here, our load callbacks will check the
* state, and react appropriately (like not adding results to the model). */
clear_search_state (dialog);
gtk_list_box_select_row (GTK_LIST_BOX (priv->list_box), NULL);
priv->cancellable = g_cancellable_new ();
#if DEBUG_REFS
g_object_weak_ref (G_OBJECT (priv->cancellable), (GWeakNotify) on_cancellable_finalize, dialog);
#endif
search_text = gtk_entry_get_text (GTK_ENTRY (entry));
if (g_strcmp0 (search_text, "") == 0)
{
g_clear_pointer (&priv->current_text, g_free);
g_clear_pointer (&priv->icon_string, g_free);
gtk_widget_hide (priv->loading_bar);
gtk_list_store_clear (GTK_LIST_STORE (priv->search_icon_store));
}
else
if (strlen (search_text) < 2)
{
return;
}
else
{
g_free (priv->current_text);
priv->current_text = g_strdup (search_text);
gtk_widget_show (priv->loading_bar);
gtk_list_store_clear (GTK_LIST_STORE (priv->search_icon_store));
gtk_icon_view_set_model (GTK_ICON_VIEW (priv->icon_view), GTK_TREE_MODEL (priv->search_icon_store));
if (g_strrstr (search_text, "/"))
{
if (priv->allow_paths)
{
search_path (dialog, search_text, priv->search_icon_store);
}
}
else
{
search_icon_name (dialog, search_text, priv->search_icon_store);
}
}
}
static void
on_icon_view_selection_changed (GtkIconView *icon_view,
gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
GList *selected_items;
gchar *icon_string = NULL;
priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
selected_items = gtk_icon_view_get_selected_items (icon_view);
if (selected_items == NULL)
{
gtk_widget_set_sensitive (GTK_WIDGET (priv->select_button), FALSE);
}
else
{
GtkTreePath *tree_path;
GtkTreeModel *model;
GtkTreeIter iter;
gtk_widget_set_sensitive (GTK_WIDGET (priv->select_button), TRUE);
tree_path = selected_items->data;
model = gtk_icon_view_get_model (icon_view);
gtk_tree_model_get_iter (model, &iter, tree_path);
gtk_tree_model_get (model, &iter, COLUMN_FULL_NAME, &icon_string, -1);
}
if (priv->icon_string != NULL)
{
g_free (priv->icon_string);
}
priv->icon_string = icon_string;
g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free);
}
static void
on_icon_store_icons_added (GtkTreeModel *tree_model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
GtkTreePath *new_path;
priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
if (tree_model != gtk_icon_view_get_model (GTK_ICON_VIEW (priv->icon_view))) {
return;
}
new_path = gtk_tree_path_new_first ();
gtk_icon_view_select_path (GTK_ICON_VIEW (priv->icon_view), new_path);
gtk_tree_path_free (new_path);
}
static void
on_browse_button_clicked (GtkButton *button,
gpointer user_data)
{
XAppIconChooserDialog *dialog = user_data;
XAppIconChooserDialogPrivate *priv;
GtkWidget *file_dialog;
const gchar *search_text;
GtkFileFilter *file_filter;
gint response;
priv = xapp_icon_chooser_dialog_get_instance_private (dialog);
file_dialog = gtk_file_chooser_dialog_new (_("Select image file"),
GTK_WINDOW (dialog),
GTK_FILE_CHOOSER_ACTION_OPEN,
_("Cancel"),
GTK_RESPONSE_CANCEL,
_("Open"),
GTK_RESPONSE_ACCEPT,
NULL);
search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_bar));
if (g_strrstr (search_text, "/"))
{
gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (file_dialog), search_text);
}
else
{
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_dialog), "/usr/share/icons/");
}
file_filter = gtk_file_filter_new ();
gtk_file_filter_set_name (file_filter, _("Image"));
gtk_file_filter_add_pixbuf_formats (file_filter);
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
response = gtk_dialog_run (GTK_DIALOG (file_dialog));
if (response == GTK_RESPONSE_ACCEPT)
{
gchar *filename;
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_dialog));
gtk_entry_set_text (GTK_ENTRY (priv->search_bar), filename);
g_free (filename);
}
gtk_widget_destroy (file_dialog);
}
static void
on_select_button_clicked (GtkButton *button,
gpointer user_data)
{
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
}
static void
on_cancel_button_clicked (GtkButton *button,
gpointer user_data)
{
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_CANCEL);
}
static gboolean
on_delete_event (GtkWidget *widget,
GdkEventAny *event)
{
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (widget), GTK_RESPONSE_CANCEL);
return TRUE;
}
static void
on_icon_view_item_activated (GtkIconView *iconview,
GtkTreePath *path,
gpointer user_data)
{
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
}
static gboolean
on_search_bar_key_pressed (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
guint keyval;
priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
gdk_event_get_keyval (event, &keyval);
switch (keyval)
{
case GDK_KEY_Escape:
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_CANCEL);
return TRUE;
case GDK_KEY_Return:
case GDK_KEY_KP_Enter:
if (priv->icon_string != NULL)
{
xapp_icon_chooser_dialog_close (XAPP_ICON_CHOOSER_DIALOG (user_data), GTK_RESPONSE_OK);
}
return TRUE;
}
return FALSE;
}
static void
on_default_button_clicked (GtkButton *button,
gpointer user_data)
{
XAppIconChooserDialogPrivate *priv;
priv = xapp_icon_chooser_dialog_get_instance_private (user_data);
gtk_entry_set_text (GTK_ENTRY (priv->search_bar), priv->default_icon);
}