diff options
Diffstat (limited to 'src/gui/iconSelectWindow.vala')
-rw-r--r-- | src/gui/iconSelectWindow.vala | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala new file mode 100644 index 0000000..ce610ea --- /dev/null +++ b/src/gui/iconSelectWindow.vala @@ -0,0 +1,450 @@ +///////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011-2015 by Simon Schneegans +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +///////////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A window which allows selection of an Icon of the user's current icon +/// theme. Custom icons/images can be selested as well. Loading of icons +/// happens in an extra thread and a spinner is displayed while loading. +///////////////////////////////////////////////////////////////////////// + +public class IconSelectWindow : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// This signal gets emitted when the user selects a new icon. + ///////////////////////////////////////////////////////////////////// + + public signal void on_ok(string icon_name); + + ///////////////////////////////////////////////////////////////////// + /// Stores the currently selected icon. + ///////////////////////////////////////////////////////////////////// + + private string active_icon = ""; + + ///////////////////////////////////////////////////////////////////// + /// The ListStore storing all theme-icons. + ///////////////////////////////////////////////////////////////////// + + private static Gtk.ListStore icon_list = null; + + ///////////////////////////////////////////////////////////////////// + /// True, if the icon theme is currently reloaded. + ///////////////////////////////////////////////////////////////////// + + private static bool loading = false; + + ///////////////////////////////////////////////////////////////////// + /// If set to true, the icon list will be reloaded next time the + /// window opens. + ///////////////////////////////////////////////////////////////////// + + private static bool need_reload = true; + + ///////////////////////////////////////////////////////////////////// + /// Icons of these contexts won't appear in the list. + ///////////////////////////////////////////////////////////////////// + + private const string disabled_contexts = "Animations, FileSystems"; + + ///////////////////////////////////////////////////////////////////// + /// The list of icons, filtered according to the chosen type and + /// filter string. + ///////////////////////////////////////////////////////////////////// + + private Gtk.TreeModelFilter icon_list_filtered = null; + + ///////////////////////////////////////////////////////////////////// + /// The Gtk widget displaying the icons. + ///////////////////////////////////////////////////////////////////// + + private Gtk.IconView icon_view = null; + + ///////////////////////////////////////////////////////////////////// + /// This spinner is displayed when the icons are loaded. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Spinner spinner = null; + + ///////////////////////////////////////////////////////////////////// + /// A Gtk widget used for custom icon/image selection. + ///////////////////////////////////////////////////////////////////// + + private Gtk.FileChooserWidget file_chooser = null; + + ///////////////////////////////////////////////////////////////////// + /// The notebook containing the different icon choice possibilities: + /// from the theme or custom. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Notebook tabs = null; + + ///////////////////////////////////////////////////////////////////// + /// The main window. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Window window = null; + + ///////////////////////////////////////////////////////////////////// + /// A little structure containing data for one icon in the icon_view. + ///////////////////////////////////////////////////////////////////// + + private class ListEntry { + public string name; + public IconContext context; + public Gdk.Pixbuf pixbuf; + } + + ///////////////////////////////////////////////////////////////////// + /// This queue is used for icon loading. A loading thread pushes + /// icons into it --- the main thread updates the icon_view + /// accordingly. + ///////////////////////////////////////////////////////////////////// + + private GLib.AsyncQueue<ListEntry?> load_queue; + + ///////////////////////////////////////////////////////////////////// + /// Possible icon types. + ///////////////////////////////////////////////////////////////////// + + private enum IconContext { + ALL, + APPS, + ACTIONS, + PLACES, + FILES, + EMOTES, + OTHER + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new IconSelectWindow. + ///////////////////////////////////////////////////////////////////// + + public IconSelectWindow(Gtk.Window parent) { + try { + this.load_queue = new GLib.AsyncQueue<ListEntry?>(); + + if (IconSelectWindow.icon_list == null) { + IconSelectWindow.icon_list = new Gtk.ListStore(3, typeof(string), // icon name + typeof(IconContext), // icon type + typeof(Gdk.Pixbuf)); // the icon itself + + // disable sorting until all icons are loaded + // else loading becomes horribly slow + IconSelectWindow.icon_list.set_default_sort_func(() => {return 0;}); + + // reload if icon theme changes + Gtk.IconTheme.get_default().changed.connect(() => { + if (this.window.visible) load_icons(); + else IconSelectWindow.need_reload = true; + }); + } + + // make the icon_view filterable + this.icon_list_filtered = new Gtk.TreeModelFilter(IconSelectWindow.icon_list, null); + + Gtk.Builder builder = new Gtk.Builder(); + + builder.add_from_file (Paths.ui_files + "/icon_select.ui"); + + this.window = builder.get_object("window") as Gtk.Window; + this.window.set_transient_for(parent); + this.window.set_modal(true); + + this.tabs = builder.get_object("tabs") as Gtk.Notebook; + + this.spinner = builder.get_object("spinner") as Gtk.Spinner; + this.spinner.start(); + + (builder.get_object("ok-button") as Gtk.Button).clicked.connect(on_ok_button_clicked); + (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(on_cancel_button_clicked); + + var combo_box = builder.get_object("combo-box") as Gtk.Box; + + // context combo + var context_combo = new Gtk.ComboBoxText(); + context_combo.append_text(_("All icons")); + context_combo.append_text(_("Applications")); + context_combo.append_text(_("Actions")); + context_combo.append_text(_("Places")); + context_combo.append_text(_("File types")); + context_combo.append_text(_("Emotes")); + context_combo.append_text(_("Miscellaneous")); + + context_combo.set_active(0); + + context_combo.changed.connect(() => { + this.icon_list_filtered.refilter(); + }); + + combo_box.pack_start(context_combo, false, false); + + // string filter entry + var filter = builder.get_object("filter-entry") as Gtk.Entry; + + // only display items which have the selected type + // and whose name contains the text entered in the entry + this.icon_list_filtered.set_visible_func((model, iter) => { + string name = ""; + IconContext context = IconContext.ALL; + model.get(iter, 0, out name); + model.get(iter, 1, out context); + + if (name == null) return false; + + return (context_combo.get_active() == context || + context_combo.get_active() == IconContext.ALL) && + name.down().contains(filter.text.down()); + }); + + // clear when the users clicks on the "clear" icon + filter.icon_release.connect((pos, event) => { + if (pos == Gtk.EntryIconPosition.SECONDARY) + filter.text = ""; + }); + + // refilter on input + filter.notify["text"].connect(() => { + this.icon_list_filtered.refilter(); + }); + + // container for the icon_view + var scroll = builder.get_object("icon-scrolledwindow") as Gtk.ScrolledWindow; + + // displays the filtered icons + this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered); + this.icon_view.item_width = 32; + this.icon_view.item_padding = 2; + this.icon_view.pixbuf_column = 2; + this.icon_view.tooltip_column = 0; + + // set active_icon if selection changes + this.icon_view.selection_changed.connect(() => { + foreach (var path in this.icon_view.get_selected_items()) { + Gtk.TreeIter iter; + this.icon_list_filtered.get_iter(out iter, path); + this.icon_list_filtered.get(iter, 0, out this.active_icon); + } + }); + + // hide this window when the user activates an icon + this.icon_view.item_activated.connect((path) => { + Gtk.TreeIter iter; + this.icon_list_filtered.get_iter(out iter, path); + this.icon_list_filtered.get(iter, 0, out this.active_icon); + this.on_ok(this.active_icon); + this.window.hide(); + }); + + scroll.add(this.icon_view); + + // file chooser widget + this.file_chooser = builder.get_object("filechooser") as Gtk.FileChooserWidget; + var file_filter = new Gtk.FileFilter(); + file_filter.add_pixbuf_formats(); + file_filter.set_filter_name(_("All supported image formats")); + file_chooser.add_filter(file_filter); + + // set active_icon if the user selected a file + file_chooser.selection_changed.connect(() => { + if (file_chooser.get_filename() != null && + GLib.FileUtils.test(file_chooser.get_filename(), + GLib.FileTest.IS_REGULAR)) + + this.active_icon = file_chooser.get_filename(); + }); + + // hide this window when the user activates a file + file_chooser.file_activated.connect(() => { + this.active_icon = file_chooser.get_filename(); + this.on_ok(this.active_icon); + this.window.hide(); + }); + + this.window.set_focus(this.icon_view); + this.window.delete_event.connect(this.window.hide_on_delete); + + } catch (GLib.Error e) { + error("Could not load UI: %s\n", e.message); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Displays the window. The icons are reloaded if neccessary. + ///////////////////////////////////////////////////////////////////// + + public void show() { + this.window.show_all(); + this.spinner.hide(); + + if (IconSelectWindow.need_reload) { + IconSelectWindow.need_reload = false; + this.load_icons(); + } + } + + public static void clear_icons() { + if (IconSelectWindow.icon_list != null) { + IconSelectWindow.need_reload = true; + IconSelectWindow.icon_list.clear(); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Makes the window select the icon of the given Pie. + ///////////////////////////////////////////////////////////////////// + + public void set_icon(string icon) { + this.active_icon = icon; + + if (icon.contains("/")) { + this.file_chooser.set_filename(icon); + this.tabs.set_current_page(1); + } else { + this.icon_list_filtered.foreach((model, path, iter) => { + string name = ""; + model.get(iter, 0, out name); + + if (name == icon) { + this.icon_view.select_path(path); + this.icon_view.scroll_to_path(path, true, 0.5f, 0.0f); + this.icon_view.set_cursor(path, null, false); + } + return (name == icon); + }); + + this.tabs.set_current_page(0); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the user clicks the ok button. + ///////////////////////////////////////////////////////////////////// + + private void on_ok_button_clicked() { + this.on_ok(this.active_icon); + this.window.hide(); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the user clicks the cancel button. + ///////////////////////////////////////////////////////////////////// + + private void on_cancel_button_clicked() { + this.window.hide(); + } + + ///////////////////////////////////////////////////////////////////// + /// (Re)load all icons. + ///////////////////////////////////////////////////////////////////// + + private void load_icons() { + // only if it's not loading currently + if (!IconSelectWindow.loading) { + IconSelectWindow.loading = true; + IconSelectWindow.icon_list.clear(); + + // display the spinner + if (spinner != null) + this.spinner.visible = true; + + // disable sorting of the icon_view - else it's horribly slow + IconSelectWindow.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING); + + this.load_all.begin(); + + // insert loaded icons every 200 ms + Timeout.add(200, () => { + while (this.load_queue.length() > 0) { + var new_entry = this.load_queue.pop(); + Gtk.TreeIter current; + IconSelectWindow.icon_list.append(out current); + IconSelectWindow.icon_list.set(current, 0, new_entry.name, + 1, new_entry.context, + 2, new_entry.pixbuf); + } + + // enable sorting of the icon_view if loading finished + if (!IconSelectWindow.loading) { + IconSelectWindow.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING); + } + + return loading; + }); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Loads all icons of an icon theme and pushes them into the + /// load_queue. + ///////////////////////////////////////////////////////////////////// + + private async void load_all() { + var icon_theme = Gtk.IconTheme.get_default(); + + foreach (var context in icon_theme.list_contexts()) { + if (!disabled_contexts.contains(context)) { + foreach (var icon in icon_theme.list_icons(context)) { + + IconContext icon_context = IconContext.OTHER; + switch(context) { + case "Apps": case "Applications": + icon_context = IconContext.APPS; break; + case "Emotes": + icon_context = IconContext.EMOTES; break; + case "Places": case "Devices": + icon_context = IconContext.PLACES; break; + case "Mimetypes": + icon_context = IconContext.FILES; break; + case "Actions": + icon_context = IconContext.ACTIONS; break; + default: break; + } + + Idle.add(load_all.callback); + yield; + + try { + // create a new entry for the queue + var new_entry = new ListEntry(); + new_entry.name = icon; + new_entry.context = icon_context; + new_entry.pixbuf = icon_theme.load_icon(icon, 32, 0); + + // some icons have only weird sizes... do not include them + if (new_entry.pixbuf.width == 32) + this.load_queue.push(new_entry); + + } catch (GLib.Error e) { + warning("Failed to load image " + icon); + } + } + } + } + + // finished loading + IconSelectWindow.loading = false; + + // hide the spinner + if (spinner != null) + spinner.visible = false; + } +} + +} |