From 6451a495637c6e3047a5a56573cffc6e3de9a515 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Wed, 19 Oct 2011 10:56:04 +0200 Subject: Imported Upstream version 0.2+gitdfdad95 --- src/gui/about.vala | 43 ++ src/gui/cellRendererIcon.vala | 132 ++++++ src/gui/iconSelectWindow.vala | 349 ++++++++++++++ src/gui/indicator.vala | 160 +++++++ src/gui/pieList.vala | 1018 +++++++++++++++++++++++++++++++++++++++++ src/gui/preferences.vala | 338 ++++++++++++++ src/gui/themeList.vala | 95 ++++ src/gui/tipViewer.vala | 172 +++++++ 8 files changed, 2307 insertions(+) create mode 100644 src/gui/about.vala create mode 100644 src/gui/cellRendererIcon.vala create mode 100644 src/gui/iconSelectWindow.vala create mode 100644 src/gui/indicator.vala create mode 100644 src/gui/pieList.vala create mode 100644 src/gui/preferences.vala create mode 100644 src/gui/themeList.vala create mode 100644 src/gui/tipViewer.vala (limited to 'src/gui') diff --git a/src/gui/about.vala b/src/gui/about.vala new file mode 100644 index 0000000..1ace9cb --- /dev/null +++ b/src/gui/about.vala @@ -0,0 +1,43 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A simple about Dialog. +///////////////////////////////////////////////////////////////////////// + +public class GnomePieAboutDialog: Gtk.AboutDialog { + + public GnomePieAboutDialog () { + string[] devs = {"Simon Schneegans ", + "Francesco Piccinno"}; + string[] artists = {"Simon Schneegans "}; + GLib.Object ( + artists : artists, + authors : devs, + copyright : "Copyright (C) 2011 Simon Schneegans ", + program_name: "Gnome-Pie", + logo_icon_name: "gnome-pie", + website: "http://www.simonschneegans.de/?page_id=12", + website_label: "www.gnome-pie.simonschneegans.de", + version: "0.2" + ); + } +} + +} diff --git a/src/gui/cellRendererIcon.vala b/src/gui/cellRendererIcon.vala new file mode 100644 index 0000000..959a0b7 --- /dev/null +++ b/src/gui/cellRendererIcon.vala @@ -0,0 +1,132 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A cellrenderer which displays an Icon. When clicked onto, a window +/// opens for selecting another icon. This needs to be a subclass of +/// Gtk.CellRendererText because Gtk.CellRendererPixbuf can't receive +/// click events. Internally it stores a Gtk.CellRendererPixbuf +/// which renders and stuff. +///////////////////////////////////////////////////////////////////////// + +public class CellRendererIcon : Gtk.CellRendererText { + + ///////////////////////////////////////////////////////////////////// + /// This signal is emitted when the user selects another icon. + ///////////////////////////////////////////////////////////////////// + + public signal void on_select(string path, string icon); + + ///////////////////////////////////////////////////////////////////// + /// The IconSelectWindow which is shown on click. + ///////////////////////////////////////////////////////////////////// + + private IconSelectWindow select_window = null; + + ///////////////////////////////////////////////////////////////////// + /// The internal Renderer used for drawing. + ///////////////////////////////////////////////////////////////////// + + private Gtk.CellRendererPixbuf renderer = null; + + ///////////////////////////////////////////////////////////////////// + /// A helper variable, needed to emit the current path. + ///////////////////////////////////////////////////////////////////// + + private string current_path = ""; + + public string icon_name { get; set; } + + ///////////////////////////////////////////////////////////////////// + /// Forward some parts of the CellRendererPixbuf's interface. + ///////////////////////////////////////////////////////////////////// + + public bool follow_state { + get { return renderer.follow_state; } + set { renderer.follow_state = value; } + } + + public bool icon_sensitive { + get { return renderer.sensitive; } + set { renderer.sensitive = value; } + } + + public Gdk.Pixbuf pixbuf { + owned get { return renderer.pixbuf; } + set { renderer.pixbuf = value; } + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new CellRendererIcon. + ///////////////////////////////////////////////////////////////////// + + public CellRendererIcon() { + this.select_window = new IconSelectWindow(); + this.renderer = new Gtk.CellRendererPixbuf(); + + this.select_window.on_select.connect((icon) => { + this.on_select(current_path, icon); + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Forward some parts of the CellRendererPixbuf's interface. + ///////////////////////////////////////////////////////////////////// + + public override void get_size (Gtk.Widget widget, Gdk.Rectangle? cell_area, + out int x_offset, out int y_offset, + out int width, out int height) { + + this.renderer.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height); + } + + ///////////////////////////////////////////////////////////////////// + /// Forward some parts of the CellRendererPixbuf's interface. + ///////////////////////////////////////////////////////////////////// + + public override void render (Gdk.Window window, Gtk.Widget widget, + Gdk.Rectangle bg_area, + Gdk.Rectangle cell_area, + Gdk.Rectangle expose_area, + Gtk.CellRendererState flags) { + + this.renderer.render(window, widget, bg_area, cell_area, expose_area, flags); + } + + ///////////////////////////////////////////////////////////////////// + /// Open the IconSelectWindow on click. + ///////////////////////////////////////////////////////////////////// + + public override unowned Gtk.CellEditable start_editing( + Gdk.Event event, Gtk.Widget widget, string path, Gdk.Rectangle bg_area, + Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { + + this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); + this.select_window.set_modal(true); + + this.current_path = path; + this.select_window.show(); + this.select_window.active_icon = this.icon_name; + + return this.renderer.start_editing(event, widget, path, bg_area, cell_area, flags); + } +} + +} + diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala new file mode 100644 index 0000000..2274ec5 --- /dev/null +++ b/src/gui/iconSelectWindow.vala @@ -0,0 +1,349 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A window which allows selection of an Icon of the user's current icon +/// theme. Loading of Icons happens in an extra thread and a spinner is +/// displayed while loading. +///////////////////////////////////////////////////////////////////////// + +public class IconSelectWindow : Gtk.Dialog { + + private static Gtk.ListStore icon_list = null; + + private static bool loading {get; set; default = false;} + private static bool need_reload {get; set; default = true;} + + private const string disabled_contexts = "Animations, FileSystems, MimeTypes"; + private Gtk.TreeModelFilter icon_list_filtered = null; + private Gtk.IconView icon_view = null; + private Gtk.Spinner spinner = null; + + private Gtk.FileChooserWidget file_chooser = null; + + private Gtk.Notebook tabs = null; + + private class ListEntry { + public string name; + public IconContext context; + public Gdk.Pixbuf pixbuf; + } + + private GLib.AsyncQueue load_queue; + + private enum IconContext { + ALL, + APPS, + ACTIONS, + PLACES, + FILES, + EMOTES, + OTHER + } + + public string _active_icon = "application-default-icon"; + + public string active_icon { + get { + return _active_icon; + } + set { + if (value.contains("/")) { + this.file_chooser.set_filename(value); + 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 == value) { + 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 == value); + }); + + this.tabs.set_current_page(0); + } + } + } + + public signal void on_select(string icon_name); + + public IconSelectWindow() { + this.title = _("Choose an Icon"); + this.set_size_request(520, 520); + this.delete_event.connect(hide_on_delete); + this.load_queue = new GLib.AsyncQueue(); + + if (this.icon_list == null) { + this.icon_list = new Gtk.ListStore(3, typeof(string), typeof(IconContext), typeof(Gdk.Pixbuf)); + this.icon_list.set_default_sort_func(() => {return 0;}); + + Gtk.IconTheme.get_default().changed.connect(() => { + if (this.visible) load_icons(); + else need_reload = true; + }); + } + + this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null); + + var container = new Gtk.VBox(false, 12); + container.set_border_width(12); + + // tab container + this.tabs = new Gtk.Notebook(); + + var theme_tab = new Gtk.VBox(false, 12); + theme_tab.set_border_width(12); + + var context_combo = new Gtk.ComboBox.text(); + 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(); + }); + + theme_tab.pack_start(context_combo, false, false); + + var filter = new Gtk.Entry(); + filter.primary_icon_stock = Gtk.Stock.FIND; + filter.primary_icon_activatable = false; + filter.secondary_icon_stock = Gtk.Stock.CLEAR; + theme_tab.pack_start(filter, false, false); + + 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()); + }); + + filter.icon_release.connect((pos, event) => { + if (pos == Gtk.EntryIconPosition.SECONDARY) + filter.text = ""; + }); + + filter.notify["text"].connect(() => { + this.icon_list_filtered.refilter(); + }); + + var scroll = new Gtk.ScrolledWindow (null, null); + scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + scroll.set_shadow_type (Gtk.ShadowType.IN); + + this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered); + this.icon_view.item_width = 32; + this.icon_view.item_padding = 3; + this.icon_view.pixbuf_column = 2; + this.icon_view.tooltip_column = 0; + + 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); + icon_list_filtered.get(iter, 0, out this._active_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_select(this._active_icon); + this.hide(); + }); + + scroll.add(this.icon_view); + + theme_tab.pack_start(scroll, true, true); + + tabs.append_page(theme_tab, new Gtk.Label(_("Icon Theme"))); + + var custom_tab = new Gtk.VBox(false, 6); + custom_tab.border_width = 12; + + this.file_chooser = new Gtk.FileChooserWidget(Gtk.FileChooserAction.OPEN); + var file_filter = new Gtk.FileFilter(); + file_filter.add_pixbuf_formats(); + file_filter.set_name(_("All supported image formats")); + file_chooser.add_filter(file_filter); + + 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(); + }); + + file_chooser.file_activated.connect(() => { + this._active_icon = file_chooser.get_filename(); + this.on_select(this._active_icon); + this.hide(); + }); + + + custom_tab.pack_start(file_chooser, true, true); + + tabs.append_page(custom_tab, new Gtk.Label(_("Custom Icon"))); + + container.pack_start(tabs, true, true); + + // button box + var bottom_box = new Gtk.HBox(false, 0); + + var bbox = new Gtk.HButtonBox(); + bbox.set_spacing(6); + bbox.set_layout(Gtk.ButtonBoxStyle.END); + + var cancel_button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL); + cancel_button.clicked.connect(() => { + this.hide(); + }); + bbox.pack_start(cancel_button); + + var ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK); + ok_button.clicked.connect(() => { + this.on_select(this._active_icon); + this.hide(); + }); + bbox.pack_start(ok_button); + + bottom_box.pack_end(bbox, false); + + this.spinner = new Gtk.Spinner(); + this.spinner.set_size_request(16, 16); + this.spinner.start(); + + bottom_box.pack_start(this.spinner, false, false); + + container.pack_start(bottom_box, false, false); + + this.vbox.pack_start(container, true, true); + + this.vbox.show_all(); + + this.set_focus(this.icon_view); + } + + public override void show() { + base.show(); + this.action_area.hide(); + + if (this.need_reload) { + this.need_reload = false; + this.load_icons(); + } + } + + private void load_icons() { + if (!this.loading) { + this.loading = true; + this.icon_list.clear(); + + if (spinner != null) + this.spinner.visible = true; + + this.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING); + + try { + unowned Thread loader = Thread.create(load_thread, false); + loader.set_priority(ThreadPriority.LOW); + } catch (GLib.ThreadError e) { + error("Failed to create icon loader thread!"); + } + + Timeout.add(200, () => { + while (this.load_queue.length() > 0) { + var new_entry = this.load_queue.pop(); + Gtk.TreeIter current; + this.icon_list.append(out current); + this.icon_list.set(current, 0, new_entry.name, + 1, new_entry.context, + 2, new_entry.pixbuf); + } + + if (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING); + + return loading; + }); + } + } + + private void* load_thread() { + 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; + } + + try { + 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); + } + } + } + } + + this.loading = false; + + if (spinner != null) + spinner.visible = this.loading; + + return null; + } +} + +} diff --git a/src/gui/indicator.vala b/src/gui/indicator.vala new file mode 100644 index 0000000..8033cb7 --- /dev/null +++ b/src/gui/indicator.vala @@ -0,0 +1,160 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// An appindicator sitting in the panel. It owns the settings menu. +///////////////////////////////////////////////////////////////////////// + +public class Indicator : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// The internally used indicator. + ///////////////////////////////////////////////////////////////////// + + #if HAVE_APPINDICATOR + private AppIndicator.Indicator indicator { private get; private set; } + #else + private Gtk.StatusIcon indicator {private get; private set; } + private Gtk.Menu menu {private get; private set; } + #endif + + ///////////////////////////////////////////////////////////////////// + /// The Preferences Menu of Gnome-Pie. + ///////////////////////////////////////////////////////////////////// + + private Preferences prefs { private get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// Returns true, when the indicator is currently visible. + ///////////////////////////////////////////////////////////////////// + + public bool active { + get { + + #if HAVE_APPINDICATOR + return indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE; + #else + return indicator.get_visible(); + #endif + } + set { + #if HAVE_APPINDICATOR + if (value) indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); + else indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE); + #else + indicator.set_visible(value); + #endif + } + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, constructs a new Indicator, residing in the user's panel. + ///////////////////////////////////////////////////////////////////// + + public Indicator() { + #if HAVE_APPINDICATOR + string path = ""; + string icon = "indicator-applet"; + try { + path = GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources"; + icon = "gnome-pie-indicator"; + } catch (GLib.FileError e) { + warning("Failed to get path of executable!"); + } + + this.indicator = new AppIndicator.Indicator.with_path("Gnome-Pie", icon, + AppIndicator.IndicatorCategory.APPLICATION_STATUS, path); + var menu = new Gtk.Menu(); + #else + this.indicator = new Gtk.StatusIcon(); + try { + var file = GLib.File.new_for_path(GLib.Path.build_filename( + GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources", + "gnome-pie-indicator.svg" + )); + + if (!file.query_exists()) + this.indicator.set_from_icon_name("gnome-pie-indicator"); + else + this.indicator.set_from_file(file.get_path()); + } catch (GLib.FileError e) { + warning("Failed to get path of executable!"); + this.indicator.set_from_icon_name("gnome-pie-indicator"); + } + + this.menu = new Gtk.Menu(); + var menu = this.menu; + #endif + + this.prefs = new Preferences(); + + // preferences item + var item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.PREFERENCES, null); + item.activate.connect(() => { + this.prefs.show(); + }); + + item.show(); + menu.append(item); + + // about item + item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.ABOUT, null); + item.show(); + item.activate.connect(() => { + var about = new GnomePieAboutDialog(); + about.run(); + about.destroy(); + }); + menu.append(item); + + // separator + var sepa = new Gtk.SeparatorMenuItem(); + sepa.show(); + menu.append(sepa); + + // quit item + item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null); + item.activate.connect(Gtk.main_quit); + item.show(); + menu.append(item); + + #if HAVE_APPINDICATOR + this.indicator.set_menu(menu); + #else + this.indicator.popup_menu.connect((btn, time) => { + this.menu.popup(null, null, null, btn, time); + }); + #endif + + this.active = Config.global.show_indicator; + Config.global.notify["show-indicator"].connect((s, p) => { + this.active = Config.global.show_indicator; + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Shows the preferences menu. + ///////////////////////////////////////////////////////////////////// + + public void show_preferences() { + this.prefs.show(); + } +} + +} diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala new file mode 100644 index 0000000..df6135a --- /dev/null +++ b/src/gui/pieList.vala @@ -0,0 +1,1018 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +// A very complex Widget. This is by far the most ugly file of this project +// but well, this list *is* complex... sorry ;) + +class PieList : Gtk.TreeView { + + private Gtk.ListStore groups; + private Gtk.ListStore pies; + private Gtk.ListStore actions; + private Gtk.TreeStore data; + + private const int small_icon = 24; + private const int large_icon = 36; + + // data positions in the data ListStore + private enum DataPos {IS_QUICKACTION, ICON, NAME, TYPE_ID, ACTION_TYPE, + ICON_PIXBUF, FONT_WEIGHT, ICON_NAME_EDITABLE, QUICKACTION_VISIBLE, QUICKACTION_ACTIVATABLE, + TYPE_VISIBLE, GROUP_VISIBLE, APP_VISIBLE, KEY_VISIBLE, PIE_VISIBLE, + URI_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP, + DISPLAY_COMMAND_KEY, DISPLAY_COMMAND_PIE, DISPLAY_COMMAND_URI, + REAL_COMMAND_GROUP, REAL_COMMAND_PIE, REAL_COMMAND_KEY} + + // data positions in the actions ListStore + private enum ActionPos {NAME, TYPE, CAN_QUICKACTION, ICON_NAME_EDITABLE} + + // data positions in the pies ListStore + private enum PiePos {NAME, ID} + + // data positions in the groups ListStore + private enum GroupPos {NAME, TYPE, ICON} + + public PieList() { + GLib.Object(); + + Gtk.TreeIter last; + + // group choices + this.groups = new Gtk.ListStore(3, typeof(string), // group name + typeof(string), // group type + typeof(string)); // group icon + + // add all registered group types + foreach (var type in GroupRegistry.types) { + this.groups.append(out last); + this.groups.set(last, GroupPos.NAME, GroupRegistry.names[type], + GroupPos.TYPE, type.name(), + GroupPos.ICON, GroupRegistry.icons[type]); + } + + // pie choices + this.pies = new Gtk.ListStore(2, typeof(string), // pie name + typeof(string)); // pie id + + // action type choices + this.actions = new Gtk.ListStore(4, typeof(string), // type name + typeof(string), // action type + typeof(bool), // can be quickaction + typeof(bool)); // icon/name editable + + // add all registered action types + foreach (var type in ActionRegistry.types) { + this.actions.append(out last); + this.actions.set(last, ActionPos.NAME, ActionRegistry.names[type], + ActionPos.TYPE, type.name(), + ActionPos.CAN_QUICKACTION, true, + ActionPos.ICON_NAME_EDITABLE, ActionRegistry.icon_name_editables[type]); + } + // and one type for groups + this.actions.append(out last); + this.actions.set(last, ActionPos.NAME, _("Slice group"), + ActionPos.TYPE, typeof(ActionGroup).name(), + ActionPos.CAN_QUICKACTION, false, + ActionPos.ICON_NAME_EDITABLE, false); + + // main data model + this.data = new Gtk.TreeStore(24, typeof(bool), // is quickaction + typeof(string), // icon + typeof(string), // name + typeof(string), // slice: type label, pie: "ID: %id" + typeof(string), // typeof(action), typeof(ActionGroup).name() if group action, pie_id if Pie + + typeof(Gdk.Pixbuf), // icon pixbuf + typeof(int), // font weight + + typeof(bool), // icon/name editable + + typeof(bool), // quickaction visible + typeof(bool), // quickaction activatable + typeof(bool), // type visible + typeof(bool), // group renderer visible + typeof(bool), // app renderer visible + typeof(bool), // key renderer visible + typeof(bool), // pie renderer visible + typeof(bool), // uri renderer visible + + typeof(string), // display command group + typeof(string), // display command app + typeof(string), // display command key + typeof(string), // display command pie + typeof(string), // display command uri + + typeof(string), // real command group + typeof(string), // real command pie + typeof(string)); // real command key + + + this.set_model(this.data); + this.set_grid_lines(Gtk.TreeViewGridLines.NONE); + this.set_enable_tree_lines(false); + this.set_reorderable(false); + this.set_level_indentation(-10); + + // create the gui + // icon column + var icon_column = new Gtk.TreeViewColumn(); + icon_column.title = _("Icon"); + icon_column.expand = false; + + // quickaction checkbox + var check_render = new Gtk.CellRendererToggle(); + check_render.activatable = true; + check_render.radio = true; + check_render.width = 15; + + check_render.toggled.connect((path) => { + Gtk.TreeIter toggled; + this.data.get_iter_from_string(out toggled, path); + + bool current = false; + this.data.get(toggled, DataPos.IS_QUICKACTION, out current); + + // set all others off + Gtk.TreeIter parent; + this.data.iter_parent(out parent, toggled); + string parent_pos = this.data.get_string_from_iter(parent); + int child_count = this.data.iter_n_children(parent); + + for (int i=0; i { + Gtk.TreeIter iter; + this.data.get_iter_from_string(out iter, path); + int icon_size = this.data.iter_depth(iter) == 0 ? this.large_icon : this.small_icon; + + this.data.set(iter, DataPos.ICON, icon_name); + this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(icon_name, icon_size)); + + this.update_pie(iter); + this.update_linked(); + }); + + icon_column.pack_start(icon_render, false); + icon_column.add_attribute(icon_render, "icon_name", DataPos.ICON); + icon_column.add_attribute(icon_render, "pixbuf", DataPos.ICON_PIXBUF); + icon_column.add_attribute(icon_render, "editable", DataPos.ICON_NAME_EDITABLE); + icon_column.add_attribute(icon_render, "icon_sensitive", DataPos.ICON_NAME_EDITABLE); + + // command column + var command_column = new Gtk.TreeViewColumn(); + command_column.title = _("Command"); + command_column.resizable = true; + + // slice group + var command_renderer_group = new Gtk.CellRendererCombo(); + command_renderer_group.editable = true; + command_renderer_group.has_entry = false; + command_renderer_group.text_column = 0; + command_renderer_group.ellipsize = Pango.EllipsizeMode.END; + command_renderer_group.model = this.groups; + + command_renderer_group.changed.connect((path, iter) => { + string display_name; + string type; + string icon; + + this.groups.get(iter, GroupPos.NAME, out display_name); + this.groups.get(iter, GroupPos.TYPE, out type); + this.groups.get(iter, GroupPos.ICON, out icon); + + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_GROUP, display_name); + this.data.set(data_iter, DataPos.REAL_COMMAND_GROUP, type); + this.data.set(data_iter, DataPos.NAME, display_name); + this.data.set(data_iter, DataPos.ICON, icon); + + this.update_pie(data_iter); + }); + + command_column.pack_end(command_renderer_group, true); + command_column.add_attribute(command_renderer_group, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_group, "text", DataPos.DISPLAY_COMMAND_GROUP); + command_column.add_attribute(command_renderer_group, "visible", DataPos.GROUP_VISIBLE); + + + // app action + var command_renderer_app = new Gtk.CellRendererText(); + command_renderer_app.editable = true; + command_renderer_app.ellipsize = Pango.EllipsizeMode.END; + + command_renderer_app.edited.connect((path, command) => { + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_APP, command); + + this.update_pie(data_iter); + }); + + command_column.pack_end(command_renderer_app, true); + command_column.add_attribute(command_renderer_app, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_app, "text", DataPos.DISPLAY_COMMAND_APP); + command_column.add_attribute(command_renderer_app, "visible", DataPos.APP_VISIBLE); + + + // key action + var command_renderer_key = new Gtk.CellRendererAccel(); + command_renderer_key.editable = true; + command_renderer_key.ellipsize = Pango.EllipsizeMode.END; + + command_renderer_key.accel_edited.connect((path, key, mods) => { + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + string label = Gtk.accelerator_get_label(key, mods); + string accelerator = Gtk.accelerator_name(key, mods); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, label); + this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, accelerator); + + this.update_pie(data_iter); + }); + + command_renderer_key.accel_cleared.connect((path) => { + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, _("Not bound")); + this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, ""); + + this.update_pie(data_iter); + }); + + command_column.pack_end(command_renderer_key, true); + command_column.add_attribute(command_renderer_key, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_key, "text", DataPos.DISPLAY_COMMAND_KEY); + command_column.add_attribute(command_renderer_key, "visible", DataPos.KEY_VISIBLE); + + + // pie action + var command_renderer_pie = new Gtk.CellRendererCombo(); + command_renderer_pie.editable = true; + command_renderer_pie.has_entry = false; + command_renderer_pie.text_column = 0; + command_renderer_pie.ellipsize = Pango.EllipsizeMode.END; + command_renderer_pie.model = this.pies; + + command_renderer_pie.changed.connect((path, iter) => { + string name; + string id; + + this.pies.get(iter, PiePos.NAME, out name); + this.pies.get(iter, PiePos.ID, out id); + + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_PIE, name); + this.data.set(data_iter, DataPos.REAL_COMMAND_PIE, id); + + this.update_pie(data_iter); + this.update_linked(); + }); + + command_column.pack_end(command_renderer_pie, true); + command_column.add_attribute(command_renderer_pie, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_pie, "text", DataPos.DISPLAY_COMMAND_PIE); + command_column.add_attribute(command_renderer_pie, "visible", DataPos.PIE_VISIBLE); + + + // uri action + var command_renderer_uri = new Gtk.CellRendererText(); + command_renderer_uri.editable = true; + command_renderer_uri.ellipsize = Pango.EllipsizeMode.END; + + command_renderer_uri.edited.connect((path, uri) => { + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_URI, uri); + + this.update_pie(data_iter); + }); + + command_column.pack_end(command_renderer_uri, true); + command_column.add_attribute(command_renderer_uri, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_uri, "text", DataPos.DISPLAY_COMMAND_URI); + command_column.add_attribute(command_renderer_uri, "visible", DataPos.URI_VISIBLE); + + + // type column + var type_column = new Gtk.TreeViewColumn(); + type_column.title = _("Pie-ID / Action type"); + type_column.resizable = true; + + var type_render = new Gtk.CellRendererCombo(); + type_render.editable = true; + type_render.has_entry = false; + type_render.model = actions; + type_render.text_column = 0; + type_render.ellipsize = Pango.EllipsizeMode.END; + + // change command_render's visibility accordingly + type_render.changed.connect((path, iter) => { + string text = ""; + string type; + bool can_quickaction; + bool icon_name_editable; + + this.actions.get(iter, ActionPos.NAME, out text); + this.actions.get(iter, ActionPos.TYPE, out type); + this.actions.get(iter, ActionPos.CAN_QUICKACTION, out can_quickaction); + this.actions.get(iter, ActionPos.ICON_NAME_EDITABLE, out icon_name_editable); + + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.TYPE_ID, text); + this.data.set(data_iter, DataPos.ACTION_TYPE, type); + this.data.set(data_iter, DataPos.QUICKACTION_ACTIVATABLE, can_quickaction); + this.data.set(data_iter, DataPos.ICON_NAME_EDITABLE, icon_name_editable); + + // set all command renderes invisible + this.data.set(data_iter, DataPos.GROUP_VISIBLE, false); + this.data.set(data_iter, DataPos.APP_VISIBLE, false); + this.data.set(data_iter, DataPos.KEY_VISIBLE, false); + this.data.set(data_iter, DataPos.PIE_VISIBLE, false); + this.data.set(data_iter, DataPos.URI_VISIBLE, false); + + // set one visible + int type_id = 0; + if(type == typeof(AppAction).name()) type_id = 1; + else if(type == typeof(KeyAction).name()) type_id = 2; + else if(type == typeof(PieAction).name()) type_id = 3; + else if(type == typeof(UriAction).name()) type_id = 4; + else type_id = 0; + + this.data.set(data_iter, DataPos.GROUP_VISIBLE + type_id, true); + + this.update_linked(); + this.update_pie(data_iter); + + //this.set_cursor(new Gtk.TreePath.from_string(path), command_column, true); + }); + + type_column.pack_start(type_render, true); + type_column.add_attribute(type_render, "sensitive", DataPos.TYPE_VISIBLE); + type_column.add_attribute(type_render, "editable", DataPos.TYPE_VISIBLE); + type_column.add_attribute(type_render, "text", DataPos.TYPE_ID); + + // name column + var name_column = new Gtk.TreeViewColumn(); + name_column.title = _("Name"); + name_column.expand = true; + name_column.resizable = true; + + var name_render = new Gtk.CellRendererText(); + name_render.editable = true; + name_render.ellipsize = Pango.EllipsizeMode.END; + + name_render.edited.connect((path, text) => { + Gtk.TreeIter iter; + this.data.get_iter_from_string(out iter, path); + + this.data.set(iter, DataPos.NAME, text); + + // try to change icon to a fitting one + string icon; + this.data.get(iter, DataPos.ICON, out icon); + if (icon == "application-default-icon" && Gtk.IconTheme.get_default().has_icon(text.down())) { + this.data.set(iter, DataPos.ICON, text.down()); + } + + this.update_pie(iter); + this.update_linked(); + + //this.set_cursor(new Gtk.TreePath.from_string(path), type_column, true); + }); + + name_column.pack_start(name_render, true); + name_column.add_attribute(name_render, "weight", DataPos.FONT_WEIGHT); + name_column.add_attribute(name_render, "text", DataPos.NAME); + name_column.add_attribute(name_render, "sensitive", DataPos.ICON_NAME_EDITABLE); + name_column.add_attribute(name_render, "editable", DataPos.ICON_NAME_EDITABLE); + + this.append_column(icon_column); + this.append_column(name_column); + this.append_column(type_column); + this.append_column(command_column); + + this.realize.connect(this.load); + + // context menu + var menu = new Gtk.Menu(); + + var item = new Gtk.ImageMenuItem.with_label(_("Add new Pie")); + item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); + item.activate.connect(this.add_empty_pie); + menu.append(item); + + item = new Gtk.ImageMenuItem.with_label(_("Add new Slice")); + item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); + item.activate.connect(this.add_empty_slice); + menu.append(item); + + var sepa = new Gtk.SeparatorMenuItem(); + menu.append(sepa); + + item = new Gtk.ImageMenuItem.with_label(_("Delete")); + item.set_image(new Gtk.Image.from_stock(Gtk.Stock.DELETE, Gtk.IconSize.MENU)); + item.activate.connect(this.delete_selection); + menu.append(item); + + menu.show_all(); + + this.button_press_event.connect((event) => { + if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) { + menu.popup(null, null, null, event.button, event.time); + } + return false; + }); + + // setup drag'n'drop + Gtk.TargetEntry uri_source = {"text/uri-list", 0, 0}; + Gtk.TargetEntry[] entries = { uri_source }; + + this.drag_data_received.connect(this.on_dnd_received); + this.drag_data_get.connect(this.on_dnd_source); + this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); + + this.get_selection().changed.connect(() => { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + if (this.data.iter_depth(selected) == 0) { + this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK); + } else { + this.unset_rows_drag_source(); + } + } + }); + + this.drag_begin.connect(() => { + this.unset_rows_drag_dest(); + }); + + this.drag_end.connect(() => { + this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); + }); + } + + // moves the selected slice up + public void selection_up() { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + Gtk.TreePath path = this.data.get_path(selected); + Gtk.TreeIter? before = null;; + if (path.prev() && this.data.get_iter(out before, path)) { + this.data.swap(selected, before); + this.get_selection().changed(); + this.update_pie(selected); + } + } + } + + // moves the selected slice down + public void selection_down() { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + Gtk.TreePath path = this.data.get_path(selected); + Gtk.TreeIter? after = null; + path.next(); + if (this.data.get_iter(out after, path)) { + this.data.swap(selected, after); + this.get_selection().changed(); + this.update_pie(selected); + } + } + } + + // updates the entire list, checking for changed cross-references via PieActions + // updates their names and icons if needed + private void update_linked() { + this.data.foreach((model, path, iter) => { + string action_type; + this.data.get(iter, DataPos.ACTION_TYPE, out action_type); + + if (action_type == typeof(PieAction).name()) { + string command; + this.data.get(iter, DataPos.REAL_COMMAND_PIE, out command); + + var referee = PieManager.all_pies[command]; + + if (referee != null) { + this.data.set(iter, DataPos.ICON, referee.icon); + this.data.set(iter, DataPos.NAME, referee.name); + this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(referee.icon, this.small_icon)); + this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, referee.name); + } else { + // referenced Pie does not exist anymore or no is selected; + // select the first one... + Gtk.TreeIter first_pie; + this.pies.get_iter_first(out first_pie); + + string name; + string id; + + this.pies.get(first_pie, PiePos.NAME, out name); + this.pies.get(first_pie, PiePos.ID, out id); + + this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, name); + this.data.set(iter, DataPos.REAL_COMMAND_PIE, id); + + update_linked(); + } + } else if (action_type == typeof(ActionGroup).name()) { + string command; + this.data.get(iter, DataPos.REAL_COMMAND_GROUP, out command); + + if (command == "") { + // no group is selected, select the first one... + Gtk.TreeIter first_group; + this.groups.get_iter_first(out first_group); + + string name; + string type; + string icon; + + this.groups.get(first_group, GroupPos.NAME, out name); + this.groups.get(first_group, GroupPos.TYPE, out type); + this.groups.get(first_group, GroupPos.ICON, out icon); + + this.data.set(iter, DataPos.DISPLAY_COMMAND_GROUP, name); + this.data.set(iter, DataPos.NAME, name); + this.data.set(iter, DataPos.REAL_COMMAND_GROUP, type); + this.data.set(iter, DataPos.ICON, icon); + } + } + + return false; + }); + } + + // adds a new, empty pie to the list + private void add_empty_pie() { + var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", ""); + + Gtk.TreeIter last; + this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id); + + Gtk.TreeIter parent; + this.data.append(out parent, null); + this.data.set(parent, DataPos.IS_QUICKACTION, false, + DataPos.ICON, new_one.icon, + DataPos.NAME, new_one.name, + DataPos.TYPE_ID, "ID: " + new_one.id, + DataPos.ACTION_TYPE, new_one.id, + DataPos.ICON_PIXBUF, this.load_icon(new_one.icon, this.large_icon), + DataPos.FONT_WEIGHT, 800, + DataPos.ICON_NAME_EDITABLE, true, + DataPos.QUICKACTION_VISIBLE, false, + DataPos.QUICKACTION_ACTIVATABLE, false, + DataPos.TYPE_VISIBLE, false, + DataPos.GROUP_VISIBLE, false, + DataPos.APP_VISIBLE, false, + DataPos.KEY_VISIBLE, true, + DataPos.PIE_VISIBLE, false, + DataPos.URI_VISIBLE, false, + DataPos.DISPLAY_COMMAND_GROUP, "", + DataPos.DISPLAY_COMMAND_APP, "", + DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), + DataPos.DISPLAY_COMMAND_PIE, "", + DataPos.DISPLAY_COMMAND_URI, "", + DataPos.REAL_COMMAND_GROUP, "", + DataPos.REAL_COMMAND_PIE, "", + DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(new_one.id)); + + + this.get_selection().select_iter(parent); + this.scroll_to_cell(this.data.get_path(parent), null, true, 0.5f, 0.0f); + } + + // adds a new empty slice to the list + private void add_empty_slice() { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + var path = this.data.get_path(selected); + if (path != null) { + if (path.get_depth() == 2) + this.data.iter_parent(out selected, selected); + + this.load_action(selected, new AppAction(_("New Action"), "application-default-icon", "")); + + Gtk.TreeIter new_one; + this.data.iter_nth_child(out new_one, selected, this.data.iter_n_children(selected)-1); + this.expand_to_path(this.data.get_path(new_one)); + this.get_selection().select_iter(new_one); + this.scroll_to_cell(this.data.get_path(new_one), null, true, 0.5f, 0.0f); + + this.update_pie(selected); + } + } else { + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, + Gtk.MessageType.INFO, + Gtk.ButtonsType.CLOSE, + _("You have to select a Pie to add a Slice to!")); + dialog.run(); + dialog.destroy(); + } + } + + // writes the contents of action to the position pointed by slice + private void write_action(Action action, Gtk.TreeIter slice) { + this.data.set(slice, DataPos.IS_QUICKACTION, action.is_quick_action, + DataPos.ICON, action.icon, + DataPos.NAME, action.name, + DataPos.TYPE_ID, ActionRegistry.names[action.get_type()], + DataPos.ACTION_TYPE, action.get_type().name(), + DataPos.ICON_PIXBUF, this.load_icon(action.icon, this.small_icon), + DataPos.FONT_WEIGHT, 400, + DataPos.ICON_NAME_EDITABLE, !(action is PieAction), + DataPos.QUICKACTION_VISIBLE, true, + DataPos.QUICKACTION_ACTIVATABLE, true, + DataPos.TYPE_VISIBLE, true, + DataPos.GROUP_VISIBLE, false, + DataPos.APP_VISIBLE, action is AppAction, + DataPos.KEY_VISIBLE, action is KeyAction, + DataPos.PIE_VISIBLE, action is PieAction, + DataPos.URI_VISIBLE, action is UriAction, + DataPos.DISPLAY_COMMAND_GROUP, "", + DataPos.DISPLAY_COMMAND_APP, (action is AppAction) ? action.display_command : "", + DataPos.DISPLAY_COMMAND_KEY, (action is KeyAction) ? action.display_command : _("Not bound"), + DataPos.DISPLAY_COMMAND_PIE, (action is PieAction) ? action.display_command : "", + DataPos.DISPLAY_COMMAND_URI, (action is UriAction) ? action.display_command : "", + DataPos.REAL_COMMAND_GROUP, "", + DataPos.REAL_COMMAND_PIE, (action is PieAction) ? action.real_command : "", + DataPos.REAL_COMMAND_KEY, (action is KeyAction) ? action.real_command : ""); + } + + // deletes the currently selected pie or slice + private void delete_selection() { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + var path = this.data.get_path(selected); + if (path != null) { + if (path.get_depth() == 1) + this.delete_pie(selected); + else + this.delete_slice(selected); + } + } else { + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, + Gtk.MessageType.INFO, + Gtk.ButtonsType.CLOSE, + _("You have to select a Pie or a Slice to delete!")); + dialog.run(); + dialog.destroy(); + } + } + + // deletes the given pie + private void delete_pie(Gtk.TreeIter pie) { + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, + Gtk.MessageType.QUESTION, + Gtk.ButtonsType.YES_NO, + _("Do you really want to delete the selected Pie with all contained Slices?")); + + dialog.response.connect((response) => { + if (response == Gtk.ResponseType.YES) { + string id; + this.data.get(pie, DataPos.ACTION_TYPE, out id); + this.data.remove(pie); + PieManager.remove_pie(id); + + this.pies.foreach((model, path, iter) => { + string pies_id; + this.pies.get(iter, PiePos.ID, out pies_id); + + if (id == pies_id) { + this.pies.remove(iter); + return true; + } + + return false; + }); + + this.update_linked(); + } + }); + + dialog.run(); + dialog.destroy(); + } + + // deletes the given slice + private void delete_slice(Gtk.TreeIter slice) { + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, + Gtk.MessageType.QUESTION, + Gtk.ButtonsType.YES_NO, + _("Do you really want to delete the selected Slice?")); + + dialog.response.connect((response) => { + if (response == Gtk.ResponseType.YES) { + Gtk.TreeIter parent; + this.data.iter_parent(out parent, slice); + this.data.remove(slice); + this.update_pie(parent); + } + }); + + dialog.run(); + dialog.destroy(); + } + + // loads all pies to the list + private void load() { + foreach (var pie in PieManager.all_pies.entries) { + this.load_pie(pie.value); + } + } + + // loads one given pie to the list + private void load_pie(Pie pie) { + if (pie.id.length == 3) { + + Gtk.TreeIter last; + this.pies.append(out last); this.pies.set(last, PiePos.NAME, pie.name, + PiePos.ID, pie.id); + + Gtk.TreeIter parent; + this.data.append(out parent, null); + this.data.set(parent, DataPos.IS_QUICKACTION, false, + DataPos.ICON, pie.icon, + DataPos.NAME, pie.name, + DataPos.TYPE_ID, "ID: " + pie.id, + DataPos.ACTION_TYPE, pie.id, + DataPos.ICON_PIXBUF, this.load_icon(pie.icon, this.large_icon), + DataPos.FONT_WEIGHT, 800, + DataPos.ICON_NAME_EDITABLE, true, + DataPos.QUICKACTION_VISIBLE, false, + DataPos.QUICKACTION_ACTIVATABLE, false, + DataPos.TYPE_VISIBLE, false, + DataPos.GROUP_VISIBLE, false, + DataPos.APP_VISIBLE, false, + DataPos.KEY_VISIBLE, true, + DataPos.PIE_VISIBLE, false, + DataPos.URI_VISIBLE, false, + DataPos.DISPLAY_COMMAND_GROUP, "", + DataPos.DISPLAY_COMMAND_APP, "", + DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), + DataPos.DISPLAY_COMMAND_PIE, "", + DataPos.DISPLAY_COMMAND_URI, "", + DataPos.REAL_COMMAND_GROUP, "", + DataPos.REAL_COMMAND_PIE, "", + DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(pie.id)); + + foreach (var group in pie.action_groups) { + this.load_group(parent, group); + } + } + } + + // loads a given group + private void load_group(Gtk.TreeIter parent, ActionGroup group) { + if (group.get_type() == typeof(ActionGroup)) { + foreach (var action in group.actions) { + this.load_action(parent, action); + } + } else { + Gtk.TreeIter child; + this.data.append(out child, parent); + this.data.set(child, DataPos.IS_QUICKACTION, false, + DataPos.ICON, GroupRegistry.icons[group.get_type()], + DataPos.NAME, GroupRegistry.names[group.get_type()], + DataPos.TYPE_ID, _("Slice group"), + DataPos.ACTION_TYPE, typeof(ActionGroup).name(), + DataPos.ICON_PIXBUF, this.load_icon(GroupRegistry.icons[group.get_type()], this.small_icon), + DataPos.FONT_WEIGHT, 400, + DataPos.ICON_NAME_EDITABLE, false, + DataPos.QUICKACTION_VISIBLE, true, + DataPos.QUICKACTION_ACTIVATABLE, false, + DataPos.TYPE_VISIBLE, true, + DataPos.GROUP_VISIBLE, true, + DataPos.APP_VISIBLE, false, + DataPos.KEY_VISIBLE, false, + DataPos.PIE_VISIBLE, false, + DataPos.URI_VISIBLE, false, + DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()], + DataPos.DISPLAY_COMMAND_APP, "", + DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), + DataPos.DISPLAY_COMMAND_PIE, "", + DataPos.DISPLAY_COMMAND_URI, "", + DataPos.REAL_COMMAND_GROUP, group.get_type().name(), + DataPos.REAL_COMMAND_PIE, "", + DataPos.REAL_COMMAND_KEY, ""); + } + } + + // loads a given slice + private void load_action(Gtk.TreeIter parent, Action action) { + Gtk.TreeIter child; + this.data.append(out child, parent); + this.write_action(action, child); + } + + // applies all changes done to the given pie + private void update_pie(Gtk.TreeIter slice_or_pie) { + // get pie iter + var path = this.data.get_path(slice_or_pie); + if (path != null) { + var pie = slice_or_pie; + if (path.get_depth() == 2) + this.data.iter_parent(out pie, slice_or_pie); + + // get information on pie + string id; + string icon; + string name; + string hotkey; + + this.data.get(pie, DataPos.ICON, out icon); + this.data.get(pie, DataPos.NAME, out name); + this.data.get(pie, DataPos.ACTION_TYPE, out id); + this.data.get(pie, DataPos.REAL_COMMAND_KEY, out hotkey); + + // remove pie + PieManager.remove_pie(id); + + this.pies.foreach((model, path, iter) => { + string pies_id; + this.pies.get(iter, PiePos.ID, out pies_id); + + if (id == pies_id) { + this.pies.set(iter, PiePos.NAME, name); + return true; + } + + return false; + }); + + // create new pie + var new_pie = PieManager.create_persistent_pie(name, icon, hotkey, id); + + // add actions accordingly + if (this.data.iter_has_child(pie)) { + Gtk.TreeIter child; + this.data.iter_children(out child, pie); + + do { + // get slice information + string slice_type; + string slice_icon; + string slice_name; + bool is_quick_action; + + this.data.get(child, DataPos.ICON, out slice_icon); + this.data.get(child, DataPos.NAME, out slice_name); + this.data.get(child, DataPos.ACTION_TYPE, out slice_type); + this.data.get(child, DataPos.IS_QUICKACTION, out is_quick_action); + + if (slice_type == typeof(AppAction).name()) { + string slice_command; + this.data.get(child, DataPos.DISPLAY_COMMAND_APP, out slice_command); + var group = new ActionGroup(new_pie.id); + group.add_action(new AppAction(slice_name, slice_icon, slice_command, is_quick_action)); + new_pie.add_group(group); + } else if (slice_type == typeof(KeyAction).name()) { + string slice_command; + this.data.get(child, DataPos.REAL_COMMAND_KEY, out slice_command); + var group = new ActionGroup(new_pie.id); + group.add_action(new KeyAction(slice_name, slice_icon, slice_command, is_quick_action)); + new_pie.add_group(group); + } else if (slice_type == typeof(PieAction).name()) { + string slice_command; + this.data.get(child, DataPos.REAL_COMMAND_PIE, out slice_command); + var group = new ActionGroup(new_pie.id); + group.add_action(new PieAction(slice_command, is_quick_action)); + new_pie.add_group(group); + } else if (slice_type == typeof(UriAction).name()) { + string slice_command; + this.data.get(child, DataPos.DISPLAY_COMMAND_URI, out slice_command); + var group = new ActionGroup(new_pie.id); + group.add_action(new UriAction(slice_name, slice_icon, slice_command, is_quick_action)); + new_pie.add_group(group); + } else if (slice_type == typeof(ActionGroup).name()) { + string slice_command; + this.data.get(child, DataPos.REAL_COMMAND_GROUP, out slice_command); + + var group = GLib.Object.new(GLib.Type.from_name(slice_command), "parent_id", new_pie.id); + new_pie.add_group(group as ActionGroup); + } + + } while (this.data.iter_next(ref child)); + } + } + } + + // creates new action when the list receives a drag'n'drop event + private void on_dnd_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time_) { + string[] uris = selection_data.get_uris(); + + Gtk.TreePath path; + Gtk.TreeViewDropPosition pos; + + // check for valid position + if (!this.get_dest_row_at_pos(x, y, out path, out pos) + || (path.to_string() == "0" && pos == Gtk.TreeViewDropPosition.BEFORE)) { + + warning("Failed to insert Slice: Invalid location!"); + return; + } + + // get position to insert (when child: after, when parent: as first child) + Gtk.TreeIter parent; + int insert_pos = 0; + if (path.get_depth() == 1) { + if (pos == Gtk.TreeViewDropPosition.BEFORE) { + path.prev(); + this.data.get_iter(out parent, path); + insert_pos = this.data.iter_n_children(parent); + } else { + this.data.get_iter(out parent, path); + } + } else { + if (pos == Gtk.TreeViewDropPosition.BEFORE) { + insert_pos = path.get_indices()[1]; + } else { + insert_pos = path.get_indices()[1]+1; + } + + path.up(); + this.data.get_iter(out parent, path); + } + + foreach (var uri in uris) { + Gtk.TreeIter new_child; + this.data.insert(out new_child, parent, insert_pos); + this.write_action(ActionRegistry.new_for_uri(uri), new_child); + } + + this.update_pie(parent); + } + + private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) { + Gtk.TreeIter selected; + if (this.get_selection().get_selected(null, out selected)) { + string id = ""; + this.data.get(selected, DataPos.ACTION_TYPE, out id); + selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"}); + } + } + + private Gdk.Pixbuf load_icon(string name, int size) { + Gdk.Pixbuf pixbuf = null; + + try { + if (name.contains("/")) + pixbuf = new Gdk.Pixbuf.from_file_at_size(name, size, size); + else + pixbuf = new Gdk.Pixbuf.from_file_at_size(Icon.get_icon_file(name, size), size, size); + } catch (GLib.Error e) { + warning(e.message); + } + + return pixbuf; + } +} + +} diff --git a/src/gui/preferences.vala b/src/gui/preferences.vala new file mode 100644 index 0000000..f43fd4a --- /dev/null +++ b/src/gui/preferences.vala @@ -0,0 +1,338 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// The Gtk settings menu of Gnome-Pie. +///////////////////////////////////////////////////////////////////////// + +public class Preferences : Gtk.Window { + + ///////////////////////////////////////////////////////////////////// + /// C'tor, constructs the whole dialog. Many thanks to the + /// synapse-project, since some of this code is taken from there! + ///////////////////////////////////////////////////////////////////// + + public Preferences() { + this.title = _("Gnome-Pie - Settings"); + this.set_position(Gtk.WindowPosition.CENTER); + this.set_size_request(550, 550); + this.resizable = false; + this.icon_name = "gnome-pie"; + this.delete_event.connect(hide_on_delete); + + // main container + var main_vbox = new Gtk.VBox(false, 12); + main_vbox.border_width = 12; + add(main_vbox); + + // tab container + var tabs = new Gtk.Notebook(); + + // general tab + var general_tab = new Gtk.VBox(false, 6); + general_tab.border_width = 12; + + // behavior frame + var behavior_frame = new Gtk.Frame(null); + behavior_frame.set_shadow_type(Gtk.ShadowType.NONE); + var behavior_frame_label = new Gtk.Label(null); + behavior_frame_label.set_markup(Markup.printf_escaped ("%s", _("Behavior"))); + behavior_frame.set_label_widget(behavior_frame_label); + + var behavior_vbox = new Gtk.VBox (false, 6); + var align = new Gtk.Alignment (0.5f, 0.5f, 1.0f, 1.0f); + align.set_padding (6, 12, 12, 12); + align.add (behavior_vbox); + behavior_frame.add (align); + + // Autostart checkbox + var autostart = new Gtk.CheckButton.with_label (_("Startup on Login")); + autostart.tooltip_text = _("If checked, Gnome-Pie will start when you log in."); + autostart.active = Config.global.auto_start; + autostart.toggled.connect(autostart_toggled); + behavior_vbox.pack_start(autostart, false); + + // Indicator icon + var indicator = new Gtk.CheckButton.with_label (_("Show Indicator")); + indicator.tooltip_text = _("If checked, an indicator for easy access of the settings menu is shown in your panel."); + indicator.active = Config.global.show_indicator; + indicator.toggled.connect(indicator_toggled); + behavior_vbox.pack_start(indicator, false); + + // Open Pies at Mouse + var open_at_mouse = new Gtk.CheckButton.with_label (_("Open Pies at Mouse")); + open_at_mouse.tooltip_text = _("If checked, pies will open at your pointer. Otherwise they'll pop up in the middle of the screen."); + open_at_mouse.active = Config.global.open_at_mouse; + open_at_mouse.toggled.connect(open_at_mouse_toggled); + behavior_vbox.pack_start(open_at_mouse, false); + + // Click to activate + var click_to_activate = new Gtk.CheckButton.with_label (_("Turbo mode")); + click_to_activate.tooltip_text = _("If checked, the pie closes when its keystroke is released. The currently hovered slice gets executed. This allows very fast selection but disables keyboard navigation."); + click_to_activate.active = Config.global.turbo_mode; + click_to_activate.toggled.connect(turbo_mode_toggled); + behavior_vbox.pack_start(click_to_activate, false); + + // Slider + var slider_hbox = new Gtk.HBox (false, 6); + behavior_vbox.pack_start(slider_hbox); + + var scale_label = new Gtk.Label(_("Global Scale")); + slider_hbox.pack_start(scale_label, false, false); + + var scale_slider = new Gtk.HScale.with_range(0.5, 2.0, 0.05); + scale_slider.set_value(Config.global.global_scale); + scale_slider.value_pos = Gtk.PositionType.RIGHT; + + bool changing = false; + bool changed_again = false; + + scale_slider.value_changed.connect(() => { + if (!changing) { + changing = true; + Timeout.add(300, () => { + if (changed_again) { + changed_again = false; + return true; + } + + Config.global.global_scale = scale_slider.get_value(); + Config.global.load_themes(Config.global.theme.name); + changing = false; + return false; + }); + } else { + changed_again = true; + } + }); + + slider_hbox.pack_end(scale_slider, true, true); + + general_tab.pack_start (behavior_frame, false); + + // theme frame + var theme_frame = new Gtk.Frame(null); + theme_frame.set_shadow_type(Gtk.ShadowType.NONE); + var theme_frame_label = new Gtk.Label(null); + theme_frame_label.set_markup(Markup.printf_escaped("%s", _("Themes"))); + theme_frame.set_label_widget(theme_frame_label); + + // scrollable frame + var scroll = new Gtk.ScrolledWindow (null, null); + align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); + align.set_padding(6, 12, 12, 12); + align.add(scroll); + theme_frame.add(align); + + scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + scroll.set_shadow_type (Gtk.ShadowType.IN); + + // themes list + var theme_list = new ThemeList(); + scroll.add(theme_list); + + general_tab.pack_start (theme_frame, true, true); + tabs.append_page(general_tab, new Gtk.Label(_("General"))); + + // pies tab + var pies_tab = new Gtk.VBox(false, 6); + pies_tab.border_width = 12; + tabs.append_page(pies_tab, new Gtk.Label(_("Pies"))); + + // scrollable frame + scroll = new Gtk.ScrolledWindow (null, null); + align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); + align.add(scroll); + pies_tab.add(align); + + scroll.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); + scroll.set_shadow_type (Gtk.ShadowType.IN); + + // pies list + var pie_list = new PieList(); + scroll.add(pie_list); + + // bottom box + var info_box = new Gtk.HBox (false, 6); + + // info image + var info_image = new Gtk.Image.from_stock (Gtk.Stock.INFO, Gtk.IconSize.MENU); + info_box.pack_start (info_image, false); + + // info label + var info_label = new TipViewer({ + _("You can right-click in the list for adding or removing entries."), + _("You can reset Gnome-Pie to its default options with the terminal command \"gnome-pie --reset\"."), + _("The radiobutton at the beginning of each slice-line indicates the QuickAction of the pie."), + _("Pies can be opened with the terminal command \"gnome-pie --open=ID\"."), + _("Feel free to visit Gnome-Pie's homepage at %s!").printf("gnome-pie.simonschneegans.de"), + _("You can drag'n'drop applications from your main menu to the list above."), + _("If you want to give some feedback, please write an e-mail to %s!").printf("code@simonschneegans.de"), + _("You may drag'n'drop URLs and bookmarks from your internet browser to the list above."), + _("Bugs can be reported at %s!").printf("Github"), + _("It's possible to drag'n'drop files and folders from your file browser to the list above."), + _("In order to create a launcher for a Pie, drag the Pie from the list to your desktop!") + }); + this.show.connect(info_label.start_slide_show); + this.hide.connect(info_label.stop_slide_show); + + info_box.pack_start (info_label); + + // down Button + var down_button = new Gtk.Button(); + down_button.tooltip_text = _("Moves the selected Slice down"); + down_button.sensitive = false; + var down_image = new Gtk.Image.from_stock (Gtk.Stock.GO_DOWN, Gtk.IconSize.LARGE_TOOLBAR); + down_button.add(down_image); + down_button.clicked.connect (() => { + pie_list.selection_down(); + }); + + info_box.pack_end(down_button, false, false); + + // up Button + var up_button = new Gtk.Button(); + up_button.tooltip_text = _("Moves the selected Slice up"); + up_button.sensitive = false; + var up_image = new Gtk.Image.from_stock (Gtk.Stock.GO_UP, Gtk.IconSize.LARGE_TOOLBAR); + up_button.add(up_image); + up_button.clicked.connect (() => { + pie_list.selection_up(); + }); + + info_box.pack_end(up_button, false, false); + + pie_list.get_selection().changed.connect(() => { + Gtk.TreeIter selected; + if (pie_list.get_selection().get_selected(null, out selected)) { + Gtk.TreePath path = pie_list.model.get_path(selected); + if (path.get_depth() == 1) { + up_button.sensitive = false; + down_button.sensitive = false; + } else { + up_button.sensitive = true; + down_button.sensitive = true; + + int child_pos = path.get_indices()[1]; + + if (child_pos == 0) + up_button.sensitive = false; + + path.up(); + Gtk.TreeIter parent_iter; + pie_list.model.get_iter(out parent_iter, path); + if (child_pos == pie_list.model.iter_n_children(parent_iter)-1) + down_button.sensitive = false; + + } + } + }); + + pies_tab.pack_start (info_box, false); + + main_vbox.pack_start(tabs); + + // close button + var bbox = new Gtk.HButtonBox (); + bbox.set_layout (Gtk.ButtonBoxStyle.END); + var close_button = new Gtk.Button.from_stock (Gtk.Stock.CLOSE); + close_button.clicked.connect (() => { + hide(); + // save settings on close + Config.global.save(); + Pies.save(); + }); + bbox.pack_start (close_button); + + main_vbox.pack_start(bbox, false); + + main_vbox.show_all(); + } + + ///////////////////////////////////////////////////////////////////// + /// Creates or deletes the autostart file. This code is inspired + /// by project synapse as well. + ///////////////////////////////////////////////////////////////////// + + private void autostart_toggled(Gtk.ToggleButton check_box) { + bool active = check_box.active; + if (!active && FileUtils.test(Paths.autostart, FileTest.EXISTS)) { + // delete the autostart file + FileUtils.remove (Paths.autostart); + } + else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) { + string autostart_entry = + "#!/usr/bin/env xdg-open\n" + + "[Desktop Entry]\n" + + "Name=Gnome-Pie\n" + + "Exec=gnome-pie\n" + + "Encoding=UTF-8\n" + + "Type=Application\n" + + "X-GNOME-Autostart-enabled=true\n" + + "Icon=gnome-pie\n"; + + // create the autostart file + string autostart_dir = GLib.Path.get_dirname(Paths.autostart); + if (!FileUtils.test(autostart_dir, FileTest.EXISTS | FileTest.IS_DIR)) { + DirUtils.create_with_parents(autostart_dir, 0755); + } + + try { + FileUtils.set_contents(Paths.autostart, autostart_entry); + FileUtils.chmod(Paths.autostart, 0755); + } catch (Error e) { + var d = new Gtk.MessageDialog (this, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, + "%s", e.message); + d.run (); + d.destroy (); + } + } + } + + ///////////////////////////////////////////////////////////////////// + /// Shows or hides the indicator. + ///////////////////////////////////////////////////////////////////// + + private void indicator_toggled(Gtk.ToggleButton check_box) { + var check = check_box as Gtk.CheckButton; + Config.global.show_indicator = check.active; + } + + ///////////////////////////////////////////////////////////////////// + /// Toggles whether the Pies are shown at the mouse or in the middle + /// of the screen. + ///////////////////////////////////////////////////////////////////// + + private void open_at_mouse_toggled(Gtk.ToggleButton check_box) { + var check = check_box as Gtk.CheckButton; + Config.global.open_at_mouse = check.active; + } + + ///////////////////////////////////////////////////////////////////// + /// Toggles whether the user has to click with the mouse in order to + /// activate a slice. + ///////////////////////////////////////////////////////////////////// + + private void turbo_mode_toggled(Gtk.ToggleButton check_box) { + var check = check_box as Gtk.CheckButton; + Config.global.turbo_mode = check.active; + } +} + +} diff --git a/src/gui/themeList.vala b/src/gui/themeList.vala new file mode 100644 index 0000000..7eadcdb --- /dev/null +++ b/src/gui/themeList.vala @@ -0,0 +1,95 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A widget displaying all available themes of Gnome-Pie. +///////////////////////////////////////////////////////////////////////// + +class ThemeList : Gtk.TreeView { + + ///////////////////////////////////////////////////////////////////// + /// The currently selected row. + ///////////////////////////////////////////////////////////////////// + + private Gtk.TreeIter active { private get; private set; } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, constructs the Widget. + ///////////////////////////////////////////////////////////////////// + + public ThemeList() { + GLib.Object(); + + var data = new Gtk.ListStore(2, typeof(bool), // selected + typeof(string)); // description + base.set_model(data); + base.set_headers_visible(false); + base.set_rules_hint(true); + base.set_grid_lines(Gtk.TreeViewGridLines.NONE); + + var main_column = new Gtk.TreeViewColumn(); + var check_render = new Gtk.CellRendererToggle(); + check_render.set_radio(true); + check_render.set_activatable(true); + main_column.pack_start(check_render, false); + + // switch the theme if the entry has been toggled + check_render.toggled.connect((r, path) => { + Gtk.TreeIter toggled; + data.get_iter(out toggled, new Gtk.TreePath.from_string(path)); + + if (toggled != this.active) { + Timeout.add(10, () => { + int index = int.parse(path); + Config.global.theme = Config.global.themes[index]; + Config.global.theme.load(); + Config.global.theme.load_images(); + return false; + }); + + data.set(this.active, 0, false); + data.set(toggled, 0, true); + + this.active = toggled; + } + }); + + var theme_render = new Gtk.CellRendererText(); + main_column.pack_start(theme_render, true); + + base.append_column(main_column); + + main_column.add_attribute(check_render, "active", 0); + main_column.add_attribute(theme_render, "markup", 1); + + // load all themes into the list + var themes = Config.global.themes; + foreach(var theme in themes) { + Gtk.TreeIter current; + data.append(out current); + data.set(current, 0, theme == Config.global.theme); + data.set(current, 1, "" + theme.name + "\n" + theme.description + + " - " + _("by") + " " + theme.author + ""); + if(theme == Config.global.theme) + this.active = current; + } + } +} + +} diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala new file mode 100644 index 0000000..c653dd9 --- /dev/null +++ b/src/gui/tipViewer.vala @@ -0,0 +1,172 @@ +/* +Copyright (c) 2011 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 . +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A widget showing tips. The tips are beautifully faded in and out. +///////////////////////////////////////////////////////////////////////// + +public class TipViewer : Gtk.Label { + + ///////////////////////////////////////////////////////////////////// + /// Some settings tweaking the behavior of the TipViewer. + ///////////////////////////////////////////////////////////////////// + + private const double fade_time = 0.5; + private const double frame_rate = 20.0; + private const double delay = 7.0; + + ///////////////////////////////////////////////////////////////////// + /// False, if the playback of tips is stopped. + ///////////////////////////////////////////////////////////////////// + + private bool playing = false; + + ///////////////////////////////////////////////////////////////////// + /// An array containing all tips. + ///////////////////////////////////////////////////////////////////// + + private string[] tips; + + ///////////////////////////////////////////////////////////////////// + /// The index of the currently displayed tip. + ///////////////////////////////////////////////////////////////////// + + private int index = -1; + + ///////////////////////////////////////////////////////////////////// + /// Colors of the font and the background. Used for fading effects. + ///////////////////////////////////////////////////////////////////// + + private Gdk.Color fg; + private Gdk.Color bg; + + ///////////////////////////////////////////////////////////////////// + /// The fading value. + ///////////////////////////////////////////////////////////////////// + + private AnimatedValue alpha; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes all members and sets the basic layout. + ///////////////////////////////////////////////////////////////////// + + public TipViewer(string[] tips) { + this.tips = tips; + this.fg = this.get_style().fg[0]; + this.bg = this.get_style().bg[0]; + + this.alpha = new AnimatedValue.linear(1.0, 0.0, this.fade_time); + + this.set_alignment (0.0f, 0.5f); + this.wrap = true; + this.set_use_markup(true); + this.modify_font(Pango.FontDescription.from_string("9")); + + this.set_random_tip(); + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the playback of tips. + ///////////////////////////////////////////////////////////////////// + + public void start_slide_show() { + if (!this.playing && tips.length > 1) { + this.playing = true; + GLib.Timeout.add((uint)(this.delay*1000.0), () => { + this.fade_out(); + + GLib.Timeout.add((uint)(1000.0*this.fade_time), () => { + this.set_random_tip(); + this.fade_in(); + return false; + }); + + return this.playing; + }); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Stops the playback of tips. + ///////////////////////////////////////////////////////////////////// + + public void stop_slide_show() { + this.playing = false; + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the fading in. + ///////////////////////////////////////////////////////////////////// + + private void fade_in() { + this.alpha = new AnimatedValue.linear(this.alpha.val, 1.0, this.fade_time); + + GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { + this.alpha.update(1.0/this.frame_rate); + this.update_label(); + + return (this.alpha.val != 1.0); + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Starts the fading out. + ///////////////////////////////////////////////////////////////////// + + private void fade_out() { + this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, this.fade_time); + + GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { + this.alpha.update(1.0/this.frame_rate); + this.update_label(); + + return (this.alpha.val != 0.0); + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Updates the color of the label. Called every frame while fading. + ///////////////////////////////////////////////////////////////////// + + private void update_label() { + Gdk.Color color = {(uint32)(fg.pixel*this.alpha.val + bg.pixel*(1.0 - this.alpha.val)), + (uint16)(fg.red*this.alpha.val + bg.red*(1.0 - this.alpha.val)), + (uint16)(fg.green*this.alpha.val + bg.green*(1.0 - this.alpha.val)), + (uint16)(fg.blue*this.alpha.val + bg.blue*(1.0 - this.alpha.val))}; + + this.modify_fg(Gtk.StateType.NORMAL, color); + } + + ///////////////////////////////////////////////////////////////////// + /// Chooses the next random tip. + ///////////////////////////////////////////////////////////////////// + + private void set_random_tip() { + if (tips.length > 1) { + int next_index = -1; + do { + next_index = GLib.Random.int_range(0, tips.length); + } while (next_index == this.index); + this.index = next_index; + this.label = tips[this.index]; + } + } +} + +} -- cgit v1.2.3