summaryrefslogtreecommitdiff
path: root/src
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
Imported Upstream version 0.2+gitdfdad95upstream/0.2+gitdfdad95
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt82
-rw-r--r--src/actionGroups/actionGroup.vala75
-rw-r--r--src/actionGroups/bookmarkGroup.vala148
-rw-r--r--src/actionGroups/clipboardGroup.vala116
-rw-r--r--src/actionGroups/devicesGroup.vala129
-rw-r--r--src/actionGroups/groupRegistry.vala89
-rw-r--r--src/actionGroups/menuGroup.vala254
-rw-r--r--src/actionGroups/sessionGroup.vala68
-rw-r--r--src/actions/action.vala77
-rw-r--r--src/actions/actionRegistry.vala200
-rw-r--r--src/actions/appAction.vala72
-rw-r--r--src/actions/keyAction.vala77
-rw-r--r--src/actions/pieAction.vala95
-rw-r--r--src/actions/sigAction.vala63
-rw-r--r--src/actions/uriAction.vala71
-rw-r--r--src/deamon.vala175
-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
-rw-r--r--src/pies/defaultConfig.vala70
-rw-r--r--src/pies/load.vala230
-rw-r--r--src/pies/pie.vala96
-rw-r--r--src/pies/pieManager.vala231
-rw-r--r--src/pies/save.vala81
-rw-r--r--src/renderers/centerRenderer.vala146
-rw-r--r--src/renderers/pieRenderer.vala208
-rw-r--r--src/renderers/pieWindow.vala263
-rw-r--r--src/renderers/sliceRenderer.vala182
-rw-r--r--src/themes/centerLayer.vala111
-rw-r--r--src/themes/sliceLayer.vala65
-rw-r--r--src/themes/theme.vala489
-rw-r--r--src/utilities/animatedValue.vala190
-rw-r--r--src/utilities/bindingManager.vala196
-rw-r--r--src/utilities/color.vala305
-rw-r--r--src/utilities/config.vala202
-rw-r--r--src/utilities/icon.vala102
-rw-r--r--src/utilities/image.vala163
-rw-r--r--src/utilities/key.vala139
-rw-r--r--src/utilities/logger.vala194
-rw-r--r--src/utilities/paths.vala211
-rw-r--r--src/utilities/renderedText.vala110
-rw-r--r--src/utilities/themedIcon.vala161
47 files changed, 8243 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..b3b8ed3
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,82 @@
+################################################################
+# Actually compile the executable
+################################################################
+
+# determine source and header files
+file(GLOB VALA_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.vala */*.vala)
+
+if (${INDICATOR_FOUND})
+ SET(DEFINES --define HAVE_APPINDICATOR)
+endif(${INDICATOR_FOUND})
+
+if (${GMENU3_FOUND})
+ LIST(APPEND DEFINES --define HAVE_GMENU_3)
+endif (${GMENU3_FOUND})
+
+# use valac to compile sources to c files
+vala_precompile(
+ VALA_C
+ ${VALA_SRC}
+ PACKAGES
+ ${VALA_PKGS}
+ OPTIONS
+ --vapidir=${CMAKE_SOURCE_DIR}/vapi/
+ --thread
+ ${DEFINES}
+)
+
+# compile c-sources
+add_executable(gnome-pie ${VALA_C})
+
+# install executable
+install(
+ TARGETS
+ gnome-pie
+ RUNTIME DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/bin
+)
+
+# install credits
+install(
+ FILES
+ ${CMAKE_SOURCE_DIR}/README
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/doc/gnome-pie
+)
+
+# install locales
+install(
+ DIRECTORY
+ ${CMAKE_SOURCE_DIR}/resources/locale
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share
+ PATTERN *.po EXCLUDE
+ PATTERN *.pot EXCLUDE
+ PATTERN *.sh EXCLUDE
+)
+
+# install themes
+install(
+ DIRECTORY
+ ${CMAKE_SOURCE_DIR}/resources/themes
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/gnome-pie
+)
+
+# install icons
+install(
+ FILES
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie.svg
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie-indicator.svg
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps
+)
+
+# desktop file
+install(
+ FILES
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie.desktop
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/applications
+)
+
diff --git a/src/actionGroups/actionGroup.vala b/src/actionGroups/actionGroup.vala
new file mode 100644
index 0000000..a6b52ff
--- /dev/null
+++ b/src/actionGroups/actionGroup.vala
@@ -0,0 +1,75 @@
+/*
+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 base class storing a set of Actions. Derived classes may define
+// how these Actions are created. This base class serves for custom
+// actions, defined by the user.
+/////////////////////////////////////////////////////////////////////////
+
+public class ActionGroup : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list of all stored actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gee.ArrayList<Action?> actions { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the pie to which this group is attached.
+ /////////////////////////////////////////////////////////////////////
+
+ public string parent_id { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public ActionGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ construct {
+ this.actions = new Gee.ArrayList<Action?>();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is deleted.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual void on_remove() {}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds a new Action to the group.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_action(Action new_action) {
+ this.actions.add(new_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Removes all Actions from the group.
+ /////////////////////////////////////////////////////////////////////
+
+ public void delete_all() {
+ actions.clear();
+ }
+}
+
+}
diff --git a/src/actionGroups/bookmarkGroup.vala b/src/actionGroups/bookmarkGroup.vala
new file mode 100644
index 0000000..f4ba66e
--- /dev/null
+++ b/src/actionGroups/bookmarkGroup.vala
@@ -0,0 +1,148 @@
+/*
+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 group of Actions, which represent the users gtk-bookmarks, his home
+/// directory, desktop and trash. It stay up-to-date, even if the
+/// bookmarks change.
+/////////////////////////////////////////////////////////////////////////
+
+public class BookmarkGroup : ActionGroup {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of ActionGroup. It sets the display
+ /// name for this ActionGroup, it's icon name and the string used in
+ /// the pies.conf file for this kind of ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out string icon, out string settings_name) {
+ name = _("Bookmarks");
+ icon = "user-bookmarks";
+ settings_name = "bookmarks";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two members needed to avoid useless, frequent changes of the
+ /// stored Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool changing = false;
+ private bool changed_again = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public BookmarkGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Construct block loads the bookmarks of the user and adds a file
+ /// monitor in order to update the BookmarkGroup when the bookmarks
+ /// of the user change.
+ /////////////////////////////////////////////////////////////////////
+
+ construct {
+ this.load();
+
+ // add monitor
+ var bookmark_file = GLib.File.new_for_path(
+ GLib.Environment.get_home_dir()).get_child(".gtk-bookmarks");
+
+ if (bookmark_file.query_exists()) {
+ try {
+ var monitor = bookmark_file.monitor(GLib.FileMonitorFlags.NONE);
+ monitor.changed.connect(this.reload);
+ } catch (GLib.Error e) {
+ warning(e.message);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds Actions for each gtk-bookmark of the user and for his home
+ /// folder, desktop and trash.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load() {
+ // add home folder
+ this.add_action(ActionRegistry.new_for_uri("file://" + GLib.Environment.get_home_dir()));
+
+ // add .gtk-bookmarks
+ var bookmark_file = GLib.File.new_for_path(
+ GLib.Environment.get_home_dir()).get_child(".gtk-bookmarks");
+
+ if (!bookmark_file.query_exists()) {
+ warning("Failed to find file \".gtk-bookmarks\"!");
+ return;
+ }
+
+ try {
+ var dis = new DataInputStream(bookmark_file.read());
+ string line;
+ while ((line = dis.read_line(null)) != null) {
+ var parts = line.split(" ");
+
+ string uri = parts[0];
+ string name = parts[1];
+
+ this.add_action(ActionRegistry.new_for_uri(uri, name));
+ }
+ } catch (Error e) {
+ error ("%s", e.message);
+ }
+
+ // add trash
+ this.add_action(ActionRegistry.new_for_uri("trash:///"));
+
+ // add desktop
+ this.add_action(ActionRegistry.new_for_uri("file://" + GLib.Environment.get_user_special_dir(GLib.UserDirectory.DESKTOP)));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads all Bookmarks. Is called when the user's gtk-bookmarks
+ /// file changes.
+ /////////////////////////////////////////////////////////////////////
+
+ private void reload() {
+ // avoid too frequent changes...
+ if (!this.changing) {
+ this.changing = true;
+ Timeout.add(200, () => {
+ if (this.changed_again) {
+ this.changed_again = false;
+ return true;
+ }
+
+ // reload
+ message("Bookmarks changed...");
+ this.delete_all();
+ this.load();
+
+ this.changing = false;
+ return false;
+ });
+ } else {
+ this.changed_again = true;
+ }
+ }
+}
+
+}
diff --git a/src/actionGroups/clipboardGroup.vala b/src/actionGroups/clipboardGroup.vala
new file mode 100644
index 0000000..0e95b65
--- /dev/null
+++ b/src/actionGroups/clipboardGroup.vala
@@ -0,0 +1,116 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This Group keeps a history of the last used Clipboard entries.
+/////////////////////////////////////////////////////////////////////////
+
+public class ClipboardGroup : ActionGroup {
+
+ /////////////////////////////////////////////////////////////////////
+ ///
+ /////////////////////////////////////////////////////////////////////
+
+ private class ClipboardItem : GLib.Object {
+
+ public string name { get; private set; }
+ public string icon { get; private set; }
+
+ private Gtk.SelectionData contents;
+
+ public ClipboardItem(Gtk.SelectionData contents) {
+ this.contents = contents.copy();
+ this.name = this.contents.get_text() ?? "";
+ this.icon = "edit-paste";
+ }
+
+ public void paste() {
+ debug(name);
+ }
+ }
+
+ public ClipboardGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of ActionGroup. It sets the display
+ /// name for this ActionGroup, it's icon name and the string used in
+ /// the pies.conf file for this kind of ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out string icon, out string settings_name) {
+ name = _("Clipboard");
+ icon = "edit-paste";
+ settings_name = "clipboard";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The clipboard to be monitored.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Clipboard clipboard;
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// The maximum remembered items of the clipboard.
+ /////////////////////////////////////////////////////////////////////
+
+ private const int max_items = 6;
+
+ private Gee.ArrayList<ClipboardItem?> items;
+
+ construct {
+ this.items = new Gee.ArrayList<ClipboardItem?>();
+ this.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
+ this.clipboard.owner_change.connect(this.on_change);
+ }
+
+ private void on_change() {
+ if (this.clipboard.wait_is_text_available()) {
+ this.clipboard.request_contents(Gdk.Atom.intern("text/plain", false), this.add_item);
+ }
+ }
+
+ private void add_item(Gtk.Clipboard c, Gtk.SelectionData contents) {
+ var new_item = new ClipboardItem(contents);
+
+ if (this.items.size == this.max_items)
+ this.items.remove_at(0);
+
+ this.items.add(new_item);
+
+ // update slices
+ this.delete_all();
+
+ for (int i=0; i<this.items.size; ++i) {
+ var action = new SigAction(items[i].name, items[i].icon, i.to_string());
+ action.activated.connect(() => {
+ this.items[int.parse(action.real_command)].paste();
+ });
+ this.add_action(action);
+ }
+
+ }
+}
+
+}
diff --git a/src/actionGroups/devicesGroup.vala b/src/actionGroups/devicesGroup.vala
new file mode 100644
index 0000000..3d2ced0
--- /dev/null
+++ b/src/actionGroups/devicesGroup.vala
@@ -0,0 +1,129 @@
+/*
+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 ActionGroup which contains all currently plugged-in devices,
+/// such as CD-ROM's or USB-sticks.
+/////////////////////////////////////////////////////////////////////
+
+public class DevicesGroup : ActionGroup {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of ActionGroup. It sets the display
+ /// name for this ActionGroup, it's icon name and the string used in
+ /// the pies.conf file for this kind of ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out string icon, out string settings_name) {
+ name = _("Devices");
+ icon = "harddrive";
+ settings_name = "devices";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two members needed to avoid useless, frequent changes of the
+ /// stored Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool changing = false;
+ private bool changed_again = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The VolumeMonitor used to check for added or removed devices.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.VolumeMonitor monitor;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public DevicesGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Construct block loads all currently plugged-in devices and
+ /// connects signal handlers to the VolumeMonitor.
+ /////////////////////////////////////////////////////////////////////
+
+ construct {
+ this.monitor = GLib.VolumeMonitor.get();
+
+ this.load();
+
+ // add monitor
+ this.monitor.mount_added.connect(this.reload);
+ this.monitor.mount_removed.connect(this.reload);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all currently plugged-in devices.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load() {
+ // add root device
+ this.add_action(new UriAction(_("Root"), "harddrive", "file:///"));
+
+ // add all other devices
+ foreach(var mount in this.monitor.get_mounts()) {
+ // get icon
+ var icon_names = mount.get_icon().to_string().split(" ");
+
+ string icon = "";
+ foreach (var icon_name in icon_names) {
+ if (Gtk.IconTheme.get_default().has_icon(icon_name)) {
+ icon = icon_name;
+ break;
+ }
+ }
+
+ this.add_action(new UriAction(mount.get_name(), icon, mount.get_root().get_uri()));
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads all devices. Is called when the VolumeMonitor changes.
+ /////////////////////////////////////////////////////////////////////
+
+ private void reload() {
+ // avoid too frequent changes...
+ if (!this.changing) {
+ this.changing = true;
+ Timeout.add(200, () => {
+ if (this.changed_again) {
+ this.changed_again = false;
+ return true;
+ }
+
+ // reload
+ message("Devices changed...");
+ this.delete_all();
+ this.load();
+
+ this.changing = false;
+ return false;
+ });
+ } else {
+ this.changed_again = true;
+ }
+ }
+}
+
+}
diff --git a/src/actionGroups/groupRegistry.vala b/src/actionGroups/groupRegistry.vala
new file mode 100644
index 0000000..94169d5
--- /dev/null
+++ b/src/actionGroups/groupRegistry.vala
@@ -0,0 +1,89 @@
+/*
+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 which has knowledge on all possible acion group types.
+/////////////////////////////////////////////////////////////////////////
+
+public class GroupRegistry : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list containing all available ActionGroup types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.ArrayList<Type> types { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Three maps associating a displayable name for each ActionGroup,
+ /// an icon name and a name for the pies.conf file with it's type.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.HashMap<Type, string> names { get; private set; }
+ public static Gee.HashMap<Type, string> icons { get; private set; }
+ public static Gee.HashMap<Type, string> settings_names { get; private set; }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Registers all ActionGroup types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ types = new Gee.ArrayList<Type>();
+
+ names = new Gee.HashMap<Type, string>();
+ icons = new Gee.HashMap<Type, string>();
+ settings_names = new Gee.HashMap<Type, string>();
+
+ string name = "";
+ string icon = "";
+ string settings_name = "";
+
+ BookmarkGroup.register(out name, out icon, out settings_name);
+ types.add(typeof(BookmarkGroup));
+ names.set(typeof(BookmarkGroup), name);
+ icons.set(typeof(BookmarkGroup), icon);
+ settings_names.set(typeof(BookmarkGroup), settings_name);
+
+ DevicesGroup.register(out name, out icon, out settings_name);
+ types.add(typeof(DevicesGroup));
+ names.set(typeof(DevicesGroup), name);
+ icons.set(typeof(DevicesGroup), icon);
+ settings_names.set(typeof(DevicesGroup), settings_name);
+
+ MenuGroup.register(out name, out icon, out settings_name);
+ types.add(typeof(MenuGroup));
+ names.set(typeof(MenuGroup), name);
+ icons.set(typeof(MenuGroup), icon);
+ settings_names.set(typeof(MenuGroup), settings_name);
+
+ SessionGroup.register(out name, out icon, out settings_name);
+ types.add(typeof(SessionGroup));
+ names.set(typeof(SessionGroup), name);
+ icons.set(typeof(SessionGroup), icon);
+ settings_names.set(typeof(SessionGroup), settings_name);
+
+// ClipboardGroup.register(out name, out icon, out settings_name);
+// types.add(typeof(ClipboardGroup));
+// names.set(typeof(ClipboardGroup), name);
+// icons.set(typeof(ClipboardGroup), icon);
+// settings_names.set(typeof(ClipboardGroup), settings_name);
+ }
+}
+
+}
diff --git a/src/actionGroups/menuGroup.vala b/src/actionGroups/menuGroup.vala
new file mode 100644
index 0000000..07a4bd1
--- /dev/null
+++ b/src/actionGroups/menuGroup.vala
@@ -0,0 +1,254 @@
+/*
+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 ActionGroup which displays the user's main menu. It's a bit ugly,
+/// but it supports both, an older version and libgnome-menus-3 at the
+/// same time.
+/////////////////////////////////////////////////////////////////////////
+
+public class MenuGroup : ActionGroup {
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of ActionGroup. It sets the display
+ /// name for this ActionGroup, it's icon name and the string used in
+ /// the pies.conf file for this kind of ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out string icon, out string settings_name) {
+ name = _("Main menu");
+ icon = "gnome-main-menu";
+ settings_name = "menu";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if this MenuGroup is the top most menu.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool is_toplevel {get; construct set; default = true;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// The menu tree displayed by the MenuGroup. Only set for the
+ /// toplevel MenuGroup.
+ /////////////////////////////////////////////////////////////////////
+
+ private GMenu.Tree menu = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list of all sub menus of this MenuGroup.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gee.ArrayList<MenuGroup?> childs;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two members needed to avoid useless, frequent changes of the
+ /// stored Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool changing = false;
+ private bool changed_again = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members. Used for the toplevel menu.
+ /////////////////////////////////////////////////////////////////////
+
+ public MenuGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id, is_toplevel : true);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members. Used for sub menus.
+ /////////////////////////////////////////////////////////////////////
+
+ public MenuGroup.sub_menu(string parent_id) {
+ GLib.Object(parent_id : parent_id, is_toplevel : false);
+ }
+
+ construct {
+ this.childs = new Gee.ArrayList<MenuGroup?>();
+
+ if (this.is_toplevel) {
+ #if HAVE_GMENU_3
+ this.menu = new GMenu.Tree("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED);
+ this.menu.changed.connect(this.reload);
+ #endif
+
+ this.load_toplevel();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts to load the menu.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_toplevel() {
+ #if HAVE_GMENU_3
+ try {
+ if (this.menu.load_sync()) {
+ this.load_contents(this.menu.get_root_directory(), this.parent_id);
+ }
+ } catch (GLib.Error e) {
+ warning(e.message);
+ }
+ #else
+ this.menu = GMenu.Tree.lookup ("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED);
+ this.menu.add_monitor(this.reload);
+ var dir = this.menu.get_root_directory();
+ this.load_contents(dir, this.parent_id);
+ #endif
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses the main menu recursively.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_contents(GMenu.TreeDirectory dir, string parent_id) {
+ #if HAVE_GMENU_3
+ var item = dir.iter();
+
+ while (true) {
+ var type = item.next();
+ if (type == GMenu.TreeItemType.INVALID)
+ break;
+
+ if (type == GMenu.TreeItemType.DIRECTORY && !item.get_directory().get_is_nodisplay()) {
+ // create a MenuGroup for sub menus
+ string[] icons = item.get_directory().get_icon().to_string().split(" ");
+ string final_icon = "application-default-icon";
+
+ // search for available icons
+ foreach (var icon in icons) {
+ if (Gtk.IconTheme.get_default().has_icon(icon)) {
+ final_icon = icon;
+ break;
+ }
+ }
+
+ var sub_menu = PieManager.create_dynamic_pie(item.get_directory().get_name(), final_icon);
+ var group = new MenuGroup.sub_menu(sub_menu.id);
+ group.add_action(new PieAction(parent_id, true));
+ group.load_contents(item.get_directory(), sub_menu.id);
+ childs.add(group);
+
+ sub_menu.add_group(group);
+
+ this.add_action(new PieAction(sub_menu.id));
+
+ } else if (type == GMenu.TreeItemType.ENTRY ) {
+ // create an AppAction for entries
+ if (!item.get_entry().get_is_excluded()) {
+ this.add_action(ActionRegistry.new_for_app_info(item.get_entry().get_app_info()));
+ }
+ }
+ }
+ #else
+ foreach (var item in dir.get_contents()) {
+ switch(item.get_type()) {
+ case GMenu.TreeItemType.DIRECTORY:
+ // create a MenuGroup for sub menus
+ if (!((GMenu.TreeDirectory)item).get_is_nodisplay()) {
+ var sub_menu = PieManager.create_dynamic_pie(
+ ((GMenu.TreeDirectory)item).get_name(),
+ ((GMenu.TreeDirectory)item).get_icon());
+ var group = new MenuGroup.sub_menu(sub_menu.id);
+ group.add_action(new PieAction(parent_id, true));
+ group.load_contents((GMenu.TreeDirectory)item, sub_menu.id);
+ childs.add(group);
+
+ sub_menu.add_group(group);
+
+ this.add_action(new PieAction(sub_menu.id));
+ }
+ break;
+
+ case GMenu.TreeItemType.ENTRY:
+ // create an AppAction for entries
+ if (!((GMenu.TreeEntry)item).get_is_nodisplay() && !((GMenu.TreeEntry)item).get_is_excluded()) {
+ this.add_action(new AppAction(((GMenu.TreeEntry)item).get_name(),
+ ((GMenu.TreeEntry)item).get_icon(),
+ ((GMenu.TreeEntry)item).get_exec()));
+ }
+ break;
+ }
+ }
+ #endif
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads the menu.
+ /////////////////////////////////////////////////////////////////////
+
+ private void reload() {
+ // avoid too frequent changes...
+ if (!this.changing) {
+ this.changing = true;
+ Timeout.add(500, () => {
+ if (this.changed_again) {
+ this.changed_again = false;
+ return true;
+ }
+
+ // reload
+ message("Main menu changed...");
+ #if !HAVE_GMENU_3
+ this.menu.remove_monitor(this.reload);
+ #endif
+
+ this.clear();
+ this.load_toplevel();
+
+ this.changing = false;
+ return false;
+ });
+ } else {
+ this.changed_again = true;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Deletes all generated Pies, when the toplevel menu is deleted.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void on_remove() {
+ if (this.is_toplevel)
+ this.clear();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Clears this ActionGroup recursively.
+ /////////////////////////////////////////////////////////////////////
+
+ private void clear() {
+ foreach (var child in childs)
+ child.clear();
+
+ if (!this.is_toplevel)
+ PieManager.remove_pie(this.parent_id);
+
+ this.delete_all();
+
+ this.childs.clear();
+
+ #if !HAVE_GMENU_3
+ this.menu = null;
+ #endif
+
+ }
+}
+
+}
diff --git a/src/actionGroups/sessionGroup.vala b/src/actionGroups/sessionGroup.vala
new file mode 100644
index 0000000..9fcab1d
--- /dev/null
+++ b/src/actionGroups/sessionGroup.vala
@@ -0,0 +1,68 @@
+/*
+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 ActionGroup which has three Actions: Logout, Shutdown and
+/// Reboot.
+/////////////////////////////////////////////////////////////////////
+
+public class SessionGroup : ActionGroup {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of ActionGroup. It sets the display
+ /// name for this ActionGroup, it's icon name and the string used in
+ /// the pies.conf file for this kind of ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out string icon, out string settings_name) {
+ name = _("Session Control");
+ icon = "gnome-logout";
+ settings_name = "session";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public SessionGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Construct block adds the three Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ construct {
+ this.add_action(new AppAction(_("Shutdown"), "gnome-shutdown",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestShutdown"));
+
+ this.add_action(new AppAction(_("Logout"), "gnome-session-logout",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1"));
+
+ this.add_action(new AppAction(_("Reboot"), "gnome-session-reboot",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestReboot"));
+ }
+
+ // TODO: check for available interfaces --- these may work too:
+ // dbus-send --print-reply --system --dest=org.freedesktop.Hal /org/freedesktop/Hal/devices/computer org.freedesktop.Hal.Device.SystemPowerManagement.Shutdown
+ // dbus-send --print-reply --dest=org.kde.ksmserver /KSMServer org.kde.KSMServerInterface.logout 0 2 2
+ // dbus-send --system --print-reply --dest="org.freedesktop.ConsoleKit" /org/freedesktop/ConsoleKit/Manager org.freedesktop.ConsoleKit.Manager.Stop
+}
+
+}
diff --git a/src/actions/action.vala b/src/actions/action.vala
new file mode 100644
index 0000000..ceed357
--- /dev/null
+++ b/src/actions/action.vala
@@ -0,0 +1,77 @@
+/*
+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 base class for actions, which are executed when the user
+/// activates a pie's slice.
+/////////////////////////////////////////////////////////////////////////
+
+public abstract class Action : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The command which gets executed when user activates the Slice.
+ /// It may be anything but has to be representable with a string.
+ /////////////////////////////////////////////////////////////////////
+
+ public abstract string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The command displayed to the user. It should be a bit more
+ /// beautiful than the real_command.
+ /////////////////////////////////////////////////////////////////////
+
+ public abstract string display_command { get; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The name of the Action.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual string name { get; protected set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The name of the icon of this Action. It should be in the users
+ /// current icon theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual string icon { get; protected set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if this Action is the quickAction of the associated Pie.
+ /// The quickAction of a Pie gets executed when the users clicks on
+ /// the center of a Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual bool is_quick_action { get; protected set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Action(string name, string icon, bool is_quick_action) {
+ GLib.Object(name : name, icon : icon, is_quick_action : is_quick_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the user activates the Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public abstract void activate();
+}
+
+}
diff --git a/src/actions/actionRegistry.vala b/src/actions/actionRegistry.vala
new file mode 100644
index 0000000..091865f
--- /dev/null
+++ b/src/actions/actionRegistry.vala
@@ -0,0 +1,200 @@
+/*
+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 which has knowledge on all possible acion types.
+/////////////////////////////////////////////////////////////////////////
+
+public class ActionRegistry : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list containing all available Action types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.ArrayList<Type> types { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Three maps associating a displayable name for each Action,
+ /// whether it has a custom icon and a name for the pies.conf
+ /// file with it's type.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.HashMap<Type, string> names { get; private set; }
+ public static Gee.HashMap<Type, bool> icon_name_editables { get; private set; }
+ public static Gee.HashMap<Type, string> settings_names { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Registers all Action types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ types = new Gee.ArrayList<Type>();
+
+ names = new Gee.HashMap<Type, string>();
+ icon_name_editables = new Gee.HashMap<Type, bool>();
+ settings_names = new Gee.HashMap<Type, string>();
+
+ string name = "";
+ bool icon_name_editable = true;
+ string settings_name = "";
+
+ AppAction.register(out name, out icon_name_editable, out settings_name);
+ types.add(typeof(AppAction));
+ names.set(typeof(AppAction), name);
+ icon_name_editables.set(typeof(AppAction), icon_name_editable);
+ settings_names.set(typeof(AppAction), settings_name);
+
+ KeyAction.register(out name, out icon_name_editable, out settings_name);
+ types.add(typeof(KeyAction));
+ names.set(typeof(KeyAction), name);
+ icon_name_editables.set(typeof(KeyAction), icon_name_editable);
+ settings_names.set(typeof(KeyAction), settings_name);
+
+ PieAction.register(out name, out icon_name_editable, out settings_name);
+ types.add(typeof(PieAction));
+ names.set(typeof(PieAction), name);
+ icon_name_editables.set(typeof(PieAction), icon_name_editable);
+ settings_names.set(typeof(PieAction), settings_name);
+
+ UriAction.register(out name, out icon_name_editable, out settings_name);
+ types.add(typeof(UriAction));
+ names.set(typeof(UriAction), name);
+ icon_name_editables.set(typeof(UriAction), icon_name_editable);
+ settings_names.set(typeof(UriAction), settings_name);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper method which creates an Action, appropriate for the
+ /// given URI. This can result in an UriAction or in an AppAction,
+ /// depending on the Type of the URI.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? new_for_uri(string uri, string? name = null) {
+ var file = GLib.File.new_for_uri(uri);
+ var scheme = file.get_uri_scheme();
+
+ string final_icon = "";
+ string final_name = file.get_basename();
+
+ switch (scheme) {
+ case "application":
+ var file_name = uri.split("//")[1];
+
+ var desktop_file = GLib.File.new_for_path("/usr/share/applications/" + file_name);
+ if (desktop_file.query_exists())
+ return new_for_desktop_file(desktop_file.get_path());
+
+ break;
+
+ case "trash":
+ final_icon = "user-trash";
+ final_name = _("Trash");
+ break;
+
+ case "http": case "https":
+ final_icon = "www";
+ break;
+
+ case "ftp": case "sftp":
+ final_icon = "folder-remote";
+ break;
+
+ default:
+ try {
+ var info = file.query_info("*", GLib.FileQueryInfoFlags.NONE);
+
+ if (info.get_content_type() == "application/x-desktop")
+ return new_for_desktop_file(file.get_parse_name());
+
+ // search for an appropriate icon
+ var gicon = info.get_icon();
+ string[] icons = gicon.to_string().split(" ");
+
+ foreach (var icon in icons) {
+ if (Gtk.IconTheme.get_default().has_icon(icon)) {
+ final_icon = icon;
+ break;
+ }
+ }
+
+ } catch (GLib.Error e) {
+ warning(e.message);
+ }
+
+ break;
+ }
+
+ if (!Gtk.IconTheme.get_default().has_icon(final_icon))
+ final_icon = "application-default-icon";
+
+ if (name != null)
+ final_name = name;
+
+ return new UriAction(final_name, final_icon, uri);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper method which creates an AppAction for given AppInfo.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? new_for_app_info(GLib.AppInfo info) {
+ string[] icons = info.get_icon().to_string().split(" ");
+ string final_icon = "application-default-icon";
+
+ // search for available icons
+ foreach (var icon in icons) {
+ if (Gtk.IconTheme.get_default().has_icon(icon)) {
+ final_icon = icon;
+ break;
+ }
+ }
+
+ return new AppAction(info.get_display_name() , final_icon, info.get_commandline());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper method which creates an AppAction for given *.desktop
+ /// file.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? new_for_desktop_file(string file_name) {
+ var info = new DesktopAppInfo.from_filename(file_name);
+ return new_for_app_info(info);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper method which creates an AppAction for given mime type.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? default_for_mime_type(string type) {
+ var info = AppInfo.get_default_for_type(type, false);
+ return new_for_app_info(info);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper method which creates an AppAction for given uri scheme.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? default_for_uri(string uri) {
+ var info = AppInfo. get_default_for_uri_scheme(uri);
+ return new_for_app_info(info);
+ }
+}
+
+}
diff --git a/src/actions/appAction.vala b/src/actions/appAction.vala
new file mode 100644
index 0000000..d8363e4
--- /dev/null
+++ b/src/actions/appAction.vala
@@ -0,0 +1,72 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This type of Action launches an application or a custom command.
+/////////////////////////////////////////////////////////////////////////
+
+public class AppAction : Action {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of Action. It sets the display name
+ /// for this Action, whether it has a custom Icon/Name and the string
+ /// used in the pies.conf file for this kind of Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out bool icon_name_editable, out string settings_name) {
+ name = _("Launch application");
+ icon_name_editable = true;
+ settings_name = "app";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the command line.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Simply returns the real_command. No beautification.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string display_command { get {return real_command;} }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public AppAction(string name, string icon, string command, bool is_quick_action = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Launches the desired command.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate() {
+ try{
+ var item = GLib.AppInfo.create_from_commandline(this.real_command, null, GLib.AppInfoCreateFlags.NONE);
+ item.launch(null, null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+}
+
+}
diff --git a/src/actions/keyAction.vala b/src/actions/keyAction.vala
new file mode 100644
index 0000000..0f6d094
--- /dev/null
+++ b/src/actions/keyAction.vala
@@ -0,0 +1,77 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This type of Action "presses" a key stroke.
+/////////////////////////////////////////////////////////////////////////
+
+public class KeyAction : Action {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of Action. It sets the display name
+ /// for this Action, whether it has a custom Icon/Name and the string
+ /// used in the pies.conf file for this kind of Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out bool icon_name_editable, out string settings_name) {
+ name = _("Press key stroke");
+ icon_name_editable = true;
+ settings_name = "key";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the accelerator of this action.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human readable form of the accelerator.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string display_command { get {return key.label;} }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The simulated key which gets 'pressed' on execution.
+ /////////////////////////////////////////////////////////////////////
+
+ public Key key { get; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public KeyAction(string name, string icon, string command, bool is_quick_action = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action);
+ }
+
+ construct {
+ this.key = new Key(real_command);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Presses the desired key.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate() {
+ key.press();
+ }
+}
+
+}
diff --git a/src/actions/pieAction.vala b/src/actions/pieAction.vala
new file mode 100644
index 0000000..53ea919
--- /dev/null
+++ b/src/actions/pieAction.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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This Action opens another pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieAction : Action {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of Action. It sets the display name
+ /// for this Action, whether it has a custom Icon/Name and the string
+ /// used in the pies.conf file for this kind of Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out bool icon_name_editable, out string settings_name) {
+ name = _("Open Pie");
+ icon_name_editable = false;
+ settings_name = "pie";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the ID of the referenced Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name of the referenced Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string display_command { get {return name;} }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name of the referenced Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string name {
+ get {
+ var referee = PieManager.all_pies[real_command];
+ if (referee != null)
+ return referee.name;
+ return "";
+ }
+ protected set {}
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the icon of the referenced Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string icon {
+ get {
+ var referee = PieManager.all_pies[real_command];
+ if (referee != null)
+ return referee.icon;
+ return "";
+ }
+ protected set {}
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieAction(string id, bool is_quick_action = false) {
+ GLib.Object(name : "", icon : "", real_command : id, is_quick_action : is_quick_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the desired Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate() {
+ PieManager.open_pie(real_command);
+ }
+}
+
+}
diff --git a/src/actions/sigAction.vala b/src/actions/sigAction.vala
new file mode 100644
index 0000000..cec9836
--- /dev/null
+++ b/src/actions/sigAction.vala
@@ -0,0 +1,63 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This type of Action can't be selected by the user, therefore there is
+/// no register() method for this class. But it may be useful for
+/// ActionGroups: It emits a signal on activation.
+/////////////////////////////////////////////////////////////////////////
+
+public class SigAction : Action {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal is emitted on activation.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void activated();
+
+ /////////////////////////////////////////////////////////////////////
+ /// This may store something useful.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Only for inheritance... Greetings to Liskov.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string display_command { get {return real_command;} }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public SigAction(string name, string icon, string command, bool is_quick_action = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Emits the signal on activation.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate() {
+ this.activated();
+ }
+}
+
+}
diff --git a/src/actions/uriAction.vala b/src/actions/uriAction.vala
new file mode 100644
index 0000000..25d5c75
--- /dev/null
+++ b/src/actions/uriAction.vala
@@ -0,0 +1,71 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This type of Action opens the default application for an URI.
+/////////////////////////////////////////////////////////////////////////
+
+public class UriAction : Action {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Used to register this type of Action. It sets the display name
+ /// for this Action, whether it has a custom Icon/Name and the string
+ /// used in the pies.conf file for this kind of Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void register(out string name, out bool icon_name_editable, out string settings_name) {
+ name = _("Open URI");
+ icon_name_editable = true;
+ settings_name = "uri";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The URI of this Action.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string real_command { get; construct set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns only the real URI. An URI can't be beautified.
+ /////////////////////////////////////////////////////////////////////
+
+ public override string display_command { get {return real_command;} }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public UriAction(string name, string icon, string command, bool is_quick_action = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the default application for the URI.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate() {
+ try{
+ GLib.AppInfo.launch_default_for_uri(real_command, null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+}
+
+}
diff --git a/src/deamon.vala b/src/deamon.vala
new file mode 100644
index 0000000..af232eb
--- /dev/null
+++ b/src/deamon.vala
@@ -0,0 +1,175 @@
+/*
+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/>.
+*/
+
+/////////////////////////////////////////////////////////////////////
+/// TODO-List:
+/// IconSelectWindow
+/// PieList
+/// PieWindow
+/// CenterRenderer
+/// SliceRenderer
+/// PieRenderer
+/////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class runs in the background. It has an Indicator sitting in the
+/// user's panel. It initializes everything and guarantees that there is
+/// only one instance of Gnome-Pie running.
+/////////////////////////////////////////////////////////////////////////
+
+public class Deamon : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The beginning of everything.
+ /////////////////////////////////////////////////////////////////////
+
+ public static int main(string[] args) {
+ Logger.init();
+ Gtk.init(ref args);
+ Paths.init();
+
+ // create the Deamon and run it
+ var deamon = new GnomePie.Deamon();
+ deamon.run(args);
+
+ return 0;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The AppIndicator of Gnome-Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private Indicator indicator = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Varaibles set by the commend line parser.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string open_pie = null;
+ private static bool reset = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Available command line options.
+ /////////////////////////////////////////////////////////////////////
+
+ private const GLib.OptionEntry[] options = {
+ { "open", 'o', 0, GLib.OptionArg.STRING, out open_pie,
+ "Open the Pie with the given ID", "ID" },
+ { "reset", 'r', 0, GLib.OptionArg.NONE, out reset,
+ "Reset all options to default values" },
+ { null }
+ };
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor of the Deamon. It checks whether it's the firts running
+ /// instance of Gnome-Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void run(string[] args) {
+ // create command line options
+ var context = new GLib.OptionContext("");
+ context.set_help_enabled(true);
+ context.add_main_entries(options, null);
+ context.add_group(Gtk.get_option_group (false));
+
+ try {
+ context.parse(ref args);
+ } catch(GLib.OptionError error) {
+ warning(error.message);
+ }
+
+ if (this.reset) {
+ if (GLib.FileUtils.remove(Paths.pie_config) == 0)
+ message("Removed file \"%s\"", Paths.pie_config);
+ if (GLib.FileUtils.remove(Paths.settings) == 0)
+ message("Removed file \"%s\"", Paths.settings);
+ return;
+ }
+
+ // create unique application
+ var app = new Unique.App("org.gnome.gnomepie", null);
+
+ if (app.is_running) {
+ // inform the running instance of the pie to be opened
+ if (open_pie != null) {
+ var data = new Unique.MessageData();
+ data.set_text(open_pie, open_pie.length);
+ app.send_message(Unique.Command.ACTIVATE, data);
+ return;
+ }
+
+ app.send_message(Unique.Command.ACTIVATE, null);
+ return;
+ }
+
+ // wait for incoming messages
+ app.message_received.connect((cmd, data, event_time) => {
+ if (cmd == Unique.Command.ACTIVATE) {
+ var pie = data.get_text();
+
+ if (pie != "") PieManager.open_pie(pie);
+ else this.indicator.show_preferences();
+
+ return Unique.Response.OK;
+ }
+
+ return Unique.Response.PASSTHROUGH;
+ });
+
+ // init toolkits and static stuff
+ Gdk.threads_init();
+ ActionRegistry.init();
+ GroupRegistry.init();
+ PieManager.init();
+ Icon.init();
+ ThemedIcon.init();
+ RenderedText.init();
+
+ // init locale support
+ Intl.bindtextdomain ("gnomepie", Paths.locales);
+ Intl.textdomain ("gnomepie");
+
+ // launch the indicator
+ this.indicator = new Indicator();
+
+ // connect SigHandlers
+ Posix.signal(Posix.SIGINT, sig_handler);
+ Posix.signal(Posix.SIGTERM, sig_handler);
+
+ // finished loading... so run the prog!
+ message("Started happily...");
+
+ // open pie if neccessary
+ if (open_pie != null) PieManager.open_pie(open_pie);
+
+ Gtk.main();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Print a nifty message when the prog is killed.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void sig_handler(int sig) {
+ stdout.printf("\n");
+ message("Caught signal (%d), bye!".printf(sig));
+ Gtk.main_quit();
+ }
+}
+
+}
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];
+ }
+ }
+}
+
+}
diff --git a/src/pies/defaultConfig.vala b/src/pies/defaultConfig.vala
new file mode 100644
index 0000000..bd981b5
--- /dev/null
+++ b/src/pies/defaultConfig.vala
@@ -0,0 +1,70 @@
+/*
+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 helper class which creates a user-specific default configuration.
+/////////////////////////////////////////////////////////////////////////
+
+namespace Pies {
+
+ public void create_default_config() {
+
+ // add a pie with playback controls
+ var multimedia = PieManager.create_persistent_pie(_("Multimedia"), "stock_media-play", "<Control><Alt>m");
+ multimedia.add_action(new KeyAction(_("Next Track"), "stock_media-next", "XF86AudioNext", true));
+ multimedia.add_action(new KeyAction(_("Stop"), "stock_media-stop", "XF86AudioStop"));
+ multimedia.add_action(new KeyAction(_("Previous Track"), "stock_media-prev", "XF86AudioPrev"));
+ multimedia.add_action(new KeyAction(_("Play/Pause"), "stock_media-play", "XF86AudioPlay"));
+
+ // add a pie with the users default applications
+ var apps = PieManager.create_persistent_pie(_("Applications"), "applications-accessories", "<Control><Alt>a");
+ apps.add_action(ActionRegistry.default_for_mime_type("text/plain"));
+ apps.add_action(ActionRegistry.default_for_mime_type("audio/ogg"));
+ apps.add_action(ActionRegistry.default_for_mime_type("video/ogg"));
+ apps.add_action(ActionRegistry.default_for_mime_type("image/jpg"));
+ apps.add_action(ActionRegistry.default_for_uri("http"));
+ apps.add_action(ActionRegistry.default_for_uri("mailto"));
+
+ // add a pie with the users bookmarks and devices
+ var bookmarks = PieManager.create_persistent_pie(_("Bookmarks"), "user-bookmarks", "<Control><Alt>b");
+ bookmarks.add_group(new BookmarkGroup(bookmarks.id));
+ bookmarks.add_group(new DevicesGroup(bookmarks.id));
+
+ // add a pie with session controls
+ var session = PieManager.create_persistent_pie(_("Session"), "gnome-session-halt", "<Control><Alt>q");
+ session.add_group(new SessionGroup(session.id));
+
+ // add a pie with a main menu
+ var menu = PieManager.create_persistent_pie(_("Main Menu"), "alacarte", "<Control><Alt>space");
+ menu.add_group(new MenuGroup(menu.id));
+
+ // add a pie with window controls
+ var window = PieManager.create_persistent_pie(_("Window"), "gnome-window-manager", "<Control><Alt>w");
+ window.add_action(new KeyAction(_("Scale"), "top", "<Control><Alt>s"));
+ window.add_action(new KeyAction(_("Minimize"), "bottom", "<Alt>F9", true));
+ window.add_action(new KeyAction(_("Close"), "window-close", "<Alt>F4"));
+ window.add_action(new KeyAction(_("Maximize"), "window_fullscreen", "<Alt>F10"));
+ window.add_action(new KeyAction(_("Restore"), "window_nofullscreen", "<Alt>F5"));
+
+ // save the configuration to file
+ Pies.save();
+ }
+}
+
+}
diff --git a/src/pies/load.vala b/src/pies/load.vala
new file mode 100644
index 0000000..912ddf0
--- /dev/null
+++ b/src/pies/load.vala
@@ -0,0 +1,230 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A helper method which loads pies according to the configuration file.
+/////////////////////////////////////////////////////////////////////////
+
+namespace Pies {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all Pies from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ // check whether the config file exists
+ if (!GLib.File.new_for_path(Paths.pie_config).query_exists()) {
+ message("Creating new configuration file in \"" + Paths.pie_config + "\".");
+ Pies.create_default_config();
+ return;
+ }
+
+ // load the settings file
+ Xml.Parser.init();
+ Xml.Doc* piesXML = Xml.Parser.parse_file(Paths.pie_config);
+
+ if (piesXML != null) {
+ // begin parsing at the root element
+ Xml.Node* root = piesXML->get_root_element();
+ if (root != null) {
+ for (Xml.Node* node = root->children; node != null; node = node->next) {
+ if (node->type == Xml.ElementType.ELEMENT_NODE) {
+ string node_name = node->name.down();
+ switch (node_name) {
+ case "pie":
+ parse_pie(node);
+ break;
+ default:
+ warning("Invalid child element <" + node_name + "> in <pies> element pies.conf!");
+ break;
+ }
+ }
+ }
+ Xml.Parser.cleanup();
+
+ } else {
+ warning("Error loading pies: pies.conf is empty! The cake is a lie...");
+ }
+
+ delete piesXML;
+
+ } else {
+ warning("Error loading pies: pies.conf not found! The cake is a lie...");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <pie> element from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void parse_pie(Xml.Node* node) {
+ string hotkey = "";
+ string name = "";
+ string icon = "";
+ string id = "";
+ int quick_action = -1;
+
+ // parse all attributes of this node
+ for (Xml.Attr* attribute = node->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "hotkey":
+ hotkey = attr_content;
+ break;
+ case "quickaction":
+ quick_action = int.parse(attr_content);
+ break;
+ case "name":
+ name = attr_content;
+ break;
+ case "icon":
+ icon = attr_content;
+ break;
+ case "id":
+ id = attr_content;
+ break;
+ default:
+ warning("Invalid setting \"" + attr_name + "\" in pies.conf!");
+ break;
+ }
+ }
+
+ if (name == "") {
+ warning("Invalid <pie> element in pies.conf: No name specified!");
+ return;
+ }
+
+ // add a new Pie with the loaded properties
+ var pie = PieManager.create_persistent_pie(name, icon, hotkey, id);
+
+ // and parse all child elements
+ for (Xml.Node* slice = node->children; slice != null; slice = slice->next) {
+ if (slice->type == Xml.ElementType.ELEMENT_NODE) {
+ string node_name = slice->name.down();
+ switch (node_name) {
+ case "slice":
+ parse_slice(slice, pie);
+ break;
+ case "group":
+ parse_group(slice, pie);
+ break;
+ default:
+ warning("Invalid child element <" + node_name + "> in <pie> element in pies.conf!");
+ break;
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <slice> element from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void parse_slice(Xml.Node* slice, Pie pie) {
+ string name="";
+ string icon="";
+ string command="";
+ string type="";
+ bool quick_action = false;
+
+ // parse all attributes of this node
+ for (Xml.Attr* attribute = slice->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "name":
+ name = attr_content;
+ break;
+ case "icon":
+ icon = attr_content;
+ break;
+ case "command":
+ command = attr_content;
+ break;
+ case "type":
+ type = attr_content;
+ break;
+ case "quickaction":
+ quick_action = bool.parse(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <slice> element in pies.conf!");
+ break;
+ }
+ }
+
+ Action action = null;
+
+ // create a new Action according to the loaded type
+ foreach (var action_type in ActionRegistry.types) {
+ if (ActionRegistry.settings_names[action_type] == type) {
+
+ action = GLib.Object.new(action_type, "name", name,
+ "icon", icon,
+ "real_command", command,
+ "is_quick_action", quick_action) as Action;
+ break;
+ }
+ }
+
+ if (action != null) pie.add_action(action);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <group> element from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void parse_group(Xml.Node* slice, Pie pie) {
+ string type="";
+
+ // parse all attributes of this node
+ for (Xml.Attr* attribute = slice->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "type":
+ type = attr_content;
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <group> element in pies.conf!");
+ break;
+ }
+ }
+
+ ActionGroup group = null;
+
+ // create a new ActionGroup according to the loaded type
+ foreach (var group_type in GroupRegistry.types) {
+ if (GroupRegistry.settings_names[group_type] == type) {
+ group = GLib.Object.new(group_type, "parent_id", pie.id) as ActionGroup;
+ break;
+ }
+ }
+
+ if (group != null) pie.add_group(group);
+ }
+}
+
+}
diff --git a/src/pies/pie.vala b/src/pies/pie.vala
new file mode 100644
index 0000000..0f87d8f
--- /dev/null
+++ b/src/pies/pie.vala
@@ -0,0 +1,96 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class stores information on a pie. A pie consists of a name, an
+/// icon name and an unique ID. Furthermore it has an arbitrary amount
+/// of ActionGroups storing Actions.
+/////////////////////////////////////////////////////////////////////////
+
+public class Pie : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The name of this Pie. It has not to be unique.
+ /////////////////////////////////////////////////////////////////////
+
+ public string name { get; construct; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The name of the icon to be used for this Pie. It should exist in
+ /// the users current icon theme, else a standard icon will be used.
+ /////////////////////////////////////////////////////////////////////
+
+ public string icon { get; construct; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of this Pie. It has to be unique among all Pies. This ID
+ /// consists of three digits when the Pie was created by the user,
+ /// of four digits when it was created dynamically by another class,
+ /// for example by an ActionGroup.
+ /////////////////////////////////////////////////////////////////////
+
+ public string id { get; construct; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores all ActionGroups of this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gee.ArrayList<ActionGroup?> action_groups { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all given members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Pie(string id, string name, string icon) {
+ GLib.Object(id: id, name: name, icon:icon);
+
+ this.action_groups = new Gee.ArrayList<ActionGroup?>();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Should be called when this Pie is deleted, in order to clean up
+ /// stuff created by contained ActionGroups.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual void on_remove() {
+ foreach (var action_group in action_groups)
+ action_group.on_remove();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds an Action to this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_action(Action action) {
+ var group = new ActionGroup(this.id);
+ group.add_action(action);
+ this.add_group(group);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds an ActionGroup to this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_group(ActionGroup group) {
+ this.action_groups.add(group);
+ }
+}
+
+}
+
diff --git a/src/pies/pieManager.vala b/src/pies/pieManager.vala
new file mode 100644
index 0000000..eb031d0
--- /dev/null
+++ b/src/pies/pieManager.vala
@@ -0,0 +1,231 @@
+/*
+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 static class which stores all Pies. It can be used to add, delete
+/// and open Pies.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieManager : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A map of all Pies. It contains both, dynamic and persistent Pies.
+ /// They are associated to their ID's.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.HashMap<string, Pie?> all_pies { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores all global hotkeys.
+ /////////////////////////////////////////////////////////////////////
+
+ private static BindingManager bindings;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if any pie has the current focus. If it is closing this
+ /// will be false already.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool a_pie_is_opened = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes all Pies. They are loaded from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ all_pies = new Gee.HashMap<string, Pie?>();
+ bindings = new BindingManager();
+
+ // load all Pies from th pies.conf file
+ Pies.load();
+
+ // open the according pie if it's hotkey is pressed
+ bindings.on_press.connect((id) => {
+ open_pie(id);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the Pie with the given ID, if it exists.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void open_pie(string id) {
+ if (!a_pie_is_opened) {
+ Pie? pie = all_pies[id];
+
+ if (pie != null) {
+ a_pie_is_opened = true;
+
+ var window = new PieWindow();
+ window.load_pie(pie);
+ window.open();
+
+ window.on_closing.connect(() => {
+ a_pie_is_opened = false;
+ });
+ } else {
+ warning("Failed to open pie with ID \"" + id + "\": ID does not exist!");
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the hotkey which the Pie with the given ID is bound to.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_accelerator_of(string id) {
+ return bindings.get_accelerator_of(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human-readable version of the hotkey which the Pie
+ /// with the given ID is bound to.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_accelerator_label_of(string id) {
+ return bindings.get_accelerator_label_of(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name of the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_name_of(string id) {
+ Pie? pie = all_pies[id];
+ if (pie == null) return "";
+ else return pie.name;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new Pie which is displayed in the configuration dialog
+ /// and gets saved.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Pie create_persistent_pie(string name, string icon_name, string hotkey, string? desired_id = null) {
+ Pie pie = create_pie(name, icon_name, 100, 999, desired_id);
+
+ if (hotkey != "") bindings.bind(hotkey, pie.id);
+
+ create_launcher(pie.id);
+
+ return pie;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new Pie which is not displayed in the configuration
+ /// dialog and is not saved.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Pie create_dynamic_pie(string name, string icon_name, string? desired_id = null) {
+ return create_pie(name, icon_name, 1000, 9999, desired_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds a new Pie. Can't be accesd from outer scope. Use
+ /// create_persistent_pie or create_dynamic_pie instead.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Pie create_pie(string name, string icon_name, int min_id, int max_id, string? desired_id = null) {
+ var random = new GLib.Rand();
+
+ string final_id;
+
+ if (desired_id == null)
+ final_id = random.int_range(min_id, max_id).to_string();
+ else {
+ final_id = desired_id;
+ final_id.canon("0123456789", '_');
+ final_id = final_id.replace("_", "");
+
+ int id = int.parse(final_id);
+
+ if (id < min_id || id > max_id) {
+ final_id = random.int_range(min_id, max_id).to_string();
+ warning("The ID for pie \"" + name + "\" should be in range %u - %u! Using \"" + final_id + "\" instead of \"" + desired_id + "\"...", min_id, max_id);
+ }
+ }
+
+ if (all_pies.has_key(final_id)) {
+ var tmp = final_id;
+ var id_number = int.parse(final_id) + 1;
+ if (id_number == max_id+1) id_number = min_id;
+ final_id = id_number.to_string();
+ warning("Trying to add pie \"" + name + "\": ID \"" + tmp + "\" already exists! Using \"" + final_id + "\" instead...");
+ return create_pie(name, icon_name, min_id, max_id, final_id);
+ }
+
+ Pie pie = new Pie(final_id, name, icon_name);
+ all_pies.set(final_id, pie);
+
+ return pie;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Removes the Pie with the given ID if it exists. Additionally it
+ /// unbinds it's global hotkey.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void remove_pie(string id) {
+ if (all_pies.has_key(id)) {
+ all_pies[id].on_remove();
+ all_pies.unset(id);
+ bindings.unbind(id);
+
+ if (id.length == 3)
+ remove_launcher(id);
+ }
+ else {
+ warning("Failed to remove pie with ID \"" + id + "\": ID does not exist!");
+ }
+ }
+
+ private static void create_launcher(string id) {
+ if (all_pies.has_key(id)) {
+ Pie? pie = all_pies[id];
+
+ string launcher_entry =
+ "#!/usr/bin/env xdg-open\n" +
+ "[Desktop Entry]\n" +
+ "Name=%s\n".printf(pie.name) +
+ "Exec=gnome-pie -o %s\n".printf(pie.id) +
+ "Encoding=UTF-8\n" +
+ "Type=Application\n" +
+ "Icon=%s\n".printf(pie.icon);
+
+ // create the launcher file
+ string launcher = Paths.launchers + "/%s.desktop".printf(pie.id);
+
+ try {
+ FileUtils.set_contents(launcher, launcher_entry);
+ FileUtils.chmod(launcher, 0755);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+ }
+
+ private static void remove_launcher(string id) {
+ string launcher = Paths.launchers + "/%s.desktop".printf(id);
+ if (FileUtils.test(launcher, FileTest.EXISTS)) {
+ FileUtils.remove(launcher);
+ }
+ }
+}
+
+}
diff --git a/src/pies/save.vala b/src/pies/save.vala
new file mode 100644
index 0000000..d691a95
--- /dev/null
+++ b/src/pies/save.vala
@@ -0,0 +1,81 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A helper method which saves pies in a configuration file.
+/////////////////////////////////////////////////////////////////////////
+
+namespace Pies {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Saves all Pies of the PieManager to the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ public void save() {
+ // initializes the XML-Writer
+ var writer = new Xml.TextWriter.filename(Paths.pie_config);
+ writer.set_indent(true);
+ writer.start_document("1.0");
+ writer.start_element("pies");
+
+ // iterate through all Pies
+ foreach (var pie_entry in PieManager.all_pies.entries) {
+ var pie = pie_entry.value;
+
+ // if it's no dynamically created Pie
+ if (pie.id.length == 3) {
+ // write all attributes of the Pie
+ writer.start_element("pie");
+ writer.write_attribute("name", pie.name);
+ writer.write_attribute("id", pie.id);
+ writer.write_attribute("icon", pie.icon);
+ writer.write_attribute("hotkey", PieManager.get_accelerator_of(pie.id));
+
+ // and all of it's Actions
+ foreach (var group in pie.action_groups) {
+ // if it's a custom ActionGroup
+ if (group.get_type().depth() == 2) {
+ foreach (var action in group.actions) {
+ writer.start_element("slice");
+ writer.write_attribute("type", ActionRegistry.settings_names[action.get_type()]);
+ if (ActionRegistry.icon_name_editables[action.get_type()]) {
+ writer.write_attribute("name", action.name);
+ writer.write_attribute("icon", action.icon);
+ }
+ writer.write_attribute("command", action.real_command);
+ writer.write_attribute("quickAction", action.is_quick_action ? "true" : "false");
+ writer.end_element();
+ }
+ } else {
+ writer.start_element("group");
+ writer.write_attribute("type", GroupRegistry.settings_names[group.get_type()]);
+ writer.end_element();
+ }
+ }
+ writer.end_element();
+ }
+ }
+ writer.end_element();
+ writer.end_document();
+ }
+}
+
+}
diff --git a/src/renderers/centerRenderer.vala b/src/renderers/centerRenderer.vala
new file mode 100644
index 0000000..c30e9ce
--- /dev/null
+++ b/src/renderers/centerRenderer.vala
@@ -0,0 +1,146 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// Renders the center of a Pie.
+
+public class CenterRenderer : GLib.Object {
+
+ private unowned PieRenderer parent;
+ private unowned Image? caption;
+ private Color color;
+
+ private AnimatedValue activity;
+ private AnimatedValue alpha;
+
+ public CenterRenderer(PieRenderer parent) {
+ this.parent = parent;
+ this.activity = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time);
+ this.color = new Color();
+ this.caption = null;
+ }
+
+ public void fade_out() {
+ this.activity.reset_target(0.0, Config.global.theme.fade_out_time);
+ this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
+ }
+
+ public void set_active_slice(SliceRenderer? active_slice) {
+ if (active_slice == null) {
+ this.activity.reset_target(0.0, Config.global.theme.transition_time);
+ } else {
+ this.activity.reset_target(1.0, Config.global.theme.transition_time);
+ this.caption = active_slice.caption;
+ this.color = active_slice.color;
+ }
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) {
+
+ var layers = Config.global.theme.center_layers;
+
+ this.activity.update(frame_time);
+ this.alpha.update(frame_time);
+
+ foreach (var layer in layers) {
+
+ ctx.save();
+
+ double active_speed = (layer.active_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?
+ 0.0 : layer.active_rotation_speed;
+ double inactive_speed = (layer.inactive_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?
+ 0.0 : layer.inactive_rotation_speed;
+ double max_scale = layer.active_scale*this.activity.val
+ + layer.inactive_scale*(1.0-this.activity.val);
+ double max_alpha = layer.active_alpha*this.activity.val
+ + layer.inactive_alpha*(1.0-this.activity.val);
+ double colorize = ((layer.active_colorize == true) ? this.activity.val : 0.0)
+ + ((layer.inactive_colorize == true) ? 1.0 - this.activity.val : 0.0);
+ double max_rotation_speed = active_speed*this.activity.val
+ + inactive_speed*(1.0-this.activity.val);
+ CenterLayer.RotationMode rotation_mode = ((this.activity.val > 0.5) ?
+ layer.active_rotation_mode : layer.inactive_rotation_mode);
+
+ if (rotation_mode == CenterLayer.RotationMode.TO_MOUSE) {
+ double diff = angle-layer.rotation;
+ max_rotation_speed = layer.active_rotation_speed*this.activity.val
+ + layer.inactive_rotation_speed*(1.0-this.activity.val);
+ double smoothy = fabs(diff) < 0.9 ? fabs(diff) + 0.1 : 1.0;
+ double step = max_rotation_speed*frame_time*smoothy;
+
+ if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step)
+ layer.rotation = angle;
+ else {
+ if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
+ else layer.rotation -= step;
+ }
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_ACTIVE) {
+ max_rotation_speed *= this.activity.val;
+
+ double slice_angle = parent.slice_count() > 0 ? 2*PI/parent.slice_count() : 0;
+ double direction = (int)((angle+0.5*slice_angle) / (slice_angle))*slice_angle;
+ double diff = direction-layer.rotation;
+ double step = max_rotation_speed*frame_time;
+
+ if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step)
+ layer.rotation = direction;
+ else {
+ if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
+ else layer.rotation -= step;
+ }
+
+ } else layer.rotation += max_rotation_speed*frame_time;
+
+ layer.rotation = fmod(layer.rotation+2*PI, 2*PI);
+
+ if (colorize > 0.0) ctx.push_group();
+
+ ctx.rotate(layer.rotation);
+ ctx.scale(max_scale, max_scale);
+ layer.image.paint_on(ctx, this.alpha.val*max_alpha);
+
+ if (colorize > 0.0) {
+ ctx.set_operator(Cairo.Operator.ATOP);
+ ctx.set_source_rgb(this.color.r, this.color.g, this.color.b);
+ ctx.paint_with_alpha(colorize);
+
+ ctx.set_operator(Cairo.Operator.OVER);
+ ctx.pop_group_to_source();
+ ctx.paint();
+ }
+
+ ctx.restore();
+ }
+
+ // draw caption
+ if (Config.global.theme.caption && caption != null && this.activity.val > 0) {
+ ctx.save();
+ ctx.identity_matrix();
+ int pos = this.parent.get_size()/2;
+ ctx.translate(pos, (int)(Config.global.theme.caption_position) + pos);
+ caption.paint_on(ctx, this.activity.val*this.alpha.val);
+ ctx.restore();
+ }
+ }
+}
+
+}
diff --git a/src/renderers/pieRenderer.vala b/src/renderers/pieRenderer.vala
new file mode 100644
index 0000000..5b706f4
--- /dev/null
+++ b/src/renderers/pieRenderer.vala
@@ -0,0 +1,208 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class renders a Pie. In order to accomplish that, it owns a
+/// CenterRenderer and some SliceRenderers.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieRenderer : GLib.Object {
+
+ public int quick_action { get; private set; }
+ public int active_slice { get; private set; }
+ public bool show_hotkeys { get; set; }
+
+ private int size;
+ private Gee.ArrayList<SliceRenderer?> slices;
+ private CenterRenderer center;
+ private bool key_board_control = false;
+
+ public PieRenderer() {
+ this.slices = new Gee.ArrayList<SliceRenderer?>();
+ this.center = new CenterRenderer(this);
+ this.quick_action = -1;
+ this.active_slice = -2;
+ this.size = 0;
+ }
+
+ public void load_pie(Pie pie) {
+ this.slices.clear();
+
+ int count = 0;
+ foreach (var group in pie.action_groups) {
+ foreach (var action in group.actions) {
+ var renderer = new SliceRenderer(this);
+ this.slices.add(renderer);
+ renderer.load(action, slices.size-1);
+
+ if (action.is_quick_action) {
+ this.quick_action = count;
+ }
+
+ ++count;
+ }
+ }
+
+ this.set_highlighted_slice(this.quick_action);
+
+ this.size = (int)fmax(2*Config.global.theme.radius + 2*Config.global.theme.slice_radius*Config.global.theme.max_zoom,
+ 2*Config.global.theme.center_radius);
+
+ // increase size if there are many slices
+ if (slices.size > 0) {
+ this.size = (int)fmax(this.size,
+ (((Config.global.theme.slice_radius + Config.global.theme.slice_gap)/tan(PI/slices.size))
+ + Config.global.theme.slice_radius)*2*Config.global.theme.max_zoom);
+ }
+ }
+
+ public void activate() {
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ slices[active_slice].activate();
+ this.cancel();
+ }
+
+ public void cancel() {
+ foreach (var slice in this.slices)
+ slice.fade_out();
+
+ center.fade_out();
+ }
+
+ public void select_up() {
+ int bottom = this.slice_count()/4;
+ int top = this.slice_count()*3/4;
+
+ if (this.active_slice == -1 || this.active_slice == bottom)
+ this.set_highlighted_slice(top);
+ else if (this.active_slice > bottom && this.active_slice < top)
+ this.set_highlighted_slice(this.active_slice+1);
+ else if (this.active_slice != top)
+ this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());
+ }
+
+ public void select_down() {
+ int bottom = this.slice_count()/4;
+ int top = this.slice_count()*3/4;
+
+ if (this.active_slice == -1 || this.active_slice == top)
+ this.set_highlighted_slice(bottom);
+ else if (this.active_slice > bottom && this.active_slice < top)
+ this.set_highlighted_slice(this.active_slice-1);
+ else if (this.active_slice != bottom)
+ this.set_highlighted_slice((this.active_slice+1)%this.slice_count());
+ }
+
+ public void select_left() {
+ int left = this.slice_count()/2;
+ int right = 0;
+
+ if (this.active_slice == -1 || this.active_slice == right)
+ this.set_highlighted_slice(left);
+ else if (this.active_slice > left)
+ this.set_highlighted_slice(this.active_slice-1);
+ else if (this.active_slice < left)
+ this.set_highlighted_slice(this.active_slice+1);
+ }
+
+ public void select_right() {
+ int left = this.slice_count()/2;
+ int right = 0;
+
+ if (this.active_slice == -1 || this.active_slice == left)
+ this.set_highlighted_slice(right);
+ else if (this.active_slice > left)
+ this.set_highlighted_slice((this.active_slice+1)%this.slice_count());
+ else if (this.active_slice < left && this.active_slice != right)
+ this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());
+ }
+
+ public int slice_count() {
+ return slices.size;
+ }
+
+ public int get_size() {
+ return size;
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, int mouse_x, int mouse_y) {
+ double distance = sqrt(mouse_x*mouse_x + mouse_y*mouse_y);
+ double angle = 0.0;
+
+ if (this.key_board_control) {
+ angle = 2.0*PI*this.active_slice/(double)slice_count();
+ } else {
+
+ if (distance > 0) {
+ angle = acos(mouse_x/distance);
+ if (mouse_y < 0)
+ angle = 2*PI - angle;
+ }
+
+ int next_active_slice = this.active_slice;
+
+ if (distance < Config.global.theme.active_radius
+ && this.quick_action >= 0 && this.quick_action < this.slices.size) {
+
+ next_active_slice = this.quick_action;
+ angle = 2.0*PI*quick_action/(double)slice_count();
+ } else if (distance > Config.global.theme.active_radius && this.slice_count() > 0) {
+ next_active_slice = (int)(angle*slices.size/(2*PI) + 0.5) % this.slice_count();
+ } else {
+ next_active_slice = -1;
+ }
+
+ this.set_highlighted_slice(next_active_slice);
+ }
+
+ center.draw(frame_time, ctx, angle, distance);
+
+ foreach (var slice in this.slices)
+ slice.draw(frame_time, ctx, angle, distance);
+ }
+
+ public void on_mouse_move() {
+ this.key_board_control = false;
+ }
+
+ public void set_highlighted_slice(int index) {
+ if (index != this.active_slice) {
+ if (index >= 0 && index < this.slice_count())
+ this.active_slice = index;
+ else if (this.quick_action >= 0)
+ this.active_slice = this.quick_action;
+ else
+ this.active_slice = -1;
+
+ SliceRenderer? active = (this.active_slice >= 0 && this.active_slice < this.slice_count()) ?
+ this.slices[this.active_slice] : null;
+
+ center.set_active_slice(active);
+
+ foreach (var slice in this.slices)
+ slice.set_active_slice(active);
+
+ this.key_board_control = true;
+ }
+ }
+}
+
+}
diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala
new file mode 100644
index 0000000..c4ac2ec
--- /dev/null
+++ b/src/renderers/pieWindow.vala
@@ -0,0 +1,263 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// An invisible window. Used to draw Pies onto.
+
+public class PieWindow : Gtk.Window {
+
+ public signal void on_closing();
+
+ private PieRenderer renderer;
+ private bool closing = false;
+ private GLib.Timer timer;
+
+ private bool has_compositing = false;
+
+ private Image background = null;
+
+ public PieWindow() {
+ this.renderer = new PieRenderer();
+
+ this.set_title("Gnome-Pie");
+ this.set_skip_taskbar_hint(true);
+ this.set_skip_pager_hint(true);
+ this.set_keep_above(true);
+ this.set_type_hint(Gdk.WindowTypeHint.SPLASHSCREEN);
+ this.set_decorated(false);
+ this.set_resizable(false);
+ this.icon_name = "gnome-pie";
+ this.set_accept_focus(false);
+
+ if (this.screen.is_composited()) {
+ this.set_colormap(this.screen.get_rgba_colormap());
+ this.has_compositing = true;
+ }
+
+ this.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.KEY_RELEASE_MASK |
+ Gdk.EventMask.KEY_PRESS_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK);
+
+ this.button_release_event.connect ((e) => {
+ if (e.button == 1) this.activate_slice();
+ else this.cancel();
+ return true;
+ });
+
+ // remember last pressed key in order to disable key repeat
+ uint last_key = 0;
+ this.key_press_event.connect((e) => {
+ if (e.keyval != last_key) {
+ last_key = e.keyval;
+ this.handle_key_press(e.keyval);
+ }
+ return true;
+ });
+
+ this.key_release_event.connect((e) => {
+ last_key = 0;
+ if (Config.global.turbo_mode)
+ this.activate_slice();
+ else
+ this.handle_key_release(e.keyval);
+ return true;
+ });
+
+ this.motion_notify_event.connect((e) => {
+ this.renderer.on_mouse_move();
+ return true;
+ });
+
+ this.expose_event.connect(this.draw);
+ }
+
+ public void load_pie(Pie pie) {
+ this.renderer.load_pie(pie);
+ this.set_window_position();
+ this.set_size_request(renderer.get_size(), renderer.get_size());
+ }
+
+ public void open() {
+ this.realize();
+
+ if (!this.has_compositing) {
+ int x, y, width, height;
+ this.get_position(out x, out y);
+ this.get_size(out width, out height);
+ this.background = new Image.capture_screen(x, y, width+1, height+1);
+ }
+
+ this.show();
+ this.fix_focus();
+
+ this.timer = new GLib.Timer();
+ this.timer.start();
+ this.queue_draw();
+
+ Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {
+ this.queue_draw();
+ return this.visible;
+ });
+ }
+
+ private bool draw(Gtk.Widget da, Gdk.EventExpose event) {
+ // clear the window
+ var ctx = Gdk.cairo_create(this.window);
+
+ if (this.has_compositing) {
+ ctx.set_operator (Cairo.Operator.CLEAR);
+ ctx.paint();
+ ctx.set_operator (Cairo.Operator.OVER);
+ } else {
+ ctx.set_operator (Cairo.Operator.OVER);
+ ctx.set_source_surface(background.surface, -1, -1);
+ ctx.paint();
+ }
+
+ ctx.translate(this.width_request*0.5, this.height_request*0.5);
+
+ double mouse_x = 0.0, mouse_y = 0.0;
+ this.get_pointer(out mouse_x, out mouse_y);
+
+ double frame_time = this.timer.elapsed();
+ this.timer.reset();
+
+ this.renderer.draw(frame_time, ctx, (int)(mouse_x - this.width_request*0.5),
+ (int)(mouse_y - this.height_request*0.5));
+
+ return true;
+ }
+
+ private void activate_slice() {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ this.unfix_focus();
+ this.renderer.activate();
+
+ Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.destroy();
+ //ThemedIcon.clear_cache();
+ return false;
+ });
+ }
+ }
+
+ private void cancel() {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ this.unfix_focus();
+ this.renderer.cancel();
+
+ Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.destroy();
+ //ThemedIcon.clear_cache();
+ return false;
+ });
+ }
+ }
+
+ private void set_window_position() {
+ if(Config.global.open_at_mouse) this.set_position(Gtk.WindowPosition.MOUSE);
+ else this.set_position(Gtk.WindowPosition.CENTER);
+ }
+
+ private void handle_key_press(uint key) {
+ if (Gdk.keyval_name(key) == "Escape") this.cancel();
+ else if (Gdk.keyval_name(key) == "Return") this.activate_slice();
+ else if (!Config.global.turbo_mode) {
+ if (Gdk.keyval_name(key) == "Up") this.renderer.select_up();
+ else if (Gdk.keyval_name(key) == "Down") this.renderer.select_down();
+ else if (Gdk.keyval_name(key) == "Left") this.renderer.select_left();
+ else if (Gdk.keyval_name(key) == "Right") this.renderer.select_right();
+ else if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = true;
+ else {
+ int index = -1;
+
+ if (key >= 48 && key <= 57) index = (int)key - 48;
+ else if (key >= 97 && key <= 122) index = (int)key - 87;
+ else if (key >= 65 && key <= 90) index = (int)key - 55;
+
+ if (index >= 0 && index < this.renderer.slice_count()) {
+ this.renderer.set_highlighted_slice(index);
+
+ if (this.renderer.active_slice == index) {
+ GLib.Timeout.add((uint)(Config.global.theme.transition_time*1000.0), ()=> {
+ this.activate_slice();
+ return false;
+ });
+ }
+
+ }
+ }
+ }
+ }
+
+ private void handle_key_release(uint key) {
+ if (!Config.global.turbo_mode) {
+ if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = false;
+ }
+ }
+
+ // utilities for grabbing focus
+ // Code from Gnome-Do/Synapse
+ private void fix_focus() {
+ uint32 timestamp = Gtk.get_current_event_time();
+ this.present_with_time(timestamp);
+ this.get_window().raise();
+ this.get_window().focus(timestamp);
+
+ int i = 0;
+ Timeout.add(100, () => {
+ if (++i >= 100) return false;
+ return !try_grab_window();
+ });
+ }
+
+ // Code from Gnome-Do/Synapse
+ private void unfix_focus() {
+ uint32 time = Gtk.get_current_event_time();
+ Gdk.pointer_ungrab(time);
+ Gdk.keyboard_ungrab(time);
+ Gtk.grab_remove(this);
+ }
+
+ // Code from Gnome-Do/Synapse
+ private bool try_grab_window() {
+ uint time = Gtk.get_current_event_time();
+ if (Gdk.pointer_grab(this.get_window(), true, Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK,
+ null, null, time) == Gdk.GrabStatus.SUCCESS) {
+
+ if (Gdk.keyboard_grab(this.get_window(), true, time) == Gdk.GrabStatus.SUCCESS) {
+ Gtk.grab_add(this);
+ return true;
+ } else {
+ Gdk.pointer_ungrab(time);
+ return false;
+ }
+ }
+ return false;
+ }
+}
+
+}
diff --git a/src/renderers/sliceRenderer.vala b/src/renderers/sliceRenderer.vala
new file mode 100644
index 0000000..08c880f
--- /dev/null
+++ b/src/renderers/sliceRenderer.vala
@@ -0,0 +1,182 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// Renders a Slice of a Pie. According to the current theme.
+
+public class SliceRenderer : GLib.Object {
+
+ public bool active {get; private set; default = false;}
+ public Image caption {get; private set;}
+ public Color color {get; private set;}
+
+ private Image active_icon;
+ private Image inactive_icon;
+ private Image hotkey;
+
+ private Action action;
+
+ private unowned PieRenderer parent;
+ private int position;
+
+ private AnimatedValue fade;
+ private AnimatedValue scale;
+ private AnimatedValue alpha;
+ private AnimatedValue fade_rotation;
+ private AnimatedValue fade_scale;
+
+ public SliceRenderer(PieRenderer parent) {
+ this.parent = parent;
+
+ this.fade = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time);
+ this.scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ 1.0/Config.global.theme.max_zoom,
+ 1.0/Config.global.theme.max_zoom,
+ Config.global.theme.transition_time,
+ Config.global.theme.springiness);
+ this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ Config.global.theme.fade_in_zoom, 1.0,
+ Config.global.theme.fade_in_time,
+ Config.global.theme.springiness);
+ this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ Config.global.theme.fade_in_rotation, 0.0,
+ Config.global.theme.fade_in_time);
+ }
+
+ public void load(Action action, int position) {
+ this.position = position;
+ this.action = action;
+
+
+ if (Config.global.theme.caption)
+ this.caption = new RenderedText(action.name,
+ Config.global.theme.caption_width,
+ Config.global.theme.caption_height,
+ Config.global.theme.caption_font);
+
+ this.active_icon = new ThemedIcon(action.icon, true);
+ this.inactive_icon = new ThemedIcon(action.icon, false);
+
+ this.color = new Color.from_icon(this.active_icon);
+
+ string hotkey_label = "";
+ if (position < 10) {
+ hotkey_label = "%u".printf(position);
+ } else if (position < 36) {
+ hotkey_label = "%c".printf((char)(55 + position));
+ }
+
+ this.hotkey = new RenderedText(hotkey_label, (int)Config.global.theme.slice_radius*2,
+ (int)Config.global.theme.slice_radius*2, "sans 20");
+ }
+
+ public void activate() {
+ action.activate();
+ }
+
+ public void fade_out() {
+ this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
+ this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
+ this.fade_scale.val,
+ Config.global.theme.fade_out_zoom,
+ Config.global.theme.fade_out_time,
+ Config.global.theme.springiness);
+ this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
+ this.fade_rotation.val,
+ Config.global.theme.fade_out_rotation,
+ Config.global.theme.fade_out_time);
+ }
+
+ public void set_active_slice(SliceRenderer? active_slice) {
+ if (active_slice == this) {
+ this.fade.reset_target(1.0, Config.global.theme.transition_time);
+ } else {
+ this.fade.reset_target(0.0, Config.global.theme.transition_time);
+ }
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) {
+
+ double direction = 2.0 * PI * position/parent.slice_count() + this.fade_rotation.val;
+ double max_scale = 1.0/Config.global.theme.max_zoom;
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ if (diff < 2 * PI * Config.global.theme.zoom_range)
+ max_scale = (Config.global.theme.max_zoom/(diff * (Config.global.theme.max_zoom - 1)
+ /(2 * PI * Config.global.theme.zoom_range) + 1))
+ /Config.global.theme.max_zoom;
+
+ active = ((parent.active_slice >= 0) && (diff < PI/parent.slice_count()));
+
+ max_scale = (parent.active_slice >= 0 ? max_scale : 1.0/Config.global.theme.max_zoom);
+
+ if (fabs(this.scale.end - max_scale) > Config.global.theme.max_zoom*0.005)
+ this.scale.reset_target(max_scale, Config.global.theme.transition_time);
+
+ this.scale.update(frame_time);
+ this.alpha.update(frame_time);
+ this.fade.update(frame_time);
+ this.fade_scale.update(frame_time);
+ this.fade_rotation.update(frame_time);
+
+ ctx.save();
+
+ double radius = Config.global.theme.radius;
+
+ if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /(radius/Config.global.theme.max_zoom)) > PI/parent.slice_count()) {
+ radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /tan(PI/parent.slice_count())*Config.global.theme.max_zoom;
+ }
+
+ ctx.scale(scale.val*fade_scale.val, scale.val*fade_scale.val);
+ ctx.translate(cos(direction)*radius, sin(direction)*radius);
+
+ ctx.push_group();
+
+ ctx.set_operator(Cairo.Operator.ADD);
+
+ if (fade.val > 0.0) active_icon.paint_on(ctx, this.alpha.val*this.fade.val);
+ if (fade.val < 1.0) inactive_icon.paint_on(ctx, this.alpha.val*(1.0 - fade.val));
+
+ if (this.parent.show_hotkeys) {
+ ctx.set_operator(Cairo.Operator.ATOP);
+ ctx.set_source_rgba(0, 0, 0, 0.5);
+ ctx.paint();
+ }
+
+ ctx.set_operator(Cairo.Operator.OVER);
+
+
+ ctx.pop_group_to_source();
+ ctx.paint();
+
+ if (this.parent.show_hotkeys)
+ this.hotkey.paint_on(ctx, 1.0);
+
+ ctx.restore();
+ }
+}
+
+}
diff --git a/src/themes/centerLayer.vala b/src/themes/centerLayer.vala
new file mode 100644
index 0000000..3469fd0
--- /dev/null
+++ b/src/themes/centerLayer.vala
@@ -0,0 +1,111 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class representing a layer of the center of a pie. Each theme
+/// may have plenty of them.
+/////////////////////////////////////////////////////////////////////////
+
+public class CenterLayer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Possible rotation modes.
+ /// AUTO: Turns the layer continously.
+ /// TO_MOUSE: Turns the layer always to the pointer.
+ /// TO_ACTIVE: Turns the layer to the active slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public enum RotationMode {AUTO, TO_MOUSE, TO_ACTIVE}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Information on the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image image {get; private set;}
+ public string image_file;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Properties for the active state of this layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public double active_scale {get; private set;}
+ public double active_rotation_speed {get; private set;}
+ public double active_alpha {get; private set;}
+ public bool active_colorize {get; private set;}
+ public RotationMode active_rotation_mode {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Properties for the inactive state of this layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public double inactive_scale {get; private set;}
+ public double inactive_rotation_speed {get; private set;}
+ public double inactive_alpha {get; private set;}
+ public bool inactive_colorize {get; private set;}
+ public RotationMode inactive_rotation_mode {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// The current rotation of this layer. TODO: Remove this.
+ /////////////////////////////////////////////////////////////////////
+
+ public double rotation {get; set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper variable for image loading.
+ /////////////////////////////////////////////////////////////////////
+
+ private int center_radius;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members of the layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public CenterLayer(string image_file, int center_radius, double active_scale, double active_rotation_speed,
+ double active_alpha, bool active_colorize, RotationMode active_rotation_mode,
+ double inactive_scale, double inactive_rotation_speed,
+ double inactive_alpha, bool inactive_colorize, RotationMode inactive_rotation_mode) {
+
+ this.image_file = image_file;
+ this.center_radius = center_radius;
+
+ this.active_scale = active_scale;
+ this.active_rotation_speed = active_rotation_speed;
+ this.active_alpha = active_alpha;
+ this.active_colorize = active_colorize;
+ this.active_rotation_mode = active_rotation_mode;
+
+ this.inactive_scale = inactive_scale;
+ this.inactive_rotation_speed = inactive_rotation_speed;
+ this.inactive_alpha = inactive_alpha;
+ this.inactive_colorize = inactive_colorize;
+ this.inactive_rotation_mode = inactive_rotation_mode;
+
+ this.rotation = 0.0;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_image() {
+ this.image = new Image.from_file_at_size(image_file, 2*center_radius, 2*center_radius);
+ }
+}
+
+}
diff --git a/src/themes/sliceLayer.vala b/src/themes/sliceLayer.vala
new file mode 100644
index 0000000..2620912
--- /dev/null
+++ b/src/themes/sliceLayer.vala
@@ -0,0 +1,65 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class representing a layer of a slice of a pie. Each theme may
+/// have plenty of them.
+/////////////////////////////////////////////////////////////////////////
+
+public class SliceLayer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Information on the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image image {get; set;}
+ public string icon_file {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Properties of this layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool colorize {get; private set; }
+ public bool is_icon {get; private set;}
+ public int icon_size {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members of the layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public SliceLayer(string icon_file, int icon_size, bool colorize, bool is_icon) {
+ this.icon_file = icon_file;
+ this.colorize = colorize;
+ this.is_icon = is_icon;
+ this.icon_size = icon_size;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_image() {
+ if (this.icon_file == "" && this.is_icon == true)
+ this.image = new Image.empty(this.icon_size, this.icon_size, new Color.from_rgb(1, 1, 1));
+ else
+ this.image = new Image.from_file_at_size(this.icon_file, this.icon_size, this.icon_size);
+ }
+}
+
+}
diff --git a/src/themes/theme.vala b/src/themes/theme.vala
new file mode 100644
index 0000000..fa6f55a
--- /dev/null
+++ b/src/themes/theme.vala
@@ -0,0 +1,489 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A theme of Gnome-Pie. Can be loaded from XML-Files.
+/////////////////////////////////////////////////////////////////////////
+
+public class Theme : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Properties of a theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public string directory {get; private set; default="";}
+ public string name {get; private set; default="";}
+ public string description {get; private set; default="";}
+ public string author {get; private set; default="";}
+ public string email {get; private set; default="";}
+ public double radius {get; private set; default=150;}
+ public double max_zoom {get; private set; default=1.2;}
+ public double zoom_range {get; private set; default=0.2;}
+ public double transition_time {get; private set; default=0.5;}
+ public double fade_in_time {get; private set; default=0.2;}
+ public double fade_out_time {get; private set; default=0.1;}
+ public double fade_in_zoom {get; private set; default=1.0;}
+ public double fade_out_zoom {get; private set; default=1.0;}
+ public double fade_in_rotation {get; private set; default=0.0;}
+ public double fade_out_rotation{get; private set; default=0.0;}
+ public double springiness {get; private set; default=0.0;}
+ public double center_radius {get; private set; default=90.0;}
+ public double active_radius {get; private set; default=45.0;}
+ public double slice_radius {get; private set; default=32.0;}
+ public double slice_gap {get; private set; default=14.0;}
+ public bool caption {get; private set; default=false;}
+ public string caption_font {get; private set; default="sans 12";}
+ public int caption_width {get; private set; default=100;}
+ public int caption_height {get; private set; default=100;}
+ public double caption_position {get; private set; default=0.0;}
+ public Color caption_color {get; private set; default=new Color();}
+
+ public Gee.ArrayList<CenterLayer?> center_layers {get; private set;}
+ public Gee.ArrayList<SliceLayer?> active_slice_layers {get; private set;}
+ public Gee.ArrayList<SliceLayer?> inactive_slice_layers {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a theme object for a given theme directory. This
+ /// directory should contain a theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ public Theme(string dir) {
+ this.center_layers = new Gee.ArrayList<CenterLayer?>();
+ this.active_slice_layers = new Gee.ArrayList<SliceLayer?>();
+ this.inactive_slice_layers = new Gee.ArrayList<SliceLayer?>();
+
+ this.directory = dir;
+
+ this.load();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the theme from its directory. Images have to be loaded
+ /// explicitly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ this.center_layers.clear();
+ this.active_slice_layers.clear();
+ this.inactive_slice_layers.clear();
+
+ Xml.Parser.init();
+ string path = this.directory + "/theme.xml";
+
+ Xml.Doc* themeXML = Xml.Parser.parse_file(path);
+ if (themeXML == null) {
+ warning("Error parsing theme: \"" + path + "\" not found!");
+ return;
+ }
+
+ Xml.Node* root = themeXML->get_root_element();
+ if (root == null) {
+ delete themeXML;
+ warning("Invalid theme \"" + this.directory + "\": theme.xml is empty!");
+ return;
+ }
+
+ this.parse_root(root);
+
+ delete themeXML;
+ Xml.Parser.cleanup();
+
+ this.radius *= max_zoom;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all images of the theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_images() {
+ foreach (var layer in this.center_layers)
+ layer.load_image();
+ foreach (var layer in this.active_slice_layers)
+ layer.load_image();
+ foreach (var layer in this.inactive_slice_layers)
+ layer.load_image();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The following methods parse specific parts of the theme file.
+ /// Nothing special here, just some boring code.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_root(Xml.Node* root) {
+ for (Xml.Attr* attribute = root->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "name":
+ name = attr_content;
+ break;
+ case "description":
+ description = attr_content;
+ break;
+ case "email":
+ email = attr_content;
+ break;
+ case "author":
+ author = attr_content;
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <theme> element!");
+ break;
+ }
+ }
+ for (Xml.Node* node = root->children; node != null; node = node->next) {
+ if (node->type == Xml.ElementType.ELEMENT_NODE) {
+ parse_pie(node);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <pie> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_pie(Xml.Node* pie) {
+ for (Xml.Attr* attribute = pie->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "radius":
+ radius = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ case "maxzoom":
+ max_zoom = double.parse(attr_content);
+ break;
+ case "zoomrange":
+ zoom_range = double.parse(attr_content);
+ break;
+ case "transitiontime":
+ transition_time = double.parse(attr_content);
+ break;
+ case "fadeintime":
+ fade_in_time = double.parse(attr_content);
+ break;
+ case "fadeouttime":
+ fade_out_time = double.parse(attr_content);
+ break;
+ case "fadeinzoom":
+ fade_in_zoom = double.parse(attr_content);
+ break;
+ case "fadeoutzoom":
+ fade_out_zoom = double.parse(attr_content);
+ break;
+ case "fadeinrotation":
+ fade_in_rotation = double.parse(attr_content);
+ break;
+ case "fadeoutrotation":
+ fade_out_rotation = double.parse(attr_content);
+ break;
+ case "springiness":
+ springiness = double.parse(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <pie> element!");
+ break;
+ }
+ }
+ for (Xml.Node* node = pie->children; node != null; node = node->next) {
+ if (node->type == Xml.ElementType.ELEMENT_NODE) {
+ string element_name = node->name.down();
+ switch (element_name) {
+ case "center":
+ parse_center(node);
+ break;
+ case "slices":
+ parse_slices(node);
+ break;
+ case "caption":
+ caption = true;
+ parse_caption(node);
+ break;
+ default:
+ warning("Invalid child element \"" + element_name + "\" in <pie> element!");
+ break;
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <center> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_center(Xml.Node* center) {
+ for (Xml.Attr* attribute = center->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "radius":
+ center_radius = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ case "activeradius":
+ active_radius = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <center> element!");
+ break;
+ }
+ }
+ for (Xml.Node* node = center->children; node != null; node = node->next) {
+ if (node->type == Xml.ElementType.ELEMENT_NODE) {
+ string element_name = node->name.down();
+
+ if (element_name == "center_layer") {
+ parse_center_layer(node);
+ } else {
+ warning("Invalid child element \"" + element_name + "\" in <center> element!");
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <slices> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_slices(Xml.Node* slices) {
+ for (Xml.Attr* attribute = slices->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "radius":
+ slice_radius = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ case "mingap":
+ slice_gap = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <slices> element!");
+ break;
+ }
+ }
+ for (Xml.Node* node = slices->children; node != null; node = node->next) {
+ if (node->type == Xml.ElementType.ELEMENT_NODE) {
+ string element_name = node->name.down();
+
+ if (element_name == "activeslice" || element_name == "inactiveslice") {
+ parse_slice_layers(node);
+ } else {
+ warning("Invalid child element \"" + element_name + "\" in <slices> element!");
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <center_layer> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_center_layer(Xml.Node* layer) {
+
+ string file = "";
+ double active_rotation_speed = 0.0;
+ double inactive_rotation_speed = 0.0;
+ double active_scale = 1.0;
+ double inactive_scale = 1.0;
+ double active_alpha = 1.0;
+ double inactive_alpha = 1.0;
+ bool active_colorize = false;
+ bool inactive_colorize = false;
+ CenterLayer.RotationMode active_rotation_mode = CenterLayer.RotationMode.AUTO;
+ CenterLayer.RotationMode inactive_rotation_mode = CenterLayer.RotationMode.AUTO;
+
+ for (Xml.Attr* attribute = layer->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "file":
+ file = attr_content;
+ break;
+ case "active_scale":
+ active_scale = double.parse(attr_content);
+ break;
+ case "active_alpha":
+ active_alpha = double.parse(attr_content);
+ break;
+ case "active_rotationmode":
+ switch (attr_content.down()) {
+ case "auto":
+ active_rotation_mode = CenterLayer.RotationMode.AUTO;
+ break;
+ case "turn_to_active":
+ active_rotation_mode = CenterLayer.RotationMode.TO_ACTIVE;
+ break;
+ case "turn_to_mouse":
+ active_rotation_mode = CenterLayer.RotationMode.TO_MOUSE;
+ break;
+ default:
+ warning("Invalid value \"" + attr_content + "\" for attribute \"" + attr_name + "\" in <center_layer> element!");
+ break;
+ }
+ break;
+ case "active_rotationspeed":
+ active_rotation_speed = double.parse(attr_content);
+ break;
+ case "active_colorize":
+ active_colorize = bool.parse(attr_content);
+ break;
+ case "inactive_scale":
+ inactive_scale = double.parse(attr_content);
+ break;
+ case "inactive_alpha":
+ inactive_alpha = double.parse(attr_content);
+ break;
+ case "inactive_rotationmode":
+ switch (attr_content.down()) {
+ case "auto":
+ inactive_rotation_mode = CenterLayer.RotationMode.AUTO;
+ break;
+ case "turn_to_active":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_ACTIVE;
+ break;
+ case "turn_to_mouse":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_MOUSE;
+ break;
+ default:
+ warning("Invalid value \"" + attr_content + "\" for attribute \"" + attr_name + "\" in <center_layer> element!");
+ break;
+ }
+ break;
+ case "inactive_rotationspeed":
+ inactive_rotation_speed = double.parse(attr_content);
+ break;
+ case "inactive_colorize":
+ inactive_colorize = bool.parse(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <center_layer> element!");
+ break;
+ }
+ }
+
+ double max_scale = GLib.Math.fmax(active_scale, inactive_scale);
+ center_layers.add(new CenterLayer(directory + "/" + file, (int)(center_radius*max_scale), active_scale/max_scale, active_rotation_speed, active_alpha, active_colorize, active_rotation_mode,
+ inactive_scale/max_scale, inactive_rotation_speed, inactive_alpha, inactive_colorize, inactive_rotation_mode));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <slice_layer> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_slice_layers(Xml.Node* slice) {
+ for (Xml.Node* layer = slice->children; layer != null; layer = layer->next) {
+ if (layer->type == Xml.ElementType.ELEMENT_NODE) {
+ string element_name = layer->name.down();
+
+ if (element_name == "slice_layer") {
+ string file = "";
+ double scale = 1.0;
+ bool is_icon = false;
+ bool colorize = false;
+
+ for (Xml.Attr* attribute = layer->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "file":
+ file = attr_content;
+ break;
+ case "scale":
+ scale = double.parse(attr_content);
+ break;
+ case "type":
+ if (attr_content == "icon")
+ is_icon = true;
+ else if (attr_content != "file")
+ warning("Invalid attribute content " + attr_content + " for attribute " + attr_name + " in <slice_layer> element!");
+ break;
+ case "colorize":
+ colorize = bool.parse(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <slice_layer> element!");
+ break;
+ }
+ }
+
+ if (file != "")
+ file = directory + "/" + file;
+
+ int size = 2*(int)(slice_radius*scale*max_zoom);
+
+ if (slice->name.down() == "activeslice") {
+ active_slice_layers.add(new SliceLayer(file, size, colorize, is_icon));
+ } else {
+ inactive_slice_layers.add(new SliceLayer(file, size, colorize, is_icon));
+ }
+
+ } else {
+ warning("Invalid child element \"" + element_name + "\" in <" + slice->name + "> element!");
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a <caption> element from the theme.xml file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void parse_caption(Xml.Node* caption) {
+ for (Xml.Attr* attribute = caption->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "font":
+ caption_font = attr_content;
+ break;
+ case "width":
+ caption_width = (int)(int.parse(attr_content) * Config.global.global_scale);
+ if (caption_width % 2 == 1)
+ --caption_width;
+ break;
+ case "height":
+ caption_height = (int)(int.parse(attr_content) * Config.global.global_scale);
+ if (caption_height % 2 == 1)
+ --caption_height;
+ break;
+ case "position":
+ caption_position = double.parse(attr_content) * Config.global.global_scale;
+ break;
+ case "color":
+ caption_color = new Color.from_string(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <caption> element!");
+ break;
+ }
+ }
+
+ }
+
+}
+
+}
diff --git a/src/utilities/animatedValue.vala b/src/utilities/animatedValue.vala
new file mode 100644
index 0000000..32ab889
--- /dev/null
+++ b/src/utilities/animatedValue.vala
@@ -0,0 +1,190 @@
+/*
+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 class which interpolates smoothly between to given values.
+/// Duration and interpolation mode can be specified.
+/////////////////////////////////////////////////////////////////////////
+
+public class AnimatedValue : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The direction of the interpolation.
+ /////////////////////////////////////////////////////////////////////
+
+ public enum Direction { IN, OUT, IN_OUT, OUT_IN }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Type of the interpolation, linear or cubic.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum Type { LINEAR, CUBIC }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Current value, interpolated.
+ /////////////////////////////////////////////////////////////////////
+
+ public double val { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starting value of the interpolation.
+ /////////////////////////////////////////////////////////////////////
+
+ public double start { get; private set; default=0.0; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Final value of the interpolation.
+ /////////////////////////////////////////////////////////////////////
+
+ public double end { get; private set; default=0.0; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The current state. In range 0 .. 1
+ /////////////////////////////////////////////////////////////////////
+
+ private double state = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Duration of the interpolation. Should be in the same unit as
+ /// taken for the update() method.
+ /////////////////////////////////////////////////////////////////////
+
+ private double duration = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The amount of over-shooting of the cubicly interpolated value.
+ /////////////////////////////////////////////////////////////////////
+
+ private double multiplier = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Type of the interpolation, linear or cubic.
+ /////////////////////////////////////////////////////////////////////
+
+ private Type type = Type.LINEAR;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The direction of the interpolation.
+ /////////////////////////////////////////////////////////////////////
+
+ private Direction direction = Direction.IN;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new linearly interpolated value.
+ /////////////////////////////////////////////////////////////////////
+
+ public AnimatedValue.linear(double start, double end, double duration) {
+ this.val = start;
+ this.start = start;
+ this.end = end;
+ this.duration = duration;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new cubicly interpolated value.
+ /////////////////////////////////////////////////////////////////////
+
+ public AnimatedValue.cubic(Direction direction, double start, double end, double duration, double multiplier = 0) {
+ this.val = start;
+ this.start = start;
+ this.end = end;
+ this.duration = duration;
+ this.direction = direction;
+ this.type = Type.CUBIC;
+ this.multiplier = multiplier;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Resets the final value of the interpolation to a new value. The
+ /// current state is taken for the beginning from now.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reset_target(double end, double duration) {
+ this.start = this.val;
+ this.end = end;
+ this.duration = duration;
+ this.state = 0.0;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the interpolated value according to it's type.
+ /////////////////////////////////////////////////////////////////////
+
+ public void update(double time) {
+ this.state += time/this.duration;
+
+ if (state < 1) {
+
+ switch (this.type) {
+ case Type.LINEAR:
+ this.val = update_linear();
+ break;
+ case Type.CUBIC:
+ switch (this.direction) {
+ case Direction.IN:
+ this.val = update_ease_in();
+ return;
+ case Direction.OUT:
+ this.val = update_ease_out();
+ return;
+ case Direction.IN_OUT:
+ this.val = update_ease_in_out();
+ return;
+ case Direction.OUT_IN:
+ this.val = update_ease_out_in();
+ return;
+ }
+ break;
+ }
+ } else if (this.val != this.end) {
+ this.val = this.end;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The following equations are based on Robert Penner's easing
+ /// equations. See (http://www.robertpenner.com/easing/) and their
+ /// adaption by Zeh Fernando, Nate Chatellier and Arthur Debert for
+ /// the Tweener class. See (http://code.google.com/p/tweener/).
+ /////////////////////////////////////////////////////////////////////
+
+ private double update_linear(double t = this.state, double s = this.start, double e = this.end) {
+ return (s + t*(e - s));
+ }
+
+ private double update_ease_in(double t = this.state, double s = this.start, double e = this.end) {
+ return (s + (t*t*((multiplier+1)*t-multiplier))*(e - s));
+ }
+
+ private double update_ease_out(double t = this.state, double s = this.start, double e = this.end) {
+ return (s + ((t-1) * (t-1) * ((multiplier+1)*(t-1)+multiplier) + 1) * (e - s));
+ }
+
+ private double update_ease_in_out(double t = this.state, double s = this.start, double e = this.end) {
+ if (this.state < 0.5) return update_ease_in(t*2, s, e - (e-s)*0.5);
+ else return update_ease_out(t*2-1, s + (e-s)*0.5, e);
+ }
+
+ private double update_ease_out_in(double t = this.state, double s = this.start, double e = this.end) {
+ if (this.state < 0.5) return update_ease_out(t*2, s, e - (e-s)*0.5);
+ else return update_ease_in(t*2-1, s + (e-s)*0.5, e);
+ }
+}
+
+}
diff --git a/src/utilities/bindingManager.vala b/src/utilities/bindingManager.vala
new file mode 100644
index 0000000..8795124
--- /dev/null
+++ b/src/utilities/bindingManager.vala
@@ -0,0 +1,196 @@
+/*
+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 {
+
+/////////////////////////////////////////////////////////////////////////
+/// Globally binds key stroke to given ID's. When one of the bound
+/// strokes is invoked, a signal with the according ID is emitted.
+/////////////////////////////////////////////////////////////////////////
+
+public class BindingManager : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a stored binding is invoked. The according ID is
+ /// passed as argument.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_press(string id);
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list storing bindings, which are invoked even if Gnome-Pie
+ /// doesn't have the current focus
+ /////////////////////////////////////////////////////////////////////
+
+ private Gee.List<Keybinding> bindings = new Gee.ArrayList<Keybinding>();
+
+ /////////////////////////////////////////////////////////////////////
+ /// Ignored modifier masks, used to grab all keys even if these locks
+ /// are active.
+ /////////////////////////////////////////////////////////////////////
+
+ private static uint[] lock_modifiers = {
+ 0,
+ Gdk.ModifierType.MOD2_MASK,
+ Gdk.ModifierType.LOCK_MASK,
+ Gdk.ModifierType.MOD5_MASK,
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK,
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.MOD5_MASK,
+ Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK,
+ Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK
+ };
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper class to store keybinding
+ /////////////////////////////////////////////////////////////////////
+
+ private class Keybinding {
+
+ public Keybinding(string accelerator, int keycode, Gdk.ModifierType modifiers, string id) {
+ this.accelerator = accelerator;
+ this.keycode = keycode;
+ this.modifiers = modifiers;
+ this.id = id;
+ }
+
+ public string accelerator { get; set; }
+ public int keycode { get; set; }
+ public Gdk.ModifierType modifiers { get; set; }
+ public string id { get; set; }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor adds the event filter to the root window.
+ /////////////////////////////////////////////////////////////////////
+
+ public BindingManager() {
+ // init filter to retrieve X.Events
+ Gdk.Window rootwin = Gdk.get_default_root_window();
+ if(rootwin != null) {
+ rootwin.add_filter(event_filter);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Binds the ID to the given accelerator.
+ /////////////////////////////////////////////////////////////////////
+
+ public void bind(string accelerator, string id) {
+ uint keysym;
+ Gdk.ModifierType modifiers;
+ Gtk.accelerator_parse(accelerator, out keysym, out modifiers);
+
+ if (keysym == 0) {
+ warning("Invalid keystroke: " + accelerator);
+ return;
+ }
+
+ Gdk.Window rootwin = Gdk.get_default_root_window();
+ X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin);
+ X.ID xid = Gdk.x11_drawable_get_xid(rootwin);
+ int keycode = display.keysym_to_keycode(keysym);
+
+ if(keycode != 0) {
+ Gdk.error_trap_push();
+
+ foreach(uint lock_modifier in lock_modifiers) {
+ display.grab_key(keycode, modifiers|lock_modifier, xid, false, X.GrabMode.Async, X.GrabMode.Async);
+ }
+
+ Gdk.flush();
+
+ Keybinding binding = new Keybinding(accelerator, keycode, modifiers, id);
+ bindings.add(binding);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Unbinds the accelerator of the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void unbind(string id) {
+ Gdk.Window rootwin = Gdk.get_default_root_window();
+ X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin);
+ X.ID xid = Gdk.x11_drawable_get_xid(rootwin);
+ Gee.List<Keybinding> remove_bindings = new Gee.ArrayList<Keybinding>();
+ foreach(var binding in bindings) {
+ if(id == binding.id) {
+ foreach(uint lock_modifier in lock_modifiers) {
+ display.ungrab_key(binding.keycode, binding.modifiers, xid);
+ }
+ remove_bindings.add(binding);
+ }
+ }
+
+ bindings.remove_all(remove_bindings);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human readable accelerator for the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_accelerator_label_of(string id) {
+ string accelerator = this.get_accelerator_of(id);
+
+ if (accelerator == "")
+ return _("Not bound");
+
+ uint key = 0;
+ Gdk.ModifierType mods;
+ Gtk.accelerator_parse(accelerator, out key, out mods);
+ return Gtk.accelerator_get_label(key, mods);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the accelerator to which the given ID is bound.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_accelerator_of(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.accelerator;
+ }
+ }
+
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Event filter method needed to fetch X.Events
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) {
+ Gdk.FilterReturn filter_return = Gdk.FilterReturn.CONTINUE;
+
+ void* pointer = &gdk_xevent;
+ X.Event* xevent = (X.Event*) pointer;
+
+ if(xevent->type == X.EventType.KeyPress) {
+ foreach(var binding in bindings) {
+ // remove NumLock, CapsLock and ScrollLock from key state
+ uint event_mods = xevent.xkey.state & ~ (lock_modifiers[7]);
+ if(xevent->xkey.keycode == binding.keycode && event_mods == binding.modifiers) {
+ on_press(binding.id);
+ }
+ }
+ }
+
+ return filter_return;
+ }
+}
+
+}
diff --git a/src/utilities/color.vala b/src/utilities/color.vala
new file mode 100644
index 0000000..836411e
--- /dev/null
+++ b/src/utilities/color.vala
@@ -0,0 +1,305 @@
+/*
+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/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A Color class with full rgb/hsv support
+/// and some useful utility methods.
+/////////////////////////////////////////////////////////////////////////
+
+public class Color: GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Private members, storing the actual color information.
+ /// In range 0 .. 1
+ /////////////////////////////////////////////////////////////////////
+
+ private float _r;
+ private float _g;
+ private float _b;
+ private float _a;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a white Color.
+ /////////////////////////////////////////////////////////////////////
+
+ public Color() {
+ Color.from_rgb(1.0f, 1.0f, 1.0f);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a solid color with the given RGB values.
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_rgb(float red, float green, float blue) {
+ Color.from_rgba(red, green, blue, 1.0f);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a translucient color with the given RGBA values.
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_rgba(float red, float green, float blue, float alpha) {
+ r = red;
+ g = green;
+ b = blue;
+ a = alpha;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a color from the given Gdk.Color
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_gdk(Gdk.Color color) {
+ Color.from_rgb(
+ (float)color.red/65535.0f,
+ (float)color.green/65535.0f,
+ (float)color.blue/65535.0f
+ );
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a color, parsed from a string, such as #22EE33
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_string(string hex_string) {
+ Gdk.Color color;
+ Gdk.Color.parse(hex_string, out color);
+ Color.from_gdk(color);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Gets the main color from an Image. Code from Unity.
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_icon(Image icon) {
+ unowned uchar[] data = icon.surface.get_data();
+
+ uint width = icon.surface.get_width();
+ uint height = icon.surface.get_height();
+ uint row_bytes = icon.surface.get_stride();
+
+ double total = 0.0;
+ double rtotal = 0.0;
+ double gtotal = 0.0;
+ double btotal = 0.0;
+
+ for (uint i = 0; i < width; ++i) {
+ for (uint j = 0; j < height; ++j) {
+ uint pixel = j * row_bytes + i * 4;
+ double b = data[pixel + 0]/255.0;
+ double g = data[pixel + 1]/255.0;
+ double r = data[pixel + 2]/255.0;
+ double a = data[pixel + 3]/255.0;
+
+ double saturation = (fmax (r, fmax (g, b)) - fmin (r, fmin (g, b)));
+ double relevance = 0.1 + 0.9 * a * saturation;
+
+ rtotal += (r * relevance);
+ gtotal += (g * relevance);
+ btotal += (b * relevance);
+
+ total += relevance;
+ }
+ }
+
+ Color.from_rgb((float)(rtotal/total), (float)(gtotal/total), (float)(btotal/total));
+
+ if (s > 0.15f) s = 0.65f;
+
+ v = 1.0f;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The reddish part of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float r {
+ get {
+ return _r;
+ }
+ set {
+ if (value > 1.0f) _r = 1.0f;
+ else if (value < 0.0f) _r = 0.0f;
+ else _r = value;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The greenish part of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float g {
+ get {
+ return _g;
+ }
+ set {
+ if (value > 1.0f) _g = 1.0f;
+ else if (value < 0.0f) _g = 0.0f;
+ else _g = value;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The blueish part of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float b {
+ get {
+ return _b;
+ }
+ set {
+ if (value > 1.0f) _b = 1.0f;
+ else if (value < 0.0f) _b = 0.0f;
+ else _b = value;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The transparency of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float a {
+ get {
+ return _a;
+ }
+ set {
+ if (value > 1.0f) _a = 1.0f;
+ else if (value < 0.0f) _a = 0.0f;
+ else _a = value;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The hue of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float h {
+ get {
+ if (s > 0.0f) {
+ float maxi = fmaxf(fmaxf(r, g), b);
+ float mini = fminf(fminf(r, g), b);
+
+ if (maxi == r)
+ return fmodf(60.0f*((g-b)/(maxi-mini))+360.0f, 360.0f);
+ else if (maxi == g)
+ return fmodf(60.0f*(2.0f + (b-r)/(maxi-mini))+360.0f, 360.0f);
+ else
+ return fmodf(60.0f*(4.0f + (r-g)/(maxi-mini))+360.0f, 360.0f);
+ }
+ else return 0.0f;
+ }
+ set {
+ setHSV(value, s, v);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The saturation of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float s {
+ get {
+ if (v == 0.0f) return 0.0f;
+ else return ((v-fminf(fminf(r, g), b)) / v);
+ }
+ set {
+ if (value > 1.0f) setHSV(h, 1.0f, v);
+ else if (value < 0.0f) setHSV(h, 0.0f, v);
+ else setHSV(h, value, v);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The value of the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public float v {
+ get {
+ return fmaxf(fmaxf(r, g), b);
+ }
+ set {
+ if (value > 1) setHSV(h, s, 1.0f);
+ else if (value < 0) setHSV(h, s, 0.0f);
+ else setHSV(h, s, value);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Inverts the color.
+ /////////////////////////////////////////////////////////////////////
+
+ public void invert() {
+ h += 180.0f;
+ v = 1.0f - v;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Private member, used to apply color changes.
+ /////////////////////////////////////////////////////////////////////
+
+ private void setHSV(float hue, float saturation, float val) {
+ if(saturation == 0) {
+ r = val;
+ g = val;
+ b = val;
+ return;
+ }
+ hue = fmodf(hue, 360);
+ hue /= 60;
+ int i = (int) floorf(hue);
+ float f = hue - i;
+
+ switch(i) {
+ case 0:
+ r = val;
+ g = val * (1.0f - saturation * (1.0f - f));
+ b = val * (1.0f - saturation);
+ break;
+ case 1:
+ r = val * (1.0f - saturation * f);
+ g = val;
+ b = val * (1.0f - saturation);
+ break;
+ case 2:
+ r = val * (1.0f - saturation);
+ g = val;
+ b = val * (1.0f - saturation * (1.0f - f));
+ break;
+ case 3:
+ r = val * (1.0f - saturation);
+ g = val * (1.0f - saturation * f);
+ b = val;
+ break;
+ case 4:
+ r = val * (1.0f - saturation * (1.0f - f));
+ g = val * (1.0f - saturation);
+ b = val;
+ break;
+ default:
+ r = val;
+ g = val * (1.0f - saturation);
+ b = val * (1.0f - saturation * f);
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/utilities/config.vala b/src/utilities/config.vala
new file mode 100644
index 0000000..c5dedd5
--- /dev/null
+++ b/src/utilities/config.vala
@@ -0,0 +1,202 @@
+/*
+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 singleton class for storing global settings. These settings can
+/// be loaded from and saved to an XML file.
+/////////////////////////////////////////////////////////////////////////
+
+public class Config : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The singleton instance of this class.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Config _instance = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the singleton instance.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Config global {
+ get {
+ if (_instance == null) {
+ _instance = new Config();
+ _instance.load();
+ }
+ return _instance;
+ }
+ private set {
+ _instance = value;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// All settings variables.
+ /////////////////////////////////////////////////////////////////////
+
+ public Theme theme { get; set; }
+ public double refresh_rate { get; set; default = 60.0; }
+ public double global_scale { get; set; default = 1.0; }
+ public bool show_indicator { get; set; default = true; }
+ public bool open_at_mouse { get; set; default = true; }
+ public bool turbo_mode { get; set; default = false; }
+ public bool auto_start { get; set; default = false; }
+ public Gee.ArrayList<Theme?> themes { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Saves all above variables to a file.
+ /////////////////////////////////////////////////////////////////////
+
+ public void save() {
+ var writer = new Xml.TextWriter.filename(Paths.settings);
+ writer.start_document("1.0");
+ writer.start_element("settings");
+ writer.write_attribute("theme", theme.name);
+ writer.write_attribute("refresh_rate", refresh_rate.to_string());
+ writer.write_attribute("global_scale", global_scale.to_string());
+ writer.write_attribute("show_indicator", show_indicator ? "true" : "false");
+ writer.write_attribute("open_at_mouse", open_at_mouse ? "true" : "false");
+ writer.write_attribute("turbo_mode", turbo_mode ? "true" : "false");
+ writer.end_element();
+ writer.end_document();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all settings variables from a file.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load() {
+
+ // check for auto_start filename
+ this.auto_start = FileUtils.test(Paths.autostart, FileTest.EXISTS);
+
+ // parse the settings file
+ Xml.Parser.init();
+ Xml.Doc* settingsXML = Xml.Parser.parse_file(Paths.settings);
+ bool error_occrured = false;
+ string theme_name = "";
+
+ if (settingsXML != null) {
+
+ Xml.Node* root = settingsXML->get_root_element();
+ if (root != null) {
+
+ for (Xml.Attr* attribute = root->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ switch (attr_name) {
+ case "theme":
+ theme_name = attr_content;
+ break;
+ case "refresh_rate":
+ refresh_rate = double.parse(attr_content);
+ break;
+ case "global_scale":
+ global_scale = double.parse(attr_content);
+ global_scale.clamp(0.5, 2.0);
+ break;
+ case "show_indicator":
+ show_indicator = bool.parse(attr_content);
+ break;
+ case "open_at_mouse":
+ open_at_mouse = bool.parse(attr_content);
+ break;
+ case "turbo_mode":
+ turbo_mode = bool.parse(attr_content);
+ break;
+ default:
+ warning("Invalid setting \"" + attr_name + "\" in gnome-pie.conf!");
+ break;
+ }
+ }
+
+ Xml.Parser.cleanup();
+
+ } else {
+ warning("Error loading settings: gnome-pie.conf is empty! Using defaults...");
+ error_occrured = true;
+ }
+
+ delete settingsXML;
+
+ } else {
+ warning("Error loading settings: gnome-pie.conf not found! Using defaults...");
+ error_occrured = true;
+ }
+
+ load_themes(theme_name);
+
+ if (error_occrured) save();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Registers all themes in the user's and in the global
+ /// theme directory.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_themes(string current) {
+ themes = new Gee.ArrayList<Theme?>();
+ try {
+ string name;
+
+ // load global themes
+ var d = Dir.open(Paths.global_themes);
+ while ((name = d.read_name()) != null) {
+ var theme = new Theme(Paths.global_themes + "/" + name);
+ if (theme != null)
+ themes.add(theme);
+ }
+
+ // load local themes
+ d = Dir.open(Paths.local_themes);
+ while ((name = d.read_name()) != null) {
+ var theme = new Theme(Paths.local_themes + "/" + name);
+ if (theme != null)
+ themes.add(theme);
+ }
+
+ } catch (Error e) {
+ warning (e.message);
+ }
+
+ if (themes.size > 0) {
+ if (current == "") {
+ current = "Unity";
+ warning("No theme specified! Using default...");
+ }
+ foreach (var t in themes) {
+ if (t.name == current) {
+ theme = t;
+ theme.load_images();
+ break;
+ }
+ }
+ if (theme == null) {
+ theme = themes[0];
+ warning("Theme \"" + current + "\" not found! Using fallback...");
+ }
+ }
+ else error("No theme found!");
+ }
+
+}
+
+}
diff --git a/src/utilities/icon.vala b/src/utilities/icon.vala
new file mode 100644
index 0000000..1c8a9f4
--- /dev/null
+++ b/src/utilities/icon.vala
@@ -0,0 +1,102 @@
+/*
+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 class representing a square-shaped icon, loaded from the users
+/// icon theme.
+/////////////////////////////////////////////////////////////////////////
+
+public class Icon : Image {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A cache which stores loaded icon. It is cleared when the icon
+ /// theme of the user changes. The key is in form <filename>@<size>.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Gee.HashMap<string, Cairo.ImageSurface?> cache { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes the cache.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ clear_cache();
+
+ Gtk.IconTheme.get_default().changed.connect(() => {
+ clear_cache();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Clears the cache.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void clear_cache() {
+ cache = new Gee.HashMap<string, Cairo.ImageSurface?>();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an icon from the current icon theme of the user.
+ /////////////////////////////////////////////////////////////////////
+
+ public Icon(string icon_name, int size) {
+ var cached = this.cache.get("%s@%u".printf(icon_name, size));
+
+ if (cached == null) {
+ this.load_file_at_size(this.get_icon_file(icon_name, size), size, size);
+ this.cache.set("%s@%u".printf(icon_name, size), this.surface);
+ } else {
+ this.surface = cached;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the size of the icon in pixels. Greetings to Liskov.
+ /////////////////////////////////////////////////////////////////////
+
+ public int size() {
+ return base.width();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the filename for a given system icon.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_icon_file(string icon_name, int size) {
+ string result = "";
+
+ var icon_theme = Gtk.IconTheme.get_default();
+ var file = icon_theme.lookup_icon(icon_name, size, 0);
+ if (file != null) result = file.get_filename();
+
+ if (result == "") {
+ warning("Icon \"" + icon_name + "\" not found! Using default icon...");
+ icon_name = "application-default-icon";
+ file = icon_theme.lookup_icon(icon_name, size, 0);
+ if (file != null) result = file.get_filename();
+ }
+
+ if (result == "")
+ warning("Icon \"" + icon_name + "\" not found! Will be ugly...");
+
+ return result;
+ }
+}
+
+}
diff --git a/src/utilities/image.vala b/src/utilities/image.vala
new file mode 100644
index 0000000..836e4e2
--- /dev/null
+++ b/src/utilities/image.vala
@@ -0,0 +1,163 @@
+/*
+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 class which loads image files. It can load image files in various
+/// formats, including jpeg, png and svg.
+/////////////////////////////////////////////////////////////////////////
+
+public class Image : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The internally used surface.
+ /////////////////////////////////////////////////////////////////////
+
+ public Cairo.ImageSurface surface { public get; protected set; default=null; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates an empty Image.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image.empty(int width, int height, Color? color = null) {
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+
+ if (color != null) {
+ var ctx = this.context();
+ ctx.set_source_rgb(color.r, color.g, color.b);
+ ctx.paint();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates an image from the the given filename.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image.from_file(string filename) {
+ this.load_file(filename);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates an image from the the given filename at a given size.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image.from_file_at_size(string filename, int width, int height) {
+ this.load_file_at_size(filename, width, height);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates an image from the the given Gdk.Pixbuf.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image.from_pixbuf(Gdk.Pixbuf pixbuf) {
+ this.load_pixbuf(pixbuf);
+ }
+
+ public Image.capture_screen(int posx, int posy, int width, int height) {
+ Gdk.Window root = Gdk.get_default_root_window();
+ Gdk.Pixbuf pixbuf = Gdk.pixbuf_get_from_drawable(null, root, null, posx, posy, 0, 0, width, height);
+
+ this.load_pixbuf(pixbuf);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an image from the the given filename.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_file(string filename) {
+ try {
+ var pixbuf = new Gdk.Pixbuf.from_file(filename);
+
+ if (pixbuf != null) {
+ this.load_pixbuf(pixbuf);
+ } else {
+ warning("Failed to load " + filename + "!");
+ }
+ } catch (GLib.Error e) {
+ message("Error loading image file: %s", e.message);
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an image from the the given filename at a given size.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_file_at_size(string filename, int width, int height) {
+ try {
+ var pixbuf = new Gdk.Pixbuf.from_file_at_size(filename, width, height);
+
+ if (pixbuf != null) {
+ this.load_pixbuf(pixbuf);
+ } else {
+ warning("Failed to load " + filename + "!");
+ }
+ } catch (GLib.Error e) {
+ message("Error loading image file: %s", e.message);
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an image from the the given Gdk.Pixbuf.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_pixbuf(Gdk.Pixbuf pixbuf) {
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, pixbuf.width, pixbuf.height);
+
+ var ctx = this.context();
+ Gdk.cairo_set_source_pixbuf(ctx, pixbuf, 1.0, 1.0);
+ ctx.paint();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Paints the image onto the given Cairo.Context
+ /////////////////////////////////////////////////////////////////////
+
+ public void paint_on(Cairo.Context ctx, double alpha = 1.0) {
+ ctx.set_source_surface(this.surface, -0.5*this.width()-1, -0.5*this.height()-1);
+ if (alpha >= 1.0) ctx.paint();
+ else ctx.paint_with_alpha(alpha);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a Cairo.Context for the Image.
+ /////////////////////////////////////////////////////////////////////
+
+ public Cairo.Context context() {
+ return new Cairo.Context(this.surface);;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the width of the image in pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ public int width() {
+ return this.surface.get_width();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the height of the image in pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ public int height() {
+ return this.surface.get_height();
+ }
+}
+
+}
diff --git a/src/utilities/key.vala b/src/utilities/key.vala
new file mode 100644
index 0000000..6700b16
--- /dev/null
+++ b/src/utilities/key.vala
@@ -0,0 +1,139 @@
+/*
+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 class which represents a key stroke. It can be used to "press"
+/// the associated keys.
+/////////////////////////////////////////////////////////////////////////
+
+public class Key : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some static members, which are often used by this class.
+ /////////////////////////////////////////////////////////////////////
+
+ private static X.Display display;
+
+ private static int shift_code;
+ private static int ctrl_code;
+ private static int alt_code;
+ private static int super_code;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A human readable form of the Key's accelerator.
+ /////////////////////////////////////////////////////////////////////
+
+ public string label { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The accelerator of the Key.
+ /////////////////////////////////////////////////////////////////////
+
+ public string accelerator { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Keycode and modifiers of this stroke.
+ /////////////////////////////////////////////////////////////////////
+
+ private int key_code;
+ private Gdk.ModifierType modifiers;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Key(string stroke) {
+ this.accelerator = stroke;
+
+ uint keysym;
+ Gtk.accelerator_parse(stroke, out keysym, out this.modifiers);
+ this.key_code = display.keysym_to_keycode(keysym);
+ this.label = Gtk.accelerator_get_label(keysym, this.modifiers);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes static members.
+ /////////////////////////////////////////////////////////////////////
+
+ static construct {
+ display = new X.Display();
+
+ shift_code = display.keysym_to_keycode(Gdk.keyval_from_name("Shift_L"));
+ ctrl_code = display.keysym_to_keycode(Gdk.keyval_from_name("Control_L"));
+ alt_code = display.keysym_to_keycode(Gdk.keyval_from_name("Alt_L"));
+ super_code = display.keysym_to_keycode(Gdk.keyval_from_name("Super_L"));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Simulates the pressing of the Key .
+ /////////////////////////////////////////////////////////////////////
+
+ public void press() {
+ // store currently pressed modifier keys
+ Gdk.ModifierType current_modifiers = get_modifiers();
+
+ // release them and press the desired ones
+ press_modifiers(current_modifiers, false);
+ press_modifiers(this.modifiers, true);
+
+ // send events to X
+ display.flush();
+
+ // press and release the actual key
+ X.Test.fake_key_event(this.display, this.key_code, true, 0);
+ X.Test.fake_key_event(this.display, this.key_code, false, 0);
+
+ // release the pressed modifiers and re-press the keys hold down by the user
+ press_modifiers(this.modifiers, false);
+ press_modifiers(current_modifiers, true);
+
+ // send events to X
+ display.flush();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method returning currently hold down modifier keys.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.ModifierType get_modifiers() {
+ Gdk.ModifierType modifiers;
+ Gdk.Display.get_default().get_pointer(null, null, null, out modifiers);
+ return modifiers;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which 'presses' the desired modifier keys.
+ /////////////////////////////////////////////////////////////////////
+
+ private void press_modifiers(Gdk.ModifierType modifiers, bool down) {
+ if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0)
+ X.Test.fake_key_event(this.display, this.ctrl_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0)
+ X.Test.fake_key_event(this.display, this.shift_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0)
+ X.Test.fake_key_event(this.display, this.alt_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0)
+ X.Test.fake_key_event(this.display, this.super_code, down, 0);
+ }
+}
+
+}
diff --git a/src/utilities/logger.vala b/src/utilities/logger.vala
new file mode 100644
index 0000000..3108ba3
--- /dev/null
+++ b/src/utilities/logger.vala
@@ -0,0 +1,194 @@
+/*
+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 static class which beautifies the messages of the default logger.
+/// Some of this code is inspired by plank's written by Robert Dyer.
+/// Thanks a lot for this project!
+/////////////////////////////////////////////////////////////////////////
+
+public class Logger {
+
+ /////////////////////////////////////////////////////////////////////
+ /// If these are set to false, the according messages are not shown
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool display_info { get; set; default = true; }
+ public static bool display_debug { get; set; default = true; }
+ public static bool display_warning { get; set; default = true; }
+ public static bool display_error { get; set; default = true; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// If true, a time stamp is shown in each message.
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool display_time { get; set; default = true; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// If true, the origin of the message is shown. In form file:line
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool display_file { get; set; default = false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A regex, used to format the standard message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Regex regex = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Possible terminal colors.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum Color {
+ BLACK,
+ RED,
+ GREEN,
+ YELLOW,
+ BLUE,
+ PURPLE,
+ TURQUOISE,
+ WHITE
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates the regex and binds the handler.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ try {
+ regex = new Regex("""(.*)\.vala(:\d+): (.*)""");
+ } catch {}
+
+ GLib.Log.set_default_handler(log_func);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays an Info message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void info(string message) {
+ if (display_info) {
+ stdout.printf(set_color(Color.GREEN, false) + "[" + get_time() + "MESSAGE]" + message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Debug message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void debug(string message) {
+ if (display_debug) {
+ stdout.printf(set_color(Color.BLUE, false) + "[" + get_time() + " DEBUG ]" + message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Warning message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void warning(string message) {
+ if (display_warning) {
+ stdout.printf(set_color(Color.YELLOW, false) + "[" + get_time() + "WARNING]" + message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Error message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void error(string message) {
+ if (display_error) {
+ stdout.printf(set_color(Color.RED, false) + "[" + get_time() + " ERROR ]" + message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which resets the terminal color.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string reset_color() {
+ return "\x001b[0m";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which sets the terminal color.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string set_color(Color color, bool bold) {
+ if (bold) return "\x001b[1;%dm".printf((int)color + 30);
+ else return "\x001b[0;%dm".printf((int)color + 30);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the current time in hh:mm:ss:mmmmmm
+ /////////////////////////////////////////////////////////////////////
+
+ private static string get_time() {
+ if (display_time) {
+ var now = new DateTime.now_local ();
+ return "%.2d:%.2d:%.2d:%.6d ".printf (now.get_hour (), now.get_minute (), now.get_second (), now.get_microsecond ());
+ } else {
+ return "";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method to format the message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string create_message(string message) {
+ if (display_file && regex != null && regex.match(message)) {
+ var parts = regex.split(message);
+ return " [%s%s]%s %s\n".printf(parts[1], parts[2], reset_color(), parts[3]);
+ } else if (regex != null && regex.match(message)) {
+ var parts = regex.split(message);
+ return "%s %s\n".printf(reset_color(), parts[3]);
+ } else {
+ return reset_color() + " " + message + "\n";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The handler function.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void log_func(string? d, LogLevelFlags flags, string message) {
+
+ switch (flags) {
+ case LogLevelFlags.LEVEL_ERROR:
+ case LogLevelFlags.LEVEL_CRITICAL:
+ error(create_message(message));
+ break;
+ case LogLevelFlags.LEVEL_INFO:
+ case LogLevelFlags.LEVEL_MESSAGE:
+ info(create_message(message));
+ break;
+ case LogLevelFlags.LEVEL_DEBUG:
+ debug(create_message(message));
+ break;
+ case LogLevelFlags.LEVEL_WARNING:
+ default:
+ warning(create_message(message));
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/utilities/paths.vala b/src/utilities/paths.vala
new file mode 100644
index 0000000..1c42176
--- /dev/null
+++ b/src/utilities/paths.vala
@@ -0,0 +1,211 @@
+/*
+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 static class which stores all relevant paths used by Gnome-Pie.
+/// These depend upon the location from which the program was launched.
+/////////////////////////////////////////////////////////////////////////
+
+public class Paths : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The file settings file,
+ /// usually ~/.config/gnome-pie/gnome-pie.conf.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string settings { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The file pie configuration file
+ /// usually ~/.config/gnome-pie/pies.conf.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string pie_config { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The directory containing themes installed by the user
+ /// usually ~/.config/gnome-pie/themes.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string local_themes { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The directory containing pre-installed themes
+ /// usually /usr/share/gnome-pie/themes.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string global_themes { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The directory containing locale files
+ /// usually /usr/share/locale.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string locales { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The autostart file of gnome-pie_config
+ /// usually ~/.config/autostart/gnome-pie.desktop.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string autostart { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The path where all pie-launchers are stored
+ /// usually ~/.config/gnome-pie/launchers.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string launchers { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes all values above.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+
+ // append resources to icon search path to icon theme, if neccasary
+ try {
+ var icon_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources");
+
+ if (icon_dir.query_exists()) {
+ string path = icon_dir.get_path();
+ Gtk.IconTheme.get_default().append_search_path(path);
+ }
+
+ Gtk.IconTheme.get_default().append_search_path("/usr/share/pixmaps/");
+
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+
+ // get global paths
+ var default_dir = GLib.File.new_for_path("/usr/share/gnome-pie/");
+ if(!default_dir.query_exists()) {
+ default_dir = GLib.File.new_for_path("/usr/local/share/gnome-pie/");
+
+ if(!default_dir.query_exists()) {
+ try {
+ default_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources");
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+ }
+ }
+
+ global_themes = default_dir.get_path() + "/themes";
+
+ // get locales path
+ var locale_dir = GLib.File.new_for_path("/usr/share/locale/de/LC_MESSAGES/gnomepie.mo");
+ if(locale_dir.query_exists()) {
+ locale_dir = GLib.File.new_for_path("/usr/share/locale");
+ } else {
+ locale_dir = GLib.File.new_for_path("/usr/local/share/locale/de/LC_MESSAGES/gnomepie.mo");
+ if(locale_dir.query_exists()) {
+ locale_dir = GLib.File.new_for_path("/usr/local/share/locale");
+ } else {
+
+ try {
+ locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ GLib.FileUtils.read_link("/proc/self/exe"))).get_child(
+ "resources/locale/de/LC_MESSAGES/gnomepie.mo");
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+
+ if(locale_dir.query_exists()) {
+ try {
+ locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources/locale");
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+ }
+ }
+ }
+
+ locales = locale_dir.get_path();
+
+ // get local paths
+ var config_dir = GLib.File.new_for_path(
+ GLib.Environment.get_user_config_dir()).get_child("gnome-pie");
+
+ // create config_dir if neccasary
+ if(!config_dir.query_exists()) {
+ try {
+ config_dir.make_directory();
+ } catch (GLib.Error e) {
+ error(e.message);
+ }
+ }
+
+ // create local themes directory if neccasary
+ var themes_dir = config_dir.get_child("themes");
+ if(!themes_dir.query_exists()) {
+ try {
+ themes_dir.make_directory();
+ } catch (GLib.Error e) {
+ error(e.message);
+ }
+ }
+
+ local_themes = themes_dir.get_path();
+
+ // create launchers directory if neccasary
+ var launchers_dir = config_dir.get_child("launchers");
+ if(!launchers_dir.query_exists()) {
+ try {
+ launchers_dir.make_directory();
+ } catch (GLib.Error e) {
+ error(e.message);
+ }
+ }
+
+ launchers = launchers_dir.get_path();
+
+ // check for config file
+ var config_file = config_dir.get_child("pies.conf");
+
+ pie_config = config_file.get_path();
+ settings = config_dir.get_path() + "/gnome-pie.conf";
+
+ // autostart file name
+ autostart = GLib.Path.build_filename(GLib.Environment.get_user_config_dir(),
+ "autostart", "gnome-pie.desktop", null);
+
+ // print results
+ if (!GLib.File.new_for_path(pie_config).query_exists())
+ warning("Failed to find pie configuration file \"pies.conf\"! (This should only happen when Gnome-Pie is started for the first time...)");
+
+ if (!GLib.File.new_for_path(settings).query_exists())
+ warning("Failed to find settings file \"gnome-pie.conf\"!");
+
+ if (!GLib.File.new_for_path(local_themes).query_exists())
+ warning("Failed to find local themes directory!");
+
+ if (!GLib.File.new_for_path(launchers).query_exists())
+ warning("Failed to find launchers directory!");
+
+ if (!GLib.File.new_for_path(global_themes).query_exists())
+ warning("Failed to find global themes directory!");
+ }
+}
+
+}
diff --git a/src/utilities/renderedText.vala b/src/utilities/renderedText.vala
new file mode 100644
index 0000000..924742a
--- /dev/null
+++ b/src/utilities/renderedText.vala
@@ -0,0 +1,110 @@
+/*
+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 class representing string, rendered on an Image.
+/////////////////////////////////////////////////////////////////////////
+
+public class RenderedText : Image {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A cache which stores images. It is cleared when the theme of
+ /// Gnome-Pie changes.
+ /// The key is in form <string>@<width>x<height>:<font>.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Gee.HashMap<string, Cairo.ImageSurface?> cache { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes the cache.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ clear_cache();
+
+ Config.global.notify["theme"].connect(() => {
+ clear_cache();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Clears the cache.
+ /////////////////////////////////////////////////////////////////////
+
+ static void clear_cache() {
+ cache = new Gee.HashMap<string, Cairo.ImageSurface?>();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new image representation of a string.
+ /////////////////////////////////////////////////////////////////////
+
+ public RenderedText(string text, int width, int height, string font) {
+ var cached = this.cache.get("%s@%ux%u:%s".printf(text, width, height, font));
+
+ if (cached == null) {
+ this.render_text(text, width, height, font);
+ this.cache.set("%s@%ux%u:%s".printf(text, width, height, font), this.surface);
+ } else {
+ this.surface = cached;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new transparent image, with text written onto.
+ /////////////////////////////////////////////////////////////////////
+
+ public void render_text(string text, int width, int height, string font) {
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+
+ var ctx = this.context();
+
+ // set the color as specified in the current theme
+ Color color = Config.global.theme.caption_color;
+ ctx.set_source_rgb(color.r, color.g, color.g);
+
+ var layout = Pango.cairo_create_layout(ctx);
+ layout.set_width(Pango.units_from_double(width));
+
+ var font_description = Pango.FontDescription.from_string(font);
+ font_description.set_size((int)(font_description.get_size() * Config.global.global_scale));
+
+ layout.set_font_description(font_description);
+ layout.set_text(text, -1);
+
+ // add newlines at the end of each line, in order to allow ellipsizing
+ string broken_string = "";
+ foreach (var line in layout.get_lines()) {
+ broken_string = broken_string.concat(text.substring(line.start_index, line.length), "\n");
+ }
+ layout.set_text(broken_string, broken_string.length-1);
+
+ layout.set_ellipsize(Pango.EllipsizeMode.END);
+ layout.set_alignment(Pango.Alignment.CENTER);
+
+ Pango.Rectangle extents;
+ layout.get_pixel_extents(null, out extents);
+ ctx.move_to(0, (int)(0.5*(height - extents.height)));
+
+ Pango.cairo_update_layout(ctx, layout);
+ Pango.cairo_show_layout(ctx, layout);
+ }
+}
+
+}
diff --git a/src/utilities/themedIcon.vala b/src/utilities/themedIcon.vala
new file mode 100644
index 0000000..29ae380
--- /dev/null
+++ b/src/utilities/themedIcon.vala
@@ -0,0 +1,161 @@
+/*
+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 class representing a square-shaped icon, themed according to the
+/// current theme of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class ThemedIcon : Image {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A cache which stores loaded icon. The key is the icon name. When
+ /// the users icon theme or the theme of Gnome-Pie changes, these
+ /// cahces are cleared.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Gee.HashMap<string, Cairo.ImageSurface?> active_cache { private get; private set; }
+ private static Gee.HashMap<string, Cairo.ImageSurface?> inactive_cache { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes the caches.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ clear_cache();
+
+ Config.global.notify["theme"].connect(() => {
+ clear_cache();
+ });
+
+ Gtk.IconTheme.get_default().changed.connect(() => {
+ clear_cache();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Clears the cache.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void clear_cache() {
+ active_cache = new Gee.HashMap<string, Cairo.ImageSurface?>();
+ inactive_cache = new Gee.HashMap<string, Cairo.ImageSurface?>();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Paint a slice icon according to the current theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public ThemedIcon(string icon_name, bool active) {
+ // check cache
+ var current_cache = active ? active_cache : inactive_cache;
+ var cached = current_cache.get(icon_name);
+
+ if (cached != null) {
+ this.surface = cached;
+ return;
+ }
+
+ // get layers for the desired slice type
+ var layers = active ? Config.global.theme.active_slice_layers : Config.global.theme.inactive_slice_layers;
+
+ // get max size
+ int size = 0;
+ foreach (var layer in layers) {
+ if (layer.image.width() > size) size = layer.image.width();
+ }
+
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, size, size);
+
+ // get size of icon layer
+ int icon_size = size;
+ foreach (var layer in layers) {
+ if (layer.is_icon) icon_size = layer.image.width();
+ }
+
+ Image icon;
+ if (icon_name.contains("/"))
+ icon = new Image.from_file_at_size(icon_name, icon_size, icon_size);
+ else
+ icon = new Icon(icon_name, icon_size);
+
+ var color = new Color.from_icon(icon);
+ var ctx = this.context();
+
+ ctx.translate(size/2, size/2);
+ ctx.set_operator(Cairo.Operator.OVER);
+
+ // now render all layers on top of each other
+ foreach (var layer in layers) {
+
+ if (layer.colorize) {
+ ctx.push_group();
+ }
+
+ if (layer.is_icon) {
+
+ ctx.push_group();
+
+ layer.image.paint_on(ctx);
+
+ ctx.set_operator(Cairo.Operator.IN);
+
+ if (layer.image.width() != icon_size) {
+ if (icon_name.contains("/"))
+ icon = new Image.from_file_at_size(icon_name, layer.image.width(), layer.image.width());
+ else
+ icon = new Icon(icon_name,layer.image.width());
+ }
+
+ icon.paint_on(ctx);
+
+ ctx.pop_group_to_source();
+ ctx.paint();
+ ctx.set_operator(Cairo.Operator.OVER);
+
+ } else {
+ layer.image.paint_on(ctx);
+ }
+
+ // colorize the whole layer if neccasary
+ if (layer.colorize) {
+ ctx.set_operator(Cairo.Operator.ATOP);
+ ctx.set_source_rgb(color.r, color.g, color.b);
+ ctx.paint();
+
+ ctx.set_operator(Cairo.Operator.OVER);
+ ctx.pop_group_to_source();
+ ctx.paint();
+ }
+ }
+
+ // store the surface in cache
+ current_cache.set(icon_name, this.surface);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the size of the icon in pixels. Greetings to Liskov.
+ /////////////////////////////////////////////////////////////////////
+
+ public int size() {
+ return base.width();
+ }
+}
+
+}