summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorAlessandro Ghedini <al3xbio@gmail.com>2011-10-19 10:56:04 +0200
committerAlessandro Ghedini <al3xbio@gmail.com>2011-10-19 10:56:04 +0200
commit6451a495637c6e3047a5a56573cffc6e3de9a515 (patch)
tree7c3eb29532e7c4b36a9da13c5890664fb816959b /src/gui
Imported Upstream version 0.2+gitdfdad95upstream/0.2+gitdfdad95
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/about.vala43
-rw-r--r--src/gui/cellRendererIcon.vala132
-rw-r--r--src/gui/iconSelectWindow.vala349
-rw-r--r--src/gui/indicator.vala160
-rw-r--r--src/gui/pieList.vala1018
-rw-r--r--src/gui/preferences.vala338
-rw-r--r--src/gui/themeList.vala95
-rw-r--r--src/gui/tipViewer.vala172
8 files changed, 2307 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+*/
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A simple about Dialog.
+/////////////////////////////////////////////////////////////////////////
+
+public class GnomePieAboutDialog: Gtk.AboutDialog {
+
+ public GnomePieAboutDialog () {
+ string[] devs = {"Simon Schneegans <code@simonschneegans.de>",
+ "Francesco Piccinno"};
+ string[] artists = {"Simon Schneegans <code@simonschneegans.de>"};
+ GLib.Object (
+ artists : artists,
+ authors : devs,
+ copyright : "Copyright (C) 2011 Simon Schneegans <code@simonschneegans.de>",
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+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<ListEntry?> 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<ListEntry?>();
+
+ 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<void*>(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 <http://www.gnu.org/licenses/>.
+*/
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+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<child_count; ++i) {
+ Gtk.TreeIter child;
+ this.data.get_iter_from_string(out child, "%s:%d".printf(parent_pos, i));
+ this.data.set(child, DataPos.IS_QUICKACTION, false);
+ }
+
+ // toggle selected
+ this.data.set(toggled, DataPos.IS_QUICKACTION, !current);
+
+ this.update_pie(toggled);
+ });
+
+ icon_column.pack_start(check_render, false);
+ icon_column.add_attribute(check_render, "activatable", DataPos.QUICKACTION_ACTIVATABLE);
+ icon_column.add_attribute(check_render, "sensitive", DataPos.QUICKACTION_ACTIVATABLE);
+ icon_column.add_attribute(check_render, "visible", DataPos.QUICKACTION_VISIBLE);
+ icon_column.add_attribute(check_render, "active", DataPos.IS_QUICKACTION);
+
+
+ // icon
+ var icon_render = new GnomePie.CellRendererIcon();
+ icon_render.editable = true;
+
+ icon_render.on_select.connect((path, icon_name) => {
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+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 ("<b>%s</b>", _("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("<b>%s</b>", _("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("<a href='http://gnome-pie.simonschneegans.de'>gnome-pie.simonschneegans.de</a>"),
+ _("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("<a href='mailto:code@simonschneegans.de'>code@simonschneegans.de</a>"),
+ _("You may drag'n'drop URLs and bookmarks from your internet browser to the list above."),
+ _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"),
+ _("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 <http://www.gnu.org/licenses/>.
+*/
+
+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, "<b>" + theme.name + "</b>\n" + theme.description
+ + " <small> - " + _("by") + " " + theme.author + "</small>");
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+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];
+ }
+ }
+}
+
+}