summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt100
-rw-r--r--src/actionGroups/actionGroup.vala113
-rw-r--r--src/actionGroups/bookmarkGroup.vala151
-rw-r--r--src/actionGroups/clipboardGroup.vala194
-rw-r--r--src/actionGroups/devicesGroup.vala124
-rw-r--r--src/actionGroups/groupRegistry.vala110
-rw-r--r--src/actionGroups/menuGroup.vala250
-rw-r--r--src/actionGroups/sessionGroup.vala76
-rw-r--r--src/actionGroups/windowListGroup.vala193
-rw-r--r--src/actions/action.vala77
-rw-r--r--src/actions/actionRegistry.vala219
-rw-r--r--src/actions/appAction.vala76
-rw-r--r--src/actions/keyAction.vala81
-rw-r--r--src/actions/pieAction.vala103
-rw-r--r--src/actions/sigAction.vala63
-rw-r--r--src/actions/uriAction.vala77
-rw-r--r--src/daemon.vala262
-rw-r--r--src/gui/aboutWindow.vala84
-rw-r--r--src/gui/iconSelectWindow.vala450
-rw-r--r--src/gui/indicator.vala180
-rw-r--r--src/gui/newSliceWindow.vala433
-rw-r--r--src/gui/newsWindow.vala73
-rw-r--r--src/gui/pieComboList.vala155
-rw-r--r--src/gui/pieList.vala275
-rw-r--r--src/gui/pieOptionsWindow.vala315
-rw-r--r--src/gui/piePreview.vala387
-rw-r--r--src/gui/piePreviewAddSign.vala224
-rw-r--r--src/gui/piePreviewCenter.vala109
-rw-r--r--src/gui/piePreviewDeleteSign.vala195
-rw-r--r--src/gui/piePreviewRenderer.vala443
-rw-r--r--src/gui/piePreviewSliceRenderer.vala276
-rw-r--r--src/gui/preferencesWindow.vala604
-rw-r--r--src/gui/sliceTypeList.vala173
-rw-r--r--src/gui/themeList.vala118
-rw-r--r--src/gui/tipViewer.vala163
-rw-r--r--src/gui/triggerSelectButton.vala163
-rw-r--r--src/images/icon.vala135
-rw-r--r--src/images/image.vala215
-rw-r--r--src/images/renderedText.vala152
-rw-r--r--src/images/themedIcon.vala130
-rw-r--r--src/pies/defaultConfig.vala74
-rw-r--r--src/pies/load.vala208
-rw-r--r--src/pies/pie.vala122
-rw-r--r--src/pies/pieManager.vala347
-rw-r--r--src/pies/save.vala89
-rw-r--r--src/renderers/centerRenderer.vala232
-rw-r--r--src/renderers/pieRenderer.vala890
-rwxr-xr-xsrc/renderers/pieWindow.vala500
-rw-r--r--src/renderers/sliceRenderer.vala295
-rw-r--r--src/themes/centerLayer.vala116
-rw-r--r--src/themes/sliceLayer.vala105
-rw-r--r--src/themes/theme.vala650
-rw-r--r--src/themes/themeImporter.vala62
-rw-r--r--src/utilities/animatedValue.vala197
-rw-r--r--src/utilities/archiveReader.vala123
-rw-r--r--src/utilities/archiveWriter.vala139
-rw-r--r--src/utilities/bindingManager.vala428
-rw-r--r--src/utilities/color.vala327
-rw-r--r--src/utilities/config.vala239
-rw-r--r--src/utilities/focusGrabber.vala97
-rw-r--r--src/utilities/key.vala161
-rw-r--r--src/utilities/logger.vala270
-rw-r--r--src/utilities/paths.vala286
-rw-r--r--src/utilities/trigger.vala357
64 files changed, 14035 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..4d1194f
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,100 @@
+################################################################
+# Actually compile the executable
+################################################################
+
+# determine source and header files
+file(GLOB VALA_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.vala */*.vala)
+
+if (${INDICATOR_FOUND})
+ LIST(APPEND DEFINES --define HAVE_APPINDICATOR)
+endif(${INDICATOR_FOUND})
+if (${INDICATOR3_FOUND})
+ LIST(APPEND DEFINES --define HAVE_APPINDICATOR)
+endif(${INDICATOR3_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
+ --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.md
+ 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 UI files
+install(
+ DIRECTORY
+ ${CMAKE_SOURCE_DIR}/resources/ui
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/gnome-pie
+)
+
+# install icons
+install(
+ FILES
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie.svg
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie-symbolic.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
+)
+
+# install manpage
+install(
+ FILES
+ ${CMAKE_SOURCE_DIR}/resources/gnome-pie.1
+ DESTINATION
+ ${CMAKE_INSTALL_PREFIX}/share/man/man1
+)
+
diff --git a/src/actionGroups/actionGroup.vala b/src/actionGroups/actionGroup.vala
new file mode 100644
index 0000000..85488ad
--- /dev/null
+++ b/src/actionGroups/actionGroup.vala
@@ -0,0 +1,113 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+// A 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() {}
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is saved.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual void on_save(Xml.TextWriter writer) {
+ writer.write_attribute("type", GroupRegistry.descriptions[this.get_type().name()].id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is loaded.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual void on_load(Xml.Node* data) {}
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes all contained Slices no Quick Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ public void disable_quickactions() {
+ foreach (var action in actions) {
+ action.is_quickaction = false;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true, if one o the contained Slices is a Quick Action
+ /////////////////////////////////////////////////////////////////////
+
+ public bool has_quickaction() {
+ foreach (var action in actions) {
+ if (action.is_quickaction) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+}
diff --git a/src/actionGroups/bookmarkGroup.vala b/src/actionGroups/bookmarkGroup.vala
new file mode 100644
index 0000000..791d609
--- /dev/null
+++ b/src/actionGroups/bookmarkGroup.vala
@@ -0,0 +1,151 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Bookmarks");
+ description.icon = "user-bookmarks";
+ description.description = _("Shows a Slice for each of your directory Bookmarks.");
+ description.id = "bookmarks";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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..58409de
--- /dev/null
+++ b/src/actionGroups/clipboardGroup.vala
@@ -0,0 +1,194 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This Group keeps a history of the last used Clipboard entries.
+/// Experimental. Not enabled.
+/////////////////////////////////////////////////////////////////////////
+
+public class ClipboardGroup : ActionGroup {
+
+ /////////////////////////////////////////////////////////////////////
+
+ private class ClipboardItem : GLib.Object {
+
+ public string name { get; protected set; }
+ public string icon { get; protected set; }
+
+ protected Gtk.Clipboard clipboard { get; set; }
+ protected static Key paste_key = new Key.from_string("<Control>v");
+
+ public virtual void paste() {}
+ }
+
+ /////////////////////////////////////////////////////////////////////
+
+ private class TextClipboardItem : ClipboardItem {
+
+ public TextClipboardItem(Gtk.Clipboard clipboard) {
+ GLib.Object(clipboard : clipboard,
+ name : clipboard.wait_for_text(),
+ icon : "edit-paste");
+
+ // check whether a file has been copied and search for a cool icon
+ var first_line = this.name.substring(0, this.name.index_of("\n"));
+ var file = GLib.File.new_for_path(first_line);
+
+ if (file.query_exists()) {
+ try {
+ var info = file.query_info("standard::icon", 0);
+ this.icon = Icon.get_icon_name(info.get_icon());
+ } catch (Error e) {
+ warning("Failed to generate icon for ClipboardGroupItem.");
+ }
+ }
+ }
+
+ public override void paste() {
+ clipboard.set_text(name, name.length);
+ paste_key.press();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+
+ private class ImageClipboardItem : ClipboardItem {
+
+ private Gdk.Pixbuf image { get; set; }
+
+ public ImageClipboardItem(Gtk.Clipboard clipboard) {
+ GLib.Object(clipboard : clipboard,
+ name : _("Image data"),
+ icon : "image-viewer");
+ this.image = clipboard.wait_for_image();
+ }
+
+ public override void paste() {
+ clipboard.set_image(image);
+ paste_key.press();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The maximum remembered items of the clipboard.
+ /////////////////////////////////////////////////////////////////////
+
+ public int max_items {get; set; default=8; }
+
+ /////////////////////////////////////////////////////////////////////
+
+ 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Clipboard");
+ description.icon = "edit-paste";
+ description.description = _("Manages your Clipboard.");
+ description.id = "clipboard";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The clipboard to be monitored.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Clipboard clipboard;
+
+ private bool ignore_next_change = false;
+
+ 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is saved.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void on_save(Xml.TextWriter writer) {
+ base.on_save(writer);
+ writer.write_attribute("max_items", this.max_items.to_string());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is loaded.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void on_load(Xml.Node* data) {
+ for (Xml.Attr* attribute = data->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ if (attr_name == "max_items") {
+ this.max_items = int.parse(attr_content);
+ }
+ }
+ }
+
+ private void on_change() {
+ if (ignore_next_change) {
+ ignore_next_change = false;
+ return;
+ }
+
+ if (this.clipboard.wait_is_text_available()) {
+ if (clipboard.wait_for_text() != null) {
+ add_item(new TextClipboardItem(this.clipboard));
+ }
+ } else if (this.clipboard.wait_is_image_available()) {
+ add_item(new ImageClipboardItem(this.clipboard));
+ }
+ }
+
+ private void add_item(ClipboardItem item) {
+
+ // remove one item if there are too many
+ if (this.items.size == this.max_items) {
+ this.items.remove_at(0);
+ }
+
+ this.items.add(item);
+
+ // update slices
+ this.delete_all();
+
+ for (int i=this.items.size-1; i>=0; --i) {
+ var action = new SigAction(items[i].name, items[i].icon, i.to_string());
+ action.activated.connect(() => {
+ ignore_next_change = true;
+ 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..e18f4c0
--- /dev/null
+++ b/src/actionGroups/devicesGroup.vala
@@ -0,0 +1,124 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////
+/// 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Devices");
+ description.icon = "drive-harddisk";
+ description.description = _("Shows a Slice for each plugged in devices, like USB-Sticks.");
+ description.id = "devices";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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"), "drive-harddisk", "file:///"));
+
+ // add all other devices
+ foreach(var mount in this.monitor.get_mounts()) {
+ // get icon
+ var icon = mount.get_icon();
+
+ this.add_action(new UriAction(mount.get_name(), Icon.get_icon_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..c97cf95
--- /dev/null
+++ b/src/actionGroups/groupRegistry.vala
@@ -0,0 +1,110 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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<string> types { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A map 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<string, TypeDescription?> descriptions { get; private set; }
+
+ public class TypeDescription {
+ public string name { get; set; default=""; }
+ public string icon { get; set; default=""; }
+ public string description { get; set; default=""; }
+ public string id { get; set; default=""; }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Registers all ActionGroup types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ types = new Gee.ArrayList<string>();
+ descriptions = new Gee.HashMap<string, TypeDescription?>();
+
+ TypeDescription type_description;
+
+ type_description = BookmarkGroup.register();
+ types.add(typeof(BookmarkGroup).name());
+ descriptions.set(typeof(BookmarkGroup).name(), type_description);
+
+ type_description = ClipboardGroup.register();
+ types.add(typeof(ClipboardGroup).name());
+ descriptions.set(typeof(ClipboardGroup).name(), type_description);
+
+ type_description = DevicesGroup.register();
+ types.add(typeof(DevicesGroup).name());
+ descriptions.set(typeof(DevicesGroup).name(), type_description);
+
+ type_description = MenuGroup.register();
+ types.add(typeof(MenuGroup).name());
+ descriptions.set(typeof(MenuGroup).name(), type_description);
+
+ type_description = SessionGroup.register();
+ types.add(typeof(SessionGroup).name());
+ descriptions.set(typeof(SessionGroup).name(), type_description);
+
+ type_description = WindowListGroup.register();
+ types.add(typeof(WindowListGroup).name());
+ descriptions.set(typeof(WindowListGroup).name(), type_description);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a Group for a given type name.
+ /////////////////////////////////////////////////////////////////////
+
+ public static ActionGroup? create_group(string type_id, string parent_id) {
+ switch (type_id) {
+ case "bookmarks":
+ return new BookmarkGroup(parent_id);
+ case "clipboard":
+ return new ClipboardGroup(parent_id);
+ case "devices":
+ return new DevicesGroup(parent_id);
+ case "menu":
+ return new MenuGroup(parent_id);
+ case "session":
+ return new SessionGroup(parent_id);
+ case "window_list":
+ return new WindowListGroup(parent_id);
+ // deprecated
+ case "workspace_window_list":
+ var group = new WindowListGroup(parent_id);
+ group.current_workspace_only = true;
+ return group;
+ }
+
+ return null;
+ }
+}
+
+}
diff --git a/src/actionGroups/menuGroup.vala b/src/actionGroups/menuGroup.vala
new file mode 100644
index 0000000..ccb5407
--- /dev/null
+++ b/src/actionGroups/menuGroup.vala
@@ -0,0 +1,250 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Main menu");
+ description.icon = "start-here";
+ description.description = _("Displays your main menu structure.");
+ description.id = "menu";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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
+
+ // get icon
+ var icon = item.get_directory().get_icon();
+
+ var sub_menu = PieManager.create_dynamic_pie(item.get_directory().get_name(), Icon.get_icon_name(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..42afafc
--- /dev/null
+++ b/src/actionGroups/sessionGroup.vala
@@ -0,0 +1,76 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////
+/// 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Session Control");
+ description.icon = "system-log-out";
+ description.description = _("Shows a Slice for Shutdown, Reboot, and Hibernate.");
+ description.id = "session";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public SessionGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Construct block adds the three Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ construct {
+// string iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.gnome.SessionManager", "/org/gnome/SessionManager");
+// iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer");
+// iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.kde.ksmserver", "/KSMServer");
+// iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager");
+
+ this.add_action(new AppAction(_("Shutdown"), "system-shutdown",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Shutdown"));
+
+ this.add_action(new AppAction(_("Logout"), "system-log-out",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1"));
+
+ this.add_action(new AppAction(_("Reboot"), "view-refresh",
+ "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Reboot"));
+ }
+
+ // TODO: check for available interfaces --- these may work too:
+ // dbus-send --print-reply --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 --print-reply --dest="org.freedesktop.ConsoleKit" /org/freedesktop/ConsoleKit/Manager org.freedesktop.ConsoleKit.Manager.Stop
+}
+
+}
diff --git a/src/actionGroups/windowListGroup.vala b/src/actionGroups/windowListGroup.vala
new file mode 100644
index 0000000..69029a7
--- /dev/null
+++ b/src/actionGroups/windowListGroup.vala
@@ -0,0 +1,193 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////
+/// This group displays a list of all running application windows.
+/////////////////////////////////////////////////////////////////////
+
+public class WindowListGroup : 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 GroupRegistry.TypeDescription register() {
+ var description = new GroupRegistry.TypeDescription();
+ description.name = _("Group: Window List");
+ description.icon = "preferences-system-windows";
+ description.description = _("Shows a Slice for each of your opened Windows. Almost like Alt-Tab.");
+ description.id = "window_list";
+ return description;
+ }
+
+ public bool current_workspace_only { get; set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two members needed to avoid useless, frequent changes of the
+ /// stored Actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool changing = false;
+ private bool changed_again = false;
+
+ private Wnck.Screen screen;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public WindowListGroup(string parent_id) {
+ GLib.Object(parent_id : parent_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all windows.
+ /////////////////////////////////////////////////////////////////////
+
+ construct {
+ this.screen = Wnck.Screen.get_default();
+
+ this.screen.window_opened.connect(reload);
+ this.screen.window_closed.connect(reload);
+ this.screen.active_workspace_changed.connect(reload);
+
+ this.update();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is saved.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void on_save(Xml.TextWriter writer) {
+ base.on_save(writer);
+ writer.write_attribute("current_workspace_only", this.current_workspace_only.to_string());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the ActionGroup is loaded.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void on_load(Xml.Node* data) {
+ for (Xml.Attr* attribute = data->properties; attribute != null; attribute = attribute->next) {
+ string attr_name = attribute->name.down();
+ string attr_content = attribute->children->content;
+
+ if (attr_name == "current_workspace_only") {
+ this.current_workspace_only = bool.parse(attr_content);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all currently opened windows and creates actions for them.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update() {
+ unowned GLib.List<Wnck.Window?> windows = this.screen.get_windows();
+
+ var matcher = Bamf.Matcher.get_default();
+
+ foreach (var window in windows) {
+ if (window.get_window_type() == Wnck.WindowType.NORMAL
+ && !window.is_skip_pager() && !window.is_skip_tasklist()
+ && (!current_workspace_only || (window.get_workspace() != null
+ && window.get_workspace() == this.screen.get_active_workspace()))) {
+
+ var application = window.get_application();
+ var bamf_app = matcher.get_application_for_xid((uint32)window.get_xid());
+
+ string name = window.get_name();
+
+ if (name.length > 30)
+ name = name.substring(0, 30) + "...";
+
+ var action = new SigAction(
+ name,
+ (bamf_app == null) ? application.get_icon_name().down() : bamf_app.get_icon(),
+ "%lu".printf(window.get_xid())
+ );
+ action.activated.connect((time_stamp) => {
+ Wnck.Screen.get_default().force_update();
+
+ var xid = (X.Window)uint64.parse(action.real_command);
+ var win = Wnck.Window.get(xid);
+
+ if (win.get_workspace() != null) {
+ //select the workspace
+ if (win.get_workspace() != win.get_screen().get_active_workspace()) {
+ win.get_workspace().activate(time_stamp);
+ }
+
+ //select the viewport inside the workspace
+ if (!win.is_in_viewport(win.get_workspace()) ) {
+ int xp, yp, widthp, heightp, scx, scy, nx, ny, wx, wy;
+ win.get_geometry (out xp, out yp, out widthp, out heightp);
+ scx = win.get_screen().get_width();
+ scy = win.get_screen().get_height();
+ wx = win.get_workspace().get_viewport_x();
+ wy = win.get_workspace().get_viewport_y();
+ if (scx > 0 && scy > 0) {
+ nx= ((wx+xp) / scx) * scx;
+ ny= ((wy+yp) / scy) * scy;
+ win.get_screen().move_viewport(nx, ny);
+ }
+ }
+ }
+
+ if (win.is_minimized()) {
+ win.unminimize(time_stamp);
+ }
+
+ win.activate_transient(time_stamp);
+ });
+ this.add_action(action);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads all running applications.
+ /////////////////////////////////////////////////////////////////////
+
+ 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
+ this.delete_all();
+ this.update();
+
+ this.changing = false;
+ return false;
+ });
+ } else {
+ this.changed_again = true;
+ }
+ }
+}
+
+}
diff --git a/src/actions/action.vala b/src/actions/action.vala
new file mode 100644
index 0000000..1e6437e
--- /dev/null
+++ b/src/actions/action.vala
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The name of the icon of this Action. It should be in the users
+ /// current icon theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public virtual string icon { get; 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_quickaction { get; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Action(string name, string icon, bool is_quickaction) {
+ GLib.Object(name : name, icon : icon, is_quickaction : is_quickaction);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This one is called, when the user activates the Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public abstract void activate(uint32 time_stamp);
+}
+
+}
diff --git a/src/actions/actionRegistry.vala b/src/actions/actionRegistry.vala
new file mode 100644
index 0000000..9a22cc7
--- /dev/null
+++ b/src/actions/actionRegistry.vala
@@ -0,0 +1,219 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A which has knowledge on all possible acion types.
+/////////////////////////////////////////////////////////////////////////
+
+public class ActionRegistry : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list containing all available Action types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.ArrayList<string> types { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A map 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<string, TypeDescription?> descriptions { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A helper class storing information on a Action type.
+ /////////////////////////////////////////////////////////////////////
+
+ public class TypeDescription {
+ public string name { get; set; default=""; }
+ public string icon { get; set; default=""; }
+ public string description { get; set; default=""; }
+ public string id { get; set; default=""; }
+ public bool icon_name_editable { get; set; default=false; }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Registers all Action types.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ types = new Gee.ArrayList<string>();
+ descriptions = new Gee.HashMap<string, TypeDescription?>();
+
+ TypeDescription type_description;
+
+ types.add(typeof(AppAction).name());
+ type_description = AppAction.register();
+ descriptions.set(typeof(AppAction).name(), type_description);
+
+ types.add(typeof(KeyAction).name());
+ type_description = KeyAction.register();
+ descriptions.set(typeof(KeyAction).name(), type_description);
+
+ types.add(typeof(PieAction).name());
+ type_description = PieAction.register();
+ descriptions.set(typeof(PieAction).name(), type_description);
+
+ types.add(typeof(UriAction).name());
+ type_description = UriAction.register();
+ descriptions.set(typeof(UriAction).name(), type_description);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new Action from the given type name.
+ /////////////////////////////////////////////////////////////////////
+
+ public static Action? create_action(string type_id, string name, string icon, string command, bool quickaction) {
+ switch (type_id) {
+ case "app": return new AppAction(name, icon, command, quickaction);
+ case "key": return new KeyAction(name, icon, command, quickaction);
+ case "uri": return new UriAction(name, icon, command, quickaction);
+ case "pie": return new PieAction(command, quickaction);
+ }
+
+ return null;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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";
+ final_name = get_domain_name(uri);
+ break;
+
+ case "ftp": case "sftp":
+ final_icon = "folder-remote";
+ final_name = get_domain_name(uri);
+ 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 icon = info.get_icon();
+ final_icon = Icon.get_icon_name(icon);
+
+ } catch (GLib.Error e) {
+ warning(e.message);
+ }
+
+ break;
+ }
+
+ if (!Gtk.IconTheme.get_default().has_icon(final_icon))
+ final_icon = "stock_unknown";
+
+ 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) {
+ // get icon
+ var icon = info.get_icon();
+
+ return new AppAction(info.get_display_name(), Icon.get_icon_name(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) {
+ // check whether its a desktop file to open one of Gnome-Pie's pies
+ if (file_name.has_prefix(Paths.launchers)) {
+ string id = file_name.substring((long)file_name.length - 11, 3);
+ return new PieAction(id);
+ }
+
+ 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns for example www.google.com when http://www.google.de/?q=h
+ /// is given.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string get_domain_name(string url) {
+ int domain_end = url.index_of_char('/', 7);
+ int domain_begin = url.index_of_char('/', 0) + 2;
+
+ if (domain_begin < domain_end) return url.substring(domain_begin, domain_end-domain_begin);
+
+ return url;
+ }
+}
+
+}
diff --git a/src/actions/appAction.vala b/src/actions/appAction.vala
new file mode 100644
index 0000000..e1ca3a2
--- /dev/null
+++ b/src/actions/appAction.vala
@@ -0,0 +1,76 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 ActionRegistry.TypeDescription register() {
+ var description = new ActionRegistry.TypeDescription();
+ description.name = _("Launch application");
+ description.icon = "application-x-executable";
+ description.description = _("Executes the given command.");
+ description.icon_name_editable = true;
+ description.id = "app";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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_quickaction = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quickaction : is_quickaction);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Launches the desired command.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate(uint32 time_stamp) {
+ 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..cbe8b6e
--- /dev/null
+++ b/src/actions/keyAction.vala
@@ -0,0 +1,81 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 ActionRegistry.TypeDescription register() {
+ var description = new ActionRegistry.TypeDescription();
+ description.name = _("Press hotkey");
+ description.icon = "preferences-desktop-keyboard-shortcuts";
+ description.description = _("Simulates the activation of a hotkey.");
+ description.icon_name_editable = true;
+ description.id = "key";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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_quickaction = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quickaction : is_quickaction);
+ }
+
+ construct {
+ this.key = new Key.from_string(real_command);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Presses the desired key.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate(uint32 time_stamp) {
+ key.press();
+ }
+}
+
+}
diff --git a/src/actions/pieAction.vala b/src/actions/pieAction.vala
new file mode 100644
index 0000000..931c9d3
--- /dev/null
+++ b/src/actions/pieAction.vala
@@ -0,0 +1,103 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 ActionRegistry.TypeDescription register() {
+ var description = new ActionRegistry.TypeDescription();
+ description.name = _("Open Pie");
+ description.icon = "gnome-pie";
+ description.description = _("Opens another Pie of Gnome-Pie. You may create sub menus this way.");
+ description.icon_name_editable = false;
+ description.id = "pie";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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) {
+ owned_name = "↪" + referee.name;
+ return owned_name;
+ }
+ return "";
+ }
+ protected set {}
+ }
+
+ private string owned_name;
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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_quickaction = false) {
+ GLib.Object(name : "", icon : "", real_command : id, is_quickaction : is_quickaction);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the desired Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate(uint32 time_stamp) {
+ PieManager.open_pie(real_command);
+ }
+}
+
+}
diff --git a/src/actions/sigAction.vala b/src/actions/sigAction.vala
new file mode 100644
index 0000000..bf9374d
--- /dev/null
+++ b/src/actions/sigAction.vala
@@ -0,0 +1,63 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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(uint32 time_stamp);
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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_quickaction = false) {
+ GLib.Object(name : name, icon : icon, real_command : command, is_quickaction : is_quickaction);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Emits the signal on activation.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate(uint32 time_stamp) {
+ this.activated(time_stamp);
+ }
+}
+
+}
diff --git a/src/actions/uriAction.vala b/src/actions/uriAction.vala
new file mode 100644
index 0000000..d0a41b8
--- /dev/null
+++ b/src/actions/uriAction.vala
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 ActionRegistry.TypeDescription register() {
+ var description = new ActionRegistry.TypeDescription();
+ description.name = _("Open URI");
+ description.icon = "web-browser";
+ description.description = _("Opens a given location. You may use URL's or files paths.");
+ description.icon_name_editable = true;
+ description.id = "uri";
+ return description;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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_quickaction = false) {
+ GLib.Object(name : name, icon : icon,
+ real_command : command.has_prefix("www") ? "http://" + command : command,
+ is_quickaction : is_quickaction);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the default application for the URI.
+ /////////////////////////////////////////////////////////////////////
+
+ public override void activate(uint32 time_stamp) {
+ try{
+ GLib.AppInfo.launch_default_for_uri(real_command, null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+}
+
+}
diff --git a/src/daemon.vala b/src/daemon.vala
new file mode 100644
index 0000000..c5912b3
--- /dev/null
+++ b/src/daemon.vala
@@ -0,0 +1,262 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 Daemon : GLib.Application {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The current version of Gnome-Pie
+ /////////////////////////////////////////////////////////////////////
+
+ public static string version;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Varaibles set by the commend line parser.
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool disable_header_bar = false;
+ public static bool disable_stack_switcher = false;
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// true if init_pies() has been called already
+ /////////////////////////////////////////////////////////////////////
+ private bool initialized = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The beginning of everything.
+ /////////////////////////////////////////////////////////////////////
+
+ public static int main(string[] args) {
+ version = "0.6.8";
+
+ // disable overlay scrollbar --- hacky workaround for black /
+ // transparent background
+ GLib.Environment.set_variable("LIBOVERLAY_SCROLLBAR", "0", true);
+
+ Logger.init();
+ Gtk.init(ref args);
+ Paths.init();
+
+ // create the Daemon and run it
+ var deamon = new GnomePie.Daemon();
+ 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;
+ private static bool print_ids = false;
+
+ private static bool handled_local_args = 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" },
+ { "no-header-bar", 'b', 0, GLib.OptionArg.NONE,
+ out disable_header_bar,
+ "Disables the usage of GTK.HeaderBar" },
+ { "no-stack-switcher", 's', 0, GLib.OptionArg.NONE,
+ out disable_stack_switcher,
+ "Disables the usage of GTK.StackSwitcher" },
+ { "print-ids", 'p', 0, GLib.OptionArg.NONE,
+ out print_ids,
+ "Prints all Pie names with their according IDs" },
+ { null }
+ };
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor of the Daemon. It checks whether it's the first running
+ /// instance of Gnome-Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public Daemon() {
+ Object(application_id: "org.gnome.gnomepie",
+ flags: GLib.ApplicationFlags.HANDLES_COMMAND_LINE);
+
+ // init locale support
+ Intl.bindtextdomain("gnomepie", Paths.locales);
+ Intl.textdomain("gnomepie");
+
+ // connect SigHandlers
+ Posix.signal(Posix.SIGINT, sig_handler);
+ Posix.signal(Posix.SIGTERM, sig_handler);
+
+ this.startup.connect(()=>{
+
+ message("Welcome to Gnome-Pie " + version + "!");
+
+ this.init_pies();
+
+ // launch the indicator
+ this.indicator = new Indicator();
+
+ if (open_pie != null && open_pie != "") {
+ PieManager.open_pie(open_pie);
+ open_pie = "";
+ }
+
+ // finished loading... so run the prog!
+ message("Started happily...");
+ hold();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Call handle_command_line on program launch.
+ /////////////////////////////////////////////////////////////////////
+
+ protected override bool local_command_line(
+ ref unowned string[] args, out int exit_status) {
+
+ exit_status = 0;
+
+ // copy command line
+ string*[] _args = new string[args.length];
+ for (int i = 0; i < args.length; i++) {
+ _args[i] = args[i];
+ }
+ return handle_command_line(_args, false);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Call handle_command_line when a remote instance was launched.
+ /////////////////////////////////////////////////////////////////////
+
+ protected override int command_line(GLib.ApplicationCommandLine cmd) {
+ if (handled_local_args) {
+ string[] tmp = cmd.get_arguments();
+ unowned string[] remote_args = tmp;
+ handle_command_line(remote_args, true);
+ }
+ handled_local_args = true;
+ return 0;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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));
+ GLib.Application.get_default().release();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Print a nifty message when the prog is killed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void init_pies() {
+ if (!this.initialized) {
+
+ // init static stuff
+ ActionRegistry.init();
+ GroupRegistry.init();
+
+ // load all pies
+ PieManager.init();
+
+ // initialize icon cache
+ Icon.init();
+
+ this.initialized = true;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Handles command line parameters.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool handle_command_line(string[] args, bool called_from_remote) {
+
+ var context = new GLib.OptionContext(" - Launches the pie menu for linux.");
+ 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);
+ message("Run '%s' to launch Gnome-Pie or run '%s --help' to" +
+ " see a full list of available command line options.\n",
+ args[0], args[0]);
+ }
+
+ if (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);
+ }
+
+ // do not notify the already running instance (if any)
+ return true;
+ }
+
+ if (print_ids) {
+ this.init_pies();
+ PieManager.print_ids();
+ print_ids = false;
+
+ // do not notify the already running instance (if any)
+ return true;
+ }
+
+
+ if (called_from_remote) {
+ if (open_pie != null && open_pie != "") {
+ PieManager.open_pie(open_pie);
+ open_pie = "";
+ } else {
+ this.indicator.show_preferences();
+ }
+ }
+
+ // notify the already running instance (if any)
+ return false;
+ }
+}
+
+}
diff --git a/src/gui/aboutWindow.vala b/src/gui/aboutWindow.vala
new file mode 100644
index 0000000..73fb1be
--- /dev/null
+++ b/src/gui/aboutWindow.vala
@@ -0,0 +1,84 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A simple about dialog.
+/////////////////////////////////////////////////////////////////////////
+
+public class AboutWindow: Gtk.AboutDialog {
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new about dialog. The entries are sorted alpha-
+ /// betically.
+ /////////////////////////////////////////////////////////////////////
+
+ public AboutWindow () {
+ string[] devs = {
+ "Simon Schneegans <code@simonschneegans.de>",
+ "Gabriel Dubatti <gdubatti@gmail.com>",
+ "Francesco Piccinno <stack.box@gmail.com>",
+ "György Balló <ballogyor@gmail.com>"
+ };
+ string[] artists = {
+ "Simon Schneegans <code@simonschneegans.de>"
+ };
+ string[] translators = {
+ "Simon Schneegans <code@simonschneegans.de> (DE, EN)",
+ "Riccardo Traverso <gr3yfox.fw@gmail.com> (IT)",
+ "Magnun Leno <magnun@codecommunity.org> (PT-BR)",
+ "Kim Boram <Boramism@gmail.com> (KO)",
+ "Eduardo Anabalon <lalo1412@gmail.com> (ES)",
+ "Moo <hazap@hotmail.com> (LT)",
+ "Gabriel Dubatti <gdubatti@gmail.com> (ES)",
+ "Grégoire Bellon-Gervais <greggbg@gmail.com> (FR)",
+ "Raphaël Rochet <raphael@rri.fr> (FR)",
+ "Alex Maxime <cad.maxime@gmail.com> (FR)",
+ "Eugene Roskin <pams@imail.ru> (RU)",
+ "Ting Zhou <tzhou@haverford.edu> (ZH-CN)",
+ "Martin Dinov <martindinov@yahoo.com> (BG)"
+ };
+
+ // sort translators
+ GLib.List<string> translator_list = new GLib.List<string>();
+ foreach (var translator in translators)
+ translator_list.append(translator);
+
+ translator_list.sort((a, b) => {
+ return a.ascii_casecmp(b);
+ });
+
+ string translator_string = "";
+ foreach (var translator in translator_list)
+ translator_string += translator + "\n";
+
+ GLib.Object (
+ artists : artists,
+ authors : devs,
+ translator_credits : translator_string,
+ copyright : "Copyright (C) 2011-2015 Simon Schneegans <code@simonschneegans.de>",
+ program_name: "Gnome-Pie",
+ logo_icon_name: "gnome-pie",
+ website: "http://simmesimme.github.io/gnome-pie.html",
+ website_label: "Homepage",
+ version: Daemon.version
+ );
+ }
+}
+
+}
diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala
new file mode 100644
index 0000000..ce610ea
--- /dev/null
+++ b/src/gui/iconSelectWindow.vala
@@ -0,0 +1,450 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A window which allows selection of an Icon of the user's current icon
+/// theme. Custom icons/images can be selested as well. Loading of icons
+/// happens in an extra thread and a spinner is displayed while loading.
+/////////////////////////////////////////////////////////////////////////
+
+public class IconSelectWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new icon.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_ok(string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the currently selected icon.
+ /////////////////////////////////////////////////////////////////////
+
+ private string active_icon = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ListStore storing all theme-icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Gtk.ListStore icon_list = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the icon theme is currently reloaded.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool loading = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// If set to true, the icon list will be reloaded next time the
+ /// window opens.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool need_reload = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Icons of these contexts won't appear in the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private const string disabled_contexts = "Animations, FileSystems";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The list of icons, filtered according to the chosen type and
+ /// filter string.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeModelFilter icon_list_filtered = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Gtk widget displaying the icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.IconView icon_view = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// This spinner is displayed when the icons are loaded.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Spinner spinner = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A Gtk widget used for custom icon/image selection.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.FileChooserWidget file_chooser = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The notebook containing the different icon choice possibilities:
+ /// from the theme or custom.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Notebook tabs = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The main window.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Window window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A little structure containing data for one icon in the icon_view.
+ /////////////////////////////////////////////////////////////////////
+
+ private class ListEntry {
+ public string name;
+ public IconContext context;
+ public Gdk.Pixbuf pixbuf;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This queue is used for icon loading. A loading thread pushes
+ /// icons into it --- the main thread updates the icon_view
+ /// accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.AsyncQueue<ListEntry?> load_queue;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Possible icon types.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum IconContext {
+ ALL,
+ APPS,
+ ACTIONS,
+ PLACES,
+ FILES,
+ EMOTES,
+ OTHER
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new IconSelectWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public IconSelectWindow(Gtk.Window parent) {
+ try {
+ this.load_queue = new GLib.AsyncQueue<ListEntry?>();
+
+ if (IconSelectWindow.icon_list == null) {
+ IconSelectWindow.icon_list = new Gtk.ListStore(3, typeof(string), // icon name
+ typeof(IconContext), // icon type
+ typeof(Gdk.Pixbuf)); // the icon itself
+
+ // disable sorting until all icons are loaded
+ // else loading becomes horribly slow
+ IconSelectWindow.icon_list.set_default_sort_func(() => {return 0;});
+
+ // reload if icon theme changes
+ Gtk.IconTheme.get_default().changed.connect(() => {
+ if (this.window.visible) load_icons();
+ else IconSelectWindow.need_reload = true;
+ });
+ }
+
+ // make the icon_view filterable
+ this.icon_list_filtered = new Gtk.TreeModelFilter(IconSelectWindow.icon_list, null);
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/icon_select.ui");
+
+ this.window = builder.get_object("window") as Gtk.Window;
+ this.window.set_transient_for(parent);
+ this.window.set_modal(true);
+
+ this.tabs = builder.get_object("tabs") as Gtk.Notebook;
+
+ this.spinner = builder.get_object("spinner") as Gtk.Spinner;
+ this.spinner.start();
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(on_cancel_button_clicked);
+
+ var combo_box = builder.get_object("combo-box") as Gtk.Box;
+
+ // context combo
+ var context_combo = new Gtk.ComboBoxText();
+ context_combo.append_text(_("All icons"));
+ context_combo.append_text(_("Applications"));
+ context_combo.append_text(_("Actions"));
+ context_combo.append_text(_("Places"));
+ context_combo.append_text(_("File types"));
+ context_combo.append_text(_("Emotes"));
+ context_combo.append_text(_("Miscellaneous"));
+
+ context_combo.set_active(0);
+
+ context_combo.changed.connect(() => {
+ this.icon_list_filtered.refilter();
+ });
+
+ combo_box.pack_start(context_combo, false, false);
+
+ // string filter entry
+ var filter = builder.get_object("filter-entry") as Gtk.Entry;
+
+ // only display items which have the selected type
+ // and whose name contains the text entered in the entry
+ this.icon_list_filtered.set_visible_func((model, iter) => {
+ string name = "";
+ IconContext context = IconContext.ALL;
+ model.get(iter, 0, out name);
+ model.get(iter, 1, out context);
+
+ if (name == null) return false;
+
+ return (context_combo.get_active() == context ||
+ context_combo.get_active() == IconContext.ALL) &&
+ name.down().contains(filter.text.down());
+ });
+
+ // clear when the users clicks on the "clear" icon
+ filter.icon_release.connect((pos, event) => {
+ if (pos == Gtk.EntryIconPosition.SECONDARY)
+ filter.text = "";
+ });
+
+ // refilter on input
+ filter.notify["text"].connect(() => {
+ this.icon_list_filtered.refilter();
+ });
+
+ // container for the icon_view
+ var scroll = builder.get_object("icon-scrolledwindow") as Gtk.ScrolledWindow;
+
+ // displays the filtered icons
+ this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered);
+ this.icon_view.item_width = 32;
+ this.icon_view.item_padding = 2;
+ this.icon_view.pixbuf_column = 2;
+ this.icon_view.tooltip_column = 0;
+
+ // set active_icon if selection changes
+ this.icon_view.selection_changed.connect(() => {
+ foreach (var path in this.icon_view.get_selected_items()) {
+ Gtk.TreeIter iter;
+ this.icon_list_filtered.get_iter(out iter, path);
+ this.icon_list_filtered.get(iter, 0, out this.active_icon);
+ }
+ });
+
+ // hide this window when the user activates an icon
+ this.icon_view.item_activated.connect((path) => {
+ Gtk.TreeIter iter;
+ this.icon_list_filtered.get_iter(out iter, path);
+ this.icon_list_filtered.get(iter, 0, out this.active_icon);
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ });
+
+ scroll.add(this.icon_view);
+
+ // file chooser widget
+ this.file_chooser = builder.get_object("filechooser") as Gtk.FileChooserWidget;
+ var file_filter = new Gtk.FileFilter();
+ file_filter.add_pixbuf_formats();
+ file_filter.set_filter_name(_("All supported image formats"));
+ file_chooser.add_filter(file_filter);
+
+ // set active_icon if the user selected a file
+ file_chooser.selection_changed.connect(() => {
+ if (file_chooser.get_filename() != null &&
+ GLib.FileUtils.test(file_chooser.get_filename(),
+ GLib.FileTest.IS_REGULAR))
+
+ this.active_icon = file_chooser.get_filename();
+ });
+
+ // hide this window when the user activates a file
+ file_chooser.file_activated.connect(() => {
+ this.active_icon = file_chooser.get_filename();
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ });
+
+ this.window.set_focus(this.icon_view);
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays the window. The icons are reloaded if neccessary.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.window.show_all();
+ this.spinner.hide();
+
+ if (IconSelectWindow.need_reload) {
+ IconSelectWindow.need_reload = false;
+ this.load_icons();
+ }
+ }
+
+ public static void clear_icons() {
+ if (IconSelectWindow.icon_list != null) {
+ IconSelectWindow.need_reload = true;
+ IconSelectWindow.icon_list.clear();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the window select the icon of the given Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_icon(string icon) {
+ this.active_icon = icon;
+
+ if (icon.contains("/")) {
+ this.file_chooser.set_filename(icon);
+ this.tabs.set_current_page(1);
+ } else {
+ this.icon_list_filtered.foreach((model, path, iter) => {
+ string name = "";
+ model.get(iter, 0, out name);
+
+ if (name == icon) {
+ this.icon_view.select_path(path);
+ this.icon_view.scroll_to_path(path, true, 0.5f, 0.0f);
+ this.icon_view.set_cursor(path, null, false);
+ }
+ return (name == icon);
+ });
+
+ this.tabs.set_current_page(0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicks the ok button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicks the cancel button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// (Re)load all icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_icons() {
+ // only if it's not loading currently
+ if (!IconSelectWindow.loading) {
+ IconSelectWindow.loading = true;
+ IconSelectWindow.icon_list.clear();
+
+ // display the spinner
+ if (spinner != null)
+ this.spinner.visible = true;
+
+ // disable sorting of the icon_view - else it's horribly slow
+ IconSelectWindow.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING);
+
+ this.load_all.begin();
+
+ // insert loaded icons every 200 ms
+ Timeout.add(200, () => {
+ while (this.load_queue.length() > 0) {
+ var new_entry = this.load_queue.pop();
+ Gtk.TreeIter current;
+ IconSelectWindow.icon_list.append(out current);
+ IconSelectWindow.icon_list.set(current, 0, new_entry.name,
+ 1, new_entry.context,
+ 2, new_entry.pixbuf);
+ }
+
+ // enable sorting of the icon_view if loading finished
+ if (!IconSelectWindow.loading) {
+ IconSelectWindow.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING);
+ }
+
+ return loading;
+ });
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all icons of an icon theme and pushes them into the
+ /// load_queue.
+ /////////////////////////////////////////////////////////////////////
+
+ private async void load_all() {
+ var icon_theme = Gtk.IconTheme.get_default();
+
+ foreach (var context in icon_theme.list_contexts()) {
+ if (!disabled_contexts.contains(context)) {
+ foreach (var icon in icon_theme.list_icons(context)) {
+
+ IconContext icon_context = IconContext.OTHER;
+ switch(context) {
+ case "Apps": case "Applications":
+ icon_context = IconContext.APPS; break;
+ case "Emotes":
+ icon_context = IconContext.EMOTES; break;
+ case "Places": case "Devices":
+ icon_context = IconContext.PLACES; break;
+ case "Mimetypes":
+ icon_context = IconContext.FILES; break;
+ case "Actions":
+ icon_context = IconContext.ACTIONS; break;
+ default: break;
+ }
+
+ Idle.add(load_all.callback);
+ yield;
+
+ try {
+ // create a new entry for the queue
+ var new_entry = new ListEntry();
+ new_entry.name = icon;
+ new_entry.context = icon_context;
+ new_entry.pixbuf = icon_theme.load_icon(icon, 32, 0);
+
+ // some icons have only weird sizes... do not include them
+ if (new_entry.pixbuf.width == 32)
+ this.load_queue.push(new_entry);
+
+ } catch (GLib.Error e) {
+ warning("Failed to load image " + icon);
+ }
+ }
+ }
+ }
+
+ // finished loading
+ IconSelectWindow.loading = false;
+
+ // hide the spinner
+ if (spinner != null)
+ spinner.visible = false;
+ }
+}
+
+}
diff --git a/src/gui/indicator.vala b/src/gui/indicator.vala
new file mode 100644
index 0000000..ddb85e6
--- /dev/null
+++ b/src/gui/indicator.vala
@@ -0,0 +1,180 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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 PreferencesWindow 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() {
+ string icon = "gnome-pie-symbolic";
+ var screen = (Gdk.X11.Screen)Gdk.Screen.get_default();
+ bool gnome_shell = false;
+
+ if (screen.get_window_manager_name() == "GNOME Shell") {
+ icon = "gnome-pie";
+ gnome_shell = true;
+ }
+
+ #if HAVE_APPINDICATOR
+
+ string path = "";
+ try {
+ path = GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources";
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+
+ if (gnome_shell) {
+
+ if (GLib.File.new_for_path(path).query_exists()) {
+ this.indicator = new AppIndicator.Indicator("Gnome-Pie", path + "/" + icon + ".svg",
+ AppIndicator.IndicatorCategory.APPLICATION_STATUS);
+ } else {
+ this.indicator = new AppIndicator.Indicator("Gnome-Pie", icon,
+ AppIndicator.IndicatorCategory.APPLICATION_STATUS);
+ }
+ } else {
+ 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",
+ icon + ".svg"
+ ));
+
+ if (!file.query_exists())
+ this.indicator.set_from_icon_name(icon);
+ 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(icon);
+ }
+
+ this.menu = new Gtk.Menu();
+ var menu = this.menu;
+ #endif
+
+ this.prefs = new PreferencesWindow();
+
+ // preferences item
+ var item = new Gtk.ImageMenuItem.with_mnemonic(_("_Preferences"));
+ item.activate.connect(() => {
+ this.prefs.show();
+ });
+
+ item.show();
+ menu.append(item);
+
+ // about item
+ item = new Gtk.ImageMenuItem.with_mnemonic(_("_About"));
+ item.show();
+ item.activate.connect(() => {
+ var about = new AboutWindow();
+ about.run();
+ about.destroy();
+ });
+ menu.append(item);
+
+ // separator
+ var sepa = new Gtk.SeparatorMenuItem();
+ sepa.show();
+ menu.append(sepa);
+
+ // quit item
+ item = new Gtk.ImageMenuItem.with_mnemonic(_("_Quit"));
+ item.activate.connect(()=>{
+ GLib.Application.get_default().release();
+ });
+ 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/newSliceWindow.vala b/src/gui/newSliceWindow.vala
new file mode 100644
index 0000000..89294b5
--- /dev/null
+++ b/src/gui/newSliceWindow.vala
@@ -0,0 +1,433 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A window which allows selection of a new Slice which is about to be
+/// added to a Pie. It can be also used to edit an existing Slice
+/////////////////////////////////////////////////////////////////////////
+
+public class NewSliceWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user confirms his selection.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(ActionGroup action, bool as_new_slice, int at_position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The contained list of slice types. It contains both: Groups and
+ /// single actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private SliceTypeList slice_type_list = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The IconSelectWindow used for icon selection for a Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private IconSelectWindow? icon_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some widgets of this window. Loaded by a ui-builder and stored
+ /// for later access.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Dialog window = null;
+ private Gtk.Box name_box = null;
+ private Gtk.Box command_box = null;
+ private Gtk.Button icon_button = null;
+ private Gtk.Box no_options_box = null;
+ private Gtk.Box pie_box = null;
+ private Gtk.Box hotkey_box = null;
+ private Gtk.Box uri_box = null;
+ private Gtk.Box quickaction_box = null;
+ private Gtk.Box clipboard_box = null;
+ private Gtk.Box workspace_only_box = null;
+ private Gtk.Image icon = null;
+ private Gtk.Entry name_entry = null;
+ private Gtk.Entry command_entry = null;
+ private Gtk.Entry uri_entry = null;
+ private Gtk.Switch quickaction_checkbutton = null;
+ private Gtk.Switch workspace_only_checkbutton = null;
+ private Gtk.Scale clipboard_slider = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two custom widgets. For Pie and hotkey selection respectively.
+ /////////////////////////////////////////////////////////////////////
+
+ private PieComboList pie_select = null;
+ private TriggerSelectButton key_select = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// These members store information on the currently selected Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_type = "";
+ private string current_icon = "";
+ private string current_id = "";
+ private string current_custom_icon = "";
+ private string current_hotkey = "";
+ private string current_pie_to_open = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position of the edited Slice in its parent Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private int slice_position = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the Slice i going to be added as a new Slice. Else it
+ /// will edit the Slice at slice_position in its parent Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool add_as_new_slice = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor creates a new window.
+ /////////////////////////////////////////////////////////////////////
+
+ public NewSliceWindow() {
+ try {
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/slice_select.ui");
+
+ this.slice_type_list = new SliceTypeList();
+ this.slice_type_list.on_select.connect((type, icon) => {
+
+ this.name_box.hide();
+ this.command_box.hide();
+ this.icon_button.sensitive = false;
+ this.no_options_box.hide();
+ this.pie_box.hide();
+ this.hotkey_box.hide();
+ this.uri_box.hide();
+ this.quickaction_box.hide();
+ this.workspace_only_box.hide();
+ this.clipboard_box.hide();
+
+ this.current_type = type;
+
+ switch (type) {
+ case "bookmarks": case "devices":
+ case "menu": case "session":
+ this.no_options_box.show();
+ this.set_icon(icon);
+ break;
+ case "window_list":
+ this.workspace_only_box.show();
+ this.set_icon(icon);
+ break;
+ case "clipboard":
+ this.clipboard_box.show();
+ this.set_icon(icon);
+ break;
+ case "app":
+ this.name_box.show();
+ this.command_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ case "key":
+ this.name_box.show();
+ this.hotkey_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ case "pie":
+ this.pie_box.show();
+ this.quickaction_box.show();
+ this.set_icon(PieManager.all_pies[this.pie_select.current_id].icon);
+ break;
+ case "uri":
+ this.name_box.show();
+ this.uri_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ }
+ });
+
+ this.name_box = builder.get_object("name-box") as Gtk.Box;
+ this.command_box = builder.get_object("command-box") as Gtk.Box;
+ this.icon_button = builder.get_object("icon-button") as Gtk.Button;
+ this.no_options_box = builder.get_object("no-options-box") as Gtk.Box;
+ this.pie_box = builder.get_object("pie-box") as Gtk.Box;
+ this.pie_select = new PieComboList();
+ this.pie_select.on_select.connect((id) => {
+ this.current_pie_to_open = id;
+ this.set_icon(PieManager.all_pies[id].icon);
+ });
+
+ this.pie_box.pack_start(this.pie_select, true, true);
+
+ this.hotkey_box = builder.get_object("hotkey-box") as Gtk.Box;
+ this.key_select = new TriggerSelectButton(false);
+ this.hotkey_box.pack_start(this.key_select, false, true);
+ this.key_select.on_select.connect((trigger) => {
+ this.current_hotkey = trigger.name;
+ });
+
+ this.uri_box = builder.get_object("uri-box") as Gtk.Box;
+
+ this.name_entry = builder.get_object("name-entry") as Gtk.Entry;
+ this.uri_entry = builder.get_object("uri-entry") as Gtk.Entry;
+ this.command_entry = builder.get_object("command-entry") as Gtk.Entry;
+ this.quickaction_checkbutton = builder.get_object("quick-action-checkbutton") as Gtk.Switch;
+ this.quickaction_box = builder.get_object("quickaction-box") as Gtk.Box;
+ this.icon = builder.get_object("icon") as Gtk.Image;
+
+ this.workspace_only_checkbutton = builder.get_object("workspace-only-checkbutton") as Gtk.Switch;
+ this.workspace_only_box = builder.get_object("workspace-only-box") as Gtk.Box;
+
+ this.clipboard_box = builder.get_object("clipboard-box") as Gtk.Box;
+ this.clipboard_slider = (builder.get_object("clipboard-scale") as Gtk.Scale);
+ clipboard_slider.set_range(2, 24);
+ clipboard_slider.set_value(8);
+
+ this.icon_button.clicked.connect(on_icon_button_clicked);
+
+ var scroll_area = builder.get_object("slice-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.slice_type_list);
+
+ this.window = builder.get_object("window") as Gtk.Dialog;
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(on_cancel_button_clicked);
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the parent window, in order to make this window stay in
+ /// front.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_parent(Gtk.Window parent) {
+ this.window.set_transient_for(parent);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sows the window on the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.slice_type_list.select_first();
+ this.pie_select.select_first();
+ this.key_select.set_trigger(new Trigger());
+ this.window.show_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload() {
+ this.pie_select.reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes all widgets display stuff according to the given action.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_action(ActionGroup group, int position) {
+ this.set_default(group.parent_id, position);
+
+ this.add_as_new_slice = false;
+ string type = "";
+
+ if (group.get_type().depth() == 2) {
+ var action = group.actions[0];
+ type = ActionRegistry.descriptions[action.get_type().name()].id;
+ this.select_type(type);
+
+ this.set_icon(action.icon);
+ this.quickaction_checkbutton.active = action.is_quickaction;
+ this.name_entry.text = action.name;
+
+ switch (type) {
+ case "app":
+ this.current_custom_icon = action.icon;
+ this.command_entry.text = action.real_command;
+ break;
+ case "key":
+ this.current_custom_icon = action.icon;
+ this.current_hotkey = action.real_command;
+ this.key_select.set_trigger(new Trigger.from_string(action.real_command));
+ break;
+ case "pie":
+ this.pie_select.select(action.real_command);
+ break;
+ case "uri":
+ this.current_custom_icon = action.icon;
+ this.uri_entry.text = action.real_command;
+ break;
+ }
+
+ } else {
+ type = GroupRegistry.descriptions[group.get_type().name()].id;
+ switch (type) {
+ case "clipboard":
+ this.clipboard_slider.set_value((group as ClipboardGroup).max_items);
+ break;
+ case "window_list":
+ this.workspace_only_checkbutton.active = (group as WindowListGroup).current_workspace_only;
+ break;
+
+ }
+ this.select_type(type);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects a default action.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_default(string pie_id, int position) {
+ this.slice_position = position;
+ this.add_as_new_slice = true;
+ this.current_custom_icon = "";
+ this.select_type("app");
+ this.current_id = pie_id;
+ this.key_select.set_trigger(new Trigger());
+ this.pie_select.select_first();
+ this.name_entry.text = _("Rename me!");
+ this.command_entry.text = "";
+ this.uri_entry.text = "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects a specific action type.
+ /////////////////////////////////////////////////////////////////////
+
+ private void select_type(string type) {
+ this.current_type = type;
+ this.slice_type_list.select(type);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called, when the user presses the ok button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ this.window.hide();
+
+ ActionGroup group = null;
+
+ switch (this.current_type) {
+ case "bookmarks": group = new BookmarkGroup(this.current_id); break;
+ case "devices": group = new DevicesGroup(this.current_id); break;
+ case "menu": group = new MenuGroup(this.current_id); break;
+ case "session": group = new SessionGroup(this.current_id); break;
+ case "clipboard":
+ var g = new ClipboardGroup(this.current_id);
+ g.max_items = (int)this.clipboard_slider.get_value();
+ group = g;
+ break;
+ case "window_list":
+ var g = new WindowListGroup(this.current_id);
+ g.current_workspace_only = this.workspace_only_checkbutton.active;
+ group = g;
+ break;
+ case "app":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new AppAction(this.name_entry.text, this.current_icon,
+ this.command_entry.text,
+ this.quickaction_checkbutton.active));
+ break;
+ case "key":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new KeyAction(this.name_entry.text, this.current_icon,
+ this.current_hotkey,
+ this.quickaction_checkbutton.active));
+ break;
+ case "pie":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new PieAction(this.current_pie_to_open,
+ this.quickaction_checkbutton.active));
+ break;
+ case "uri":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new UriAction(this.name_entry.text, this.current_icon,
+ this.uri_entry.text,
+ this.quickaction_checkbutton.active));
+ break;
+ }
+
+ this.on_select(group, this.add_as_new_slice, this.slice_position);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses the cancel button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses the icon select button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_icon_button_clicked(Gtk.Button button) {
+ if (this.icon_window == null) {
+ this.icon_window = new IconSelectWindow(this.window);
+ this.icon_window.on_ok.connect((icon) => {
+ this.current_custom_icon = icon;
+ this.set_icon(icon);
+ });
+ }
+
+ this.icon_window.show();
+ this.icon_window.set_icon(this.current_icon);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which sets the icon of the icon select button.
+ /// It assures that both can be displayed: A customly chosen image
+ /// from or an icon from the current theme.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_icon(string icon) {
+ if (icon.contains("/"))
+ try {
+ this.icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale(icon, this.icon.get_pixel_size(),
+ this.icon.get_pixel_size(), true);
+ } catch (GLib.Error error) {
+ warning(error.message);
+ }
+ else
+ this.icon.icon_name = icon;
+
+ this.current_icon = icon;
+ }
+}
+
+}
diff --git a/src/gui/newsWindow.vala b/src/gui/newsWindow.vala
new file mode 100644
index 0000000..cc1a77d
--- /dev/null
+++ b/src/gui/newsWindow.vala
@@ -0,0 +1,73 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////
+
+public class NewsWindow: Gtk.Dialog {
+
+ public static const int news_count = 2;
+
+ /////////////////////////////////////////////////////////////////////
+ ///
+ /////////////////////////////////////////////////////////////////////
+
+ public NewsWindow () {
+ this.title = "Gnome-Pie";
+
+ this.set_border_width(5);
+
+ var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
+
+ var image = new Gtk.Image.from_icon_name("gnome-pie", Gtk.IconSize.DIALOG);
+ box.pack_start(image);
+
+ var news = new Gtk.Label("");
+ news.wrap = true;
+ news.set_width_chars(75);
+ news.set_markup("<b>Thank you!</b>\n\n");
+
+ box.pack_start(news, false, false);
+
+ var check = new Gtk.CheckButton.with_label("Don't show this window again.");
+ check.toggled.connect((check_box) => {
+ var checky = check_box as Gtk.CheckButton;
+
+ if (checky.active) Config.global.showed_news = news_count;
+ else Config.global.showed_news = news_count-1;
+
+ Config.global.save();
+ });
+
+ box.pack_end(check);
+
+ (this.get_content_area() as Gtk.VBox).pack_start(box);
+ this.get_content_area().show_all();
+
+ this.add_button(_("_Close"), 0);
+
+ this.response.connect((id) => {
+ if (id == 0)
+ this.hide();
+ });
+ }
+}
+
+}
diff --git a/src/gui/pieComboList.vala b/src/gui/pieComboList.vala
new file mode 100644
index 0000000..f0fd22f
--- /dev/null
+++ b/src/gui/pieComboList.vala
@@ -0,0 +1,155 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A drop-down list, containing one entry for each existing Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class PieComboList : Gtk.ComboBox {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently selected row.
+ /////////////////////////////////////////////////////////////////////
+
+ public string current_id { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the data internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieComboList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(3, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(1, Gtk.SortType.ASCENDING);
+
+ base.set_model(this.data);
+
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ this.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ this.pack_start(name_render, true);
+
+ this.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ this.add_attribute(name_render, "text", DataPos.NAME);
+
+ this.changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_active_iter(out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ this.current_id = id;
+ }
+ });
+
+ reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all existing Pies to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload() {
+ Gtk.TreeIter active;
+ string id = "";
+ if (this.get_active_iter(out active))
+ this.data.get(active, DataPos.ID, out id);
+
+ data.clear();
+ foreach (var pie in PieManager.all_pies.entries) {
+ this.load_pie(pie.value);
+ }
+
+ select_first();
+ select(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.set_active_iter(active);
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ this.current_id = id;
+ } else {
+ this.on_select("");
+ this.current_id = "";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.set_active_iter(iter);
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads one given pie to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_pie(Pie pie) {
+ if (pie.id.length == 3) {
+ Gtk.TreeIter last;
+ this.data.append(out last);
+ var icon = new Icon(pie.icon, 24);
+ this.data.set(last, DataPos.ICON, icon.to_pixbuf(),
+ DataPos.NAME, pie.name,
+ DataPos.ID, pie.id);
+ }
+ }
+}
+
+}
diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala
new file mode 100644
index 0000000..77f833b
--- /dev/null
+++ b/src/gui/pieList.vala
@@ -0,0 +1,275 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A list, containing one entry for each existing Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class PieList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id);
+ public signal void on_activate();
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the data internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, ICON_NAME, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores where a drag startet.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeIter? drag_start = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Rembers the time when a last drag move event was reported. Used
+ /// to avoid frequent changes of selected Pie when a Pie is dragged
+ /// over this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private uint last_hover = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(4, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(DataPos.NAME, Gtk.SortType.ASCENDING);
+
+ this.set_model(this.data);
+ this.set_headers_visible(true);
+ this.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.width_request = 170;
+ this.set_enable_search(false);
+
+ this.set_events(Gdk.EventMask.POINTER_MOTION_MASK);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.title = _("Pies");
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ icon_render.ypad = 4;
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ name_render.ellipsize = Pango.EllipsizeMode.END;
+ name_render.ellipsize_set = true;
+ main_column.pack_start(name_render, true);
+
+ base.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ // setup drag'n'drop
+ Gtk.TargetEntry uri_source = {"text/uri-list", 0, 0};
+ Gtk.TargetEntry[] entries = { uri_source };
+ this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK);
+ this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK);
+ this.drag_data_get.connect(this.on_dnd_source);
+ this.drag_data_received.connect(this.on_dnd_received);
+ this.drag_begin.connect_after(this.on_start_drag);
+ this.drag_motion.connect(this.on_drag_move);
+ this.drag_leave.connect(() => {
+ this.last_hover = 0;
+ });
+
+ this.row_activated.connect(() => {
+ this.on_activate();
+ });
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ }
+ });
+
+ reload_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all existing Pies to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload_all() {
+ Gtk.TreeIter active;
+ string id = "";
+ if (this.get_selection().get_selected(null, out active))
+ this.data.get(active, DataPos.ID, out id);
+
+ data.clear();
+ foreach (var pie in PieManager.all_pies.entries) {
+ this.load_pie(pie.value);
+ }
+
+ select(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.get_selection().select_iter(active);
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ } else {
+ this.on_select("");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.get_selection().select_iter(iter);
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads one given pie to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_pie(Pie pie) {
+ if (pie.id.length == 3) {
+ Gtk.TreeIter last;
+ this.data.append(out last);
+ var icon = new Icon(pie.icon, 24);
+ this.data.set(last, DataPos.ICON, icon.to_pixbuf(),
+ DataPos.ICON_NAME, pie.icon,
+ DataPos.NAME,GLib.Markup.escape_text(pie.name) + "\n" +
+ "<span font-size='x-small'>" + PieManager.get_accelerator_label_of(pie.id) + "</span>",
+ DataPos.ID, pie.id);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a drag which started on this Widget was successfull.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) {
+ if (this.drag_start != null) {
+ string id = "";
+ this.data.get(this.drag_start, DataPos.ID, out id);
+ selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"});
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a drag operation is started on this Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_start_drag(Gdk.DragContext ctx) {
+ if (this.get_selection().get_selected(null, out this.drag_start)) {
+ string icon_name = "";
+ this.data.get(this.drag_start, DataPos.ICON_NAME, out icon_name);
+
+ var icon = new Icon(icon_name, 48);
+ var pixbuf = icon.to_pixbuf();
+ Gtk.drag_set_icon_pixbuf(ctx, pixbuf, icon.size()/2, icon.size()/2);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when something is dragged over this Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_drag_move(Gdk.DragContext context, int x, int y, uint time) {
+
+ Gtk.TreeViewDropPosition position;
+ Gtk.TreePath path;
+
+ if (!this.get_dest_row_at_pos(x, y, out path, out position))
+ return false;
+
+ if (position == Gtk.TreeViewDropPosition.BEFORE)
+ this.set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_BEFORE);
+ else if (position == Gtk.TreeViewDropPosition.AFTER)
+ this.set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_AFTER);
+
+ Gdk.drag_status(context, context.get_suggested_action(), time);
+
+ // avoid too frequent selection...
+ this.last_hover = time;
+
+ GLib.Timeout.add(150, () => {
+ if (this.last_hover == time)
+ this.get_selection().select_path(path);
+ return false;
+ });
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for external drags.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData selection_data, uint info, uint time_) {
+
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+
+ var pie = PieManager.all_pies[id];
+
+ foreach (var uri in selection_data.get_uris()) {
+ pie.add_action(ActionRegistry.new_for_uri(uri), 0);
+ }
+
+ this.on_select(id);
+ }
+ }
+}
+
+}
diff --git a/src/gui/pieOptionsWindow.vala b/src/gui/pieOptionsWindow.vala
new file mode 100644
index 0000000..2f9cadf
--- /dev/null
+++ b/src/gui/pieOptionsWindow.vala
@@ -0,0 +1,315 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This window allows the selection of a hotkey. It is returned in form
+/// of a Trigger. Therefore it can be either a keyboard driven hotkey or
+/// a mouse based hotkey.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieOptionsWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal is emitted when the user selects a new hot key.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_ok(Trigger trigger, string pie_name, string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some private members which are needed by other methods.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Dialog window;
+ private Gtk.CheckButton turbo;
+ private Gtk.CheckButton delayed;
+ private Gtk.CheckButton centered;
+ private Gtk.CheckButton warp;
+ private Gtk.RadioButton rshape[10];
+ private TriggerSelectButton trigger_button;
+ private Gtk.Entry name_entry = null;
+ private Gtk.Button? icon_button = null;
+ private Gtk.Image? icon = null;
+ private Gtk.Label? pie_id = null;
+
+ private IconSelectWindow? icon_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently configured trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The trigger which was active when this window was opened. It is
+ /// stored in order to check whether anything has changed when the
+ /// user clicks on OK.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger original_trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the current icon name of the pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string icon_name = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the id of the current pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Radioboxes call toggled() twice per selection change.
+ /// This flag is used to discard one of the two notifications.
+ /////////////////////////////////////////////////////////////////////
+
+ private static int notify_toggle = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs a new PieOptionsWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieOptionsWindow() {
+ try {
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/pie_options.ui");
+
+ this.window = builder.get_object("window") as Gtk.Dialog;
+ this.trigger_button = new TriggerSelectButton(true);
+ this.trigger_button.show();
+
+ this.trigger_button.on_select.connect((trigger) => {
+ this.trigger = new Trigger.from_values(
+ trigger.key_sym,
+ trigger.modifiers,
+ trigger.with_mouse,
+ this.turbo.active,
+ this.delayed.active,
+ this.centered.active,
+ this.warp.active,
+ this.get_radio_shape()
+ );
+ });
+
+ (builder.get_object("trigger-box") as Gtk.Box).pack_start(this.trigger_button, true, true);
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(this.on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(this.on_cancel_button_clicked);
+
+ this.turbo = builder.get_object("turbo-check") as Gtk.CheckButton;
+ this.turbo.toggled.connect(this.on_check_toggled);
+
+ this.delayed = builder.get_object("delay-check") as Gtk.CheckButton;
+ this.delayed.toggled.connect(this.on_check_toggled);
+
+ this.centered = builder.get_object("center-check") as Gtk.CheckButton;
+ this.centered.toggled.connect(this.on_check_toggled);
+
+ this.warp = builder.get_object("warp-check") as Gtk.CheckButton;
+ this.warp.toggled.connect(this.on_check_toggled);
+
+ for (int i= 0; i < 10; i++) {
+ this.rshape[i] = builder.get_object("rshape%d".printf(i)) as Gtk.RadioButton;
+ this.rshape[i].toggled.connect(this.on_radio_toggled);
+ }
+
+ this.name_entry = builder.get_object("name-entry") as Gtk.Entry;
+ this.name_entry.activate.connect(this.on_ok_button_clicked);
+
+ this.pie_id = builder.get_object("pie-id") as Gtk.Label;
+
+ this.icon = builder.get_object("icon") as Gtk.Image;
+ this.icon_button = builder.get_object("icon-button") as Gtk.Button;
+ this.icon_button.clicked.connect(on_icon_button_clicked);
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the parent window, in order to make this window stay in
+ /// front.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_parent(Gtk.Window parent) {
+ this.window.set_transient_for(parent);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays the window on the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.window.show_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initilizes all members to match the Trigger of the Pie with the
+ /// given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_pie(string id) {
+ var trigger = new Trigger.from_string(PieManager.get_accelerator_of(id));
+ var pie = PieManager.all_pies[id];
+
+ this.id = id;
+
+ this.turbo.active = trigger.turbo;
+ this.delayed.active = trigger.delayed;
+ this.centered.active = trigger.centered;
+ this.warp.active = trigger.warp;
+ this.set_radio_shape( trigger.shape );
+ this.original_trigger = trigger;
+ this.trigger = trigger;
+ this.name_entry.text = PieManager.get_name_of(id);
+ this.pie_id.label = "Pie-ID: " + id;
+ this.trigger_button.set_trigger(trigger);
+ this.set_icon(pie.icon);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when one of the checkboxes is toggled.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_check_toggled() {
+ if (this.trigger != null) {
+ this.trigger = new Trigger.from_values(
+ this.trigger.key_sym, this.trigger.modifiers,
+ this.trigger.with_mouse, this.turbo.active,
+ this.delayed.active, this.centered.active,
+ this.warp.active,
+ this.get_radio_shape()
+ );
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the current selected radio-button shape: 0= automatic
+ /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves
+ /// 1 | 4 | 7
+ /// 2 | 5 | 8
+ /// 3 | 6 | 9
+ /////////////////////////////////////////////////////////////////////
+
+ private int get_radio_shape() {
+ int rs;
+ for (rs= 0; rs < 10; rs++) {
+ if (this.rshape[rs].active) {
+ break;
+ }
+ }
+ return rs;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the current selected radio-button shape: 0= automatic
+ /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_radio_shape(int rs) {
+ if (rs < 0 || rs > 9) {
+ rs= 5; //replace invalid value with default= full pie
+ }
+ this.rshape[rs].active= true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called twice when one of the radioboxes is toggled.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_radio_toggled() {
+ notify_toggle= 1 - notify_toggle;
+ if (notify_toggle == 1) {
+ on_check_toggled(); //just call once
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the icon button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_icon_button_clicked(Gtk.Button button) {
+ if (this.icon_window == null) {
+ this.icon_window = new IconSelectWindow(this.window);
+ this.icon_window.on_ok.connect((icon) => {
+ set_icon(icon);
+ });
+ }
+
+ this.icon_window.show();
+ this.icon_window.set_icon(this.icon_name);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the OK-button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ var assigned_id = PieManager.get_assigned_id(this.trigger);
+
+ if (assigned_id != "" && assigned_id != this.id) {
+ // it's already assigned
+ var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " +
+ "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id));
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.get_toplevel(), Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.CANCEL, error);
+ dialog.run();
+ dialog.destroy();
+ } else {
+ // a unused hot key has been chosen, great!
+ this.on_ok(this.trigger, this.name_entry.text, this.icon_name);
+ this.window.hide();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the icon of the icon_button
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_icon(string name) {
+ this.icon_name = name;
+
+ if (name.contains("/")) {
+ try {
+ this.icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale(name,
+ this.icon.get_pixel_size(), this.icon.get_pixel_size(), true);
+ } catch (GLib.Error error) {
+ warning(error.message);
+ }
+ } else {
+ this.icon.icon_name = name;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the cancel button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+}
+
+}
diff --git a/src/gui/piePreview.vala b/src/gui/piePreview.vala
new file mode 100644
index 0000000..ce1ba96
--- /dev/null
+++ b/src/gui/piePreview.vala
@@ -0,0 +1,387 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A custom widget displaying the preview of a Pie. It can be used to
+/// configure the displayed Pie in various aspects.
+/////////////////////////////////////////////////////////////////////////
+
+class PiePreview : Gtk.DrawingArea {
+
+ /////////////////////////////////////////////////////////////////////
+ /// These get called when the last Slice is removed and when the
+ /// first Slice is added respectively.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_last_slice_removed();
+ public signal void on_first_slice_added();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The internally used renderer to draw the Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewRenderer renderer = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The window which pops up, when a Slice is added or edited.
+ /////////////////////////////////////////////////////////////////////
+
+ private NewSliceWindow? new_slice_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A timer used for calculating the frame time.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.Timer timer;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when it is possible to drag a slice from this widget.
+ /// False, when the user currently hovers over the add sign.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool drag_enabled = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the currently displayed Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position from where a Slice-drag started.
+ /////////////////////////////////////////////////////////////////////
+
+ private int drag_start_index = -1;
+ private string drag_start_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates the widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreview() {
+ this.renderer = new PiePreviewRenderer(this);
+
+ this.draw.connect(this.on_draw);
+ this.timer = new GLib.Timer();
+ this.set_events(Gdk.EventMask.POINTER_MOTION_MASK
+ | Gdk.EventMask.LEAVE_NOTIFY_MASK
+ | Gdk.EventMask.ENTER_NOTIFY_MASK);
+
+ // setup drag and drop
+ this.enable_drag_source();
+
+ Gtk.TargetEntry uri_dest = {"text/uri-list", 0, 0};
+ Gtk.TargetEntry slice_dest = {"text/plain", Gtk.TargetFlags.SAME_WIDGET, 0};
+ Gtk.TargetEntry[] destinations = { uri_dest, slice_dest };
+ Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, destinations, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK);
+
+ this.drag_begin.connect(this.on_start_drag);
+ this.drag_end.connect(this.on_end_drag);
+ this.drag_data_received.connect(this.on_dnd_received);
+
+ // connect mouse events
+ this.drag_motion.connect(this.on_drag_move);
+ this.leave_notify_event.connect(this.on_mouse_leave);
+ this.enter_notify_event.connect(this.on_mouse_enter);
+ this.motion_notify_event.connect_after(this.on_mouse_move);
+ this.button_release_event.connect_after(this.on_button_release);
+ this.button_press_event.connect_after(this.on_button_press);
+
+ this.new_slice_window = new NewSliceWindow();
+ this.new_slice_window.on_select.connect((new_action, as_new_slice, at_position) => {
+ var pie = PieManager.all_pies[this.current_id];
+
+ if (new_action.has_quickaction())
+ renderer.disable_quickactions();
+
+ if (as_new_slice) {
+ pie.add_group(new_action, at_position+1);
+ this.renderer.add_group(new_action, at_position+1);
+
+ if (this.renderer.slice_count() == 1)
+ this.on_first_slice_added();
+ } else {
+ pie.update_group(new_action, at_position);
+ this.renderer.update_group(new_action, at_position);
+ }
+ });
+
+ this.renderer.on_edit_slice.connect((pos) => {
+ this.new_slice_window.reload();
+
+ this.new_slice_window.set_parent(this.get_toplevel() as Gtk.Window);
+ this.new_slice_window.show();
+
+ var pie = PieManager.all_pies[this.current_id];
+ this.new_slice_window.set_action(pie.action_groups[pos], pos);
+ });
+
+ this.renderer.on_add_slice.connect((pos) => {
+ this.new_slice_window.reload();
+
+ this.new_slice_window.set_parent(this.get_toplevel() as Gtk.Window);
+ this.new_slice_window.show();
+
+ this.new_slice_window.set_default(this.current_id, pos);
+ });
+
+ this.renderer.on_remove_slice.connect((pos) => {
+
+ var dialog = new Gtk.MessageDialog(this.get_toplevel() as Gtk.Window, Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("Do you really want to delete this Slice?"));
+
+ dialog.response.connect((response) => {
+ if (response == Gtk.ResponseType.YES) {
+ var pie = PieManager.all_pies[this.current_id];
+
+ pie.remove_group(pos);
+ this.renderer.remove_group(pos);
+
+ if (this.renderer.slice_count() == 0)
+ this.on_last_slice_removed();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the currently displayed Pie to the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_pie(string id) {
+ var style = this.get_style_context();
+
+ this.current_id = id;
+ this.override_background_color(Gtk.StateFlags.NORMAL, style.get_background_color(Gtk.StateFlags.NORMAL));
+ this.renderer.load_pie(PieManager.all_pies[id]);
+
+ if (id == this.drag_start_id) {
+ this.renderer.hide_group(this.drag_start_index);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Begins the draw loop. It automatically ends, when the containing
+ /// window becomes invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw_loop() {
+ this.timer.start();
+ this.queue_draw();
+
+ GLib.Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {
+ this.queue_draw();
+ return this.get_toplevel().visible;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called every frame.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_draw(Cairo.Context ctx) {
+ // store the frame time
+ double frame_time = this.timer.elapsed();
+ this.timer.reset();
+
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+
+ ctx.translate((int)(allocation.width*0.5), (int)(allocation.height*0.5));
+
+ this.renderer.draw(frame_time, ctx);
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_leave(Gdk.EventCrossing event) {
+ this.renderer.on_mouse_leave();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse enters the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_enter(Gdk.EventCrossing event) {
+ this.renderer.on_mouse_enter();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves in the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_mouse_move(Gdk.EventMotion event) {
+ this.renderer.set_dnd_mode(false);
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+ this.renderer.on_mouse_move(event.x-allocation.width*0.5, event.y-allocation.height*0.5);
+
+ if (this.renderer.get_active_slice() < 0) this.disable_drag_source();
+ else this.enable_drag_source();
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_press() {
+ this.renderer.on_button_press();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is released.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_release() {
+ if (!this.renderer.drag_n_drop_mode)
+ this.renderer.on_button_release();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse is moved over this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_drag_move(Gdk.DragContext ctx, int x, int y, uint time) {
+ this.renderer.set_dnd_mode(true);
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+ this.renderer.on_mouse_move(x-allocation.width*0.5, y-allocation.height*0.5);
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user tries to drag something from this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_start_drag(Gdk.DragContext ctx) {
+ this.drag_start_index = this.renderer.get_active_slice();
+ this.drag_start_id = this.current_id;
+ var icon = this.renderer.get_active_icon();
+ var pixbuf = icon.to_pixbuf();
+
+ this.renderer.hide_group(this.drag_start_index);
+ Gtk.drag_set_icon_pixbuf(ctx, pixbuf, icon.size()/2, icon.size()/2);
+
+ this.renderer.set_dnd_mode(true);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for Slice-movement.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_end_drag(Gdk.DragContext context) {
+
+ if (context.list_targets() != null) {
+
+ int target_index = this.renderer.get_active_slice();
+ this.renderer.set_dnd_mode(false);
+
+ context.list_targets().foreach((target) => {
+ Gdk.Atom target_type = (Gdk.Atom)target;
+ if (target_type.name() == "text/plain") {
+ if (this.current_id == this.drag_start_id) {
+ var pie = PieManager.all_pies[this.current_id];
+ pie.move_group(this.drag_start_index, target_index);
+ this.renderer.show_hidden_group_at(target_index);
+ } else {
+ var src_pie = PieManager.all_pies[this.drag_start_id];
+ var dst_pie = PieManager.all_pies[this.current_id];
+ dst_pie.add_group(src_pie.action_groups[this.drag_start_index], target_index);
+ this.renderer.add_group(dst_pie.action_groups[target_index], target_index);
+
+ if (this.renderer.slices.size == 1)
+ this.on_first_slice_added();
+
+ if ((context.get_actions() & Gdk.DragAction.COPY) == 0)
+ src_pie.remove_group(this.drag_start_index);
+ }
+
+
+ }
+ });
+
+ this.drag_start_index = -1;
+ this.drag_start_id = "";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for external drags.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData selection_data, uint info, uint time_) {
+
+ var pie = PieManager.all_pies[this.current_id];
+ int position = this.renderer.get_active_slice();
+ this.renderer.set_dnd_mode(false);
+
+ foreach (var uri in selection_data.get_uris()) {
+ pie.add_action(ActionRegistry.new_for_uri(uri), position);
+ this.renderer.add_group(pie.action_groups[position], position);
+
+ if (this.renderer.slices.size == 1)
+ this.on_first_slice_added();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables this widget to be a source for drag operations.
+ /////////////////////////////////////////////////////////////////////
+
+ private void enable_drag_source() {
+ if (!this.drag_enabled) {
+ this.drag_enabled = true;
+ Gtk.TargetEntry slice_source = {"text/plain", Gtk.TargetFlags.SAME_WIDGET | Gtk.TargetFlags.SAME_APP, 0};
+ Gtk.TargetEntry[] sources = { slice_source };
+ Gtk.drag_source_set(this, Gdk.ModifierType.BUTTON1_MASK, sources, Gdk.DragAction.MOVE | Gdk.DragAction.COPY);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Disables this widget to be a source for drag operations.
+ /////////////////////////////////////////////////////////////////////
+
+ private void disable_drag_source() {
+ if (this.drag_enabled) {
+ this.drag_enabled = false;
+ Gtk.drag_source_unset(this);
+ }
+ }
+
+}
+
+}
diff --git a/src/gui/piePreviewAddSign.vala b/src/gui/piePreviewAddSign.vala
new file mode 100644
index 0000000..b3f6f7b
--- /dev/null
+++ b/src/gui/piePreviewAddSign.vala
@@ -0,0 +1,224 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A liitle plus-sign displayed on the preview widget to indicate where
+/// the user may add a new Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewAddSign : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Gets emitted, when the users clicks on this object.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image icon { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when the add sign is currently visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool visible { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position of the sign in its parent Pie. May be 2.5 for
+ /// example.
+ /////////////////////////////////////////////////////////////////////
+
+ private double position = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some values used for displaying this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ private double time = 0;
+ private double max_size = 0;
+ private double angle = 0;
+ private AnimatedValue size;
+ private AnimatedValue alpha;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewAddSign(PiePreviewRenderer parent) {
+ this.parent = parent;
+
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 2.0);
+ this.alpha = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, -3, -3, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 0.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the desired icon for this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ this.icon = new Icon("list-add", 36);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the position where this object should be displayed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_position(int position) {
+ double new_position = position;
+
+ if (!this.parent.drag_n_drop_mode)
+ new_position += 0.5;
+
+ this.position = new_position;
+ this.angle = 2.0 * PI * new_position/parent.slice_count();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.visible = true;
+ this.size.reset_target(this.max_size, 0.3);
+ this.alpha.reset_target(1.0, 0.3);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide() {
+ this.visible = false;
+ this.size.reset_target(0.0, 0.3);
+ this.alpha.reset_target(0.0, 0.3);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.max_size = size;
+ this.size.reset_target(size, 0.5);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the sign to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+
+ this.time += frame_time;
+
+ this.size.update(frame_time);
+ this.alpha.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ if (this.parent.slice_count() == 0) {
+ ctx.save();
+
+ double scale = this.clicked.val
+ + GLib.Math.sin(this.time*10)*0.02*this.alpha.val
+ + this.alpha.val*0.08 - 0.1;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx);
+
+ ctx.restore();
+
+ } else if (this.alpha.val*this.activity.val > 0) {
+ ctx.save();
+
+ // distance from the center
+ double radius = 120;
+
+ // transform the context
+ ctx.translate(cos(this.angle)*radius, sin(this.angle)*radius);
+ double scale = this.size.val*this.clicked.val
+ + this.activity.val*0.07
+ + GLib.Math.sin(this.time*10)*0.03*this.activity.val
+ - 0.1;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx, this.alpha.val*this.activity.val);
+
+ ctx.restore();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_move(double angle) {
+ if (parent.slice_count() > 0) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ if (diff < 0.5*PI/parent.slice_count()) this.activity.reset_target(1.0, 1.0);
+ else this.activity.reset_target(-3.0, 1.5);
+ } else {
+ this.activity.reset_target(1.0, 1.0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press(double x, double y) {
+ if (this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ this.on_clicked((int)this.position);
+ this.clicked.reset_target(1.0, 0.1);
+ }
+ }
+}
+
+}
diff --git a/src/gui/piePreviewCenter.vala b/src/gui/piePreviewCenter.vala
new file mode 100644
index 0000000..2a163b6
--- /dev/null
+++ b/src/gui/piePreviewCenter.vala
@@ -0,0 +1,109 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewCenter : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// THe Images displayed. When the displayed text changes the
+ /// currently displayed text becomes the old_text. So it's possible
+ /// to create a smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private RenderedText text = null;
+ private RenderedText old_text = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the currently displayed text in order to avoid frequent
+ /// and useless updates.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_text = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// An AnimatedValue for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue blend;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewCenter(PiePreviewRenderer parent) {
+ this.parent = parent;
+ this.blend = new AnimatedValue.linear(0, 0, 0);
+
+ this.text = new RenderedText("", 1, 1, "", new Color(), 1.0);
+ this.old_text = text;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the currently displayed text. It will be smoothly
+ /// blended and may contain pango markup.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_text(string text) {
+ if (text != this.current_text) {
+
+ var style = parent.parent.get_style_context();
+
+ this.old_text = this.text;
+ this.text = new RenderedText.with_markup(
+ text, 180, 180, style.get_font(Gtk.StateFlags.NORMAL).get_family()+" 10",
+ new Color.from_gdk(style.get_color(Gtk.StateFlags.NORMAL)), 1.0);
+ this.current_text = text;
+
+ this.blend.reset_target(0.0, 0.0);
+ this.blend.reset_target(1.0, 0.1);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the center to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+
+ this.blend.update(frame_time);
+
+ ctx.save();
+
+ if (this.parent.slice_count() == 0)
+ ctx.translate(0, 40);
+
+ this.old_text.paint_on(ctx, 1-this.blend.val);
+ this.text.paint_on(ctx, this.blend.val);
+
+ ctx.restore();
+ }
+}
+
+}
diff --git a/src/gui/piePreviewDeleteSign.vala b/src/gui/piePreviewDeleteSign.vala
new file mode 100644
index 0000000..a830002
--- /dev/null
+++ b/src/gui/piePreviewDeleteSign.vala
@@ -0,0 +1,195 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// The delete sign, displayed in the upper right corner of each
+/// Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewDeleteSign : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image icon { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some constants determining the look and behaviour of this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const int radius = 18;
+ private static const double globale_scale = 0.8;
+ private static const double click_cancel_treshold = 5;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when the add sign is currently visible.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool visible = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some AnimatedValues for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue size;
+ private AnimatedValue alpha;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Storing the position where a mouse click was executed. Useful for
+ /// canceling the click when the mouse moves some pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ private double clicked_x = 0.0;
+ private double clicked_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewDeleteSign() {
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 2.0);
+ this.alpha = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, -3, -3, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 0.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Action. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ this.icon = new Icon("edit-delete", PiePreviewDeleteSign.radius*2);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ if (!this.visible) {
+ this.visible = true;
+ this.alpha.reset_target(1.0, 0.3);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide() {
+ if (this.visible) {
+ this.visible = false;
+ this.alpha.reset_target(0.0, 0.3);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.size.reset_target(size, 0.2);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the sign to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.size.update(frame_time);
+ this.alpha.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ if (this.alpha.val > 0) {
+ ctx.save();
+
+ // transform the context
+ double scale = (this.size.val*this.clicked.val
+ + this.activity.val*0.2 - 0.2)*PiePreviewDeleteSign.globale_scale;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx, this.alpha.val);
+
+ ctx.restore();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_move(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ double dist = GLib.Math.pow(x-this.clicked_x, 2) + GLib.Math.pow(y-this.clicked_y, 2);
+ if (dist > PiePreviewDeleteSign.click_cancel_treshold*PiePreviewDeleteSign.click_cancel_treshold)
+ this.clicked.reset_target(1.0, 0.1);
+ }
+
+ if (GLib.Math.fabs(x) <= PiePreviewDeleteSign.radius*PiePreviewDeleteSign.globale_scale && GLib.Math.fabs(y) <= PiePreviewDeleteSign.radius*PiePreviewDeleteSign.globale_scale) {
+ this.activity.reset_target(1.0, 0.2);
+ return true;
+ }
+
+ this.activity.reset_target(0.0, 0.2);
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_button_press(double x, double y) {
+ if (this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ this.clicked_x = x;
+ this.clicked_y = y;
+ return true;
+ }
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_button_release(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ this.clicked.reset_target(1.0, 0.1);
+ this.on_clicked();
+
+ return true;
+ }
+ return false;
+ }
+}
+
+}
diff --git a/src/gui/piePreviewRenderer.vala b/src/gui/piePreviewRenderer.vala
new file mode 100644
index 0000000..53dd2fb
--- /dev/null
+++ b/src/gui/piePreviewRenderer.vala
@@ -0,0 +1,443 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A complex class which is able to draw the preview of a Pie. It can
+/// manipulate the displayed Pie as well.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// These signals get emitted when a slice is added, removed or
+ /// manipulated.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_add_slice(int position);
+ public signal void on_remove_slice(int position);
+ public signal void on_edit_slice(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when there is currently a drag going on.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool drag_n_drop_mode { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list containing all SliceRenderers of this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gee.ArrayList<PiePreviewSliceRenderer?> slices;
+
+ /////////////////////////////////////////////////////////////////////
+ /// When a Slice is moved within a Pie it is temporarily removed.
+ /// If so, it is stored in this member.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewSliceRenderer hidden_group { get; private set; default=null; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The add sign which indicates that a new Slice could be added.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewAddSign add_sign = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The object which renders the name of the currently selected Slice
+ /// in the middle.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewCenter center_renderer = null;
+ private enum CenterDisplay { NONE, ACTIVE_SLICE, DROP, ADD, DELETE }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some members storing some inter-frame-information.
+ /////////////////////////////////////////////////////////////////////
+
+ private int active_slice = -1;
+ private double angle = 0.0;
+ private double mouse_x = 0.0;
+ private double mouse_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent DrawingArea.
+ /////////////////////////////////////////////////////////////////////
+
+ public unowned Gtk.DrawingArea parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes members.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewRenderer(Gtk.DrawingArea parent) {
+ this.parent = parent;
+ this.slices = new Gee.ArrayList<PiePreviewSliceRenderer?>();
+ this.center_renderer = new PiePreviewCenter(this);
+ this.add_sign = new PiePreviewAddSign(this);
+ this.add_sign.load();
+
+ this.add_sign.on_clicked.connect((pos) => {
+ this.on_add_slice(pos);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Pie. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_pie(Pie pie) {
+ this.slices.clear();
+
+ foreach (var group in pie.action_groups) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ renderer.load(group);
+
+ this.add_slice_renderer(renderer);
+ this.connect_siganls(renderer);
+ }
+
+ this.active_slice = -1;
+ this.update_sizes();
+ this.update_positions(false);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables or disables the drag n dropn mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_dnd_mode(bool dnd) {
+ if (this.drag_n_drop_mode != dnd) {
+ this.drag_n_drop_mode = dnd;
+ this.update_positions();
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the number of Slices.
+ /////////////////////////////////////////////////////////////////////
+
+ public int slice_count() {
+ if (this.drag_n_drop_mode && !(this.slices.size == 0))
+ return slices.size+1;
+
+ return slices.size;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the index of the currently hovered Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public int get_active_slice() {
+ if (this.slices.size == 0)
+ return 0;
+
+ if (this.drag_n_drop_mode)
+ return (int)(this.angle/(2*PI)*this.slice_count() + 0.5) % this.slice_count();
+
+ return this.active_slice;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the Icon of the currently hovered Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public Icon get_active_icon() {
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ return this.slices[this.active_slice].icon;
+ else
+ return new Icon("", 24);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the entire Pie to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.add_sign.draw(frame_time, ctx);
+ this.center_renderer.draw(frame_time, ctx);
+
+ foreach (var slice in this.slices)
+ slice.draw(frame_time, ctx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_leave() {
+ this.add_sign.hide();
+ this.update_positions();
+ this.update_center(CenterDisplay.NONE);
+
+ foreach (var slice in this.slices)
+ slice.on_mouse_leave();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse enters the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_enter() {
+ this.add_sign.show();
+ this.update_positions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves in the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_move(double x, double y) {
+ this.mouse_x = x;
+ this.mouse_y = y;
+
+ this.angle = acos(x/sqrt(x*x + y*y));
+ if (y < 0) this.angle = 2*PI - this.angle;
+
+ if (!this.drag_n_drop_mode)
+ this.active_slice = -1;
+
+ bool delete_hovered = false;
+
+ for (int i=0; i<this.slices.size; ++i)
+ if (slices[i].on_mouse_move(this.angle, x, y) && !this.drag_n_drop_mode) {
+ this.active_slice = i;
+ delete_hovered = slices[i].delete_hovered;
+ }
+
+ if (this.drag_n_drop_mode) this.update_center(CenterDisplay.DROP);
+ else if (this.active_slice < 0) this.update_center(CenterDisplay.ADD);
+ else if (delete_hovered) this.update_center(CenterDisplay.DELETE);
+ else this.update_center(CenterDisplay.ACTIVE_SLICE);
+
+ this.add_sign.on_mouse_move(this.angle);
+
+ this.update_positions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is pressed over this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press() {
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].on_button_press(this.mouse_x, this.mouse_y);
+ this.add_sign.on_button_press(this.mouse_x, this.mouse_y);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is released over this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release() {
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].on_button_release(this.mouse_x, this.mouse_y);
+ this.add_sign.on_button_release(this.mouse_x, this.mouse_y);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds a new Slice to the renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_group(ActionGroup group, int at_position = -1) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ renderer.load(group);
+ this.add_slice_renderer(renderer, at_position);
+ this.connect_siganls(renderer);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Removes a Slice from the renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void remove_group(int index) {
+ if (this.slices.size > index) {
+ this.slices.remove_at(index);
+ this.update_positions();
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Hides the Slice at the given position temporarily.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide_group(int index) {
+ if (this.slices.size > index) {
+ this.hidden_group = this.slices[index];
+ this.remove_group(index);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Re-shows a Slice which has been hidden before.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show_hidden_group_at(int index) {
+ if (this.slices.size >= index && this.hidden_group != null) {
+ this.hidden_group.set_position(index, false);
+ this.add_slice_renderer(this.hidden_group, index);
+ this.hidden_group = null;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates a Slice at the given position.
+ /////////////////////////////////////////////////////////////////////
+
+ public void update_group(ActionGroup group, int index) {
+ if (this.slices.size > index) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ this.slices.set(index, renderer);
+ renderer.load(group);
+
+ this.connect_siganls(renderer);
+
+ this.update_positions(false);
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Disables all quickactions of this pie preview.
+ /////////////////////////////////////////////////////////////////////
+
+ public void disable_quickactions() {
+ foreach (var slice in this.slices)
+ slice.disable_quickactions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which adds a new Slice to the given position.
+ /////////////////////////////////////////////////////////////////////
+
+ private void add_slice_renderer(PiePreviewSliceRenderer renderer, int at_position = -1) {
+ if (at_position < 0 || at_position >= this.slices.size)
+ this.slices.add(renderer);
+ else
+ this.slices.insert(at_position, renderer);
+
+ this.update_positions(false);
+ this.update_sizes();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which connects all neccessary signals of a newly
+ /// added Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private void connect_siganls(PiePreviewSliceRenderer renderer) {
+ renderer.on_clicked.connect((pos) => {
+ this.on_edit_slice(pos);
+ });
+
+ renderer.on_remove.connect((pos) => {
+ this.on_remove_slice(pos);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Moves all slices to their positions. This may happen smoothly if
+ /// desired.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_positions(bool smoothly = true) {
+ if (this.slices.size > 0) {
+ if (this.add_sign.visible) {
+ int add_position = 0;
+ add_position = (int)(this.angle/(2*PI)*this.slice_count()) % this.slice_count();
+ this.add_sign.set_position(add_position);
+
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i, smoothly);
+ }
+
+ } else if (this.drag_n_drop_mode) {
+ int add_position = 0;
+ add_position = (int)(this.angle/(2*PI)*this.slice_count() + 0.5) % this.slice_count();
+
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i >= add_position ? i+1 : i, smoothly);
+ }
+
+ this.update_center(CenterDisplay.DROP);
+
+ } else {
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i, smoothly);
+ }
+
+ if (this.active_slice < 0) this.update_center(CenterDisplay.NONE);
+ else this.update_center(CenterDisplay.ACTIVE_SLICE);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Resizes all slices to their new sizes. This may happen smoothly
+ /// if desired.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_sizes() {
+ double size = 1.0;
+ if (this.slice_count() > 20) size = 0.5;
+ else if (this.slice_count() > 8) size = 1.0 - (double)(this.slice_count() - 8)/24.0;
+
+ this.add_sign.set_size(size);
+
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].set_size(size);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a new text in the middle of the preview.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_center(CenterDisplay display) {
+ switch (display) {
+ case CenterDisplay.ACTIVE_SLICE:
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(slices[this.active_slice].name) + "</b>\n<small>"
+ + _("Click to edit") + "\n" + _("Drag to move") + "</small>");
+ break;
+ case CenterDisplay.ADD:
+ this.center_renderer.set_text("<small>" + _("Click to add a new Slice") + "</small>");
+ break;
+ case CenterDisplay.DROP:
+ if (hidden_group == null)
+ this.center_renderer.set_text("<small>" + _("Drop to add as new Slice") + "</small>");
+ else
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(this.hidden_group.name) + "</b>\n<small>"
+ + _("Drop to move Slice") + "</small>");
+ break;
+ case CenterDisplay.DELETE:
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(slices[this.active_slice].name) + "</b>\n<small>"
+ + _("Click to delete") + "\n" + _("Drag to move") + "</small>");
+ break;
+ default:
+ this.center_renderer.set_text("");
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/gui/piePreviewSliceRenderer.vala b/src/gui/piePreviewSliceRenderer.vala
new file mode 100644
index 0000000..5b4d939
--- /dev/null
+++ b/src/gui/piePreviewSliceRenderer.vala
@@ -0,0 +1,276 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Displays the preview of a Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewSliceRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on the delete sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_remove(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Icon icon { get; private set; }
+ public ActionGroup action_group { get; private set; }
+ public string name { get; private set; default=""; }
+ public bool delete_hovered { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The delete sign, displayed in the upper right corner of each
+ /// Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewDeleteSign delete_sign = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some AnimatedValues for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue angle;
+ private AnimatedValue size;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some constants determining the look and behaviour of this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const double pie_radius = 126;
+ private static const double radius = 24;
+ private static const double delete_x = 13;
+ private static const double delete_y = -13;
+ private static const double click_cancel_treshold = 5;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Storing the position where a mouse click was executed. Useful for
+ /// canceling the click when the mouse moves some pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ private double clicked_x = 0.0;
+ private double clicked_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of this slice in a pie. Clockwise assigned, starting
+ /// from the right-most slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private int position;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewSliceRenderer(PiePreviewRenderer parent) {
+ this.delete_sign = new PiePreviewDeleteSign();
+ this.delete_sign.load();
+ this.delete_sign.on_clicked.connect(() => {
+ this.on_remove(this.position);
+ });
+
+ this.parent = parent;
+ this.angle = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.5);
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 1.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 1.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Action. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load(ActionGroup group) {
+ this.action_group = group;
+
+ // if it's a custom ActionGroup
+ if (group.get_type().depth() == 2 && group.actions.size > 0) {
+ this.icon = new Icon(group.actions[0].icon, (int)(PiePreviewSliceRenderer.radius*2));
+ this.name = group.actions[0].name;
+ } else {
+ this.icon = new Icon(GroupRegistry.descriptions[group.get_type().name()].icon, (int)(PiePreviewSliceRenderer.radius*2));
+ this.name = GroupRegistry.descriptions[group.get_type().name()].name;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the position where this object should be displayed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_position(int position, bool smoothly = true) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+
+ if (direction != this.angle.end) {
+ this.position = position;
+ this.angle.reset_target(direction, smoothly ? 0.5 : 0.0);
+
+ if (!smoothly)
+ this.angle.update(1.0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.size.reset_target(size, 0.5);
+ this.delete_sign.set_size(size);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Notifies that all quick actions should be disabled.
+ /////////////////////////////////////////////////////////////////////
+
+ public void disable_quickactions() {
+ this.action_group.disable_quickactions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the slice to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.size.update(frame_time);
+ this.angle.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ ctx.save();
+
+ // transform the context
+ ctx.translate(cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius, sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius);
+
+ double scale = this.size.val*this.clicked.val
+ + this.activity.val*0.1 - 0.1;
+ ctx.save();
+
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx);
+
+ ctx.restore();
+
+ ctx.translate(PiePreviewSliceRenderer.delete_x*this.size.val, PiePreviewSliceRenderer.delete_y*this.size.val);
+ this.delete_sign.draw(frame_time, ctx);
+
+ ctx.restore();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_move(double angle, double x, double y) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ bool active = diff < 0.5*PI/parent.slice_count();
+
+ if (active) {
+ this.activity.reset_target(1.0, 0.3);
+ this.delete_sign.show();
+ } else {
+ this.activity.reset_target(0.0, 0.3);
+ this.delete_sign.hide();
+ }
+
+ if (this.clicked.end == 0.9) {
+ double dist = GLib.Math.pow(x-this.clicked_x, 2) + GLib.Math.pow(y-this.clicked_y, 2);
+ if (dist > PiePreviewSliceRenderer.click_cancel_treshold*PiePreviewSliceRenderer.click_cancel_treshold)
+ this.clicked.reset_target(1.0, 0.1);
+ }
+
+ double own_x = cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ double own_y = sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ this.delete_hovered = this.delete_sign.on_mouse_move(x - own_x - PiePreviewSliceRenderer.delete_x*this.size.val,
+ y - own_y - PiePreviewSliceRenderer.delete_y*this.size.val);
+
+ return active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_leave() {
+ this.activity.reset_target(0.0, 0.3);
+ this.delete_sign.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press(double x, double y) {
+ bool delete_pressed = false;
+ if (this.activity.end == 1.0) {
+ double own_x = cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ double own_y = sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ delete_pressed = this.delete_sign.on_button_press(x - own_x - PiePreviewSliceRenderer.delete_x*this.size.val,
+ y - own_y - PiePreviewSliceRenderer.delete_y*this.size.val);
+ }
+
+ if (!delete_pressed && this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ this.clicked_x = x;
+ this.clicked_y = y;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release(double x, double y) {
+ bool deleted = false;
+ if (this.activity.end == 1.0)
+ deleted = this.delete_sign.on_button_release(x, y);
+
+ if (!deleted && this.clicked.end == 0.9) {
+ this.clicked.reset_target(1.0, 0.1);
+ this.on_clicked(this.position);
+ }
+ }
+}
+
+}
diff --git a/src/gui/preferencesWindow.vala b/src/gui/preferencesWindow.vala
new file mode 100644
index 0000000..fff8168
--- /dev/null
+++ b/src/gui/preferencesWindow.vala
@@ -0,0 +1,604 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// The settings menu of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class PreferencesWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the currently selected Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string selected_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some Gtk widgets used by this window.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Stack? stack = null;
+ private Gtk.Notebook? notebook = null;
+
+ private Gtk.Window? window = null;
+ private Gtk.Label? no_pie_label = null;
+ private Gtk.Label? no_slice_label = null;
+ private Gtk.Box? preview_box = null;
+ private Gtk.EventBox? preview_background = null;
+ private Gtk.Button? remove_pie_button = null;
+ private Gtk.Button? edit_pie_button = null;
+ private Gtk.Button? theme_delete_button = null;
+
+ private ThemeList? theme_list = null;
+ private Gtk.ToggleButton? indicator = null;
+ private Gtk.ToggleButton? search_by_string = null;
+ private Gtk.ToggleButton? autostart = null;
+ private Gtk.ToggleButton? captions = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some custom widgets and dialogs used by this window.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreview? preview = null;
+ private PieList? pie_list = null;
+ private PieOptionsWindow? pie_options_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public PreferencesWindow() {
+ var builder = new Gtk.Builder.from_file(Paths.ui_files + "/preferences.ui");
+
+ this.window = builder.get_object("window") as Gtk.Window;
+ this.window.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.KEY_RELEASE_MASK |
+ Gdk.EventMask.KEY_PRESS_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK);
+
+ if (!Daemon.disable_header_bar) {
+ var headerbar = new Gtk.HeaderBar();
+ headerbar.show_close_button = true;
+ headerbar.title = _("Gnome-Pie Settings");
+ headerbar.subtitle = _("bake your pies!");
+ window.set_titlebar(headerbar);
+ }
+
+ this.notebook = builder.get_object("notebook") as Gtk.Notebook;
+
+ if (!Daemon.disable_stack_switcher) {
+ var main_box = builder.get_object("main-box") as Gtk.Box;
+ var pie_settings = builder.get_object("pie-settings") as Gtk.Box;
+ var general_settings = builder.get_object("general-settings") as Gtk.Box;
+
+ pie_settings.parent.remove(pie_settings);
+ general_settings.parent.remove(general_settings);
+
+ main_box.remove(this.notebook);
+
+ Gtk.StackSwitcher switcher = new Gtk.StackSwitcher();
+ switcher.margin_top = 10;
+ switcher.set_halign(Gtk.Align.CENTER);
+ main_box.pack_start(switcher, false, true, 0);
+
+ this.stack = new Gtk.Stack();
+ this.stack.transition_duration = 500;
+ this.stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
+ this.stack.homogeneous = true;
+ this.stack.halign = Gtk.Align.FILL;
+ this.stack.expand = true;
+ main_box.add(stack);
+ switcher.set_stack(stack);
+
+ this.stack.add_with_properties(general_settings, "name", "1", "title", _("General Settings"), null);
+ this.stack.add_with_properties(pie_settings, "name", "2", "title", _("Pie Settings"), null);
+ }
+
+ this.pie_list = new PieList();
+ this.pie_list.on_select.connect(this.on_pie_select);
+ this.pie_list.on_activate.connect(() => {
+ this.on_edit_pie_button_clicked();
+ });
+
+ var scroll_area = builder.get_object("pies-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.pie_list);
+
+ this.preview = new PiePreview();
+ this.preview.on_first_slice_added.connect(() => {
+ this.no_slice_label.hide();
+ });
+
+ this.preview.on_last_slice_removed.connect(() => {
+ this.no_slice_label.show();
+ });
+
+ preview_box = builder.get_object("preview-box") as Gtk.Box;
+ this.preview_box.pack_start(preview, true, true);
+ this.no_pie_label = builder.get_object("no-pie-label") as Gtk.Label;
+ this.no_slice_label = builder.get_object("no-slice-label") as Gtk.Label;
+ this.preview_background = builder.get_object("preview-background") as Gtk.EventBox;
+
+ this.remove_pie_button = builder.get_object("remove-pie-button") as Gtk.Button;
+ this.remove_pie_button.clicked.connect(on_remove_pie_button_clicked);
+
+ this.edit_pie_button = builder.get_object("edit-pie-button") as Gtk.Button;
+ this.edit_pie_button.clicked.connect(on_edit_pie_button_clicked);
+
+ (builder.get_object("add-pie-button") as Gtk.Button).clicked.connect(on_add_pie_button_clicked);
+
+ this.theme_list = new ThemeList();
+ this.theme_list.on_select_new.connect(() => {
+ this.captions.active = Config.global.show_captions;
+ if (Config.global.theme.has_slice_captions) {
+ this.captions.sensitive = true;
+ } else {
+ this.captions.sensitive = false;
+ }
+ if (Config.global.theme.is_local()) {
+ this.theme_delete_button.sensitive = true;
+ } else {
+ this.theme_delete_button.sensitive = false;
+ }
+ });
+
+ scroll_area = builder.get_object("theme-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.theme_list);
+
+ (builder.get_object("theme-help-button") as Gtk.Button).clicked.connect(() => {
+ try{
+ GLib.AppInfo.launch_default_for_uri("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/", null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ });
+
+ (builder.get_object("theme-export-button") as Gtk.Button).clicked.connect(on_export_theme_button_clicked);
+ (builder.get_object("theme-import-button") as Gtk.Button).clicked.connect(on_import_theme_button_clicked);
+ (builder.get_object("theme-reload-button") as Gtk.Button).clicked.connect(on_reload_theme_button_clicked);
+ (builder.get_object("theme-open-button") as Gtk.Button).clicked.connect(on_open_theme_button_clicked);
+ this.theme_delete_button = (builder.get_object("theme-delete-button") as Gtk.Button);
+ this.theme_delete_button.clicked.connect(on_delete_theme_button_clicked);
+
+ this.autostart = (builder.get_object("autostart-checkbox") as Gtk.ToggleButton);
+ this.autostart.toggled.connect(on_autostart_toggled);
+
+ this.indicator = (builder.get_object("indicator-checkbox") as Gtk.ToggleButton);
+ this.indicator.toggled.connect(on_indicator_toggled);
+
+ this.search_by_string = (builder.get_object("select-by-string-checkbox") as Gtk.ToggleButton);
+ this.search_by_string.toggled.connect(on_search_by_string_toggled);
+
+ this.captions = (builder.get_object("captions-checkbox") as Gtk.ToggleButton);
+ this.captions.toggled.connect(on_captions_toggled);
+
+ var scale_slider = (builder.get_object("scale-hscale") as Gtk.Scale);
+ scale_slider.set_range(0.5, 2.0);
+ scale_slider.set_increments(0.05, 0.25);
+ scale_slider.set_value(Config.global.global_scale);
+
+ 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;
+ }
+ });
+
+ var range_slider = (builder.get_object("range-hscale") as Gtk.Scale);
+ range_slider.set_range(0, 2000);
+ range_slider.set_increments(10, 100);
+ range_slider.set_value(Config.global.activation_range);
+ range_slider.value_changed.connect(() => {
+ Config.global.activation_range = (int)range_slider.get_value();
+ });
+
+ var range_slices = (builder.get_object("range-slices") as Gtk.Scale);
+ range_slices.set_range(12, 96);
+ range_slices.set_increments(4, 12);
+ range_slices.set_value(Config.global.max_visible_slices);
+ range_slices.value_changed.connect(() => {
+ Config.global.max_visible_slices = (int)range_slices.get_value();
+ });
+
+ var info_box = (builder.get_object("info-box") as Gtk.Box);
+
+ // info label
+ var info_label = new TipViewer({
+ _("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://simmesimme.github.io/gnome-pie.html'>gnome-pie.simonschneegans.de</a>"),
+ _("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 can support the development of Gnome-Pie by donating via %s.").printf("<a href='https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=X65SUVC4ZTQSC'>Paypal</a>"),
+ _("Translating Gnome-Pie to your language is easy. Translations are managed at %s.").printf("<a href='https://translate.zanata.org/zanata/iteration/view/gnome-pie/develop'>Zanata</a>"),
+ _("It's easy to create new themes for Gnome-Pie. Read the <a href='%s'>Tutorial</a> online.").printf("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/"),
+ _("It's usually a good practice to have at most twelve slices per pie."),
+ _("You can export themes you created and share them with the community!"),
+ _("The source code of Gnome-Pie is available on %s.").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"),
+ _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"),
+ _("Suggestions can be posted on %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"),
+ _("An awesome companion of Gnome-Pie is %s. It will make using your computer feel like magic!").printf("<a href='https://github.com/thjaeger/easystroke/wiki'>Easystroke</a>"),
+ _("You can drag'n'drop applications from your main menu to the pie above."),
+ _("You may drag'n'drop URLs and bookmarks from your internet browser to the pie above."),
+ _("You can drag'n'drop files and folders from your file browser to the pie above."),
+ _("You can drag'n'drop pies from the list on the left into other pies in order to create sub-pies."),
+ _("You can drag'n'drop pies from the list on the left to your desktop or dock to create a launcher for this pie.")
+ });
+ this.window.show.connect(info_label.start_slide_show);
+ this.window.hide.connect(info_label.stop_slide_show);
+
+ info_box.pack_end(info_label);
+
+ this.window.hide.connect(() => {
+ // save settings on close
+ Config.global.save();
+ Pies.save();
+
+ Timeout.add(100, () => {
+ IconSelectWindow.clear_icons();
+ return false;
+ });
+ });
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.preview.draw_loop();
+ this.window.show_all();
+ this.pie_list.select_first();
+
+ var style = this.preview_background.get_style_context();
+ this.preview_background.override_background_color(Gtk.StateFlags.NORMAL, style.get_background_color(Gtk.StateFlags.NORMAL));
+
+ this.indicator.active = Config.global.show_indicator;
+ this.autostart.active = Config.global.auto_start;
+ this.captions.active = Config.global.show_captions;
+ this.search_by_string.active = Config.global.search_by_string;
+
+ if (Config.global.theme.has_slice_captions) {
+ this.captions.sensitive = true;
+ } else {
+ this.captions.sensitive = false;
+ }
+
+ if (Config.global.theme.is_local()) {
+ this.theme_delete_button.sensitive = true;
+ } else {
+ this.theme_delete_button.sensitive = false;
+ }
+
+ if (!Daemon.disable_stack_switcher) {
+ this.stack.set_visible_child_full("2", Gtk.StackTransitionType.NONE);
+ } else {
+ this.notebook.set_current_page(1);
+ }
+ this.pie_list.has_focus = true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates or deletes the autostart file. This code is inspired
+ /// by project synapse as well.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_autostart_toggled(Gtk.ToggleButton check_box) {
+
+ bool active = check_box.active;
+ if (!active && FileUtils.test(Paths.autostart, FileTest.EXISTS)) {
+ Config.global.auto_start = false;
+ // delete the autostart file
+ FileUtils.remove (Paths.autostart);
+ }
+ else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) {
+ Config.global.auto_start = true;
+
+ string autostart_entry =
+ "#!/usr/bin/env xdg-open\n" +
+ "[Desktop Entry]\n" +
+ "Name=Gnome-Pie\n" +
+ "Exec=" + Paths.executable + "\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.window, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
+ "%s", e.message);
+ d.run();
+ d.destroy();
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Saves the current theme to an archive.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_export_theme_button_clicked(Gtk.Button button) {
+ var dialog = new Gtk.FileChooserDialog("Pick a file", this.window,
+ Gtk.FileChooserAction.SAVE,
+ "_Cancel",
+ Gtk.ResponseType.CANCEL,
+ "_Save",
+ Gtk.ResponseType.ACCEPT);
+
+ dialog.set_do_overwrite_confirmation(true);
+ dialog.set_modal(true);
+ dialog.filter = new Gtk.FileFilter();
+ dialog.filter.add_pattern ("*.tar.gz");
+ dialog.set_current_name(Config.global.theme.name + ".tar.gz");
+
+ dialog.response.connect((d, result) => {
+ if (result == Gtk.ResponseType.ACCEPT) {
+ var file = dialog.get_filename();
+ if (!file.has_suffix(".tar.gz")) {
+ file = file + ".tar.gz";
+ }
+ Config.global.theme.export(file);
+ }
+ dialog.destroy();
+ });
+ dialog.show();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Imports a new theme from an archive.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_import_theme_button_clicked(Gtk.Button button) {
+ var dialog = new Gtk.FileChooserDialog("Pick a file", this.window,
+ Gtk.FileChooserAction.OPEN,
+ "_Cancel",
+ Gtk.ResponseType.CANCEL,
+ "_Open",
+ Gtk.ResponseType.ACCEPT);
+
+ dialog.set_modal(true);
+ dialog.filter = new Gtk.FileFilter();
+ dialog.filter.add_pattern ("*.tar.gz");
+
+ var result = Gtk.MessageType.INFO;
+ var message = _("Sucessfully imported new theme!");
+
+ dialog.response.connect((d, r) => {
+ if (r == Gtk.ResponseType.ACCEPT) {
+ var file = dialog.get_filename();
+
+ var a = new ThemeImporter();
+ if (a.open(file)) {
+ if (a.is_valid_theme) {
+ if (!Config.global.has_theme(a.theme_name)) {
+ if (a.extract_to(Paths.local_themes + "/" + a.theme_name)) {
+ Config.global.load_themes(a.theme_name);
+ this.theme_list.reload();
+ } else {
+ message = _("An error occured while importing the theme: Failed to extract theme!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: A theme with this name does already exist!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: Theme archive does not contain a valid theme!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: Failed to open theme archive!");
+ result = Gtk.MessageType.ERROR;
+ }
+ a.close();
+
+ var result_dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,
+ result, Gtk.ButtonsType.CLOSE, message);
+ result_dialog.run();
+ result_dialog.destroy();
+ }
+ dialog.destroy();
+
+ });
+ dialog.show();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Deleted the slected theme.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_delete_theme_button_clicked(Gtk.Button button) {
+
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.get_toplevel(), Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("Do you really want to delete the selected theme from %s?").printf(Config.global.theme.directory));
+
+ dialog.response.connect((response) => {
+ if (response == Gtk.ResponseType.YES) {
+ Paths.delete_directory(Config.global.theme.directory);
+ Config.global.load_themes("");
+ this.theme_list.reload();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads all themes.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_reload_theme_button_clicked(Gtk.Button button) {
+ Config.global.load_themes(Config.global.theme.name);
+ this.theme_list.reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the loaction of the them in the file browser.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_open_theme_button_clicked(Gtk.Button button) {
+ try{
+ GLib.AppInfo.launch_default_for_uri("file://" + Config.global.theme.directory, null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows or hides the indicator.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_indicator_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.show_indicator = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows or hides the captions of Slices.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_captions_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.show_captions = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables or disables Slice selection by typing.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_search_by_string_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.search_by_string = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a new Pie is selected in the PieList.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_pie_select(string id) {
+ selected_id = id;
+
+ this.no_slice_label.hide();
+ this.no_pie_label.hide();
+ this.preview_box.hide();
+
+ this.remove_pie_button.sensitive = false;
+ this.edit_pie_button.sensitive = false;
+
+ if (id == "") {
+ this.no_pie_label.show();
+ } else {
+ var pie = PieManager.all_pies[selected_id];
+
+ this.preview.set_pie(id);
+ this.preview_box.show();
+
+ if (pie.action_groups.size == 0) {
+ this.no_slice_label.show();
+ }
+
+ this.remove_pie_button.sensitive = true;
+ this.edit_pie_button.sensitive = true;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the add Pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_add_pie_button_clicked(Gtk.Button button) {
+ var new_pie = PieManager.create_persistent_pie(_("New Pie"), "stock_unknown", null);
+ this.pie_list.reload_all();
+ this.pie_list.select(new_pie.id);
+
+ this.on_edit_pie_button_clicked();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the remove Pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_remove_pie_button_clicked(Gtk.Button button) {
+ if (this.selected_id != "") {
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.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) {
+ PieManager.remove_pie(selected_id);
+ this.pie_list.reload_all();
+ this.pie_list.select_first();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the edit pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_edit_pie_button_clicked(Gtk.Button? button = null) {
+ if (this.pie_options_window == null) {
+ this.pie_options_window = new PieOptionsWindow();
+ this.pie_options_window.set_parent(window);
+ this.pie_options_window.on_ok.connect((trigger, name, icon) => {
+ var pie = PieManager.all_pies[selected_id];
+ pie.name = name;
+ pie.icon = icon;
+ PieManager.bind_trigger(trigger, selected_id);
+ PieManager.create_launcher(pie.id);
+ this.pie_list.reload_all();
+ });
+ }
+
+ this.pie_options_window.set_pie(selected_id);
+ this.pie_options_window.show();
+ }
+}
+
+}
diff --git a/src/gui/sliceTypeList.vala b/src/gui/sliceTypeList.vala
new file mode 100644
index 0000000..1a9ecc4
--- /dev/null
+++ b/src/gui/sliceTypeList.vala
@@ -0,0 +1,173 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A list displaying all available Action types and ActionGroup types.
+/////////////////////////////////////////////////////////////////////////
+
+class SliceTypeList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Type.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id, string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The listore which staroes all types internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, ICON_NAME, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public SliceTypeList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(4, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(2, Gtk.SortType.ASCENDING);
+
+ base.set_model(this.data);
+ base.set_headers_visible(true);
+ base.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.set_fixed_height_mode(true);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED);
+ main_column.title = _("Slice types");
+ var icon_render = new Gtk.CellRendererPixbuf();
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ main_column.pack_start(name_render, true);
+
+ base.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ string icon = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.data.get(active, DataPos.ICON_NAME, out icon);
+ this.on_select(id, icon);
+ }
+ });
+
+ reload_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads a registered actions and action groups.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload_all() {
+ Gtk.TreeIter active;
+ string current_id = "";
+ if (this.get_selection().get_selected(null, out active))
+ this.data.get(active, DataPos.ID, out current_id);
+
+ data.clear();
+
+ foreach (var action_type in ActionRegistry.types) {
+ var description = ActionRegistry.descriptions[action_type];
+
+ Gtk.TreeIter current;
+ data.append(out current);
+ var icon = new Icon(description.icon, 36);
+ data.set(current, DataPos.ICON, icon.to_pixbuf());
+ data.set(current, DataPos.ICON_NAME, description.icon);
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(description.name) + "\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(description.description) + "</span>");
+ data.set(current, DataPos.ID, description.id);
+ }
+
+ foreach (var group_type in GroupRegistry.types) {
+ var description = GroupRegistry.descriptions[group_type];
+
+ Gtk.TreeIter current;
+ data.append(out current);
+ var icon = new Icon(description.icon, 36);
+ data.set(current, DataPos.ICON, icon.to_pixbuf());
+ data.set(current, DataPos.ICON_NAME, description.icon);
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(description.name) + "\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(description.description) + "</span>");
+ data.set(current, DataPos.ID, description.id);
+ }
+
+ select_first();
+ select(current_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first type in the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.get_selection().select_iter(active);
+ string id = "";
+ string icon = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.data.get(active, DataPos.ICON_NAME, out icon);
+ this.on_select(id, icon);
+ } else {
+ this.on_select("", "stock_unknown");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Select the given slice type.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.get_selection().select_iter(iter);
+ string icon = "";
+ this.data.get(iter, DataPos.ICON_NAME, out icon);
+ this.on_select(pie_id, icon);
+ this.scroll_to_cell(path, null, true, 0.5f, 0.5f);
+ this.has_focus = true;
+
+ return true;
+ }
+
+ return false;
+ });
+ }
+}
+
+}
diff --git a/src/gui/themeList.vala b/src/gui/themeList.vala
new file mode 100644
index 0000000..786c305
--- /dev/null
+++ b/src/gui/themeList.vala
@@ -0,0 +1,118 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A widget displaying all available themes of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class ThemeList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted, when a new theme is selected by the
+ /// user. This new theme is applied automatically, with this signal
+ /// actions may be triggered which should be executed AFTER the
+ /// change to a new theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select_new();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently selected row.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeIter active { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The positions in the data list store.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum DataPos {ICON, NAME}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public ThemeList() {
+ GLib.Object();
+
+ this.set_headers_visible(true);
+ this.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.set_fixed_height_mode(true);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.title = _("Themes");
+ main_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED);
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ icon_render.ypad = 4;
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ main_column.pack_start(name_render, true);
+
+ this.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ Timeout.add(10, () => {
+ int index = int.parse(this.model.get_path(active).to_string());
+ Config.global.theme = Config.global.themes[index];
+
+ this.on_select_new();
+
+ Config.global.theme.load();
+ Config.global.theme.load_images();
+ return false;
+ });
+ }
+ });
+
+ reload();
+ }
+
+ public void reload() {
+
+ var data = new Gtk.ListStore(2, typeof(Gdk.Pixbuf),
+ typeof(string));
+ this.set_model(data);
+
+ // 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, DataPos.ICON, theme.preview_icon.to_pixbuf());
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(theme.name)+"\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(theme.description)
+ + " - <i>"+GLib.Markup.escape_text(_("by")+" "+theme.author)
+ + "</i></span>");
+ if(theme == Config.global.theme) {
+ get_selection().select_iter(current);
+ this.scroll_to_cell(get_selection().get_selected_rows(null).nth_data(0), null, true, 0.5f, 0.5f);
+ }
+ }
+ }
+}
+
+}
diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala
new file mode 100644
index 0000000..e2158bd
--- /dev/null
+++ b/src/gui/tipViewer.vala
@@ -0,0 +1,163 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 base_delay = 3.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;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The fading value.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue alpha;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members and sets the basic layout.
+ /////////////////////////////////////////////////////////////////////
+
+ public TipViewer(string[] tips) {
+ this.tips = tips;
+
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, fade_time);
+
+ this.set_alignment (0.0f, 0.5f);
+ this.opacity = 0;
+ this.wrap = true;
+ this.valign = Gtk.Align.END;
+ this.set_use_markup(true);
+
+ this.override_font(Pango.FontDescription.from_string("8"));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts the playback of tips.
+ /////////////////////////////////////////////////////////////////////
+
+ public void start_slide_show() {
+ if (!this.playing && tips.length > 1) {
+ this.playing = true;
+ show_tip();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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, fade_time);
+
+ GLib.Timeout.add((uint)(1000.0/frame_rate), () => {
+ this.alpha.update(1.0/frame_rate);
+ this.opacity = this.alpha.val;
+
+ return (this.alpha.val != 1.0);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts the fading out.
+ /////////////////////////////////////////////////////////////////////
+
+ private void fade_out() {
+ this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, fade_time);
+
+ GLib.Timeout.add((uint)(1000.0/frame_rate), () => {
+ this.alpha.update(1.0/frame_rate);
+ this.opacity = this.alpha.val;
+
+ return (this.alpha.val != 0.0);
+ });
+ }
+
+ private void show_tip() {
+
+ this.set_random_tip();
+
+ this.fade_in();
+
+ uint delay = (uint)(base_delay*1000.0) + tips[this.index].length*30;
+
+ GLib.Timeout.add(delay, () => {
+ this.fade_out();
+
+ if (this.playing) {
+ GLib.Timeout.add((uint)(1000.0*fade_time), () => {
+ this.show_tip();
+ return false;
+ });
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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/gui/triggerSelectButton.vala b/src/gui/triggerSelectButton.vala
new file mode 100644
index 0000000..eb34066
--- /dev/null
+++ b/src/gui/triggerSelectButton.vala
@@ -0,0 +1,163 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This window allows the selection of a hotkey. It is returned in form
+/// of a Trigger. Therefore it can be either a keyboard driven hotkey or
+/// a mouse based hotkey.
+/////////////////////////////////////////////////////////////////////////
+
+public class TriggerSelectButton : Gtk.ToggleButton {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal is emitted when the user selects a new hot key.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(Trigger trigger);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently contained Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if mouse buttons can be bound as well.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool enable_mouse = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// These modifiers are ignored.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.ModifierType lock_modifiers = Gdk.ModifierType.MOD2_MASK
+ |Gdk.ModifierType.MOD4_MASK
+ |Gdk.ModifierType.MOD5_MASK
+ |Gdk.ModifierType.LOCK_MASK;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs a new TriggerSelectWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public TriggerSelectButton(bool enable_mouse) {
+ this.enable_mouse = enable_mouse;
+
+ this.toggled.connect(() => {
+ if (this.active) {
+ this.set_label(_("Press a hotkey ..."));
+ Gtk.grab_add(this);
+ FocusGrabber.grab(this.get_window(), true, true, true);
+ }
+ });
+
+ this.button_press_event.connect(this.on_button_press);
+ this.key_press_event.connect(this.on_key_press);
+ this.set_trigger(new Trigger());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the button display the given Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_trigger(Trigger trigger) {
+ this.trigger = trigger;
+ this.set_label(trigger.label);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Can be called to cancel the selection process.
+ /////////////////////////////////////////////////////////////////////
+
+ private void cancel() {
+ this.set_label(trigger.label);
+ this.set_active(false);
+ Gtk.grab_remove(this);
+ FocusGrabber.ungrab(true, true);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the button display the given Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_trigger(Trigger trigger) {
+ if (this.trigger.name != trigger.name) {
+ this.set_trigger(trigger);
+ this.on_select(this.trigger);
+ }
+
+ this.cancel();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses a keyboard key.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_key_press(Gdk.EventKey event) {
+ if (this.active) {
+ if (Gdk.keyval_name(event.keyval) == "Escape") {
+ this.cancel();
+ } else if (Gdk.keyval_name(event.keyval) == "BackSpace") {
+ this.update_trigger(new Trigger());
+ } else if (event.is_modifier == 0) {
+ Gdk.ModifierType state = event.state & ~ this.lock_modifiers;
+ this.update_trigger(new Trigger.from_values(event.keyval, state, false, false, false,
+ false, false, 5));
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses a button of the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_press(Gdk.EventButton event) {
+ if (this.active) {
+ Gtk.Allocation rect;
+ this.get_allocation(out rect);
+ if (event.x < 0 || event.x > rect.width
+ || event.y < 0 || event.y > rect.height) {
+
+ this.cancel();
+ return true;
+ }
+ }
+
+ if (this.active && this.enable_mouse) {
+ Gdk.ModifierType state = event.state & ~ this.lock_modifiers;
+ var new_trigger = new Trigger.from_values((int)event.button, state, true,
+ false, false, false, false, 5);
+
+ if (new_trigger.key_code != 1) this.update_trigger(new_trigger);
+ else this.cancel();
+
+ return true;
+ } else if (this.active) {
+ this.cancel();
+ return true;
+ }
+
+ return false;
+ }
+}
+
+}
diff --git a/src/images/icon.vala b/src/images/icon.vala
new file mode 100644
index 0000000..9cfccf8
--- /dev/null
+++ b/src/images/icon.vala
@@ -0,0 +1,135 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 = Icon.cache.get("%s@%u".printf(icon_name, size));
+
+ if (cached == null) {
+ this.load_file_at_size(Icon.get_icon_file(icon_name, size), size, size);
+ Icon.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 icon name for a given GLib.Icon.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_icon_name(GLib.Icon? icon) {
+ if (icon != null) {
+ var icon_names = icon.to_string().split(" ");
+
+ foreach (var icon_name in icon_names) {
+ if (Gtk.IconTheme.get_default().has_icon(icon_name)) {
+ return icon_name;
+ }
+ }
+ }
+
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the filename for a given system icon.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_icon_file(string icon_name, int size) {
+ string result = "";
+
+ if (icon_name.contains("/")) {
+ var file = GLib.File.new_for_path(icon_name);
+ if(file.query_exists())
+ return icon_name;
+
+ warning("Icon \"" + icon_name + "\" not found! Using default icon...");
+ }
+
+
+ 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...");
+
+ string[] default_icons = {"application-default-icon", "stock_unknown"};
+ foreach (var icon in default_icons) {
+ file = icon_theme.lookup_icon(icon, size, 0);
+ if (file != null) {
+ result = file.get_filename();
+ break;
+ }
+ }
+
+ if (result == "")
+ warning("No default icon found! Will be ugly...");
+ }
+
+ return result;
+ }
+}
+
+}
diff --git a/src/images/image.vala b/src/images/image.vala
new file mode 100644
index 0000000..a903493
--- /dev/null
+++ b/src/images/image.vala
@@ -0,0 +1,215 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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) {
+ if (pixbuf != null) this.load_pixbuf(pixbuf);
+ else this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Captures a part of the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image.capture_screen(int posx, int posy, int width, int height, bool hide_pies = true) {
+ Gdk.Window root = Gdk.get_default_root_window();
+ Gdk.Pixbuf pixbuf = Gdk.pixbuf_get_from_window(root, posx, posy, width, height);
+
+ this.load_pixbuf(pixbuf);
+
+ if (hide_pies) {
+ // check for opened pies
+ foreach (var window in PieManager.opened_windows) {
+ if (window.background != null) {
+ int x=0, y=0, dx=0, dy=0;
+ window.get_position(out x, out y);
+ window.get_size(out dx, out dy);
+
+ var ctx = this.context();
+ ctx.translate((int)(x-posx + (dx+3)/2), (int)(y-posy + (dy+3)/2));
+ window.background.paint_on(ctx);
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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 + "!");
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1);
+ }
+ } 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 + "!");
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+ }
+
+ } 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, (int)(-0.5*this.width()-1), (int)(-0.5*this.height()-1));
+ if (alpha >= 1.0) ctx.paint();
+ else ctx.paint_with_alpha(alpha);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Converts the image to a Gdk.Pixbuf.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gdk.Pixbuf to_pixbuf() {
+ if (this.surface == null || this.surface.get_data() == null)
+ return new Gdk.Pixbuf(Gdk.Colorspace.RGB, true, 8, 1, 1);
+
+ var pixbuf = new Gdk.Pixbuf.with_unowned_data(this.surface.get_data(), Gdk.Colorspace.RGB, true, 8,
+ width(), height(), this.surface.get_stride(), null);
+
+ pixbuf = pixbuf.copy();
+
+ // funny stuff here --- need to swap Red end Blue because Cairo
+ // and Gdk are different...
+ uint8* p = pixbuf.pixels;
+ for (int i=0; i<width()*height()*4-4; i+=4) {
+ var tmp = *(p + i);
+ *(p + i) = *(p + i + 2);
+ *(p + i + 2) = tmp;
+ }
+
+ return pixbuf;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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() {
+ if (this.surface != null)
+ return this.surface.get_width();
+ return 0;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the height of the image in pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ public int height() {
+ if (this.surface != null)
+ return this.surface.get_height();
+ return 0;
+ }
+}
+
+}
diff --git a/src/images/renderedText.vala b/src/images/renderedText.vala
new file mode 100644
index 0000000..2f4b82f
--- /dev/null
+++ b/src/images/renderedText.vala
@@ -0,0 +1,152 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A class representing string, rendered on an Image.
+/////////////////////////////////////////////////////////////////////////
+
+public class RenderedText : Image {
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new image representation of a string.
+ /////////////////////////////////////////////////////////////////////
+
+ public RenderedText(string text, int width, int height, string font,
+ Color color, double scale) {
+
+ this.render_text(text, width, height, font, color, scale);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new image representation of a string. This
+ /// string may contain markup information.
+ /////////////////////////////////////////////////////////////////////
+
+ public RenderedText.with_markup(string text, int width, int height, string font,
+ Color color, double scale) {
+
+ this.render_markup(text, width, height, font, color, scale);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new transparent image, with text written onto.
+ /////////////////////////////////////////////////////////////////////
+
+ public void render_text(string text, int width, int height, string font,
+ Color color, double scale) {
+
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+
+ if (text != "") {
+
+ var ctx = this.context();
+
+ // set the 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() * 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 = "";
+
+ for (int i=0; i<layout.get_line_count(); ++i) {
+
+ string next_line = "";
+ if (i == layout.get_line_count() -1)
+ next_line = text.substring(layout.get_line(i).start_index, -1);
+ else
+ next_line = text.substring(layout.get_line(i).start_index, layout.get_line(i).length);
+
+ if (broken_string == "") {
+ broken_string = next_line;
+ } else if (next_line != "") {
+ // test whether the addition of a line would cause the height to become too large
+ string broken_string_tmp = broken_string + "\n" + next_line;
+
+ var layout_tmp = Pango.cairo_create_layout(ctx);
+ layout_tmp.set_width(Pango.units_from_double(width));
+
+ layout_tmp.set_font_description(font_description);
+
+ layout_tmp.set_text(broken_string_tmp, -1);
+ Pango.Rectangle extents;
+ layout_tmp.get_pixel_extents(null, out extents);
+
+ if (extents.height > height) broken_string = broken_string + next_line;
+ else broken_string = broken_string_tmp;
+ }
+ }
+
+ layout.set_text(broken_string, -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);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a new transparent image, with text written onto.
+ /////////////////////////////////////////////////////////////////////
+
+ public void render_markup(string text, int width, int height, string font,
+ Color color, double scale) {
+
+ this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+
+ var ctx = this.context();
+
+ // set the 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() * scale));
+
+ layout.set_font_description(font_description);
+ layout.set_markup(text, -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/images/themedIcon.vala b/src/images/themedIcon.vala
new file mode 100644
index 0000000..9f4ca8a
--- /dev/null
+++ b/src/images/themedIcon.vala
@@ -0,0 +1,130 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A class representing a square-shaped icon, themed according to the
+/// current theme of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class ThemedIcon : Image {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Paint a slice icon according to the current theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public ThemedIcon(string caption, string icon_name, bool active) {
+
+ // 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 = 1;
+ foreach (var layer in layers) {
+ if (layer.image != null && 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.image != null && layer.layer_type == SliceLayer.Type.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.visibility == SliceLayer.Visibility.ANY ||
+ (Config.global.show_captions == (layer.visibility == SliceLayer.Visibility.WITH_CAPTION))) {
+
+ if (layer.colorize) {
+ ctx.push_group();
+ }
+
+ if (layer.layer_type == SliceLayer.Type.ICON) {
+ ctx.push_group();
+
+ ctx.translate(layer.x, layer.y);
+ 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.translate(-layer.x, -layer.y);
+
+ ctx.pop_group_to_source();
+ ctx.paint();
+ ctx.set_operator(Cairo.Operator.OVER);
+
+ } else if (layer.layer_type == SliceLayer.Type.CAPTION) {
+ Image text = new RenderedText(caption, layer.width, layer.height, layer.font, layer.color, Config.global.global_scale);
+ ctx.translate(layer.x, layer.y);
+ text.paint_on(ctx);
+ ctx.translate(-layer.x, -layer.y);
+ } else if (layer.layer_type == SliceLayer.Type.FILE) {
+ ctx.translate(layer.x, layer.y);
+ layer.image.paint_on(ctx);
+ ctx.translate(-layer.x, -layer.y);
+ }
+
+ // 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();
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the size of the icon in pixels. Greetings to Liskov.
+ /////////////////////////////////////////////////////////////////////
+
+ public int size() {
+ return base.width();
+ }
+}
+
+}
diff --git a/src/pies/defaultConfig.vala b/src/pies/defaultConfig.vala
new file mode 100644
index 0000000..8763a1d
--- /dev/null
+++ b/src/pies/defaultConfig.vala
@@ -0,0 +1,74 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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"), "media-playback-start", new Trigger.from_string("<Control><Alt>m"));
+ multimedia.add_action(new KeyAction(_("Next Track"), "media-skip-forward", "XF86AudioNext", true));
+ multimedia.add_action(new KeyAction(_("Stop"), "media-playback-stop", "XF86AudioStop"));
+ multimedia.add_action(new KeyAction(_("Previous Track"), "media-skip-backward", "XF86AudioPrev"));
+ multimedia.add_action(new KeyAction(_("Play/Pause"), "media-playback-start", "XF86AudioPlay"));
+
+ // add a pie with the users default applications
+ var apps = PieManager.create_persistent_pie(_("Applications"), "applications-accessories", new Trigger.from_string("<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", new Trigger.from_string("<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"), "system-log-out", new Trigger.from_string("<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"), "start-here", new Trigger.from_string("<Control><Alt>space"));
+ menu.add_group(new MenuGroup(menu.id));
+
+ // add a pie with window controls
+ var window = PieManager.create_persistent_pie(_("Window"), "preferences-system-windows", new Trigger.from_string("<Control><Alt>w"));
+ window.add_action(new KeyAction(_("Scale"), "go-top", "<Control><Alt>s"));
+ window.add_action(new KeyAction(_("Minimize"), "go-bottom", "<Alt>F9", true));
+ window.add_action(new KeyAction(_("Close"), "window-close", "<Alt>F4"));
+ window.add_action(new KeyAction(_("Maximize"), "view-fullscreen", "<Alt>F10"));
+ window.add_action(new KeyAction(_("Restore"), "view-restore", "<Alt>F5"));
+
+ // add a pie with window list group
+ var alt_tab = PieManager.create_persistent_pie("Alt Tab", "dock", new Trigger.from_string("<Control><Alt>T"));
+ alt_tab.add_group(new WindowListGroup(alt_tab.id));
+
+ // 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..0dfb423
--- /dev/null
+++ b/src/pies/load.vala
@@ -0,0 +1,208 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+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;
+ }
+
+ message("Loading Pies from \"" + Paths.pie_config + "\".");
+
+ // 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;
+ }
+ }
+ }
+ } 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 quickaction = -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":
+ quickaction = 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, new Trigger.from_string(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 quickaction = 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":
+ quickaction = bool.parse(attr_content);
+ break;
+ default:
+ warning("Invalid attribute \"" + attr_name + "\" in <slice> element in pies.conf!");
+ break;
+ }
+ }
+
+ // create a new Action according to the loaded type
+ Action action = ActionRegistry.create_action(type, name, icon, command, quickaction);
+
+ 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;
+
+ if (attr_name == "type") {
+ type = attr_content;
+ break;
+ }
+ }
+
+ ActionGroup group = GroupRegistry.create_group(type, pie.id);
+ group.on_load(slice);
+
+ 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..1699ada
--- /dev/null
+++ b/src/pies/pie.vala
@@ -0,0 +1,122 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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, int at_position = -1) {
+ var group = new ActionGroup(this.id);
+ group.add_action(action);
+ this.add_group(group, at_position);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds an ActionGroup to this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_group(ActionGroup group, int at_position = -1) {
+ if (group.has_quickaction()) {
+ foreach (var action_group in action_groups)
+ action_group.disable_quickactions();
+ }
+
+ if (at_position < 0 || at_position >= this.action_groups.size)
+ this.action_groups.add(group);
+ else
+ this.action_groups.insert(at_position, group);
+ }
+
+ public void remove_group(int index) {
+ if (this.action_groups.size > index)
+ this.action_groups.remove_at(index);
+ }
+
+ public void move_group(int from, int to) {
+ if (this.action_groups.size > from && this.action_groups.size > to) {
+ var tmp = this.action_groups[from];
+ this.remove_group(from);
+ this.add_group(tmp, to);
+ }
+ }
+
+ public void update_group(ActionGroup group, int index) {
+ if (this.action_groups.size > index)
+ this.action_groups.set(index, group);
+ }
+}
+
+}
+
diff --git a/src/pies/pieManager.vala b/src/pies/pieManager.vala
new file mode 100644
index 0000000..d2cc837
--- /dev/null
+++ b/src/pies/pieManager.vala
@@ -0,0 +1,347 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 PieWindows which are currently opened. Should be
+ /// rarely more than two...
+ /////////////////////////////////////////////////////////////////////
+
+ public static Gee.HashSet<PieWindow?> opened_windows { 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_active = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Storing the position of the last Pie. Used for subpies, which are
+ /// opened at their parents location.
+ /////////////////////////////////////////////////////////////////////
+
+ private static int last_x = 0;
+ private static int last_y = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes all Pies. They are loaded from the pies.conf file.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+ all_pies = new Gee.HashMap<string, Pie?>();
+ opened_windows = new Gee.HashSet<PieWindow?>();
+ 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_active) {
+ Pie? pie = all_pies[id];
+
+ if (pie != null) {
+
+ a_pie_is_active = true;
+
+ //change WM_CLASS so launchers can track windows properly
+ Gdk.set_program_class("gnome-pie-" + id);
+
+ var window = new PieWindow();
+ window.load_pie(pie);
+
+ window.on_closed.connect(() => {
+ opened_windows.remove(window);
+ if (opened_windows.size == 0) {
+ Icon.clear_cache();
+ }
+ });
+
+ window.on_closing.connect(() => {
+ window.get_center_pos(out last_x, out last_y);
+ a_pie_is_active = false;
+ });
+
+ opened_windows.add(window);
+
+ window.open();
+
+ //restore default WM_CLASS after window open
+ Gdk.set_program_class("Gnome-pie");
+
+ } else {
+ warning("Failed to open pie with ID \"" + id + "\": ID does not exist!");
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Prints the names of all pies with their IDs.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void print_ids() {
+ foreach(var pie in all_pies.entries) {
+ if (pie.value.id.length == 3) {
+ message(pie.value.id + " " + pie.value.name);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Bind the Pie with the given ID to the given trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void bind_trigger(Trigger trigger, string id) {
+ bindings.unbind(id);
+ bindings.bind(trigger, id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if the pie with the given id is in turbo mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool get_is_turbo(string id) {
+ return bindings.get_is_turbo(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if the pie with the given id opens in the middle of
+ /// the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool get_is_centered(string id) {
+ return bindings.get_is_centered(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if the mouse pointer will be warped to the center of
+ /// the pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool get_is_warp(string id) {
+ return bindings.get_is_warp(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if the pie with the given id is auto shaped
+ /////////////////////////////////////////////////////////////////////
+
+ public static bool get_is_auto_shape(string id) {
+ return bindings.get_is_auto_shape(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the prefered pie shape number
+ /////////////////////////////////////////////////////////////////////
+
+ public static int get_shape_number(string id) {
+ return bindings.get_shape_number(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;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name ID of the Pie bound to the given Trigger.
+ /// Returns "" if there is nothing bound to this trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string get_assigned_id(Trigger trigger) {
+ return bindings.get_assigned_id(trigger);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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, Trigger? hotkey, string? desired_id = null) {
+ Pie pie = create_pie(name, icon_name, 100, 999, desired_id);
+
+ if (hotkey != null) 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!");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a desktop file for which opens the Pie with given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public 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=%s -o %s\n".printf(Paths.executable, pie.id) +
+ "Encoding=UTF-8\n" +
+ "Type=Application\n" +
+ "Icon=%s\n".printf(pie.icon) +
+ "StartupWMClass=gnome-pie-%s\n".printf(pie.id);
+
+ // 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);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Deletes the desktop file for the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ 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..efb3fb6
--- /dev/null
+++ b/src/pies/save.vala
@@ -0,0 +1,89 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+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() {
+ message("Saving Pies to \"" + Paths.pie_config + "\".");
+
+ // 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) {
+ int slice_count = 0;
+
+ // 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.descriptions[action.get_type().name()].id);
+ if (ActionRegistry.descriptions[action.get_type().name()].icon_name_editable) {
+ 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_quickaction ? "true" : "false");
+ writer.end_element();
+
+ ++ slice_count;
+ }
+ } else {
+ writer.start_element("group");
+ group.on_save(writer);
+ writer.end_element();
+
+ slice_count += group.actions.size;
+ }
+ }
+ 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..c146216
--- /dev/null
+++ b/src/renderers/centerRenderer.vala
@@ -0,0 +1,232 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Renders the center of a Pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class CenterRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The PieRenderer which owns this CenterRenderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PieRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The caption drawn in the center. Changes when the active slice
+ /// changes.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned Image? caption;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The color of the currently active slice. Used to colorize layers.
+ /////////////////////////////////////////////////////////////////////
+
+ private Color color;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two AnimatedValues: alpha is for global transparency (when
+ /// fading in/out), activity is 1.0 if there is an active slice and
+ /// 0.0 if there is no active slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue activity;
+ private AnimatedValue alpha;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ 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;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initiates the fade-out animation by resetting the targets of the
+ /// AnimatedValues to 0.0.
+ /////////////////////////////////////////////////////////////////////
+
+ 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Should be called if the active slice of the PieRenderer changes.
+ /// The members activity, caption and color are set accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ 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;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws all center layers and the caption.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, int slice_track) {
+ // get all center_layers
+ var layers = Config.global.theme.center_layers;
+
+ // update the AnimatedValues
+ this.activity.update(frame_time);
+ this.alpha.update(frame_time);
+
+ // draw each layer
+ foreach (var layer in layers) {
+ ctx.save();
+
+ // calculate all values needed for animation/drawing
+ 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 = layer.active_rotation_speed*this.activity.val
+ + layer.inactive_rotation_speed*(1.0-this.activity.val);
+ CenterLayer.RotationMode rotation_mode = ((this.activity.val > 0.5) ?
+ layer.active_rotation_mode : layer.inactive_rotation_mode);
+
+ double direction = 0;
+
+ if (rotation_mode == CenterLayer.RotationMode.TO_MOUSE) {
+ direction = angle;
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_ACTIVE) {
+ double slice_angle = parent.total_slice_count > 0 ? 2*PI/parent.total_slice_count : 0;
+ direction = (int)((angle+0.5*slice_angle) / (slice_angle))*slice_angle;
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_SECOND) {
+ var now = new DateTime.now_local();
+ direction = 2*PI*(now.get_second()+60-15)/60;
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_MINUTE) {
+ var now = new DateTime.now_local();
+ direction = 2*PI*(now.get_minute()+60-15)/60;
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_HOUR_24) {
+ var now = new DateTime.now_local();
+ direction = 2*PI*(now.get_hour()+24-6)/24 + 2*PI*(now.get_minute())/(60*24);
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_HOUR_12) {
+ var now = new DateTime.now_local();
+ direction = 2*PI*(now.get_hour()+12-3)/12 + 2*PI*(now.get_minute())/(60*12);
+ }
+
+ if (rotation_mode == CenterLayer.RotationMode.AUTO) {
+ layer.rotation += max_rotation_speed*frame_time;
+ } else {
+ direction = Math.fmod(direction, 2*PI);
+ double diff = direction-layer.rotation;
+ 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 = direction;
+ else {
+ if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
+ else layer.rotation -= step;
+ }
+ }
+
+ layer.rotation = fmod(layer.rotation+2*PI, 2*PI);
+
+ if (colorize > 0.0) ctx.push_group();
+
+ // transform the context
+ ctx.rotate(layer.rotation);
+ ctx.scale(max_scale, max_scale);
+
+ // paint the layer
+ layer.image.paint_on(ctx, this.alpha.val*max_alpha);
+
+ // colorize it, if necessary
+ 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();
+ ctx.translate(this.parent.center_x, (int)(Config.global.theme.caption_position) + this.parent.center_y);
+ caption.paint_on(ctx, this.activity.val*this.alpha.val);
+ ctx.restore();
+ }
+
+ //scroll pie
+ if (this.alpha.val > 0.1
+ && this.parent.original_visible_slice_count < this.parent.slice_count()
+ && this.parent.original_visible_slice_count > 0) {
+ int np= (this.parent.slice_count()+this.parent.original_visible_slice_count -1)/this.parent.original_visible_slice_count;
+ int cp= this.parent.first_slice_idx / this.parent.original_visible_slice_count;
+ int r= 8; //circle radious
+ int dy= 20; //distance between circles
+ double a= 0.8 * this.alpha.val;
+ int dx= (int)Config.global.theme.center_radius + r + 10;
+ if (this.parent.center_x + dx > this.parent.size_w)
+ dx= -dx; //no right side, put scroll in the left size
+ ctx.save();
+ ctx.identity_matrix();
+ ctx.translate(this.parent.center_x + dx, this.parent.center_y - (np-1)*dy/2);
+ for (int i=0; i<np; i++) {
+ ctx.arc( 0, 0, r, 0, 2*PI );
+ if (i == cp){
+ ctx.set_source_rgba(0.3,0.3,0.3, a); //light gray stroke
+ ctx.stroke_preserve();
+ ctx.set_source_rgba(1,1,1, a); //white fill
+ ctx.fill(); //current
+ } else {
+ ctx.set_source_rgba(1,1,1, a); //white stroke
+ ctx.stroke_preserve();
+ ctx.set_source_rgba(0.3,0.3,0.3, a/4); //light gray fill
+ ctx.fill(); //current
+ }
+ ctx.translate(0, dy);
+ }
+ ctx.restore();
+ }
+ }
+}
+
+}
diff --git a/src/renderers/pieRenderer.vala b/src/renderers/pieRenderer.vala
new file mode 100644
index 0000000..1ff2b3e
--- /dev/null
+++ b/src/renderers/pieRenderer.vala
@@ -0,0 +1,890 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+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 {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of the slice used for quick action. (The action which
+ /// gets executed when the user clicks on the middle of the pie)
+ /////////////////////////////////////////////////////////////////////
+
+ public int quickaction { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of the currently active slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public int active_slice { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the hot keys are currently displayed.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool show_hotkeys { get; set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The width and height of the Pie in pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ public int size_w { get; private set; }
+ public int size_h { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Center position relative to window top-left corner
+ /////////////////////////////////////////////////////////////////////
+
+ public int center_x { get; private set; }
+ public int center_y { get; private set; }
+
+
+ ////////////////////////////////////////////////////////////////////
+ /// Possible show pie modes.
+ /// FULL_PIE: Show the pie as a complete circle.
+ /// HPIE_LEFT: Eat half pie so it can be shown at the left of the screen.
+ /// HPIE_RIGHT: Eat half pie so it can be shown at the right of the screen.
+ /// HPIE_TOP: Eat half pie so it can be shown at the top of the screen.
+ /// HPIE_BOTTOM: Eat half pie so it can be shown at the bottom of the screen.
+ /// CPIE_TOP_LEFT: Eat 3/4 pie so it can be shown at the top-left corner.
+ /// CPIE_TOP_RIGHT: Eat 3/4 pie so it can be shown at the top-right corner.
+ /// CPIE_BOT_LEFT: Eat 3/4 pie so it can be shown at the bottom-left corner.
+ /// CPIE_BOT_RIGHT: Eat 3/4 pie so it can be shown at the bottom-right corner.
+ /////////////////////////////////////////////////////////////////////
+
+ public enum ShowPieMode {
+ FULL_PIE,
+ HPIE_LEFT, HPIE_RIGHT, HPIE_TOP, HPIE_BOTTOM,
+ CPIE_TOP_LEFT, CPIE_TOP_RIGHT, CPIE_BOT_LEFT, CPIE_BOT_RIGHT}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Show pie mode: full, half-circle, corner
+ /////////////////////////////////////////////////////////////////////
+
+ public ShowPieMode pie_show_mode { get; private set; default= ShowPieMode.FULL_PIE; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Number of visible slices
+ /////////////////////////////////////////////////////////////////////
+
+ public int visible_slice_count { get; private set; }
+
+ public int original_visible_slice_count { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Number of slices in full pie (visible or not)
+ /////////////////////////////////////////////////////////////////////
+
+ public int total_slice_count { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Maximun number of visible slices in a full pie
+ /////////////////////////////////////////////////////////////////////
+
+ public int max_visible_slices { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of the first visible slice
+ /////////////////////////////////////////////////////////////////////
+
+ public int first_slice_idx { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Angular position of the first visible slice
+ /////////////////////////////////////////////////////////////////////
+
+ public double first_slice_angle { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Index of the slice where to go when up/down/left/right key is pressed
+ /// or -1 if that side of the pie was eaten
+ /////////////////////////////////////////////////////////////////////
+
+ public int up_slice_idx { get; private set; }
+ public int down_slice_idx { get; private set; }
+ public int left_slice_idx { get; private set; }
+ public int right_slice_idx { get; private set; }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the currently loaded Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public string id { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if the pie is currently navigated with the keyboard. This is
+ /// set to false as soon as the mouse moves.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool key_board_control { get; set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// All SliceRenderers used to draw this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gee.ArrayList<SliceRenderer?> slices;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The renderer for the center of this pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private CenterRenderer center;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Maximum distance from the center that activates the slices
+ /////////////////////////////////////////////////////////////////////
+ private int activation_range;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes members.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieRenderer() {
+ this.slices = new Gee.ArrayList<SliceRenderer?>();
+ this.center = new CenterRenderer(this);
+ this.quickaction = -1;
+ this.active_slice = -2;
+ this.size_w = 0;
+ this.size_h = 0;
+ this.activation_range= 300;
+
+ this.max_visible_slices= Config.global.max_visible_slices;
+
+ set_show_mode(ShowPieMode.FULL_PIE);
+ }
+
+
+ private void get_mouse_and_screen(out int mousex, out int mousey, out int screenx, out int screeny) {
+ // get the mouse position and screen resolution
+ double x = 0.0;
+ double y = 0.0;
+
+ var display = Gdk.Display.get_default();
+ var manager = display.get_device_manager();
+ GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER);
+
+ foreach(var device in list) {
+ if (device.input_source != Gdk.InputSource.KEYBOARD) {
+ Gdk.Screen screen;
+ device.get_position( out screen, out x, out y );
+ }
+ }
+ mousex= (int) x;
+ mousey= (int) y;
+ screenx= Gdk.Screen.width();
+ screeny= Gdk.Screen.height();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads a Pie. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_pie(Pie pie) {
+ this.slices.clear();
+
+ this.id = pie.id;
+
+ 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_quickaction) {
+ this.quickaction = count;
+ }
+
+ ++count;
+ }
+ }
+
+ this.select_by_index(this.quickaction);
+
+
+ ShowPieMode showpie= ShowPieMode.FULL_PIE;
+ //set full pie to determine the number of visible slices
+ set_show_mode(showpie);
+
+ int sz0= (int)fmax(2*Config.global.theme.radius + 2*Config.global.theme.visible_slice_radius*Config.global.theme.max_zoom,
+ 2*Config.global.theme.center_radius);
+
+ int sz= sz0;
+ // increase size if there are many slices
+ if (this.total_slice_count > 0) {
+ sz = (int)fmax(sz0,
+ (((Config.global.theme.slice_radius + Config.global.theme.slice_gap)/tan(PI/this.total_slice_count))
+ + Config.global.theme.visible_slice_radius)*2*Config.global.theme.max_zoom);
+ }
+
+
+
+
+ // get mouse position and screen resolution
+ int mouse_x, mouse_y, screen_x, screen_y;
+ get_mouse_and_screen( out mouse_x, out mouse_y, out screen_x, out screen_y );
+
+ //reduce the window size if needed to get closer to the actual mouse position
+ int reduce_szx= 1;
+ int reduce_szy= 1;
+
+ if (PieManager.get_is_auto_shape(pie.id) && !PieManager.get_is_centered(pie.id)) {
+ //set the best show mode that put the mouse near the center
+ if (mouse_x < sz/2) {
+ if (mouse_y < sz/2)
+ showpie= ShowPieMode.CPIE_TOP_LEFT; //show 1/4 pie
+ else if (screen_y > 0 && screen_y-mouse_y < sz/2)
+ showpie= ShowPieMode.CPIE_BOT_LEFT; //show 1/4 pie
+ else
+ showpie= ShowPieMode.HPIE_LEFT; //show 1/2 pie
+
+ } else if (mouse_y < sz/2) {
+ if (screen_x > 0 && screen_x-mouse_x < sz/2)
+ showpie= ShowPieMode.CPIE_TOP_RIGHT; //show 1/4 pie
+ else
+ showpie= ShowPieMode.HPIE_TOP; //show 1/2 pie
+
+ } else if (screen_x > 0 && screen_x-mouse_x < sz/2) {
+ if (screen_y > 0 && screen_y-mouse_y < sz/2)
+ showpie= ShowPieMode.CPIE_BOT_RIGHT; //show 1/4 pie
+ else
+ showpie= ShowPieMode.HPIE_RIGHT; //show 1/2 pie
+
+ } else if (screen_y > 0 && screen_y-mouse_y < sz/2)
+ showpie= ShowPieMode.HPIE_BOTTOM; //show 1/2 pie
+
+
+ } else {
+ //if the pie is centered in the screen, don't reduce the size
+ if (PieManager.get_is_centered(pie.id)) {
+ reduce_szx= 0;
+ reduce_szy= 0;
+ }
+
+ //select the configured shape
+ //convert from radio-buttum number to ShowPieMode enum
+ switch( PieManager.get_shape_number(pie.id) ) {
+ case 1:
+ showpie= ShowPieMode.CPIE_BOT_RIGHT;
+ if (screen_x-mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ if (screen_y-mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ case 2:
+ showpie= ShowPieMode.HPIE_RIGHT;
+ if (screen_x-mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ break;
+ case 3:
+ showpie= ShowPieMode.CPIE_TOP_RIGHT;
+ if (screen_x-mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ if (mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ case 4:
+ showpie= ShowPieMode.HPIE_BOTTOM;
+ if (screen_y-mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ case 6:
+ showpie= ShowPieMode.HPIE_TOP;
+ if (mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ case 7:
+ showpie= ShowPieMode.CPIE_BOT_LEFT;
+ if (mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ if (screen_y-mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ case 8:
+ showpie= ShowPieMode.HPIE_LEFT;
+ if (mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ break;
+ case 9:
+ showpie= ShowPieMode.CPIE_TOP_LEFT;
+ if (mouse_x > sz/2)
+ reduce_szx= 0; //keep full width
+ if (mouse_y > sz/2)
+ reduce_szy= 0; //keep full height
+ break;
+ }
+ }
+ //set the new show pie mode
+ set_show_mode(showpie);
+
+ //recalc size
+ sz = sz0;
+ if (this.total_slice_count > 0) {
+ sz = (int)fmax(sz0,
+ (((Config.global.theme.slice_radius + Config.global.theme.slice_gap)/tan(PI/this.total_slice_count))
+ + Config.global.theme.visible_slice_radius)*2*Config.global.theme.max_zoom);
+ }
+ //activation_range = normal pie radius + "outer" activation_range
+ this.activation_range= (int)((double)Config.global.activation_range + sz/(2*Config.global.theme.max_zoom));
+
+ int szx = 1; //full width
+ int szy = 1; //full height
+ switch(this.pie_show_mode) {
+ //half pie
+ case ShowPieMode.HPIE_LEFT:
+ szx = 0; //half width, center to the left
+ break;
+ case ShowPieMode.HPIE_RIGHT:
+ szx = 2; //half width, center to the right
+ break;
+ case ShowPieMode.HPIE_TOP:
+ szy = 0; //half height, center to the top
+ break;
+ case ShowPieMode.HPIE_BOTTOM:
+ szy = 2; //half height, center to the bottom
+ break;
+
+ //cuarter pie
+ case ShowPieMode.CPIE_TOP_LEFT:
+ szx = 0; //half width, center to the left
+ szy = 0; //half height, center to the top
+ break;
+ case ShowPieMode.CPIE_TOP_RIGHT:
+ szx = 2; //half width, center to the right
+ szy = 0; //half height, center to the top
+ break;
+ case ShowPieMode.CPIE_BOT_LEFT:
+ szx = 0; //half width, center to the left
+ szy = 2; //half height, center to the bottom
+ break;
+ case ShowPieMode.CPIE_BOT_RIGHT:
+ szx = 2; //half width, center to the right
+ szy = 2; //half height, center to the bottom
+ break;
+ }
+ if (reduce_szx == 0)
+ szx = 1; //don't reduce width
+ if (reduce_szy == 0)
+ szy = 1; //don't reduce height
+
+ int rc = (int)Config.global.theme.center_radius;
+ if (szx == 1 ) {
+ //full width
+ this.size_w = sz;
+ this.center_x = sz/2; //center position
+ } else {
+ //half width
+ this.size_w = sz/2 + rc;
+ if (szx == 0) {
+ this.center_x = rc; //center to the left
+ } else {
+ this.center_x = this.size_w-rc; //center to the right
+ }
+ }
+ if (szy == 1) {
+ //full heigth
+ this.size_h = sz;
+ this.center_y = sz/2; //center position
+ } else {
+ //half heigth
+ this.size_h = sz/2 + rc;
+ if (szy == 0) {
+ this.center_y = rc; //center to the top
+ } else {
+ this.center_y = this.size_h-rc; //center to the bottom
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Activates the currently active slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public void activate(uint32 time_stamp) {
+ if (this.active_slice >= this.first_slice_idx
+ && this.active_slice < this.first_slice_idx+this.visible_slice_count) {
+ slices[active_slice].activate(time_stamp);
+ }
+
+ //foreach (var slice in this.slices)
+ // slice.fade_out();
+ for (int i= 0; i < this.visible_slice_count; ++i) {
+ this.slices[ i+this.first_slice_idx ].fade_out();
+ }
+
+ center.fade_out();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Asks all renders to fade out.
+ /////////////////////////////////////////////////////////////////////
+
+ public void cancel() {
+ //foreach (var slice in this.slices)
+ // slice.fade_out();
+ for (int i= 0; i < this.visible_slice_count; ++i) {
+ this.slices[ i+this.first_slice_idx ].fade_out();
+ }
+
+ center.fade_out();
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the up-key is pressed. Selects the next slice towards
+ /// the top.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_up() {
+ move_active_slice(this.up_slice_idx, this.down_slice_idx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the down-key is pressed. Selects the next slice
+ /// towards the bottom.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_down() {
+ move_active_slice(this.down_slice_idx, this.up_slice_idx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the left-key is pressed. Selects the next slice
+ /// towards the left.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_left() {
+ move_active_slice(this.left_slice_idx, this.right_slice_idx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the right-key is pressed. Selects the next slice
+ /// towards the right.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_right() {
+ move_active_slice(this.right_slice_idx, this.left_slice_idx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the page_up-key is pressed. Selects the next
+ /// group of slices.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_nextpage() {
+ if (this.first_slice_idx+this.visible_slice_count < slices.size) {
+ //advance one page
+ this.first_slice_idx += this.visible_slice_count;
+ if (this.first_slice_idx+this.visible_slice_count >= slices.size) {
+ this.visible_slice_count= slices.size - this.first_slice_idx;
+ }
+ this.reset_slice_anim();
+ this.select_by_index(-1);
+ calc_key_navigation_pos();
+ this.key_board_control = true;
+
+ } else if (this.first_slice_idx > 0) {
+ //go to first page
+ this.first_slice_idx= 0;
+ this.reset_slice_anim();
+ //recover the original value
+ this.visible_slice_count= this.original_visible_slice_count;
+ this.reset_slice_anim();
+ this.select_by_index(-1);
+ calc_key_navigation_pos();
+ this.key_board_control = true;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the page_down-key is pressed. Selects the previous
+ /// group of slices.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_prevpage() {
+ if (this.first_slice_idx > 0) {
+ //go back one page
+ //recover the original value
+ this.visible_slice_count= this.original_visible_slice_count;
+ this.first_slice_idx -= this.visible_slice_count;
+ if (this.first_slice_idx < 0) {
+ this.first_slice_idx= 0;
+ }
+ this.reset_slice_anim();
+ this.select_by_index(-1);
+ calc_key_navigation_pos();
+ this.key_board_control = true;
+
+ } else if (this.visible_slice_count < slices.size) {
+ //go to last page
+ int n= slices.size % this.original_visible_slice_count;
+ if (n == 0)
+ //all pages have the same number of slices
+ this.visible_slice_count= this.original_visible_slice_count;
+ else
+ //last page has less slices than previous
+ this.visible_slice_count= n;
+ this.first_slice_idx= slices.size - this.visible_slice_count;
+ this.reset_slice_anim();
+ this.select_by_index(-1);
+ calc_key_navigation_pos();
+ this.key_board_control = true;
+ }
+ }
+
+ private void reset_slice_anim() {
+ //reset animation values in all the new visible slices
+ for (int i= 0; i < this.visible_slice_count; ++i)
+ this.slices[ i+this.first_slice_idx ].reset_anim();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects a slice based on a search string.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_by_string(string search) {
+ float max_similarity = 0;
+ int index = -1;
+
+ for (int i=0; i<this.visible_slice_count; ++i) {
+ float similarity = 0;
+ int cur_pos = 0;
+ var name = slices[this.first_slice_idx+i].action.name.down();
+
+ for (int j=0; j<search.length; ++j) {
+ int next_pos = name.index_of(search.substring(j, 1), cur_pos);
+
+ if (next_pos != -1) {
+ cur_pos = next_pos;
+ similarity += (float)(name.length-next_pos)/name.length + 2;
+ }
+ }
+
+ if (similarity > max_similarity) {
+ index = this.first_slice_idx+i;
+ max_similarity = similarity;
+ }
+ }
+
+ if (index >= 0 && index < slice_count()) {
+ key_board_control = true;
+ select_by_index(index);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the amount of slices in this pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public int slice_count() {
+ return slices.size;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the entire pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx, int mouse_x, int mouse_y) {
+ if (this.size_w > 0) {
+ double distance = sqrt(mouse_x*mouse_x + mouse_y*mouse_y);
+ double angle = 0.0;
+ int slice_track= 0;
+
+ if (this.key_board_control) {
+ int n= this.active_slice - this.first_slice_idx;
+ angle = 2.0*PI*n/(double)this.total_slice_count + this.first_slice_angle;
+ slice_track= 1;
+ } 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.quickaction >= this.first_slice_idx
+ && this.quickaction < this.first_slice_idx+this.visible_slice_count) {
+
+ next_active_slice = this.quickaction;
+ int n= this.quickaction - this.first_slice_idx;
+ angle = 2.0*PI*n/(double)this.total_slice_count + this.first_slice_angle;
+
+ } else if (distance > Config.global.theme.active_radius && this.total_slice_count > 0
+ && distance < this.activation_range) {
+ double a= angle-this.first_slice_angle;
+ if (a < 0)
+ a= a + 2*PI;
+ next_active_slice = (int)(a*this.total_slice_count/(2*PI) + 0.5) % this.total_slice_count;
+ if (next_active_slice >= this.visible_slice_count)
+ next_active_slice = -1;
+ else {
+ next_active_slice = next_active_slice + this.first_slice_idx;
+ slice_track= 1;
+ }
+ } else {
+ next_active_slice = -1;
+ }
+
+ this.select_by_index(next_active_slice);
+ }
+
+ center.draw(frame_time, ctx, angle, slice_track);
+
+ for (int i= 0; i < this.visible_slice_count; ++i) {
+ this.slices[ i+this.first_slice_idx ].draw(frame_time, ctx, angle, slice_track);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user moves the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_move() {
+ this.key_board_control = false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the currently active slice changes.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_by_index(int index) {
+ if (index != this.active_slice) {
+ if (index >= this.first_slice_idx && index < this.first_slice_idx+this.visible_slice_count)
+ this.active_slice = index;
+ else
+ this.active_slice = -1;
+
+ SliceRenderer? active = (this.active_slice >= 0 && this.active_slice < slices.size) ?
+ this.slices[this.active_slice] : null;
+
+ center.set_active_slice(active);
+
+ for (int i= 0; i < this.visible_slice_count; ++i) {
+ this.slices[ i+this.first_slice_idx ].set_active_slice(active);
+ }
+ }
+ }
+
+ private void set_show_mode(ShowPieMode show_mode) {
+ //The index of the first visible slice
+ this.first_slice_idx= 0;
+ //Angular position of the first visible slice
+ this.first_slice_angle= 0;
+
+ int mult= 1;
+ switch(show_mode) {
+ //half pie
+ case ShowPieMode.HPIE_LEFT:
+ mult= 2;
+ this.first_slice_angle= -PI/2;
+ break;
+ case ShowPieMode.HPIE_RIGHT:
+ mult= 2;
+ this.first_slice_angle= PI/2;
+ break;
+ case ShowPieMode.HPIE_TOP:
+ mult= 2;
+ break;
+ case ShowPieMode.HPIE_BOTTOM:
+ this.first_slice_angle= PI;
+ mult= 2;
+ break;
+
+ //cuarter pie
+ case ShowPieMode.CPIE_TOP_LEFT:
+ mult= 4;
+ break;
+ case ShowPieMode.CPIE_TOP_RIGHT:
+ this.first_slice_angle= PI/2;
+ mult= 4;
+ break;
+ case ShowPieMode.CPIE_BOT_LEFT:
+ this.first_slice_angle= -PI/2;
+ mult= 4;
+ break;
+ case ShowPieMode.CPIE_BOT_RIGHT:
+ this.first_slice_angle= PI;
+ mult= 4;
+ break;
+
+ default: //ShowPieMode.FULL_PIE or invalid values
+ show_mode= ShowPieMode.FULL_PIE;
+ break;
+ }
+ this.pie_show_mode= show_mode;
+ //limit the number of visible slices
+ int maxview= this.max_visible_slices / mult;
+ //Number of visible slices
+ this.visible_slice_count= (int)fmin(slices.size, maxview);
+ //Number of slices in full pie (visible or not)
+ this.total_slice_count= this.visible_slice_count*mult;
+ if (mult > 1 && slices.size > 1) {
+ this.total_slice_count -= mult;
+ }
+
+ //keep a copy of the original value since page up/down change it
+ original_visible_slice_count= visible_slice_count;
+
+ calc_key_navigation_pos();
+ }
+
+ private void calc_key_navigation_pos() {
+ //calc slices index for keyboard navigation
+
+ int a= this.first_slice_idx;
+ int b= this.first_slice_idx + this.visible_slice_count/4;
+ int c= this.first_slice_idx + this.visible_slice_count/2;
+ int d= this.first_slice_idx + (this.visible_slice_count*3)/4;
+ int e= this.first_slice_idx + this.visible_slice_count -1;
+ switch(this.pie_show_mode) {
+ //half pie
+ case ShowPieMode.HPIE_LEFT:
+ this.up_slice_idx= a;
+ this.right_slice_idx= c;
+ this.down_slice_idx= e;
+ this.left_slice_idx= -1; //no left slice, go up instead
+ break;
+ case ShowPieMode.HPIE_RIGHT:
+ this.down_slice_idx= a;
+ this.left_slice_idx= c;
+ this.up_slice_idx= e;
+ this.right_slice_idx= -1; //no right slice, go down instead
+ break;
+ case ShowPieMode.HPIE_TOP:
+ this.right_slice_idx= a;
+ this.down_slice_idx= c;
+ this.left_slice_idx= e;
+ this.up_slice_idx= -1; //no up slice, go left instead
+ break;
+ case ShowPieMode.HPIE_BOTTOM:
+ this.left_slice_idx= a;
+ this.up_slice_idx= c;
+ this.right_slice_idx= e;
+ this.down_slice_idx= -1; //no down slice, go right instead
+ break;
+
+ //cuarter pie
+ case ShowPieMode.CPIE_TOP_LEFT:
+ this.right_slice_idx= a;
+ this.down_slice_idx= e;
+ this.up_slice_idx= -1; //no up slice, go right instead
+ this.left_slice_idx= -1; //no left slice, go down instead
+ break;
+ case ShowPieMode.CPIE_TOP_RIGHT:
+ this.down_slice_idx= a;
+ this.left_slice_idx= e;
+ this.up_slice_idx= -1; //no up slice, go left instead
+ this.right_slice_idx= -1; //no righ slice, go down instead
+ break;
+ case ShowPieMode.CPIE_BOT_LEFT:
+ this.up_slice_idx= a;
+ this.right_slice_idx= e;
+ this.down_slice_idx= -1; //no down slice, go right instead
+ this.left_slice_idx= -1; //no left slice, go up instead
+ break;
+ case ShowPieMode.CPIE_BOT_RIGHT:
+ this.left_slice_idx= a;
+ this.up_slice_idx= e;
+ this.down_slice_idx= -1; //no down slice, go left instead
+ this.right_slice_idx= -1; //no right slice, go up instead
+ break;
+
+ default: //ShowPieMode.FULL_PIE or invalid values
+ this.right_slice_idx= a;
+ this.down_slice_idx= b;
+ this.left_slice_idx= c;
+ this.up_slice_idx= d;
+ break;
+ }
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// keyboard navigation helper
+ /// move current position one slice towards the given extreme
+ /////////////////////////////////////////////////////////////////////
+
+ private void move_active_slice(int extreme, int other_extreme ) {
+ int pos= this.active_slice;
+
+ if (pos < 0 || pos == extreme) {
+ //no actual position or allready at the extreme
+ pos= extreme; //go to the extreme pos
+
+ } else if (extreme == -1) {
+ //the extreme was eaten, just go away from the other_extreme
+ if (pos > other_extreme || other_extreme == 0) {
+ if (pos < this.visible_slice_count+this.first_slice_idx-1)
+ pos++;
+ } else if (pos > this.first_slice_idx)
+ pos--;
+
+ } else if (other_extreme == -1) {
+ //the other_extreme was eaten, just get closer to the extreme
+ if (pos < extreme)
+ pos++;
+ else if (pos > extreme)
+ pos--;
+
+ } else if (pos == other_extreme) {
+ //both extremes are present
+ //jump quickly form one extreme to the other
+ pos= extreme; //go to the extreme pos
+
+ } else {
+ //both extremes are present
+ //add or substract 1 to position in a circular manner
+ if (extreme > other_extreme) {
+ if (pos > other_extreme && pos < extreme)
+ //other_extreme < pos < extreme
+ pos= pos+1;
+ else
+ pos= pos-1;
+ } else {
+ if (pos > extreme && pos < other_extreme)
+ //extreme < pos < other_extreme
+ pos= pos-1;
+ else
+ pos= pos+1;
+ }
+
+ if (pos < this.first_slice_idx)
+ pos= this.visible_slice_count-1+this.first_slice_idx;
+
+ if (pos >= this.visible_slice_count+this.first_slice_idx)
+ pos= this.first_slice_idx;
+ }
+
+ this.select_by_index(pos);
+
+ this.key_board_control = true;
+ }
+}
+
+}
diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala
new file mode 100755
index 0000000..5accb15
--- /dev/null
+++ b/src/renderers/pieWindow.vala
@@ -0,0 +1,500 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// An invisible window. Used to draw Pies onto.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieWindow : Gtk.Window {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Signal which gets emitted when the PieWindow is about to close.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_closing();
+
+ /////////////////////////////////////////////////////////////////////
+ /// Signal which gets emitted when the PieWindow is closed.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_closed();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The background image used for fake transparency if
+ /// has_compositing is false.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image background { get; private set; default=null; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The background image position and size.
+ /////////////////////////////////////////////////////////////////////
+
+ private int back_x;
+ private int back_y;
+ private int back_sz_x;
+ private int back_sz_y;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some panels moves the window after it was realized.
+ /// This value set the maximum allowed panel height or width.
+ /// (how many pixels the window could be moved in every direction
+ /// from the screen borders towards the center)
+ /////////////////////////////////////////////////////////////////////
+
+ private int panel_sz = 64;
+
+ /////////////////////////////////////////////////////////////////////
+ /// This value set the maximum allowed mouse movement in pixels
+ /// from the capture to the show point in every direction.
+ /////////////////////////////////////////////////////////////////////
+
+ private int mouse_move = 30;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The owned renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private PieRenderer renderer;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the Pie is currently fading out.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool closing = false;
+ private bool closed = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A timer used for calculating the frame time.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.Timer timer;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the screen supports compositing.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool has_compositing = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// When a Pie is opened, pressed buttons are accumulated and
+ /// matches are searched in all slices.
+ /////////////////////////////////////////////////////////////////////
+
+ private string search_string = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets up the window.
+ /////////////////////////////////////////////////////////////////////
+
+ 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.POPUP_MENU);
+ this.set_decorated(false);
+ this.set_resizable(false);
+ this.icon_name = "gnome-pie";
+ this.set_accept_focus(false);
+ this.app_paintable = true;
+
+ // check for compositing
+ if (this.screen.is_composited()) {
+ this.set_visual(this.screen.get_rgba_visual());
+ this.has_compositing = true;
+ }
+
+ //add_events() call was removed because it causes that gnome-pie sometimes enter
+ //and infinte loop while processing some mouse-motion events.
+ //(this was seen in Ubuntu 14.04.2 64/32-bits -Glib 2.19- and in MATE 14.04.2)
+ // set up event filter
+ //this.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |
+ // Gdk.EventMask.KEY_RELEASE_MASK |
+ // Gdk.EventMask.KEY_PRESS_MASK |
+ // Gdk.EventMask.POINTER_MOTION_MASK |
+ // Gdk.EventMask.SCROLL_MASK );
+
+ // activate on left click
+ this.button_release_event.connect ((e) => {
+ if (e.button == 1 || PieManager.get_is_turbo(this.renderer.id)) this.activate_slice(e.time);
+ return true;
+ });
+
+ // cancel on right click
+ this.button_press_event.connect ((e) => {
+ if (e.button == 3) this.cancel();
+ return true;
+ });
+
+ // remember last pressed key in order to disable key repeat
+ uint last_key = 0;
+ uint32 last_time_stamp = 0;
+ this.key_press_event.connect((e) => {
+ if (e.keyval != last_key) {
+ this.handle_key_press(e.keyval, e.time, last_time_stamp, e.str);
+ last_key = e.keyval;
+ last_time_stamp = e.time;
+ }
+ return true;
+ });
+
+ // activate on key release if turbo_mode is enabled
+ this.key_release_event.connect((e) => {
+ last_key = 0;
+ if (PieManager.get_is_turbo(this.renderer.id))
+ this.activate_slice(e.time);
+ else
+ this.handle_key_release(e.keyval);
+ return true;
+ });
+
+ // notify the renderer of mouse move events
+ this.motion_notify_event.connect((e) => {
+ this.renderer.on_mouse_move();
+ return true;
+ });
+
+ this.show.connect_after(() => {
+ Gtk.grab_add(this);
+ FocusGrabber.grab(this.get_window(), true, true, false);
+ });
+
+ this.scroll_event.connect((e) => {
+ if (e.direction == Gdk.ScrollDirection.UP)
+ this.renderer.select_prevpage();
+
+ else if (e.direction == Gdk.ScrollDirection.DOWN)
+ this.renderer.select_nextpage();
+ return true;
+ });
+
+ // draw the pie on expose
+ this.draw.connect(this.draw_window);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads a Pie to be rendered.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_pie(Pie pie) {
+ this.renderer.load_pie(pie);
+ this.set_window_position(pie);
+ this.set_size_request(renderer.size_w, renderer.size_h);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the window. load_pie should have been called before.
+ /////////////////////////////////////////////////////////////////////
+
+ public void open() {
+ this.realize();
+ // capture the background image if there is no compositing
+ if (!this.has_compositing) {
+ this.get_position(out this.back_x, out this.back_y);
+ this.get_size(out this.back_sz_x, out this.back_sz_y);
+ this.back_sz_x++;
+ this.back_sz_y++;
+
+ int screenx= Gdk.Screen.width();
+ int screeny= Gdk.Screen.height();
+
+ //allow some window movement from the screen borders
+ //(some panels moves the window after it was realized)
+ int dx = this.panel_sz - this.back_x;
+ if (dx > 0)
+ this.back_sz_x += dx;
+ dx = this.panel_sz - (screenx - this.back_x - this.back_sz_x +1);
+ if (dx > 0) {
+ this.back_sz_x += dx;
+ this.back_x -= dx;
+ }
+
+ int dy = this.panel_sz - this.back_y;
+ if (dy > 0)
+ this.back_sz_y += dy;
+ dy = this.panel_sz - (screeny - this.back_y - this.back_sz_y +1);
+ if (dy > 0) {
+ this.back_sz_y += dy;
+ this.back_y -= dy;
+ }
+
+ //also tolerate some mouse movement
+ this.back_x -= this.mouse_move;
+ this.back_sz_x += this.mouse_move*2;
+ this.back_y -= this.mouse_move;
+ this.back_sz_y += this.mouse_move*2;
+
+ //make sure we don't go outside the screen
+ if (this.back_x < 0) {
+ this.back_sz_x += this.back_x;
+ this.back_x = 0;
+ }
+ if (this.back_y < 0) {
+ this.back_sz_y += this.back_y;
+ this.back_y = 0;
+ }
+ if (this.back_x + this.back_sz_x > screenx)
+ this.back_sz_x = screenx - this.back_x;
+ if (this.back_y + this.back_sz_y > screeny)
+ this.back_sz_y = screeny - this.back_y;
+ this.background = new Image.capture_screen(this.back_x, this.back_y, this.back_sz_x, this.back_sz_y);
+ }
+
+ // capture the input focus
+ this.show();
+
+ // start the timer
+ this.timer = new GLib.Timer();
+ this.timer.start();
+ this.queue_draw();
+
+ bool warp_pointer = PieManager.get_is_warp(this.renderer.id);
+
+ // the main draw loop
+ GLib.Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {
+ if (this.closed)
+ return false;
+
+ if (warp_pointer) {
+ warp_pointer = false;
+ int x, y;
+ this.get_center_pos(out x, out y);
+ this.set_mouse_position(x, y);
+ }
+
+ this.queue_draw();
+ return this.visible;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Gets the center position of the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public void get_center_pos(out int out_x, out int out_y) {
+ int x=0, y=0; //width=0, height=0;
+ this.get_position(out x, out y);
+ out_x = x + renderer.center_x;
+ out_y = y + renderer.center_y;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Gets the absolute position of the mouse pointer.
+ /////////////////////////////////////////////////////////////////////
+
+ private void get_mouse_position(out int mx, out int my) {
+ // get the mouse position
+ mx = 0;
+ my = 0;
+ Gdk.ModifierType mask;
+
+ var display = Gdk.Display.get_default();
+ var manager = display.get_device_manager();
+ GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER);
+
+ foreach(var device in list) {
+ if (device.input_source != Gdk.InputSource.KEYBOARD) {
+ this.get_window().get_device_position(device, out mx, out my, out mask);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the absolute position of the mouse pointer.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_mouse_position(int mx, int my) {
+ var display = Gdk.Display.get_default();
+ var manager = display.get_device_manager();
+ GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER);
+ foreach(var device in list) {
+ if (device.input_source != Gdk.InputSource.KEYBOARD) {
+ device.warp(Gdk.Screen.get_default(), mx, my);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draw the Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool draw_window(Cairo.Context ctx) {
+ // paint the background image if there is no compositing
+ if (this.has_compositing) {
+ ctx.set_operator (Cairo.Operator.CLEAR);
+ ctx.paint();
+ ctx.set_operator (Cairo.Operator.OVER);
+ } else {
+ //correct the background position if the window was moved
+ //since the background image was captured
+ int x, y;
+ this.get_position(out x, out y);
+ int dx = this.back_x - x;
+ int dy = this.back_y - y;
+ ctx.save();
+ ctx.translate(dx, dy);
+ ctx.set_operator (Cairo.Operator.OVER);
+ ctx.set_source_surface(background.surface, -1, -1);
+ ctx.paint();
+ ctx.restore();
+ }
+
+ // align the context to the center of the PieWindow
+ ctx.translate(this.renderer.center_x, this.renderer.center_y);
+
+ // get the mouse position
+ int mouse_x, mouse_y;
+ get_mouse_position( out mouse_x, out mouse_y );
+
+ // store the frame time
+ double frame_time = this.timer.elapsed();
+ this.timer.reset();
+
+ // render the Pie
+ this.renderer.draw(frame_time, ctx, mouse_x - (int)this.renderer.center_x,
+ mouse_y - (int)this.renderer.center_y);
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Activates the currently activate slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private void activate_slice(uint32 time_stamp) {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ Gtk.grab_remove(this);
+ FocusGrabber.ungrab();
+
+ GLib.Timeout.add(10, () => {
+ this.renderer.activate(time_stamp);
+ return false;
+ });
+
+ GLib.Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.closed = true;
+ this.on_closed();
+ this.destroy();
+ return false;
+ });
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Activates no slice and closes the PieWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ private void cancel() {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ Gtk.grab_remove(this);
+ FocusGrabber.ungrab();
+ this.renderer.cancel();
+
+ GLib.Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.closed = true;
+ this.on_closed();
+ this.destroy();
+ return false;
+ });
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the position of the window to the center of the screen or to
+ /// the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_window_position(Pie pie) {
+ if(PieManager.get_is_centered(pie.id)) this.set_position(Gtk.WindowPosition.CENTER);
+ else this.set_position(Gtk.WindowPosition.MOUSE);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Do some useful stuff when keys are pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void handle_key_press(uint key, uint32 time_stamp, uint32 last_time_stamp, string text) {
+ if (last_time_stamp + 1000 < time_stamp) {
+ this.search_string = "";
+ }
+
+ if (Gdk.keyval_name(key) == "Escape") this.cancel();
+ else if (Gdk.keyval_name(key) == "Return") this.activate_slice(time_stamp);
+ else if (Gdk.keyval_name(key) == "KP_Enter") this.activate_slice(time_stamp);
+ else if (!PieManager.get_is_turbo(this.renderer.id)) {
+ 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) == "Page_Down") this.renderer.select_nextpage();
+ else if (Gdk.keyval_name(key) == "Page_Up") this.renderer.select_prevpage();
+ else if (Gdk.keyval_name(key) == "Tab") this.renderer.select_nextpage();
+ else if (Gdk.keyval_name(key) == "ISO_Left_Tab") this.renderer.select_prevpage();
+ else if (Gdk.keyval_name(key) == "Alt_L" && !Config.global.search_by_string) this.renderer.show_hotkeys = true;
+ else {
+
+ if (Config.global.search_by_string) {
+ this.search_string += text;
+ this.renderer.select_by_string(search_string.down());
+
+ } else {
+
+ int index = -1;
+
+ if (key >= 48 && key <= 57) index = ((int)key - 39)%10;
+ 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.key_board_control = true;
+ this.renderer.select_by_index(index);
+
+ if (this.renderer.active_slice == index) {
+ GLib.Timeout.add((uint)(Config.global.theme.transition_time*1000.0), ()=> {
+ this.activate_slice(time_stamp);
+ return false;
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Do some useful stuff when keys are released.
+ /////////////////////////////////////////////////////////////////////
+
+ private void handle_key_release(uint key) {
+ if (!PieManager.get_is_turbo(this.renderer.id)) {
+ if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = false;
+ }
+ }
+}
+
+}
diff --git a/src/renderers/sliceRenderer.vala b/src/renderers/sliceRenderer.vala
new file mode 100644
index 0000000..591fbdd
--- /dev/null
+++ b/src/renderers/sliceRenderer.vala
@@ -0,0 +1,295 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Renders a Slice of a Pie. According to the current theme.
+/////////////////////////////////////////////////////////////////////////
+
+public class SliceRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Whether this slice is active (hovered) or not.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool active {get; private set; default = false;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Image which should be displayed as center caption when this
+ /// slice is active.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image caption {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// The color which should be used for colorizing center layers when
+ /// this slice is active.
+ /////////////////////////////////////////////////////////////////////
+
+ public Color color {get; private set;}
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Action which is rendered by this SliceRenderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public Action action;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The two Images used, when this slice is active or not.
+ /////////////////////////////////////////////////////////////////////
+
+ private Image active_icon;
+ private Image inactive_icon;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Image displaying the associated hot key of this slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private Image hotkey;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The PieRenderer which owns this SliceRenderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PieRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of this slice in a pie. Clockwise assigned, starting
+ /// from the right-most slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private int position;
+
+ /////////////////////////////////////////////////////////////////////
+ /// AnimatedValues needed for a slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue fade; // for transitions from active to inactive
+ private AnimatedValue scale; // for zoom effect
+ private AnimatedValue alpha; // for fading in/out
+ private AnimatedValue fade_rotation; // for fading in/out
+ private AnimatedValue fade_scale; // for fading in/out
+ private AnimatedValue wobble; // for organic wobbling
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all AnimatedValues.
+ /////////////////////////////////////////////////////////////////////
+
+ public SliceRenderer(PieRenderer parent) {
+ this.parent = parent;
+ this.reset_anim();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Put all AnimatedValues in their initial values
+ /////////////////////////////////////////////////////////////////////
+
+ public void reset_anim() {
+ this.fade = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
+ this.wobble = 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Action. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ 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,
+ Config.global.theme.caption_color,
+ Config.global.global_scale);
+
+ this.active_icon = new ThemedIcon(action.name, action.icon, true);
+ this.inactive_icon = new ThemedIcon(action.name, action.icon, false);
+
+ this.color = new Color.from_icon(this.active_icon);
+
+ string hotkey_label = "";
+ if (position < 10) {
+ hotkey_label = "%u".printf((position+1)%10);
+ } 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",
+ new Color(), Config.global.global_scale);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Activates the Action of this slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public void activate(uint32 time_stamp) {
+ action.activate(time_stamp);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initiates the fade-out animation by resetting the targets of the
+ /// AnimatedValues to 0.0.
+ /////////////////////////////////////////////////////////////////////
+
+ 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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Should be called if the active slice of the PieRenderer changes.
+ /// The members activity, caption and color are set accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ 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);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws all layers of the slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, int slice_track) {
+
+ // update the AnimatedValues
+ 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);
+ this.wobble.update(frame_time);
+
+ double direction = 2.0 * PI * (position-parent.first_slice_idx)/parent.total_slice_count
+ + parent.first_slice_angle + this.fade_rotation.val;
+ double max_scale = 1.0/Config.global.theme.max_zoom;
+ double diff = fabs(angle-direction);
+
+ if (diff > 2 * PI) {
+ diff = diff - 2 * PI;
+ }
+
+ if (diff > PI) {
+ diff = 2 * PI - diff;
+ }
+
+
+ active = ((parent.active_slice >= 0) && (diff < PI/parent.total_slice_count));
+
+ if (slice_track != 0) {
+ double wobble = Config.global.theme.wobble*diff/PI*(1-diff/PI);
+ if ((direction < angle && direction > angle - PI) || direction > PI+angle) {
+ this.wobble.reset_target(-wobble, Config.global.theme.transition_time*0.5);
+ } else {
+ this.wobble.reset_target(wobble, Config.global.theme.transition_time*0.5);
+ }
+ } else {
+ this.wobble.reset_target(0, Config.global.theme.transition_time*0.5);
+ }
+
+ direction += this.wobble.val;
+
+ 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;
+
+
+
+ max_scale = (slice_track != 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);
+
+ ctx.save();
+
+ // distance from the center
+ double radius = Config.global.theme.radius;
+
+ // increase radius if there are many slices in a pie
+ if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /(radius/Config.global.theme.max_zoom)) > PI/parent.total_slice_count) {
+ radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /tan(PI/parent.total_slice_count)*Config.global.theme.max_zoom;
+ }
+
+ // transform the context
+ 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);
+
+ // paint the images
+ 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();
+
+ // draw hotkeys if necassary
+ 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..34d473e
--- /dev/null
+++ b/src/themes/centerLayer.vala
@@ -0,0 +1,116 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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.
+ /// TO_HOUR_12: Turns the layer to the position of the current hour.
+ /// TO_HOUR_24: Turns the layer to the position of the current hour.
+ /// TO_MINUTE: Turns the layer to the position of the current minute.
+ /// TO_SECOND: Turns the layer to the position of the current second.
+ /////////////////////////////////////////////////////////////////////
+
+ public enum RotationMode {AUTO, TO_MOUSE, TO_ACTIVE, TO_HOUR_12,
+ TO_HOUR_24, TO_MINUTE, TO_SECOND}
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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..bd9e98b
--- /dev/null
+++ b/src/themes/sliceLayer.vala
@@ -0,0 +1,105 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class representing a layer of a slice of a pie. Each theme may
+/// have plenty of them.
+/////////////////////////////////////////////////////////////////////////
+
+public class SliceLayer : GLib.Object {
+
+ public enum Type { FILE, ICON, CAPTION }
+ public enum Visibility { ANY, WITH_CAPTION, WITHOUT_CAPTION }
+
+ public Type layer_type { get; private set; }
+ public Visibility visibility { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Information on the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image image {get; set;}
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Properties of this layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public string icon_file {get; private set; default="";}
+ public bool colorize {get; private set; default=false;}
+ public int icon_size {get; private set; default=1;}
+
+ public string font {get; private set; default="";}
+ public int width {get; private set; default=0;}
+ public int height {get; private set; default=0;}
+ public int x {get; private set; default=0;}
+ public int y {get; private set; default=0;}
+ public Color color {get; private set; default=new Color();}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members of the layer.
+ /////////////////////////////////////////////////////////////////////
+
+ public SliceLayer.file(string icon_file, int icon_size, int x, int y, bool colorize, Visibility visibility) {
+ this.layer_type = Type.FILE;
+ this.icon_file = icon_file;
+ this.colorize = colorize;
+ this.icon_size = icon_size;
+ this.x = x;
+ this.y = y;
+ this.visibility = visibility;
+ }
+
+ public SliceLayer.icon(string icon_file, int icon_size, int x, int y, bool colorize, Visibility visibility) {
+ this.layer_type = Type.ICON;
+ this.icon_file = icon_file;
+ this.colorize = colorize;
+ this.icon_size = icon_size;
+ this.x = x;
+ this.y = y;
+ this.visibility = visibility;
+ }
+
+ public SliceLayer.caption(string font, int width, int height, int x, int y, Color color, bool colorize, Visibility visibility) {
+ this.layer_type = Type.CAPTION;
+ this.font = font;
+ this.width = width;
+ this.height = height;
+ this.x = x;
+ this.y = y;
+ this.color = color;
+ this.visibility = visibility;
+ this.colorize = colorize;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the contained image.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_image() {
+ this.image = null;
+
+ if (this.icon_file == "" && this.layer_type == Type.ICON)
+ this.image = new Image.empty(this.icon_size, this.icon_size, new Color.from_rgb(1, 1, 1));
+ else if (this.icon_file != "")
+ 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..ccf38c2
--- /dev/null
+++ b/src/themes/theme.vala
@@ -0,0 +1,650 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+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 wobble {get; private set; default=0.0;}
+ 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 visible_slice_radius {get; private set; default=0.0;}
+ public double slice_gap {get; private set; default=14.0;}
+ public bool has_slice_captions {get; private set; default=false;}
+ 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 Icon preview_icon {get; private set; default=new Icon("gnome-pie", 36);}
+
+ 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;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the theme from its directory. Images have to be loaded
+ /// explicitly.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool load() {
+ this.center_layers.clear();
+ this.active_slice_layers.clear();
+ this.inactive_slice_layers.clear();
+
+ if (!GLib.File.new_for_path(this.directory).query_exists()) {
+ return false;
+ }
+
+ string config_file = this.directory + "/theme.xml";
+
+ if (!GLib.File.new_for_path(config_file).query_exists()) {
+ try {
+ // detect whether theme is one directory deeper
+ string child;
+ bool success = false;
+
+ // load global themes
+ var d = Dir.open(this.directory);
+ while ((child = d.read_name()) != null && !success) {
+ config_file = this.directory + "/" + child + "/theme.xml";
+ if (GLib.File.new_for_path(config_file).query_exists()) {
+ this.directory = this.directory + "/" + child;
+ success = true;
+ }
+ }
+
+ if (!success) {
+ return false;
+ }
+ } catch (Error e) {
+ warning (e.message);
+ return false;
+ }
+ }
+
+ this.preview_icon = new Icon(this.directory + "/preview.png", 36);
+
+ Xml.Parser.init();
+
+ Xml.Doc* themeXML = Xml.Parser.parse_file(config_file);
+ if (themeXML == null) {
+ warning("Failed to add theme: \"" + config_file + "\" not found!");
+ return false;
+ }
+
+ Xml.Node* root = themeXML->get_root_element();
+ if (root == null) {
+ delete themeXML;
+ warning("Failed to add theme: \"theme.xml\" is empty!");
+ return false;
+ }
+
+ this.parse_root(root);
+
+ delete themeXML;
+ Xml.Parser.cleanup();
+
+ this.radius *= max_zoom;
+
+ return true;
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Exports the theme directory to an importable archive.
+ /////////////////////////////////////////////////////////////////////
+
+ public void export(string file) {
+
+ var archive = new ArchiveWriter();
+ bool success = true;
+
+ if (!archive.open(file)) {
+ warning("Cannot open file " + file + " for writing!");
+ success = false;
+ } else if (!archive.add(this.directory)) {
+ warning("Cannot append directory " + this.directory + " to archive!");
+ success = false;
+ }
+
+ archive.close();
+
+ if (success) {
+ var message = _("Successfully exported the theme \"%s\"!").printf(this.name);
+ var dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, message);
+ dialog.run();
+ dialog.destroy();
+
+ } else {
+ var message = _("An error occured while exporting the theme \"%s\"! Please check the console output.").printf(this.name);
+ var dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, message);
+ dialog.run();
+ dialog.destroy();
+ }
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if the theme is installed to the local themes
+ /// directory.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool is_local() {
+ return this.directory.has_prefix(Paths.local_themes);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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 "wobble":
+ wobble = 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;
+ visible_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;
+ case "turn_to_hour":
+ case "turn_to_hour_12":
+ active_rotation_mode = CenterLayer.RotationMode.TO_HOUR_12;
+ break;
+ case "turn_to_hour_24":
+ active_rotation_mode = CenterLayer.RotationMode.TO_HOUR_24;
+ break;
+ case "turn_to_minute":
+ active_rotation_mode = CenterLayer.RotationMode.TO_MINUTE;
+ break;
+ case "turn_to_second":
+ active_rotation_mode = CenterLayer.RotationMode.TO_SECOND;
+ 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;
+ case "turn_to_hour":
+ case "turn_to_hour_12":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_HOUR_12;
+ break;
+ case "turn_to_hour_24":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_HOUR_24;
+ break;
+ case "turn_to_minute":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_MINUTE;
+ break;
+ case "turn_to_second":
+ inactive_rotation_mode = CenterLayer.RotationMode.TO_SECOND;
+ 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;
+ SliceLayer.Type type = SliceLayer.Type.FILE;
+ SliceLayer.Visibility visibility = SliceLayer.Visibility.ANY;
+ bool colorize = false;
+ string slice_caption_font = "sans 8";
+ int slice_caption_width = 50;
+ int slice_caption_height = 20;
+ int pos_x = 0;
+ int pos_y = 0;
+ Color slice_caption_color = new Color.from_rgb(1.0f, 1.0f, 1.0f);
+
+ 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")
+ type = SliceLayer.Type.ICON;
+ else if (attr_content == "caption")
+ type = SliceLayer.Type.CAPTION;
+ 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;
+ case "font":
+ slice_caption_font = attr_content;
+ break;
+ case "width":
+ slice_caption_width = (int)(int.parse(attr_content) * Config.global.global_scale);
+ if (slice_caption_width % 2 == 1)
+ --slice_caption_width;
+ break;
+ case "height":
+ slice_caption_height = (int)(int.parse(attr_content) * Config.global.global_scale);
+ if (slice_caption_height % 2 == 1)
+ --slice_caption_height;
+ break;
+ case "x":
+ pos_x = (int)(double.parse(attr_content) * Config.global.global_scale);
+ break;
+ case "y":
+ pos_y = (int)(double.parse(attr_content) * Config.global.global_scale);
+ break;
+ case "color":
+ slice_caption_color = new Color.from_string(attr_content);
+ break;
+ case "visibility":
+ if (attr_content == "without_caption")
+ visibility = SliceLayer.Visibility.WITHOUT_CAPTION;
+ else if (attr_content == "with_caption") {
+ this.has_slice_captions = true;
+ visibility = SliceLayer.Visibility.WITH_CAPTION;
+ } else if (attr_content != "any")
+ warning("Invalid attribute content " + attr_content + " for attribute " + attr_name + " in <slice_layer> element!");
+ 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);
+ this.visible_slice_radius = Math.fmax(slice_radius*scale, this.visible_slice_radius);
+
+ if (slice->name.down() == "activeslice") {
+ if (type == SliceLayer.Type.ICON) active_slice_layers.add(new SliceLayer.icon(file, size, pos_x, pos_y, colorize, visibility));
+ else if (type == SliceLayer.Type.CAPTION) active_slice_layers.add(new SliceLayer.caption(slice_caption_font,
+ slice_caption_width, slice_caption_height,
+ pos_x, pos_y, slice_caption_color, colorize, visibility));
+ else active_slice_layers.add(new SliceLayer.file(file, size, pos_x, pos_y, colorize, visibility));
+ } else {
+ if (type == SliceLayer.Type.ICON) inactive_slice_layers.add(new SliceLayer.icon(file, size, pos_x, pos_y, colorize, visibility));
+ else if (type == SliceLayer.Type.CAPTION) inactive_slice_layers.add(new SliceLayer.caption(slice_caption_font,
+ slice_caption_width, slice_caption_height,
+ pos_x, pos_y, slice_caption_color, colorize, visibility));
+ else inactive_slice_layers.add(new SliceLayer.file(file, size, pos_x, pos_y, colorize, visibility));
+ }
+
+ } 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/themes/themeImporter.vala b/src/themes/themeImporter.vala
new file mode 100644
index 0000000..f110696
--- /dev/null
+++ b/src/themes/themeImporter.vala
@@ -0,0 +1,62 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class provides functions to check whether an archive contains a
+/// valid Gnome-Pie theme.
+/////////////////////////////////////////////////////////////////////////
+
+public class ThemeImporter : ArchiveReader {
+
+ public bool is_valid_theme;
+ public string theme_name;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns
+ /////////////////////////////////////////////////////////////////////
+
+ public new bool open(string path) {
+
+ this.is_valid_theme = false;
+ this.theme_name = "";
+
+ var tmp_reader = new ArchiveReader();
+
+ if (tmp_reader.open(path)) {
+ try {
+ var tmp_dir = GLib.DirUtils.make_tmp("gnomepieXXXXXX");
+ if (tmp_reader.extract_to(tmp_dir)) {
+ var tmp_theme = new Theme(tmp_dir);
+ if (tmp_theme.load()) {
+ is_valid_theme = true;
+ theme_name = tmp_theme.name;
+ }
+ }
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+
+ tmp_reader.close();
+
+ return base.open(path);
+ }
+}
+
+}
diff --git a/src/utilities/animatedValue.vala b/src/utilities/animatedValue.vala
new file mode 100644
index 0000000..79be155
--- /dev/null
+++ b/src/utilities/animatedValue.vala
@@ -0,0 +1,197 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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.end = end;
+ this.duration = duration;
+ this.start = this.val;
+
+ if (duration == 0.0) {
+ this.val = end;
+ this.state = 1.0;
+ } else {
+ this.state = 0.0;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the interpolated value according to it's type.
+ /////////////////////////////////////////////////////////////////////
+
+ public void update(double time) {
+ this.state += time/this.duration;
+
+ if (this.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/archiveReader.vala b/src/utilities/archiveReader.vala
new file mode 100644
index 0000000..16e4541
--- /dev/null
+++ b/src/utilities/archiveReader.vala
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class can be used to unpack an archive to a directory.
+/////////////////////////////////////////////////////////////////////////
+
+public class ArchiveReader : GLib.Object {
+
+ private Archive.Read archive;
+ private Archive.WriteDisk writer;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Constructs a new ArchiveReader
+ /////////////////////////////////////////////////////////////////////
+
+ public ArchiveReader() {
+ this.archive = new Archive.Read();
+ this.archive.support_format_all();
+ this.archive.support_filter_all();
+
+ this.writer = new Archive.WriteDisk();
+ this.writer.set_options(
+ Archive.ExtractFlags.TIME |
+ Archive.ExtractFlags.PERM |
+ Archive.ExtractFlags.ACL |
+ Archive.ExtractFlags.FFLAGS
+ );
+ this.writer.set_standard_lookup();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Call this once after you created the ArchiveReader. Pass the
+ /// path to the target archive location.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool open(string path) {
+ return this.archive.open_filename(path, 10240) == Archive.Result.OK;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Extracts all files from the previously opened archive.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool extract_to(string directory) {
+ while (true) {
+ unowned Archive.Entry entry;
+ var r = this.archive.next_header(out entry);
+
+ if (r == Archive.Result.EOF) {
+ break;
+ }
+
+ if (r != Archive.Result.OK) {
+ warning(this.archive.error_string());
+ return false;
+ }
+
+ entry.set_pathname(directory + "/" + entry.pathname());
+
+ r = this.writer.write_header(entry);
+
+ if (r != Archive.Result.OK) {
+ warning(this.writer.error_string());
+ return false;
+ }
+
+ if (entry.size() > 0) {
+ while (true) {
+ size_t offset, size;
+ void *buff;
+
+ r = this.archive.read_data_block(out buff, out size, out offset);
+ if (r == Archive.Result.EOF) {
+ break;
+ }
+
+ if (r != Archive.Result.OK) {
+ warning(this.archive.error_string());
+ return false;
+ }
+
+ this.writer.write_data_block(buff, size, offset);
+ }
+ }
+
+ r = this.writer.finish_entry();
+
+ if (r != Archive.Result.OK) {
+ warning(this.writer.error_string());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// When all files have been added, close the directory again.
+ /////////////////////////////////////////////////////////////////////
+
+ public void close() {
+ this.archive.close();
+ this.writer.close();
+ }
+}
+
+}
diff --git a/src/utilities/archiveWriter.vala b/src/utilities/archiveWriter.vala
new file mode 100644
index 0000000..92bd31b
--- /dev/null
+++ b/src/utilities/archiveWriter.vala
@@ -0,0 +1,139 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class can be used to pack a directory of files recursively into
+/// a *.tar.gz archive.
+/////////////////////////////////////////////////////////////////////////
+
+public class ArchiveWriter : GLib.Object {
+
+ private Archive.Write archive;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Constructs a new ArchiveWriter
+ /////////////////////////////////////////////////////////////////////
+
+ public ArchiveWriter() {
+ this.archive = new Archive.Write();
+ this.archive.add_filter_gzip();
+ this.archive.set_format_pax_restricted();
+
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Call this once after you created the ArchiveWriter. Pass the
+ /// path to the target archive location.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool open(string path) {
+ return this.archive.open_filename(path) == Archive.Result.OK;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds all files of a given directory to the previously opened
+ /// archive.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool add(string directory) {
+ return add_directory(directory, directory);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// When all files have been added, close the directory again.
+ /////////////////////////////////////////////////////////////////////
+
+ public void close() {
+ this.archive.close();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Private helper function which traveres a directory recursively.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool add_directory(string directory, string relative_to) {
+ try {
+ var d = Dir.open(directory);
+ string name;
+ while ((name = d.read_name()) != null) {
+ string path = Path.build_filename(directory, name);
+ if (FileUtils.test(path, FileTest.IS_DIR)) {
+ if (!add_directory(path, relative_to)) {
+ return false;
+ }
+
+ } else if (FileUtils.test(path, FileTest.IS_REGULAR)) {
+ if (!add_file(path, relative_to)) {
+ return false;
+ }
+
+ } else {
+ warning("Packaging theme: Ignoring irregular file " + name);
+ }
+ }
+ } catch (Error e) {
+ warning (e.message);
+ return false;
+ }
+
+ return true;
+
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Private halper which adds a file to the archive.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool add_file(string path, string relative_to) {
+ var entry = new Archive.Entry();
+ entry.set_pathname(path.replace(relative_to, ""));
+
+ Posix.Stat st;
+ Posix.stat(path, out st);
+ entry.copy_stat(st);
+ entry.set_size(st.st_size);
+
+ if (this.archive.write_header(entry) == Archive.Result.OK) {
+ try {
+ var reader = File.new_for_path(path).read();
+ uint8 buffer[4096];
+
+ var len = reader.read(buffer);
+
+ while(len > 0) {
+ this.archive.write_data(buffer, len);
+ len = reader.read(buffer);
+ }
+
+ this.archive.finish_entry();
+ } catch (Error e) {
+ warning (e.message);
+ return false;
+ }
+
+ } else {
+ warning("Failed to include file " + path + " into archive");
+ return false;
+ }
+
+ return true;
+ }
+}
+
+}
diff --git a/src/utilities/bindingManager.vala b/src/utilities/bindingManager.vala
new file mode 100644
index 0000000..ac5a8fb
--- /dev/null
+++ b/src/utilities/bindingManager.vala
@@ -0,0 +1,428 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// 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
+ };
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some variables to remember which delayed binding was delayed.
+ /// When the delay passes without another event indicating that the
+ /// Trigger was released, the stored binding will be activated.
+ /////////////////////////////////////////////////////////////////////
+
+ private uint32 delayed_count = 0;
+ private X.Event? delayed_event = null;
+ private Keybinding? delayed_binding = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper class to store keybinding
+ /////////////////////////////////////////////////////////////////////
+
+ private class Keybinding {
+
+ public Keybinding(Trigger trigger, string id) {
+ this.trigger = trigger;
+ this.id = id;
+ }
+
+ public Trigger trigger { 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(Trigger trigger, string id) {
+ if (trigger.key_code != 0) {
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+ X.ID xid = Gdk.X11.get_default_root_xwindow();
+
+ Gdk.error_trap_push();
+
+ // if bound to super key we need to grab MOD4 instead
+ // (for whatever reason...)
+ var modifiers = prepare_modifiers(trigger.modifiers);
+
+ foreach(uint lock_modifier in lock_modifiers) {
+ if (trigger.with_mouse) {
+ display.grab_button(trigger.key_code, modifiers|lock_modifier, xid, false,
+ X.EventMask.ButtonPressMask | X.EventMask.ButtonReleaseMask,
+ X.GrabMode.Async, X.GrabMode.Async, xid, 0);
+ } else {
+ display.grab_key(trigger.key_code, modifiers|lock_modifier,
+ xid, false, X.GrabMode.Async, X.GrabMode.Async);
+ }
+ }
+
+ Gdk.flush();
+ Keybinding binding = new Keybinding(trigger, id);
+ bindings.add(binding);
+ display.flush();
+ } else {
+ //no key_code: just add the bindind to the list to save optional trigger parameters
+ Keybinding binding = new Keybinding(trigger, id);
+ bindings.add(binding);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Unbinds the accelerator of the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void unbind(string id) {
+ foreach (var binding in bindings) {
+ if (id == binding.id) {
+ if (binding.trigger.key_code == 0) {
+ //no key_code: just remove the bindind from the list
+ bindings.remove(binding);
+ return;
+ }
+ break;
+ }
+ }
+
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+ X.ID xid = Gdk.X11.get_default_root_xwindow();
+
+ Gee.List<Keybinding> remove_bindings = new Gee.ArrayList<Keybinding>();
+ foreach(var binding in bindings) {
+ if(id == binding.id) {
+
+ // if bound to super key we need to ungrab MOD4 instead
+ // (for whatever reason...)
+ var modifiers = prepare_modifiers(binding.trigger.modifiers);
+
+ foreach(uint lock_modifier in lock_modifiers) {
+ if (binding.trigger.with_mouse) {
+ display.ungrab_button(binding.trigger.key_code, modifiers|lock_modifier, xid);
+ } else {
+ display.ungrab_key(binding.trigger.key_code, modifiers|lock_modifier, xid);
+ }
+ }
+ remove_bindings.add(binding);
+ }
+ }
+
+ bindings.remove_all(remove_bindings);
+ display.flush();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human readable accelerator for the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_accelerator_label_of(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.label_with_specials;
+ }
+ }
+
+ return _("Not bound");
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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.trigger.name;
+ }
+ }
+
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is in turbo mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_turbo(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.turbo;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID opens centered.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_centered(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.centered;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is in warp mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_warp(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return binding.trigger.warp;
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns whether the pie with the given ID is auto shaped
+ /////////////////////////////////////////////////////////////////////
+
+ public bool get_is_auto_shape(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ return (binding.trigger.shape == 0);
+ }
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the prefered pie shape number
+ /////////////////////////////////////////////////////////////////////
+
+ public int get_shape_number(string id) {
+ foreach (var binding in bindings) {
+ if (binding.id == id) {
+ if (binding.trigger.shape == 0)
+ break; //return default if auto-shaped
+ return binding.trigger.shape; //use selected shape
+ }
+ }
+
+ return 5; //default= full pie
+ }
+
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the name ID of the Pie bound to the given Trigger.
+ /// Returns "" if there is nothing bound to this trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public string get_assigned_id(Trigger trigger) {
+ var second = Trigger.remove_optional(trigger.name);
+ if (second != "") {
+ foreach (var binding in bindings) {
+ var first = Trigger.remove_optional(binding.trigger.name);
+ if (first == second) {
+ return binding.id;
+ }
+ }
+ }
+ return "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// If SUPER_MASK is set in the input, it will be replaced with
+ /// MOD4_MASK. For some reason this is required to listen for key
+ /// presses of the super button....
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.ModifierType prepare_modifiers(Gdk.ModifierType mods) {
+ if ((mods & Gdk.ModifierType.SUPER_MASK) > 0) {
+ mods |= Gdk.ModifierType.MOD4_MASK;
+ mods = mods & ~ Gdk.ModifierType.SUPER_MASK;
+ }
+
+ return mods & ~lock_modifiers[7];
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Event filter method needed to fetch X.Events.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) {
+
+ #if VALA_0_16 || VALA_0_17
+ X.Event* xevent = (X.Event*) gdk_xevent;
+ #else
+ void* pointer = &gdk_xevent;
+ X.Event* xevent = (X.Event*) pointer;
+ #endif
+
+ if(xevent->type == X.EventType.KeyPress) {
+ foreach(var binding in bindings) {
+
+ // remove NumLock, CapsLock and ScrollLock from key state
+ var event_mods = prepare_modifiers((Gdk.ModifierType)xevent.xkey.state);
+ var bound_mods = prepare_modifiers(binding.trigger.modifiers);
+
+ if(xevent->xkey.keycode == binding.trigger.key_code &&
+ event_mods == bound_mods) {
+
+ if (binding.trigger.delayed) {
+ this.activate_delayed(binding, *xevent);
+ } else {
+ on_press(binding.id);
+ }
+ }
+ }
+ }
+ else if(xevent->type == X.EventType.ButtonPress) {
+ foreach(var binding in bindings) {
+
+ // remove NumLock, CapsLock and ScrollLock from key state
+ var event_mods = prepare_modifiers((Gdk.ModifierType)xevent.xbutton.state);
+ var bound_mods = prepare_modifiers(binding.trigger.modifiers);
+
+ if(xevent->xbutton.button == binding.trigger.key_code &&
+ event_mods == bound_mods) {
+
+ if (binding.trigger.delayed) {
+ this.activate_delayed(binding, *xevent);
+ } else {
+ on_press(binding.id);
+ }
+ }
+ }
+ }
+ else if(xevent->type == X.EventType.ButtonRelease || xevent->type == X.EventType.KeyRelease) {
+ this.activate_delayed(null, *xevent);
+ }
+
+ return Gdk.FilterReturn.CONTINUE;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This method is always called when a trigger is activated which is
+ /// delayed. Therefore on_press() is only emitted, when this method
+ /// is not called again within 300 milliseconds. Else a fake event is
+ /// sent in order to simulate the actual key which has been pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void activate_delayed(Keybinding? binding , X.Event event) {
+ // increase event count, so any waiting event will realize that
+ // something happened in the meantime
+ var current_count = ++this.delayed_count;
+
+ if (binding == null && this.delayed_event != null) {
+ // if the trigger is released and an event is currently waiting
+ // simulate that the trigger has been pressed without any inter-
+ // ference of Gnome-Pie
+ unowned X.Display display = Gdk.X11.get_default_xdisplay();
+
+ // unbind the trigger, else we'll capture that event again ;)
+ unbind(delayed_binding.id);
+
+ if (this.delayed_binding.trigger.with_mouse) {
+ // simulate mouse click
+ XTest.fake_button_event(display, this.delayed_event.xbutton.button, true, 0);
+ display.flush();
+
+ XTest.fake_button_event(display, this.delayed_event.xbutton.button, false, 0);
+ display.flush();
+
+ } else {
+ // simulate key press
+ XTest.fake_key_event(display, this.delayed_event.xkey.keycode, true, 0);
+ display.flush();
+
+ XTest.fake_key_event(display, this.delayed_event.xkey.keycode, false, 0);
+ display.flush();
+ }
+
+ // bind it again
+ bind(delayed_binding.trigger, delayed_binding.id);
+
+ this.delayed_binding = null;
+ this.delayed_event = null;
+
+ } else if (binding != null) {
+ // if the trigger has been pressed, store it and wait for any interuption
+ // within the next 300 milliseconds
+ this.delayed_event = event;
+ this.delayed_binding = binding;
+
+ Timeout.add(300, () => {
+ // if nothing has been pressed in the meantime
+ if (current_count == this.delayed_count) {
+ this.delayed_binding = null;
+ this.delayed_event = null;
+ on_press(binding.id);
+ }
+ return false;
+ });
+ }
+ }
+}
+
+}
diff --git a/src/utilities/color.vala b/src/utilities/color.vala
new file mode 100644
index 0000000..a681e02
--- /dev/null
+++ b/src/utilities/color.vala
@@ -0,0 +1,327 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+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.RGBA color) {
+ Color.from_rgba(
+ (float)color.red,
+ (float)color.green,
+ (float)color.blue,
+ (float)color.alpha
+ );
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a color from a given widget style
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_widget_style(Gtk.Widget widget, string style_name) {
+ var ctx = widget.get_style_context();
+ Gdk.RGBA color;
+ if (!ctx.lookup_color(style_name, out color)) {
+ warning("Failed to get style color for widget style \"" + style_name + "\"!");
+ }
+ Color.from_gdk(color);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates a color, parsed from a string, such as #22EE33
+ /////////////////////////////////////////////////////////////////////
+
+ public Color.from_string(string hex_string) {
+ var color = Gdk.RGBA();
+ color.parse(hex_string);
+ 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;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns this color as its hex representation.
+ /////////////////////////////////////////////////////////////////////
+
+ public string to_hex_string() {
+ return "#%02X%02X%02X".printf((int)(_r*255), (int)(_g*255), (int)(_b*255));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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..74bbcbb
--- /dev/null
+++ b/src/utilities/config.vala
@@ -0,0 +1,239 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 int activation_range { get; set; default = 200; }
+ public int max_visible_slices { get; set; default = 24; }
+ public bool show_indicator { get; set; default = true; }
+ public bool show_captions { get; set; default = false; }
+ public bool search_by_string { get; set; default = true; }
+ public bool auto_start { get; set; default = false; }
+ public int showed_news { get; set; default = 0; }
+ 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("activation_range", activation_range.to_string());
+ writer.write_attribute("max_visible_slices", max_visible_slices.to_string());
+ writer.write_attribute("show_indicator", show_indicator ? "true" : "false");
+ writer.write_attribute("show_captions", show_captions ? "true" : "false");
+ writer.write_attribute("search_by_string", search_by_string ? "true" : "false");
+ writer.write_attribute("showed_news", showed_news.to_string());
+ 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 "activation_range":
+ activation_range = int.parse(attr_content);
+ activation_range.clamp(0, 2000);
+ break;
+ case "max_visible_slices":
+ max_visible_slices = int.parse(attr_content);
+ max_visible_slices.clamp(10, 2000);
+ break;
+ case "show_indicator":
+ show_indicator = bool.parse(attr_content);
+ break;
+ case "show_captions":
+ show_captions = bool.parse(attr_content);
+ break;
+ case "search_by_string":
+ search_by_string = bool.parse(attr_content);
+ break;
+ case "showed_news":
+ showed_news = int.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 new_theme = new Theme(Paths.global_themes + "/" + name);
+
+ if (new_theme.load()) {
+ themes.add(new_theme);
+ }
+ }
+
+ // load local themes
+ d = Dir.open(Paths.local_themes);
+ while ((name = d.read_name()) != null) {
+ var new_theme = new Theme(Paths.local_themes + "/" + name);
+ if (new_theme.load())
+ themes.add(new_theme);
+ }
+
+ } catch (Error e) {
+ warning (e.message);
+ }
+
+ if (themes.size > 0) {
+ if (current == "") {
+ current = "Adwaita";
+ warning("No theme specified! Using default...");
+ }
+ foreach (var t in themes) {
+ if (t.name == current) {
+ theme = t;
+ break;
+ }
+ }
+ if (theme == null) {
+ theme = themes[0];
+ warning("Theme \"" + current + "\" not found! Using fallback...");
+ }
+ theme.load_images();
+ } else {
+ error("No theme found!");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true if a loaded theme has the given name or is in a
+ /// directory with the given name.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool has_theme(string name) {
+
+ foreach (var theme in themes) {
+ if (theme.name == name || theme.directory.has_suffix(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+}
diff --git a/src/utilities/focusGrabber.vala b/src/utilities/focusGrabber.vala
new file mode 100644
index 0000000..baa5fed
--- /dev/null
+++ b/src/utilities/focusGrabber.vala
@@ -0,0 +1,97 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Some helper methods which focus the input on a given Gtk.Window.
+/////////////////////////////////////////////////////////////////////////
+
+public class FocusGrabber : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Utilities for grabbing focus.
+ /// Code roughly from Gnome-Do/Synapse.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void grab(Gdk.Window window, bool keyboard = true, bool pointer = true, bool owner_events = true) {
+ if (keyboard || pointer) {
+ window.raise();
+ window.focus(Gdk.CURRENT_TIME);
+
+ if (!try_grab_window(window, keyboard, pointer, owner_events)) {
+ int i = 0;
+ Timeout.add(100, () => {
+ if (++i >= 100) return false;
+ return !try_grab_window(window, keyboard, pointer, owner_events);
+ });
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Code roughly from Gnome-Do/Synapse.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void ungrab(bool keyboard = true, bool pointer = true) {
+ var display = Gdk.Display.get_default();
+ var manager = display.get_device_manager();
+
+ GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER);
+
+ foreach(var device in list) {
+ if ((device.input_source == Gdk.InputSource.KEYBOARD && keyboard)
+ || (device.input_source != Gdk.InputSource.KEYBOARD && pointer))
+
+ device.ungrab(Gdk.CURRENT_TIME);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Code roughly from Gnome-Do/Synapse.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool try_grab_window(Gdk.Window window, bool keyboard, bool pointer, bool owner_events) {
+ var display = Gdk.Display.get_default();
+ var manager = display.get_device_manager();
+
+ bool grabbed_all = true;
+
+ GLib.List<weak Gdk.Device?> list = manager.list_devices(Gdk.DeviceType.MASTER);
+
+ foreach(var device in list) {
+ if ((device.input_source == Gdk.InputSource.KEYBOARD && keyboard)
+ || (device.input_source != Gdk.InputSource.KEYBOARD && pointer)) {
+
+ var status = device.grab(window, Gdk.GrabOwnership.APPLICATION, owner_events,
+ Gdk.EventMask.ALL_EVENTS_MASK, null, Gdk.CURRENT_TIME);
+
+ if (status != Gdk.GrabStatus.SUCCESS)
+ grabbed_all = false;
+ }
+ }
+
+ if (grabbed_all)
+ return true;
+
+ ungrab(keyboard, pointer);
+
+ return false;
+ }
+}
+
+}
diff --git a/src/utilities/key.vala b/src/utilities/key.vala
new file mode 100644
index 0000000..486744d
--- /dev/null
+++ b/src/utilities/key.vala
@@ -0,0 +1,161 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 to defaults.
+ /////////////////////////////////////////////////////////////////////
+
+ public Key() {
+ this.accelerator = "";
+ this.modifiers = 0;
+ this.key_code = 0;
+ this.label = _("Not bound");
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Key.from_string(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);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members.
+ /////////////////////////////////////////////////////////////////////
+
+ public Key.from_values(uint keysym, Gdk.ModifierType modifiers) {
+ this.accelerator = Gtk.accelerator_name(keysym, modifiers);
+ this.label = Gtk.accelerator_get_label(keysym, modifiers);
+ this.key_code = display.keysym_to_keycode(keysym);
+ this.modifiers = 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
+ XTest.fake_key_event(display, this.key_code, true, 0);
+ XTest.fake_key_event(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;
+ Gtk.get_current_event_state(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)
+ XTest.fake_key_event(display, ctrl_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0)
+ XTest.fake_key_event(display, shift_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0)
+ XTest.fake_key_event(display, alt_code, down, 0);
+
+ if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0)
+ XTest.fake_key_event(display, super_code, down, 0);
+ }
+}
+
+}
diff --git a/src/utilities/logger.vala b/src/utilities/logger.vala
new file mode 100644
index 0000000..7c66615
--- /dev/null
+++ b/src/utilities/logger.vala
@@ -0,0 +1,270 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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
+ /////////////////////////////////////////////////////////////////////
+
+ private static const bool display_debug = true;
+ private static const bool display_warning = true;
+ private static const bool display_error = true;
+ private static const bool display_message = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// If these are set to false, the according messages are not logged
+ /////////////////////////////////////////////////////////////////////
+
+ private static const bool log_debug = false;
+ private static const bool log_warning = true;
+ private static const bool log_error = true;
+ private static const bool log_message = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// If true, a time stamp is shown in each message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const bool display_time = false;
+ private static const bool log_time = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// If true, the origin of the message is shown. In form file:line
+ /////////////////////////////////////////////////////////////////////
+
+ private static const bool display_file = false;
+ private static const bool log_file = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A regex, used to format the standard message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Regex regex = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Limit log and statistics size to roughly 1 MB.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const int max_log_length = 1000000;
+
+ private static int log_length;
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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() {
+ log_length = -1;
+
+ try {
+ regex = new Regex("""(.*)\.vala(:\d+): (.*)""");
+ } catch {}
+
+ GLib.Log.set_handler(null, GLib.LogLevelFlags.LEVEL_MASK, log_func);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Appends a line to the log file
+ /////////////////////////////////////////////////////////////////////
+
+ private static void write_log_line(string line) {
+ var log = GLib.FileStream.open(Paths.log, "a");
+
+ if (log != null) {
+ if (log_length == -1)
+ log_length = (int)log.tell();
+
+ log.puts(line);
+ log_length += line.length;
+ }
+
+ if (log_length > max_log_length) {
+ string content = "";
+
+ try {
+ GLib.FileUtils.get_contents(Paths.log, out content);
+ int split_index = content.index_of_char('\n', log_length - (int)(max_log_length*0.9));
+ GLib.FileUtils.set_contents(Paths.log, content.substring(split_index+1));
+
+ log_length -= (split_index+1);
+ } catch (GLib.FileError e) {}
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void message(string message, string message_log) {
+ if (display_message) {
+ stdout.printf(set_color(Color.GREEN, false) + "[" + (display_time ? get_time() + " " : "") + "MESSAGE]" + message);
+ }
+
+ if (log_message) {
+ write_log_line("[" + (log_time ? get_time() + " " : "") + "MESSAGE]" + message_log);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Debug message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void debug(string message, string message_log) {
+ if (display_debug) {
+ stdout.printf(set_color(Color.BLUE, false) + "[" + (display_time ? get_time() + " " : "") + " DEBUG ]" + message);
+ }
+
+ if (log_debug) {
+ write_log_line("[" + (log_time ? get_time() + " " : "") + " DEBUG ]" + message_log);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Warning message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void warning(string message, string message_log) {
+ if (display_warning) {
+ stdout.printf(set_color(Color.YELLOW, false) + "[" + (display_time ? get_time() + " " : "") + "WARNING]" + message);
+ }
+
+ if (log_warning) {
+ write_log_line("[" + (log_time ? get_time() + " " : "") + "WARNING]" + message_log);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a Error message.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void error(string message, string message_log) {
+ if (display_error) {
+ stdout.printf(set_color(Color.RED, false) + "[" + (display_time ? get_time() + " " : "") + " ERROR ]" + message);
+ }
+
+ if (log_error) {
+ write_log_line("[" + (log_time ? get_time() + " " : "") + " ERROR ]" + message_log);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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() {
+ var now = new DateTime.now_local();
+ return "%.4d:%.2d:%.2d:%.2d:%.2d:%.2d:%.6d".printf(now.get_year(), now.get_month(), now.get_day_of_month(), now.get_hour(), now.get_minute(), now.get_second(), now.get_microsecond());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// 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";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method to format the message for logging.
+ /////////////////////////////////////////////////////////////////////
+
+ private static string create_log_message(string message) {
+ if (log_file && regex != null && regex.match(message)) {
+ var parts = regex.split(message);
+ return " [%s%s] %s\n".printf(parts[1], parts[2], parts[3]);
+ } else if (regex != null && regex.match(message)) {
+ var parts = regex.split(message);
+ return " %s\n".printf(parts[3]);
+ } else {
+ return " " + message + "\n";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The handler function.
+ /////////////////////////////////////////////////////////////////////
+
+ private static void log_func(string? d, LogLevelFlags flags, string text) {
+ switch (flags) {
+ case LogLevelFlags.LEVEL_ERROR:
+ case LogLevelFlags.LEVEL_CRITICAL:
+ error(create_message(text), create_log_message(text));
+ break;
+ case LogLevelFlags.LEVEL_INFO:
+ case LogLevelFlags.LEVEL_MESSAGE:
+ message(create_message(text), create_log_message(text));
+ break;
+ case LogLevelFlags.LEVEL_DEBUG:
+ debug(create_message(text), create_log_message(text));
+ break;
+ case LogLevelFlags.LEVEL_WARNING:
+ default:
+ warning(create_message(text), create_log_message(text));
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/utilities/paths.vala b/src/utilities/paths.vala
new file mode 100644
index 0000000..7bdd642
--- /dev/null
+++ b/src/utilities/paths.vala
@@ -0,0 +1,286 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A 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 config directory,
+ /// usually ~/.config/gnome-pie/.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string config_directory { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The log file,
+ /// usually ~/.config/gnome-pie/gnome-pie.log.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string log { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The statistics file,
+ /// usually ~/.config/gnome-pie/gnome-pie.stats.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string stats { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The settings file,
+ /// usually ~/.config/gnome-pie/gnome-pie.conf.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string settings { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The 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 directory containing UI declaration files
+ /// usually /usr/share/gnome-pie/ui/.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string ui_files { 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=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The path to the executable.
+ /////////////////////////////////////////////////////////////////////
+
+ public static string executable { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Deletes a directory recursively from disk. Use with care :)
+ /////////////////////////////////////////////////////////////////////
+
+ public static void delete_directory(string directory) {
+ try {
+ var d = Dir.open(directory);
+ string name;
+ while ((name = d.read_name()) != null) {
+ string path = Path.build_filename(directory, name);
+ if (FileUtils.test(path, FileTest.IS_DIR)) {
+ delete_directory(path);
+ } else {
+ FileUtils.remove(path);
+ }
+ }
+ DirUtils.remove(directory);
+ } catch (Error e) {
+ warning (e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initializes all values above.
+ /////////////////////////////////////////////////////////////////////
+
+ public static void init() {
+
+ // get path of executable
+ try {
+ executable = GLib.File.new_for_path(GLib.FileUtils.read_link("/proc/self/exe")).get_path();
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+
+ // append resources to icon search path to icon theme, if neccasary
+ var icon_dir = GLib.File.new_for_path(GLib.Path.get_dirname(executable)).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/");
+ Gtk.IconTheme.get_default().append_search_path("/usr/share/icons/hicolor/scalable/apps");
+ Gtk.IconTheme.get_default().append_search_path("/usr/local/share/icons/hicolor/scalable/apps");
+
+ // 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()) {
+ default_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ executable)).get_child("resources");
+ }
+ }
+
+ global_themes = default_dir.get_path() + "/themes";
+ ui_files = default_dir.get_path() + "/ui";
+
+ // 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 {
+ locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ executable)).get_child("resources/locale/de/LC_MESSAGES/gnomepie.mo");
+
+ if(locale_dir.query_exists()) {
+ locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname(
+ executable)).get_child("resources/locale");
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ config_directory = config_dir.get_path();
+
+ // 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";
+ log = config_dir.get_path() + "/gnome-pie.log";
+ stats = config_dir.get_path() + "/gnome-pie.stats";
+
+ if (!GLib.File.new_for_path(log).query_exists()) {
+ try {
+ FileUtils.set_contents(log, "");
+ } catch (GLib.FileError e) {
+ error(e.message);
+ }
+ }
+
+ if (!GLib.File.new_for_path(stats).query_exists()) {
+ try {
+ FileUtils.set_contents(stats, "");
+ } catch (GLib.FileError e) {
+ error(e.message);
+ }
+ }
+
+ // 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\"! (This should only happen when Gnome-Pie is started for the first time...)");
+
+ if (!GLib.File.new_for_path(log).query_exists())
+ warning("Failed to find log file \"gnome-pie.log\"!");
+
+ if (!GLib.File.new_for_path(stats).query_exists())
+ warning("Failed to find statistics file \"gnome-pie.stats\"!");
+
+ 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!");
+
+ if (!GLib.File.new_for_path(ui_files).query_exists())
+ warning("Failed to find UI files directory!");
+ }
+}
+
+}
diff --git a/src/utilities/trigger.vala b/src/utilities/trigger.vala
new file mode 100644
index 0000000..5373b41
--- /dev/null
+++ b/src/utilities/trigger.vala
@@ -0,0 +1,357 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class represents a hotkey, used to open pies. It supports any
+/// combination of modifier keys with keyboard and mouse buttons.
+/////////////////////////////////////////////////////////////////////////
+
+public class Trigger : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human-readable version of this Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public string label { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns a human-readable version of this Trigger. Small
+ /// identifiers for turbo mode and delayed mode are added.
+ /////////////////////////////////////////////////////////////////////
+
+ public string label_with_specials { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Trigger string. Like [delayed]<Control>button3
+ /////////////////////////////////////////////////////////////////////
+
+ public string name { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The key code of the hotkey or the button number of the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ public int key_code { get; private set; default=0; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The keysym of the hotkey or the button number of the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ public uint key_sym { get; private set; default=0; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Modifier keys pressed for this hotkey.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gdk.ModifierType modifiers { get; private set; default=0; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if this hotkey involves the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool with_mouse { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if the pie closes when the trigger hotkey is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool turbo { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if the trigger should wait a short delay before being
+ /// triggered.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool delayed { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if the pie opens in the middle of the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool centered { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True if the mouse pointer is warped to the pie's center.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool warp { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the current selected "radio-button" shape: 0= automatic
+ /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves
+ /// 1 | 4 | 7
+ /// 2 | 5 | 8
+ /// 3 | 6 | 9
+ /////////////////////////////////////////////////////////////////////
+
+ public int shape { get; private set; default=5; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new, "unbound" Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public Trigger() {
+ this.set_unbound();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new Trigger from a given Trigger string. This is
+ /// in this format: "[option(s)]<modifier(s)>button" where
+ /// "<modifier>" is something like "<Alt>" or "<Control>", "button"
+ /// something like "s", "F4" or "button0" and "[option]" is either
+ /// "[turbo]", "[centered]", "[warp]", "["delayed"]" or "["shape#"]"
+ /////////////////////////////////////////////////////////////////////
+
+ public Trigger.from_string(string trigger) {
+ this.parse_string(trigger);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new Trigger from the key values.
+ /////////////////////////////////////////////////////////////////////
+
+ public Trigger.from_values(uint key_sym, Gdk.ModifierType modifiers,
+ bool with_mouse, bool turbo, bool delayed,
+ bool centered, bool warp, int shape ) {
+
+ string trigger = (turbo ? "[turbo]" : "")
+ + (delayed ? "[delayed]" : "")
+ + (centered ? "[centered]" : "")
+ + (warp ? "[warp]" : "")
+ + (shape!=5 ? "[shape%d]".printf(shape) : "");
+
+ if (with_mouse) {
+ trigger += Gtk.accelerator_name(0, modifiers) + "button%u".printf(key_sym);
+ } else {
+ trigger += Gtk.accelerator_name(key_sym, modifiers);
+ }
+
+ this.parse_string(trigger);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Parses a Trigger string. This is
+ /// in this format: "[option(s)]<modifier(s)>button" where
+ /// "<modifier>" is something like "<Alt>" or "<Control>", "button"
+ /// something like "s", "F4" or "button0" and "[option]" is either
+ /// "[turbo]", "[centered]", "[warp]", "["delayed"]" or "["shape#"]"
+ /////////////////////////////////////////////////////////////////////
+
+ public void parse_string(string trigger) {
+ if (this.is_valid(trigger)) {
+ // copy string
+ string check_string = trigger;
+
+ this.name = check_string;
+
+ this.turbo = check_string.contains("[turbo]");
+ this.delayed = check_string.contains("[delayed]");
+ this.centered = check_string.contains("[centered]");
+ this.warp = check_string.contains("[warp]");
+
+ this.shape= parse_shape( check_string );
+
+ // remove optional arguments
+ check_string = remove_optional(check_string);
+
+ int button = this.get_mouse_button(check_string);
+ if (button > 0) {
+ this.with_mouse = true;
+ this.key_code = button;
+ this.key_sym = button;
+
+ Gtk.accelerator_parse(check_string, null, out this._modifiers);
+ this.label = Gtk.accelerator_get_label(0, this.modifiers);
+
+ string button_text = _("Button %i").printf(this.key_code);
+
+ if (this.key_code == 1)
+ button_text = _("LeftButton");
+ else if (this.key_code == 3)
+ button_text = _("RightButton");
+ else if (this.key_code == 2)
+ button_text = _("MiddleButton");
+
+ this.label += button_text;
+ } else {
+ //empty triggers are ok now, they carry open options as well
+ if (check_string == "") {
+ this.label = _("Not bound");
+ this.key_code = 0;
+ this.key_sym = 0;
+ this.modifiers = 0;
+ } else {
+ this.with_mouse = false;
+
+ var display = new X.Display();
+
+ uint keysym = 0;
+ Gtk.accelerator_parse(check_string, out keysym, out this._modifiers);
+ this.key_code = display.keysym_to_keycode(keysym);
+ this.key_sym = keysym;
+ this.label = Gtk.accelerator_get_label(keysym, this.modifiers);
+ }
+ }
+
+ this.label_with_specials = GLib.Markup.escape_text(this.label);
+
+ string msg= "";
+ if (this.turbo) {
+ msg= _("Turbo");
+ }
+ if (this.delayed) {
+ if (msg == "")
+ msg= _("Delayed");
+ else
+ msg += " | " + _("Delayed");
+ }
+ if (this.centered) {
+ if (msg == "")
+ msg= _("Centered");
+ else
+ msg += " | " + _("Centered");
+ }
+ if (this.warp) {
+ if (msg == "")
+ msg= _("Warp");
+ else
+ msg += " | " + _("Warp");
+ }
+ if (this.shape == 0) {
+ if (msg == "")
+ msg= _("Auto-shaped");
+ else
+ msg += " | " + _("Auto-shaped");
+ } else if (this.shape == 1 || this.shape ==3 || this.shape == 7 || this.shape == 9) {
+ if (msg == "")
+ msg= _("Quarter pie");
+ else
+ msg += " | " + _("Quarter pie");
+
+ } else if (this.shape == 2 || this.shape == 4 || this.shape == 6 || this.shape == 8) {
+ if (msg == "")
+ msg= _("Half pie");
+ else
+ msg += " | " + _("Half pie");
+ }
+ if (msg != "")
+ this.label_with_specials += (" [ " + msg + " ]");
+
+ } else {
+ this.set_unbound();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Extract shape number from trigger string
+ /// "[0]".."[9]" 0:auto 5:full pie (default)
+ /// 1,3,7,9=quarters 2,4,6,8= halves
+ /////////////////////////////////////////////////////////////////////
+
+ private int parse_shape(string trigger) {
+ int rs;
+ for( rs= 0; rs < 10; rs++ )
+ if (trigger.contains("[shape%d]".printf(rs) ))
+ return rs;
+ return 5; //default= full pie
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Resets all member variables to their defaults.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_unbound() {
+ this.label = _("Not bound");
+ this.label_with_specials = _("Not bound");
+ this.name = "";
+ this.key_code = 0;
+ this.key_sym = 0;
+ this.modifiers = 0;
+ this.turbo = false;
+ this.delayed = false;
+ this.centered = false;
+ this.warp = false;
+ this.shape = 5; //full pie
+ this.with_mouse = false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Remove optional arguments from the given string
+ /// "[turbo]", "[delayed]", "[warp]" "[centered]" and "[shape#]"
+ /////////////////////////////////////////////////////////////////////
+
+ public static string remove_optional(string trigger) {
+ string trg= trigger;
+ trg = trg.replace("[turbo]", "");
+ trg = trg.replace("[delayed]", "");
+ trg = trg.replace("[centered]", "");
+ trg = trg.replace("[warp]", "");
+ for (int rs= 0; rs < 10; rs++)
+ trg = trg.replace("[shape%d]".printf(rs), "");
+ return trg;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true, if the trigger string is in a valid format.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool is_valid(string trigger) {
+ // remove optional arguments
+ string check_string = remove_optional(trigger);
+
+ if (this.get_mouse_button(check_string) > 0) {
+ // it seems to be a valid mouse-trigger so replace button part,
+ // with something accepted by gtk, and check it with gtk
+ int button_index = check_string.index_of("button");
+ check_string = check_string.slice(0, button_index) + "a";
+ }
+
+ //empty triggers are ok now, they carry open options as well
+ if (check_string == "")
+ return true;
+
+ // now it shouls be a normal gtk accelerator
+ uint keysym = 0;
+ Gdk.ModifierType modifiers = 0;
+ Gtk.accelerator_parse(check_string, out keysym, out modifiers);
+ if (keysym == 0)
+ return false;
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the mouse button number of the given trigger string.
+ /// Returns -1 if it is not a mouse trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private int get_mouse_button(string trigger) {
+ if (trigger.contains("button")) {
+ // it seems to be a mouse-trigger so check the button part.
+ int button_index = trigger.index_of("button");
+ int number = int.parse(trigger.slice(button_index + 6, trigger.length));
+ if (number > 0)
+ return number;
+ }
+
+ return -1;
+ }
+}
+
+}