diff options
Diffstat (limited to 'src')
47 files changed, 8243 insertions, 0 deletions
| diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..b3b8ed3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,82 @@ +################################################################ +# Actually compile the executable +################################################################ + +# determine source and header files +file(GLOB VALA_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.vala */*.vala) + +if (${INDICATOR_FOUND}) +  SET(DEFINES --define HAVE_APPINDICATOR) +endif(${INDICATOR_FOUND}) + +if (${GMENU3_FOUND}) +    LIST(APPEND DEFINES --define HAVE_GMENU_3) +endif (${GMENU3_FOUND}) + +# use valac to compile sources to c files +vala_precompile( +    VALA_C +        ${VALA_SRC} +    PACKAGES +        ${VALA_PKGS} +    OPTIONS +        --vapidir=${CMAKE_SOURCE_DIR}/vapi/ +        --thread +        ${DEFINES} +) + +# compile c-sources +add_executable(gnome-pie ${VALA_C}) + +# install executable +install( +	TARGETS +		gnome-pie +	RUNTIME DESTINATION +		${CMAKE_INSTALL_PREFIX}/bin +) + +# install credits +install( +	FILES +		${CMAKE_SOURCE_DIR}/README +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share/doc/gnome-pie +) + +# install locales +install( +	DIRECTORY +		${CMAKE_SOURCE_DIR}/resources/locale +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share +	PATTERN *.po EXCLUDE +	PATTERN *.pot EXCLUDE +	PATTERN *.sh EXCLUDE +) + +# install themes +install( +	DIRECTORY +		${CMAKE_SOURCE_DIR}/resources/themes +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share/gnome-pie +) + +# install icons +install( +	FILES +		${CMAKE_SOURCE_DIR}/resources/gnome-pie.svg +		${CMAKE_SOURCE_DIR}/resources/gnome-pie-indicator.svg +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps +) + +# desktop file +install( +	FILES +		${CMAKE_SOURCE_DIR}/resources/gnome-pie.desktop +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share/applications +) + diff --git a/src/actionGroups/actionGroup.vala b/src/actionGroups/actionGroup.vala new file mode 100644 index 0000000..a6b52ff --- /dev/null +++ b/src/actionGroups/actionGroup.vala @@ -0,0 +1,75 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////	 +// A base class storing a set of Actions. Derived classes may define +// how these Actions are created. This base class serves for custom +// actions, defined by the user. +///////////////////////////////////////////////////////////////////////// + +public class ActionGroup : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// A list of all stored actions. +    ///////////////////////////////////////////////////////////////////// +     +    public Gee.ArrayList<Action?> actions { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The ID of the pie to which this group is attached. +    ///////////////////////////////////////////////////////////////////// +     +    public string parent_id { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public ActionGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    construct { +        this.actions = new Gee.ArrayList<Action?>(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// This one is called, when the ActionGroup is deleted. +    ///////////////////////////////////////////////////////////////////// +     +    public virtual void on_remove() {} +     +    ///////////////////////////////////////////////////////////////////// +    /// Adds a new Action to the group. +    ///////////////////////////////////////////////////////////////////// +     +    public void add_action(Action new_action) { +       this.actions.add(new_action); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Removes all Actions from the group. +    ///////////////////////////////////////////////////////////////////// +     +    public void delete_all() { +        actions.clear(); +    } +} + +} diff --git a/src/actionGroups/bookmarkGroup.vala b/src/actionGroups/bookmarkGroup.vala new file mode 100644 index 0000000..f4ba66e --- /dev/null +++ b/src/actionGroups/bookmarkGroup.vala @@ -0,0 +1,148 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A group of Actions, which represent the users gtk-bookmarks, his home +/// directory, desktop and trash. It stay up-to-date, even if the  +/// bookmarks change. +///////////////////////////////////////////////////////////////////////// + +public class BookmarkGroup : ActionGroup { +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Bookmarks"); +        icon = "user-bookmarks"; +        settings_name = "bookmarks"; +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Two members needed to avoid useless, frequent changes of the  +    /// stored Actions. +    ///////////////////////////////////////////////////////////////////// + +    private bool changing = false; +    private bool changed_again = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public BookmarkGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Construct block loads the bookmarks of the user and adds a file +    /// monitor in order to update the BookmarkGroup when the bookmarks +    /// of the user change. +    ///////////////////////////////////////////////////////////////////// +     +    construct { +        this.load(); +         +        // add monitor +        var bookmark_file = GLib.File.new_for_path( +            GLib.Environment.get_home_dir()).get_child(".gtk-bookmarks"); +             +        if (bookmark_file.query_exists()) { +            try { +                var monitor = bookmark_file.monitor(GLib.FileMonitorFlags.NONE); +                monitor.changed.connect(this.reload); +            } catch (GLib.Error e) { +                warning(e.message); +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Adds Actions for each gtk-bookmark of the user and for his home +    /// folder, desktop and trash. +    ///////////////////////////////////////////////////////////////////// +     +    private void load() { +        // add home folder +        this.add_action(ActionRegistry.new_for_uri("file://" + GLib.Environment.get_home_dir())); +         +        // add .gtk-bookmarks +        var bookmark_file = GLib.File.new_for_path( +            GLib.Environment.get_home_dir()).get_child(".gtk-bookmarks"); +             +        if (!bookmark_file.query_exists()) { +            warning("Failed to find file \".gtk-bookmarks\"!"); +            return; +        } +         +        try { +            var dis = new DataInputStream(bookmark_file.read()); +            string line; +            while ((line = dis.read_line(null)) != null) { +                var parts = line.split(" "); +                 +                string uri = parts[0]; +                string name = parts[1]; + +                this.add_action(ActionRegistry.new_for_uri(uri, name)); +            } +        } catch (Error e) { +            error ("%s", e.message); +        } +         +        // add trash +        this.add_action(ActionRegistry.new_for_uri("trash:///")); +         +        // add desktop +        this.add_action(ActionRegistry.new_for_uri("file://" + GLib.Environment.get_user_special_dir(GLib.UserDirectory.DESKTOP))); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Reloads all Bookmarks. Is called when the user's gtk-bookmarks +    /// file changes. +    ///////////////////////////////////////////////////////////////////// +     +    private void reload() { +        // avoid too frequent changes... +        if (!this.changing) { +            this.changing = true; +            Timeout.add(200, () => { +                if (this.changed_again) { +                    this.changed_again = false; +                    return true; +                } + +                // reload +                message("Bookmarks changed..."); +                this.delete_all(); +                this.load(); +                 +                this.changing = false; +                return false; +            }); +        } else { +            this.changed_again = true; +        }     +    } +} + +} diff --git a/src/actionGroups/clipboardGroup.vala b/src/actionGroups/clipboardGroup.vala new file mode 100644 index 0000000..0e95b65 --- /dev/null +++ b/src/actionGroups/clipboardGroup.vala @@ -0,0 +1,116 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This Group keeps a history of the last used Clipboard entries. +///////////////////////////////////////////////////////////////////////// + +public class ClipboardGroup : ActionGroup { + +    ///////////////////////////////////////////////////////////////////// +    ///  +    ///////////////////////////////////////////////////////////////////// + +    private class ClipboardItem : GLib.Object { +         +        public string name { get; private set; } +        public string icon { get; private set; } +         +        private Gtk.SelectionData contents; +     +        public ClipboardItem(Gtk.SelectionData contents) { +            this.contents = contents.copy(); +            this.name = this.contents.get_text() ?? ""; +            this.icon = "edit-paste"; +        } +         +        public void paste() { +            debug(name); +        } +    } +     +    public ClipboardGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    ///////////////////////////////////////////////////////////////////// +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Clipboard"); +        icon = "edit-paste"; +        settings_name = "clipboard"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The clipboard to be monitored. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.Clipboard clipboard; +     +     +    ///////////////////////////////////////////////////////////////////// +    /// The maximum remembered items of the clipboard. +    ///////////////////////////////////////////////////////////////////// +     +    private const int max_items = 6; +     +    private Gee.ArrayList<ClipboardItem?> items; +     +    construct { +        this.items = new Gee.ArrayList<ClipboardItem?>(); +        this.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); +        this.clipboard.owner_change.connect(this.on_change); +    } +     +    private void on_change() { +        if (this.clipboard.wait_is_text_available()) { +            this.clipboard.request_contents(Gdk.Atom.intern("text/plain", false), this.add_item); +        } +    } +     +    private void add_item(Gtk.Clipboard c, Gtk.SelectionData contents) { +        var new_item = new ClipboardItem(contents); +         +        if (this.items.size == this.max_items) +            this.items.remove_at(0); +         +        this.items.add(new_item); +         +        // update slices +        this.delete_all(); +         +        for (int i=0; i<this.items.size; ++i) { +            var action = new SigAction(items[i].name, items[i].icon, i.to_string()); +            action.activated.connect(() => { +                this.items[int.parse(action.real_command)].paste(); +            }); +            this.add_action(action); +        } + +    } +} + +} diff --git a/src/actionGroups/devicesGroup.vala b/src/actionGroups/devicesGroup.vala new file mode 100644 index 0000000..3d2ced0 --- /dev/null +++ b/src/actionGroups/devicesGroup.vala @@ -0,0 +1,129 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////// +/// An ActionGroup which contains all currently plugged-in devices,  +/// such as CD-ROM's or USB-sticks. +///////////////////////////////////////////////////////////////////// + +public class DevicesGroup : ActionGroup { +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Devices"); +        icon = "harddrive"; +        settings_name = "devices"; +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Two members needed to avoid useless, frequent changes of the  +    /// stored Actions. +    ///////////////////////////////////////////////////////////////////// + +    private bool changing = false; +    private bool changed_again = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// The VolumeMonitor used to check for added or removed devices. +    ///////////////////////////////////////////////////////////////////// +     +    private GLib.VolumeMonitor monitor; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public DevicesGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Construct block loads all currently plugged-in devices and +    /// connects signal handlers to the VolumeMonitor. +    ///////////////////////////////////////////////////////////////////// +     +    construct { +        this.monitor = GLib.VolumeMonitor.get(); +         +        this.load(); + +        // add monitor +        this.monitor.mount_added.connect(this.reload); +        this.monitor.mount_removed.connect(this.reload); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads all currently plugged-in devices. +    ///////////////////////////////////////////////////////////////////// +     +    private void load() { +        // add root device +        this.add_action(new UriAction(_("Root"), "harddrive", "file:///")); +     +        // add all other devices +        foreach(var mount in this.monitor.get_mounts()) { +            // get icon +            var icon_names = mount.get_icon().to_string().split(" "); +             +            string icon = ""; +            foreach (var icon_name in icon_names) { +                if (Gtk.IconTheme.get_default().has_icon(icon_name)) { +                    icon = icon_name; +                    break; +                } +            } +             +            this.add_action(new UriAction(mount.get_name(), icon, mount.get_root().get_uri())); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Reloads all devices. Is called when the VolumeMonitor changes. +    ///////////////////////////////////////////////////////////////////// +     +    private void reload() { +        // avoid too frequent changes... +        if (!this.changing) { +            this.changing = true; +            Timeout.add(200, () => { +                if (this.changed_again) { +                    this.changed_again = false; +                    return true; +                } + +                // reload +                message("Devices changed..."); +                this.delete_all(); +                this.load(); +                 +                this.changing = false; +                return false; +            }); +        } else { +            this.changed_again = true; +        }     +    } +} + +} diff --git a/src/actionGroups/groupRegistry.vala b/src/actionGroups/groupRegistry.vala new file mode 100644 index 0000000..94169d5 --- /dev/null +++ b/src/actionGroups/groupRegistry.vala @@ -0,0 +1,89 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A which has knowledge on all possible acion group types. +///////////////////////////////////////////////////////////////////////// + +public class GroupRegistry : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// A list containing all available ActionGroup types. +    ///////////////////////////////////////////////////////////////////// +     +    public static Gee.ArrayList<Type> types { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Three maps associating a displayable name for each ActionGroup,  +    /// an icon name and a name for the pies.conf file with it's type. +    ///////////////////////////////////////////////////////////////////// +     +    public static Gee.HashMap<Type, string> names { get; private set; } +    public static Gee.HashMap<Type, string> icons { get; private set; } +    public static Gee.HashMap<Type, string> settings_names { get; private set; } +     +     +    ///////////////////////////////////////////////////////////////////// +    /// Registers all ActionGroup types. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        types = new Gee.ArrayList<Type>(); +     +        names = new Gee.HashMap<Type, string>(); +        icons = new Gee.HashMap<Type, string>(); +        settings_names = new Gee.HashMap<Type, string>(); +     +        string name = ""; +        string icon = ""; +        string settings_name = ""; +         +        BookmarkGroup.register(out name, out icon, out settings_name); +        types.add(typeof(BookmarkGroup)); +        names.set(typeof(BookmarkGroup), name); +        icons.set(typeof(BookmarkGroup), icon); +        settings_names.set(typeof(BookmarkGroup), settings_name); +         +        DevicesGroup.register(out name, out icon, out settings_name); +        types.add(typeof(DevicesGroup)); +        names.set(typeof(DevicesGroup), name); +        icons.set(typeof(DevicesGroup), icon); +        settings_names.set(typeof(DevicesGroup), settings_name); +         +        MenuGroup.register(out name, out icon, out settings_name); +        types.add(typeof(MenuGroup)); +        names.set(typeof(MenuGroup), name); +        icons.set(typeof(MenuGroup), icon); +        settings_names.set(typeof(MenuGroup), settings_name); +         +        SessionGroup.register(out name, out icon, out settings_name); +        types.add(typeof(SessionGroup)); +        names.set(typeof(SessionGroup), name); +        icons.set(typeof(SessionGroup), icon); +        settings_names.set(typeof(SessionGroup), settings_name); +         +//        ClipboardGroup.register(out name, out icon, out settings_name); +//        types.add(typeof(ClipboardGroup)); +//        names.set(typeof(ClipboardGroup), name); +//        icons.set(typeof(ClipboardGroup), icon); +//        settings_names.set(typeof(ClipboardGroup), settings_name); +    } +} + +} diff --git a/src/actionGroups/menuGroup.vala b/src/actionGroups/menuGroup.vala new file mode 100644 index 0000000..07a4bd1 --- /dev/null +++ b/src/actionGroups/menuGroup.vala @@ -0,0 +1,254 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// An ActionGroup which displays the user's main menu. It's a bit ugly, +/// but it supports both, an older version and libgnome-menus-3 at the +/// same time. +///////////////////////////////////////////////////////////////////////// +         +public class MenuGroup : ActionGroup { +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Main menu"); +        icon = "gnome-main-menu"; +        settings_name = "menu"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// True, if this MenuGroup is the top most menu. +    ///////////////////////////////////////////////////////////////////// +     +    public bool is_toplevel {get; construct set; default = true;} +     +    ///////////////////////////////////////////////////////////////////// +    /// The menu tree displayed by the MenuGroup. Only set for the +    /// toplevel MenuGroup. +    ///////////////////////////////////////////////////////////////////// +     +    private GMenu.Tree menu = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// A list of all sub menus of this MenuGroup. +    ///////////////////////////////////////////////////////////////////// +     +    private Gee.ArrayList<MenuGroup?> childs; +     +    ///////////////////////////////////////////////////////////////////// +    /// Two members needed to avoid useless, frequent changes of the  +    /// stored Actions. +    ///////////////////////////////////////////////////////////////////// +     +    private bool changing = false; +    private bool changed_again = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. Used for the toplevel menu. +    ///////////////////////////////////////////////////////////////////// +     +    public MenuGroup(string parent_id) { +        GLib.Object(parent_id : parent_id, is_toplevel : true); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. Used for sub menus. +    ///////////////////////////////////////////////////////////////////// +     +    public MenuGroup.sub_menu(string parent_id) { +        GLib.Object(parent_id : parent_id, is_toplevel : false); +    } +     +    construct { +        this.childs = new Gee.ArrayList<MenuGroup?>(); + +        if (this.is_toplevel) { +            #if HAVE_GMENU_3 +                this.menu = new GMenu.Tree("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED); +                this.menu.changed.connect(this.reload); +            #endif  +             +            this.load_toplevel();  +        }  +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Starts to load the menu. +    ///////////////////////////////////////////////////////////////////// +     +    private void load_toplevel() { +        #if HAVE_GMENU_3 +            try { +                if (this.menu.load_sync()) { +                    this.load_contents(this.menu.get_root_directory(), this.parent_id); +                } +            } catch (GLib.Error e) { +                warning(e.message); +            } +        #else +            this.menu = GMenu.Tree.lookup ("applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED); +            this.menu.add_monitor(this.reload); +            var dir = this.menu.get_root_directory(); +            this.load_contents(dir, this.parent_id); +        #endif  +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Parses the main menu recursively. +    ///////////////////////////////////////////////////////////////////// +     +    private void load_contents(GMenu.TreeDirectory dir, string parent_id) { +        #if HAVE_GMENU_3 +            var item = dir.iter(); + +            while (true) { +                var type = item.next(); +                if (type == GMenu.TreeItemType.INVALID) +                    break; +                     +                if (type == GMenu.TreeItemType.DIRECTORY && !item.get_directory().get_is_nodisplay()) { +                    // create a MenuGroup for sub menus  +                    string[] icons = item.get_directory().get_icon().to_string().split(" "); +                    string final_icon = "application-default-icon"; +                     +                    // search for available icons +                    foreach (var icon in icons) { +                        if (Gtk.IconTheme.get_default().has_icon(icon)) { +                            final_icon = icon; +                            break; +                        } +                    } +                 +                    var sub_menu = PieManager.create_dynamic_pie(item.get_directory().get_name(), final_icon); +                    var group = new MenuGroup.sub_menu(sub_menu.id); +                    group.add_action(new PieAction(parent_id, true)); +                    group.load_contents(item.get_directory(), sub_menu.id); +                    childs.add(group); +                                                       +                    sub_menu.add_group(group); +                     +                    this.add_action(new PieAction(sub_menu.id));  + +                } else if (type == GMenu.TreeItemType.ENTRY ) { +                    // create an AppAction for entries +                    if (!item.get_entry().get_is_excluded()) { +                        this.add_action(ActionRegistry.new_for_app_info(item.get_entry().get_app_info()));  +                    }  +                } +            } +        #else +            foreach (var item in dir.get_contents()) { +                switch(item.get_type()) { +                    case GMenu.TreeItemType.DIRECTORY: +                        // create a MenuGroup for sub menus  +                        if (!((GMenu.TreeDirectory)item).get_is_nodisplay()) { +                            var sub_menu = PieManager.create_dynamic_pie( +                                                              ((GMenu.TreeDirectory)item).get_name(), +                                                              ((GMenu.TreeDirectory)item).get_icon()); +                            var group = new MenuGroup.sub_menu(sub_menu.id); +                            group.add_action(new PieAction(parent_id, true)); +                            group.load_contents((GMenu.TreeDirectory)item, sub_menu.id); +                            childs.add(group); +                                                               +                            sub_menu.add_group(group); +                             +                            this.add_action(new PieAction(sub_menu.id));  +                        }  +                        break; +                         +                    case GMenu.TreeItemType.ENTRY: +                        // create an AppAction for entries +                        if (!((GMenu.TreeEntry)item).get_is_nodisplay() && !((GMenu.TreeEntry)item).get_is_excluded()) { +                            this.add_action(new AppAction(((GMenu.TreeEntry)item).get_name(),  +                                                          ((GMenu.TreeEntry)item).get_icon(),  +                                                          ((GMenu.TreeEntry)item).get_exec()));  +                        }  +                        break; +                } +            } +        #endif +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Reloads the menu. +    ///////////////////////////////////////////////////////////////////// +     +    private void reload() { +        // avoid too frequent changes... +        if (!this.changing) { +            this.changing = true; +            Timeout.add(500, () => { +                if (this.changed_again) { +                    this.changed_again = false; +                    return true; +                } + +                // reload +                message("Main menu changed..."); +                #if !HAVE_GMENU_3 +                    this.menu.remove_monitor(this.reload); +                #endif +                 +                this.clear(); +                this.load_toplevel(); +                 +                this.changing = false; +                return false; +            }); +        } else { +            this.changed_again = true; +        }   +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Deletes all generated Pies, when the toplevel menu is deleted. +    ///////////////////////////////////////////////////////////////////// +     +    public override void on_remove() { +        if (this.is_toplevel) +            this.clear(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Clears this ActionGroup recursively. +    ///////////////////////////////////////////////////////////////////// +     +    private void clear() { +        foreach (var child in childs) +            child.clear(); + +        if (!this.is_toplevel) +            PieManager.remove_pie(this.parent_id); +             +        this.delete_all(); +         +        this.childs.clear(); +         +        #if !HAVE_GMENU_3 +            this.menu = null; +        #endif +         +    } +} + +} diff --git a/src/actionGroups/sessionGroup.vala b/src/actionGroups/sessionGroup.vala new file mode 100644 index 0000000..9fcab1d --- /dev/null +++ b/src/actionGroups/sessionGroup.vala @@ -0,0 +1,68 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////// +/// An ActionGroup which has three Actions: Logout, Shutdown and +/// Reboot. +///////////////////////////////////////////////////////////////////// + +public class SessionGroup : ActionGroup { +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Session Control"); +        icon = "gnome-logout"; +        settings_name = "session"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public SessionGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Construct block adds the three Actions. +    ///////////////////////////////////////////////////////////////////// +     +    construct { +        this.add_action(new AppAction(_("Shutdown"), "gnome-shutdown",  +            "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestShutdown")); +             +        this.add_action(new AppAction(_("Logout"), "gnome-session-logout",  +            "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1")); +             +        this.add_action(new AppAction(_("Reboot"), "gnome-session-reboot",  +            "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestReboot")); +    } +     +    // TODO: check for available interfaces --- these may work too: +    // dbus-send --print-reply --system --dest=org.freedesktop.Hal /org/freedesktop/Hal/devices/computer org.freedesktop.Hal.Device.SystemPowerManagement.Shutdown +    // dbus-send --print-reply --dest=org.kde.ksmserver /KSMServer org.kde.KSMServerInterface.logout 0 2 2  +    // dbus-send --system --print-reply --dest="org.freedesktop.ConsoleKit" /org/freedesktop/ConsoleKit/Manager org.freedesktop.ConsoleKit.Manager.Stop +} + +} diff --git a/src/actions/action.vala b/src/actions/action.vala new file mode 100644 index 0000000..ceed357 --- /dev/null +++ b/src/actions/action.vala @@ -0,0 +1,77 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A base class for actions, which are executed when the user +/// activates a pie's slice. +///////////////////////////////////////////////////////////////////////// + +public abstract class Action : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// The command which gets executed when user activates the Slice. +    /// It may be anything but has to be representable with a string. +    ///////////////////////////////////////////////////////////////////// + +    public abstract string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The command displayed to the user. It should be a bit more +    /// beautiful than the real_command. +    ///////////////////////////////////////////////////////////////////// +     +    public abstract string display_command { get; }   +     +    ///////////////////////////////////////////////////////////////////// +    /// The name of the Action. +    /////////////////////////////////////////////////////////////////////   + +    public virtual string name { get; protected set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The name of the icon of this Action. It should be in the users +    /// current icon theme. +    ///////////////////////////////////////////////////////////////////// +     +    public virtual string icon { get; protected set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// True, if this Action is the quickAction of the associated Pie. +    /// The quickAction of a Pie gets executed when the users clicks on +    /// the center of a Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public virtual bool is_quick_action { get; protected set; } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public Action(string name, string icon, bool is_quick_action) { +        GLib.Object(name : name, icon : icon, is_quick_action : is_quick_action); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// This one is called, when the user activates the Slice. +    ///////////////////////////////////////////////////////////////////// + +    public abstract void activate(); +} + +} diff --git a/src/actions/actionRegistry.vala b/src/actions/actionRegistry.vala new file mode 100644 index 0000000..091865f --- /dev/null +++ b/src/actions/actionRegistry.vala @@ -0,0 +1,200 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A which has knowledge on all possible acion types. +///////////////////////////////////////////////////////////////////////// + +public class ActionRegistry : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// A list containing all available Action types. +    ///////////////////////////////////////////////////////////////////// +     +    public static Gee.ArrayList<Type> types { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Three maps associating a displayable name for each Action,  +    /// whether it has a custom icon and a name for the pies.conf +    /// file with it's type. +    ///////////////////////////////////////////////////////////////////// +     +    public static Gee.HashMap<Type, string> names { get; private set; } +    public static Gee.HashMap<Type, bool> icon_name_editables { get; private set; } +    public static Gee.HashMap<Type, string> settings_names { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Registers all Action types. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        types = new Gee.ArrayList<Type>(); +     +        names = new Gee.HashMap<Type, string>(); +        icon_name_editables = new Gee.HashMap<Type, bool>(); +        settings_names = new Gee.HashMap<Type, string>(); +     +        string name = ""; +        bool icon_name_editable = true; +        string settings_name = ""; +         +        AppAction.register(out name, out icon_name_editable, out settings_name); +        types.add(typeof(AppAction)); +        names.set(typeof(AppAction), name); +        icon_name_editables.set(typeof(AppAction), icon_name_editable); +        settings_names.set(typeof(AppAction), settings_name); +         +        KeyAction.register(out name, out icon_name_editable, out settings_name); +        types.add(typeof(KeyAction)); +        names.set(typeof(KeyAction), name); +        icon_name_editables.set(typeof(KeyAction), icon_name_editable); +        settings_names.set(typeof(KeyAction), settings_name); +         +        PieAction.register(out name, out icon_name_editable, out settings_name); +        types.add(typeof(PieAction)); +        names.set(typeof(PieAction), name); +        icon_name_editables.set(typeof(PieAction), icon_name_editable); +        settings_names.set(typeof(PieAction), settings_name); +         +        UriAction.register(out name, out icon_name_editable, out settings_name); +        types.add(typeof(UriAction)); +        names.set(typeof(UriAction), name); +        icon_name_editables.set(typeof(UriAction), icon_name_editable); +        settings_names.set(typeof(UriAction), settings_name); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper method which creates an Action, appropriate for the  +    /// given URI. This can result in an UriAction or in an AppAction, +    /// depending on the Type of the URI.  +    ///////////////////////////////////////////////////////////////////// + +    public static Action? new_for_uri(string uri, string? name = null) { +        var file = GLib.File.new_for_uri(uri); +        var scheme = file.get_uri_scheme(); +         +        string final_icon = ""; +        string final_name = file.get_basename(); + +        switch (scheme) { +            case "application": +                var file_name = uri.split("//")[1]; +                 +                var desktop_file = GLib.File.new_for_path("/usr/share/applications/" + file_name); +                if (desktop_file.query_exists()) +                    return new_for_desktop_file(desktop_file.get_path()); + +                break; +                 +            case "trash": +                final_icon = "user-trash"; +                final_name = _("Trash"); +                break; +                 +            case "http": case "https": +                final_icon = "www"; +                break; +                 +            case "ftp": case "sftp": +                final_icon = "folder-remote"; +                break; +                 +            default: +                try { +                    var info = file.query_info("*", GLib.FileQueryInfoFlags.NONE); +                     +                    if (info.get_content_type() == "application/x-desktop") +                        return new_for_desktop_file(file.get_parse_name()); +                     +                    // search for an appropriate icon +                    var gicon = info.get_icon();                 +                    string[] icons = gicon.to_string().split(" "); +                     +                    foreach (var icon in icons) { +                        if (Gtk.IconTheme.get_default().has_icon(icon)) { +                            final_icon = icon; +                            break; +                        } +                    } +                     +                } catch (GLib.Error e) { +                    warning(e.message); +                } + +                break; +        } +         +        if (!Gtk.IconTheme.get_default().has_icon(final_icon)) +                final_icon = "application-default-icon"; +         +        if (name != null) +            final_name = name; +         +        return new UriAction(final_name, final_icon, uri); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper method which creates an AppAction for given AppInfo. +    ///////////////////////////////////////////////////////////////////// +     +    public static Action? new_for_app_info(GLib.AppInfo info) {         +        string[] icons = info.get_icon().to_string().split(" "); +        string final_icon = "application-default-icon"; +         +        // search for available icons +        foreach (var icon in icons) { +            if (Gtk.IconTheme.get_default().has_icon(icon)) { +                final_icon = icon; +                break; +            } +        } +         +        return new AppAction(info.get_display_name() , final_icon, info.get_commandline()); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper method which creates an AppAction for given *.desktop +    /// file. +    ///////////////////////////////////////////////////////////////////// +     +    public static Action? new_for_desktop_file(string file_name) { +        var info = new DesktopAppInfo.from_filename(file_name); +        return new_for_app_info(info); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper method which creates an AppAction for given mime type. +    ///////////////////////////////////////////////////////////////////// +     +    public static Action? default_for_mime_type(string type) { +        var info = AppInfo.get_default_for_type(type, false); +        return new_for_app_info(info); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper method which creates an AppAction for given uri scheme. +    ///////////////////////////////////////////////////////////////////// +     +    public static Action? default_for_uri(string uri) { +        var info = AppInfo. get_default_for_uri_scheme(uri); +        return new_for_app_info(info); +    } +} + +} diff --git a/src/actions/appAction.vala b/src/actions/appAction.vala new file mode 100644 index 0000000..d8363e4 --- /dev/null +++ b/src/actions/appAction.vala @@ -0,0 +1,72 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This type of Action launches an application or a custom command. +///////////////////////////////////////////////////////////////////////// + +public class AppAction : Action { + +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of Action. It sets the display name +    /// for this Action, whether it has a custom Icon/Name and the string +    /// used in the pies.conf file for this kind of Actions. +    ///////////////////////////////////////////////////////////////////// + +    public static void register(out string name, out bool icon_name_editable, out string settings_name) { +        name = _("Launch application"); +        icon_name_editable = true; +        settings_name = "app"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Stores the command line. +    ///////////////////////////////////////////////////////////////////// + +    public override string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Simply returns the real_command. No beautification. +    ///////////////////////////////////////////////////////////////////// +     +    public override string display_command { get {return real_command;} } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public AppAction(string name, string icon, string command, bool is_quick_action = false) { +        GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Launches the desired command. +    ///////////////////////////////////////////////////////////////////// + +    public override void activate() { +        try{ +            var item = GLib.AppInfo.create_from_commandline(this.real_command, null, GLib.AppInfoCreateFlags.NONE); +            item.launch(null, null); +    	} catch (Error e) { +	        warning(e.message); +        } +    }  +} + +} diff --git a/src/actions/keyAction.vala b/src/actions/keyAction.vala new file mode 100644 index 0000000..0f6d094 --- /dev/null +++ b/src/actions/keyAction.vala @@ -0,0 +1,77 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This type of Action "presses" a key stroke. +///////////////////////////////////////////////////////////////////////// + +public class KeyAction : Action { + +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of Action. It sets the display name +    /// for this Action, whether it has a custom Icon/Name and the string +    /// used in the pies.conf file for this kind of Actions. +    ///////////////////////////////////////////////////////////////////// + +    public static void register(out string name, out bool icon_name_editable, out string settings_name) { +        name = _("Press key stroke"); +        icon_name_editable = true; +        settings_name = "key"; +    }    +     +    ///////////////////////////////////////////////////////////////////// +    /// Stores the accelerator of this action. +    ///////////////////////////////////////////////////////////////////// +     +    public override string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns a human readable form of the accelerator. +    ///////////////////////////////////////////////////////////////////// +     +    public override string display_command { get {return key.label;} } +     +    ///////////////////////////////////////////////////////////////////// +    /// The simulated key which gets 'pressed' on execution. +    ///////////////////////////////////////////////////////////////////// +     +    public Key key { get; set; } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public KeyAction(string name, string icon, string command, bool is_quick_action = false) { +        GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action); +    } +     +    construct { +        this.key = new Key(real_command); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Presses the desired key. +    ///////////////////////////////////////////////////////////////////// + +    public override void activate() { +        key.press(); +    } +} + +} diff --git a/src/actions/pieAction.vala b/src/actions/pieAction.vala new file mode 100644 index 0000000..53ea919 --- /dev/null +++ b/src/actions/pieAction.vala @@ -0,0 +1,95 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This Action opens another pie. +///////////////////////////////////////////////////////////////////////// + +public class PieAction : Action { + +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of Action. It sets the display name +    /// for this Action, whether it has a custom Icon/Name and the string +    /// used in the pies.conf file for this kind of Actions. +    ///////////////////////////////////////////////////////////////////// + +    public static void register(out string name, out bool icon_name_editable, out string settings_name) { +        name = _("Open Pie"); +        icon_name_editable = false; +        settings_name = "pie"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Stores the ID of the referenced Pie. +    ///////////////////////////////////////////////////////////////////// + +    public override string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the name of the referenced Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public override string display_command { get {return name;} } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the name of the referenced Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public override string name { +        get { +            var referee = PieManager.all_pies[real_command]; +            if (referee != null) +                return referee.name; +            return ""; +        } +        protected set {} +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the icon of the referenced Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public override string icon { +        get { +            var referee = PieManager.all_pies[real_command]; +            if (referee != null) +                return referee.icon; +            return ""; +        } +        protected set {} +    } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public PieAction(string id, bool is_quick_action = false) { +        GLib.Object(name : "", icon : "", real_command : id, is_quick_action : is_quick_action); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Opens the desired Pie. +    ///////////////////////////////////////////////////////////////////// + +    public override void activate() { +        PieManager.open_pie(real_command); +    }  +} + +} diff --git a/src/actions/sigAction.vala b/src/actions/sigAction.vala new file mode 100644 index 0000000..cec9836 --- /dev/null +++ b/src/actions/sigAction.vala @@ -0,0 +1,63 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This type of Action can't be selected by the user, therefore there is +/// no register() method for this class. But it may be useful for +/// ActionGroups: It emits a signal on activation. +///////////////////////////////////////////////////////////////////////// + +public class SigAction : Action { + +    ///////////////////////////////////////////////////////////////////// +    /// This signal is emitted on activation. +    ///////////////////////////////////////////////////////////////////// + +    public signal void activated(); +     +    ///////////////////////////////////////////////////////////////////// +    /// This may store something useful. +    ///////////////////////////////////////////////////////////////////// + +    public override string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Only for inheritance... Greetings to Liskov. +    ///////////////////////////////////////////////////////////////////// +     +    public override string display_command { get {return real_command;} } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public SigAction(string name, string icon, string command, bool is_quick_action = false) { +        GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Emits the signal on activation. +    ///////////////////////////////////////////////////////////////////// + +    public override void activate() { +        this.activated(); +    }  +} + +} diff --git a/src/actions/uriAction.vala b/src/actions/uriAction.vala new file mode 100644 index 0000000..25d5c75 --- /dev/null +++ b/src/actions/uriAction.vala @@ -0,0 +1,71 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This type of Action opens the default application for an URI. +///////////////////////////////////////////////////////////////////////// + +public class UriAction : Action { +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of Action. It sets the display name +    /// for this Action, whether it has a custom Icon/Name and the string +    /// used in the pies.conf file for this kind of Actions. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out bool icon_name_editable, out string settings_name) { +        name = _("Open URI"); +        icon_name_editable = true; +        settings_name = "uri"; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The URI of this Action. +    ///////////////////////////////////////////////////////////////////// +     +    public override string real_command { get; construct set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns only the real URI. An URI can't be beautified. +    ///////////////////////////////////////////////////////////////////// +     +    public override string display_command { get {return real_command;} } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// + +    public UriAction(string name, string icon, string command, bool is_quick_action = false) { +        GLib.Object(name : name, icon : icon, real_command : command, is_quick_action : is_quick_action); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Opens the default application for the URI. +    ///////////////////////////////////////////////////////////////////// + +    public override void activate() { +        try{ +            GLib.AppInfo.launch_default_for_uri(real_command, null); +    	} catch (Error e) { +	        warning(e.message); +        } +    }  +} + +} diff --git a/src/deamon.vala b/src/deamon.vala new file mode 100644 index 0000000..af232eb --- /dev/null +++ b/src/deamon.vala @@ -0,0 +1,175 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +///////////////////////////////////////////////////////////////////// +/// TODO-List: +/// IconSelectWindow +/// PieList +/// PieWindow +/// CenterRenderer +/// SliceRenderer +/// PieRenderer +///////////////////////////////////////////////////////////////////// + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This class runs in the background. It has an Indicator sitting in the +/// user's panel. It initializes everything and guarantees that there is +/// only one instance of Gnome-Pie running. +///////////////////////////////////////////////////////////////////////// +	 +public class Deamon : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// The beginning of everything. +    ///////////////////////////////////////////////////////////////////// + +    public static int main(string[] args) { +        Logger.init(); +        Gtk.init(ref args); +        Paths.init(); + +        // create the Deamon and run it +        var deamon = new GnomePie.Deamon(); +        deamon.run(args); + +        return 0; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The AppIndicator of Gnome-Pie. +    ///////////////////////////////////////////////////////////////////// + +    private Indicator indicator = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// Varaibles set by the commend line parser. +    ///////////////////////////////////////////////////////////////////// +     +    private static string open_pie = null; +    private static bool reset = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// Available command line options. +    ///////////////////////////////////////////////////////////////////// +     +    private const GLib.OptionEntry[] options = { +        { "open", 'o', 0, GLib.OptionArg.STRING, out open_pie,  +          "Open the Pie with the given ID", "ID" }, +        { "reset", 'r', 0, GLib.OptionArg.NONE, out reset,  +          "Reset all options to default values" }, +        { null } +    }; + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor of the Deamon. It checks whether it's the firts running +    /// instance of Gnome-Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public void run(string[] args) { +        // create command line options +        var context = new GLib.OptionContext(""); +        context.set_help_enabled(true); +        context.add_main_entries(options, null); +        context.add_group(Gtk.get_option_group (false)); + +        try { +            context.parse(ref args); +        } catch(GLib.OptionError error) { +            warning(error.message); +        } +         +        if (this.reset) { +            if (GLib.FileUtils.remove(Paths.pie_config) == 0) +                message("Removed file \"%s\"", Paths.pie_config); +            if (GLib.FileUtils.remove(Paths.settings) == 0) +                message("Removed file \"%s\"", Paths.settings); +            return; +        } +     +        // create unique application +        var app = new Unique.App("org.gnome.gnomepie", null); + +        if (app.is_running) { +            // inform the running instance of the pie to be opened +            if (open_pie != null) { +                var data = new Unique.MessageData(); +                data.set_text(open_pie, open_pie.length); +                app.send_message(Unique.Command.ACTIVATE, data); +                return; +            }  +            +            app.send_message(Unique.Command.ACTIVATE, null); +            return; +        } +         +        // wait for incoming messages +        app.message_received.connect((cmd, data, event_time) => { +            if (cmd == Unique.Command.ACTIVATE) { +                var pie = data.get_text(); +                 +                if (pie != "") PieManager.open_pie(pie); +                else           this.indicator.show_preferences(); + +                return Unique.Response.OK; +            } + +            return Unique.Response.PASSTHROUGH; +        }); +     +        // init toolkits and static stuff +        Gdk.threads_init(); +        ActionRegistry.init(); +        GroupRegistry.init(); +        PieManager.init(); +        Icon.init(); +        ThemedIcon.init(); +        RenderedText.init(); +     +        // init locale support +        Intl.bindtextdomain ("gnomepie", Paths.locales); +        Intl.textdomain ("gnomepie"); +         +        // launch the indicator +        this.indicator = new Indicator(); + +        // connect SigHandlers +        Posix.signal(Posix.SIGINT, sig_handler); +	    Posix.signal(Posix.SIGTERM, sig_handler); +	 +	    // finished loading... so run the prog! +	    message("Started happily..."); +	     +	    // open pie if neccessary +	    if (open_pie != null) PieManager.open_pie(open_pie); +	     +	    Gtk.main(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Print a nifty message when the prog is killed. +    ///////////////////////////////////////////////////////////////////// +     +    private static void sig_handler(int sig) { +        stdout.printf("\n"); +		message("Caught signal (%d), bye!".printf(sig)); +		Gtk.main_quit(); +	} +} + +} diff --git a/src/gui/about.vala b/src/gui/about.vala new file mode 100644 index 0000000..1ace9cb --- /dev/null +++ b/src/gui/about.vala @@ -0,0 +1,43 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A simple about Dialog. +///////////////////////////////////////////////////////////////////////// + +public class GnomePieAboutDialog: Gtk.AboutDialog { + +    public GnomePieAboutDialog () { +        string[] devs = {"Simon Schneegans <code@simonschneegans.de>",  +                         "Francesco Piccinno"}; +        string[] artists = {"Simon Schneegans <code@simonschneegans.de>"}; +        GLib.Object ( +            artists : artists, +            authors : devs, +            copyright : "Copyright (C) 2011 Simon Schneegans <code@simonschneegans.de>", +            program_name: "Gnome-Pie", +            logo_icon_name: "gnome-pie", +            website: "http://www.simonschneegans.de/?page_id=12", +            website_label: "www.gnome-pie.simonschneegans.de", +            version: "0.2" +        ); +    } +} + +} diff --git a/src/gui/cellRendererIcon.vala b/src/gui/cellRendererIcon.vala new file mode 100644 index 0000000..959a0b7 --- /dev/null +++ b/src/gui/cellRendererIcon.vala @@ -0,0 +1,132 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A cellrenderer which displays an Icon. When clicked onto, a window +/// opens for selecting another icon. This needs to be a subclass of +/// Gtk.CellRendererText because Gtk.CellRendererPixbuf can't receive +/// click events. Internally it stores a Gtk.CellRendererPixbuf +/// which renders and stuff. +///////////////////////////////////////////////////////////////////////// + +public class CellRendererIcon : Gtk.CellRendererText { +     +    ///////////////////////////////////////////////////////////////////// +    /// This signal is emitted when the user selects another icon. +    ///////////////////////////////////////////////////////////////////// +     +    public signal void on_select(string path, string icon); +     +    ///////////////////////////////////////////////////////////////////// +    /// The IconSelectWindow which is shown on click. +    ///////////////////////////////////////////////////////////////////// + +    private IconSelectWindow select_window = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// The internal Renderer used for drawing. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.CellRendererPixbuf renderer = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper variable, needed to emit the current path. +    ///////////////////////////////////////////////////////////////////// +     +    private string current_path = ""; +     +    public string icon_name { get; set; } + +    ///////////////////////////////////////////////////////////////////// +    /// Forward some parts of the CellRendererPixbuf's interface. +    ///////////////////////////////////////////////////////////////////// +     +    public bool follow_state { +        get { return renderer.follow_state; } +        set { renderer.follow_state = value; } +    } +     +    public bool icon_sensitive { +        get { return renderer.sensitive; } +        set { renderer.sensitive = value; } +    } +     +    public Gdk.Pixbuf pixbuf { +        owned get { return renderer.pixbuf; } +        set { renderer.pixbuf = value; } +    } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new CellRendererIcon. +    ///////////////////////////////////////////////////////////////////// +     +    public CellRendererIcon() { +        this.select_window = new IconSelectWindow();   +        this.renderer = new Gtk.CellRendererPixbuf(); +     +        this.select_window.on_select.connect((icon) => { +            this.on_select(current_path, icon); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Forward some parts of the CellRendererPixbuf's interface. +    ///////////////////////////////////////////////////////////////////// +     +    public override void get_size (Gtk.Widget widget, Gdk.Rectangle? cell_area, +                               out int x_offset, out int y_offset, +                               out int width, out int height) { + +        this.renderer.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Forward some parts of the CellRendererPixbuf's interface. +    ///////////////////////////////////////////////////////////////////// +     +    public override void render (Gdk.Window window, Gtk.Widget widget, +                             Gdk.Rectangle bg_area, +                             Gdk.Rectangle cell_area, +                             Gdk.Rectangle expose_area, +                             Gtk.CellRendererState flags) { +                              +        this.renderer.render(window, widget, bg_area, cell_area, expose_area, flags); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Open the IconSelectWindow on click. +    ///////////////////////////////////////////////////////////////////// +     +    public override unowned Gtk.CellEditable start_editing( +        Gdk.Event event, Gtk.Widget widget, string path, Gdk.Rectangle bg_area,  +        Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { +         +        this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); +        this.select_window.set_modal(true); +         +        this.current_path = path; +        this.select_window.show(); +        this.select_window.active_icon = this.icon_name; +             +        return this.renderer.start_editing(event, widget, path, bg_area, cell_area, flags); +    } +} + +} + diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala new file mode 100644 index 0000000..2274ec5 --- /dev/null +++ b/src/gui/iconSelectWindow.vala @@ -0,0 +1,349 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A window which allows selection of an Icon of the user's current icon  +/// theme. Loading of Icons happens in an extra thread and a spinner is +/// displayed while loading. +///////////////////////////////////////////////////////////////////////// + +public class IconSelectWindow : Gtk.Dialog { + +    private static Gtk.ListStore icon_list = null; +     +    private static bool loading {get; set; default = false;} +    private static bool need_reload {get; set; default = true;} +     +    private const string disabled_contexts = "Animations, FileSystems, MimeTypes"; +    private Gtk.TreeModelFilter icon_list_filtered = null; +    private Gtk.IconView icon_view = null; +    private Gtk.Spinner spinner = null; +     +    private Gtk.FileChooserWidget file_chooser = null; +     +    private Gtk.Notebook tabs = null; + +    private class ListEntry { +        public string name; +        public IconContext context; +        public Gdk.Pixbuf pixbuf; +    } +     +    private GLib.AsyncQueue<ListEntry?> load_queue; +     +    private enum IconContext { +        ALL, +        APPS, +        ACTIONS, +        PLACES, +        FILES, +        EMOTES, +        OTHER +    } +     +    public string _active_icon = "application-default-icon"; +     +    public string active_icon { +        get { +            return _active_icon; +        } +        set { +            if (value.contains("/")) { +                this.file_chooser.set_filename(value); +                this.tabs.set_current_page(1); +            } else { +                this.icon_list_filtered.foreach((model, path, iter) => { +                    string name = ""; +                    model.get(iter, 0, out name); +                     +                    if (name == value) { +                        this.icon_view.select_path(path); +                        this.icon_view.scroll_to_path(path, true, 0.5f, 0.0f); +                        this.icon_view.set_cursor(path, null, false); +                    } +                    return (name == value); +                }); +                 +                this.tabs.set_current_page(0); +            } +        } +    } +     +    public signal void on_select(string icon_name); +     +    public IconSelectWindow() { +        this.title = _("Choose an Icon"); +        this.set_size_request(520, 520); +        this.delete_event.connect(hide_on_delete); +        this.load_queue = new GLib.AsyncQueue<ListEntry?>(); +             +        if (this.icon_list == null) { +            this.icon_list = new Gtk.ListStore(3, typeof(string), typeof(IconContext), typeof(Gdk.Pixbuf));  +            this.icon_list.set_default_sort_func(() => {return 0;}); + +            Gtk.IconTheme.get_default().changed.connect(() => { +                if (this.visible) load_icons(); +                else              need_reload = true; +            }); +        }  +         +        this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null); + +        var container = new Gtk.VBox(false, 12); +            container.set_border_width(12); + +            // tab container +            this.tabs = new Gtk.Notebook(); + +                var theme_tab = new Gtk.VBox(false, 12); +                    theme_tab.set_border_width(12); +             +                    var context_combo = new Gtk.ComboBox.text(); +                        context_combo.append_text(_("All icons")); +                        context_combo.append_text(_("Applications")); +                        context_combo.append_text(_("Actions")); +                        context_combo.append_text(_("Places")); +                        context_combo.append_text(_("File types")); +                        context_combo.append_text(_("Emotes")); +                        context_combo.append_text(_("Miscellaneous")); + +                        context_combo.set_active(0); +                         +                        context_combo.changed.connect(() => { +                            this.icon_list_filtered.refilter(); +                        }); +                         +                        theme_tab.pack_start(context_combo, false, false); +                         +                    var filter = new Gtk.Entry(); +                        filter.primary_icon_stock = Gtk.Stock.FIND; +                        filter.primary_icon_activatable = false; +                        filter.secondary_icon_stock = Gtk.Stock.CLEAR; +                        theme_tab.pack_start(filter, false, false); +                         +                        this.icon_list_filtered.set_visible_func((model, iter) => { +                            string name = ""; +                            IconContext context = IconContext.ALL; +                            model.get(iter, 0, out name); +                            model.get(iter, 1, out context); +                             +                            if (name == null) return false; +                             +                            return (context_combo.get_active() == context ||  +                                    context_combo.get_active() == IconContext.ALL) &&  +                                    name.down().contains(filter.text.down()); +                        }); +                         +                        filter.icon_release.connect((pos, event) => { +                            if (pos == Gtk.EntryIconPosition.SECONDARY) +                                filter.text = ""; +                        }); +                         +                        filter.notify["text"].connect(() => { +                            this.icon_list_filtered.refilter(); +                        }); +                     +                    var scroll = new Gtk.ScrolledWindow (null, null); +                        scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); +                        scroll.set_shadow_type (Gtk.ShadowType.IN); + +                        this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered); +                            this.icon_view.item_width = 32; +                            this.icon_view.item_padding = 3; +                            this.icon_view.pixbuf_column = 2; +                            this.icon_view.tooltip_column = 0; +                             +                            this.icon_view.selection_changed.connect(() => { +                                foreach (var path in this.icon_view.get_selected_items()) { +                                    Gtk.TreeIter iter; +                                    this.icon_list_filtered.get_iter(out iter, path); +                                    icon_list_filtered.get(iter, 0, out this._active_icon); +                                } +                            }); +                             +                            this.icon_view.item_activated.connect((path) => { +                                Gtk.TreeIter iter; +                                this.icon_list_filtered.get_iter(out iter, path); +                                this.icon_list_filtered.get(iter, 0, out this._active_icon); +                                this.on_select(this._active_icon); +                                this.hide(); +                            }); +                     +                        scroll.add(this.icon_view); +                     +                        theme_tab.pack_start(scroll, true, true); +                         +                    tabs.append_page(theme_tab, new Gtk.Label(_("Icon Theme"))); +                 +                var custom_tab = new Gtk.VBox(false, 6); +                    custom_tab.border_width = 12; +                     +                    this.file_chooser = new Gtk.FileChooserWidget(Gtk.FileChooserAction.OPEN); +                        var file_filter = new Gtk.FileFilter(); +                        file_filter.add_pixbuf_formats(); +                        file_filter.set_name(_("All supported image formats")); +                        file_chooser.add_filter(file_filter); +                         +                        file_chooser.selection_changed.connect(() => { +                            if (file_chooser.get_filename() != null && GLib.FileUtils.test(file_chooser.get_filename(), GLib.FileTest.IS_REGULAR)) +                                this._active_icon = file_chooser.get_filename(); +                        }); +                         +                        file_chooser.file_activated.connect(() => { +                            this._active_icon = file_chooser.get_filename(); +                            this.on_select(this._active_icon); +                            this.hide(); +                        }); +                     +                     +                    custom_tab.pack_start(file_chooser, true, true); +                     +                tabs.append_page(custom_tab, new Gtk.Label(_("Custom Icon"))); +                     +            container.pack_start(tabs, true, true); + +            // button box  +            var bottom_box = new Gtk.HBox(false, 0); +             +                var bbox = new Gtk.HButtonBox(); +                    bbox.set_spacing(6); +                    bbox.set_layout(Gtk.ButtonBoxStyle.END); +                     +                    var cancel_button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL); +                        cancel_button.clicked.connect(() => {  +                            this.hide(); +                        }); +                        bbox.pack_start(cancel_button); +                         +                    var ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK); +                        ok_button.clicked.connect(() => {  +                            this.on_select(this._active_icon); +                            this.hide(); +                        }); +                        bbox.pack_start(ok_button); +                         +                    bottom_box.pack_end(bbox, false); +                     +                    this.spinner = new Gtk.Spinner(); +                        this.spinner.set_size_request(16, 16); +                        this.spinner.start(); +                         +                        bottom_box.pack_start(this.spinner, false, false); +             +            container.pack_start(bottom_box, false, false); +           +        this.vbox.pack_start(container, true, true); + +        this.vbox.show_all(); + +        this.set_focus(this.icon_view); +    } +     +    public override void show() { +        base.show(); +        this.action_area.hide(); +         +        if (this.need_reload) { +            this.need_reload = false; +            this.load_icons(); +        } +    } +     +    private void load_icons() { +        if (!this.loading) { +            this.loading = true; +            this.icon_list.clear(); +             +            if (spinner != null) +                this.spinner.visible = true; + +            this.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING); +             +            try { +                unowned Thread loader = Thread.create<void*>(load_thread, false); +                loader.set_priority(ThreadPriority.LOW); +            } catch (GLib.ThreadError e) { +                error("Failed to create icon loader thread!"); +            } +             +            Timeout.add(200, () => { +                while (this.load_queue.length() > 0) { +                    var new_entry = this.load_queue.pop(); +                    Gtk.TreeIter current; +                    this.icon_list.append(out current); +                    this.icon_list.set(current, 0, new_entry.name,  +                                                1, new_entry.context, +                                                2, new_entry.pixbuf); +                } +                 +                if (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING);   + +                return loading; +            }); +        } +    } +     +    private void* load_thread() { +        var icon_theme = Gtk.IconTheme.get_default(); + +        foreach (var context in icon_theme.list_contexts()) { +            if (!disabled_contexts.contains(context)) { +                foreach (var icon in icon_theme.list_icons(context)) { +                    IconContext icon_context = IconContext.OTHER; +                    switch(context) { +                        case "Apps": case "Applications": +                            icon_context = IconContext.APPS; break; +                        case "Emotes": +                            icon_context = IconContext.EMOTES; break; +                        case "Places": case "Devices": +                            icon_context = IconContext.PLACES; break; +                        case "Mimetypes": +                            icon_context = IconContext.FILES; break; +                        case "Actions": +                            icon_context = IconContext.ACTIONS; break; +                        default: break; +                    } +                     +                    try {       +                        var new_entry = new ListEntry(); +                        new_entry.name = icon; +                        new_entry.context = icon_context; +                        new_entry.pixbuf = icon_theme.load_icon(icon, 32, 0);  +                         +                        // some icons have only weird sizes... do not include them +                        if (new_entry.pixbuf.width == 32) +                            this.load_queue.push(new_entry); +                             +                    } catch (GLib.Error e) { +                        warning("Failed to load image " + icon); +                    } +                } +            } +        } +         +        this.loading = false; +         +        if (spinner != null) +            spinner.visible = this.loading; + +        return null; +    } +} + +} diff --git a/src/gui/indicator.vala b/src/gui/indicator.vala new file mode 100644 index 0000000..8033cb7 --- /dev/null +++ b/src/gui/indicator.vala @@ -0,0 +1,160 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// An appindicator sitting in the panel. It owns the settings menu. +///////////////////////////////////////////////////////////////////////// + +public class Indicator : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// The internally used indicator. +    ///////////////////////////////////////////////////////////////////// + +    #if HAVE_APPINDICATOR +        private AppIndicator.Indicator indicator { private get; private set; } +    #else +        private Gtk.StatusIcon indicator {private get; private set; } +        private Gtk.Menu menu {private get; private set; } +    #endif + +    ///////////////////////////////////////////////////////////////////// +    /// The Preferences Menu of Gnome-Pie. +    ///////////////////////////////////////////////////////////////////// + +    private Preferences prefs { private get; private set; } + +    ///////////////////////////////////////////////////////////////////// +    /// Returns true, when the indicator is currently visible. +    ///////////////////////////////////////////////////////////////////// + +    public bool active { +        get { +         +            #if HAVE_APPINDICATOR +                return indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE; +            #else +                return indicator.get_visible(); +            #endif +        } +        set { +            #if HAVE_APPINDICATOR +                if (value) indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); +                else       indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE); +            #else +                indicator.set_visible(value); +            #endif +        } +    } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, constructs a new Indicator, residing in the user's panel. +    ///////////////////////////////////////////////////////////////////// + +    public Indicator() { +        #if HAVE_APPINDICATOR +            string path = ""; +            string icon = "indicator-applet"; +            try { +                path = GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources"; +                icon = "gnome-pie-indicator"; +            } catch (GLib.FileError e) { +                warning("Failed to get path of executable!"); +            } + +            this.indicator = new AppIndicator.Indicator.with_path("Gnome-Pie", icon, +                                 AppIndicator.IndicatorCategory.APPLICATION_STATUS, path); +            var menu = new Gtk.Menu(); +        #else +            this.indicator = new Gtk.StatusIcon(); +            try { +                var file = GLib.File.new_for_path(GLib.Path.build_filename( +                    GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources", +                    "gnome-pie-indicator.svg" +                )); + +                if (!file.query_exists()) +                  this.indicator.set_from_icon_name("gnome-pie-indicator"); +                else +                  this.indicator.set_from_file(file.get_path()); +            } catch (GLib.FileError e) { +                warning("Failed to get path of executable!"); +                this.indicator.set_from_icon_name("gnome-pie-indicator"); +            } + +            this.menu = new Gtk.Menu(); +            var menu = this.menu; +        #endif + +        this.prefs = new Preferences(); + +        // preferences item +        var item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.PREFERENCES, null); +        item.activate.connect(() => { +            this.prefs.show(); +        }); + +        item.show(); +        menu.append(item); + +        // about item +        item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.ABOUT, null); +        item.show(); +        item.activate.connect(() => { +            var about = new GnomePieAboutDialog(); +            about.run(); +            about.destroy(); +        }); +        menu.append(item); + +        // separator +        var sepa = new Gtk.SeparatorMenuItem(); +        sepa.show(); +        menu.append(sepa); + +        // quit item +        item = new Gtk.ImageMenuItem.from_stock(Gtk.Stock.QUIT, null); +        item.activate.connect(Gtk.main_quit); +        item.show(); +        menu.append(item); + +        #if HAVE_APPINDICATOR +            this.indicator.set_menu(menu); +        #else +            this.indicator.popup_menu.connect((btn, time) => { +                this.menu.popup(null, null, null, btn, time); +            }); +        #endif + +        this.active = Config.global.show_indicator; +        Config.global.notify["show-indicator"].connect((s, p) => { +            this.active = Config.global.show_indicator; +        }); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Shows the preferences menu. +    ///////////////////////////////////////////////////////////////////// + +    public void show_preferences() { +        this.prefs.show(); +    } +} + +} diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala new file mode 100644 index 0000000..df6135a --- /dev/null +++ b/src/gui/pieList.vala @@ -0,0 +1,1018 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +// A very complex Widget. This is by far the most ugly file of this project +// but well, this list *is* complex... sorry ;) + +class PieList : Gtk.TreeView { + +    private Gtk.ListStore groups; +    private Gtk.ListStore pies; +    private Gtk.ListStore actions; +    private Gtk.TreeStore data; +     +    private const int small_icon = 24; +    private const int large_icon = 36; +     +    // data positions in the data ListStore +    private enum DataPos {IS_QUICKACTION, ICON, NAME, TYPE_ID, ACTION_TYPE, +                          ICON_PIXBUF, FONT_WEIGHT, ICON_NAME_EDITABLE, QUICKACTION_VISIBLE, QUICKACTION_ACTIVATABLE, +                          TYPE_VISIBLE, GROUP_VISIBLE, APP_VISIBLE, KEY_VISIBLE, PIE_VISIBLE, +                          URI_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP,  +                          DISPLAY_COMMAND_KEY, DISPLAY_COMMAND_PIE, DISPLAY_COMMAND_URI, +                          REAL_COMMAND_GROUP, REAL_COMMAND_PIE, REAL_COMMAND_KEY} +     +    // data positions in the actions ListStore +    private enum ActionPos {NAME, TYPE, CAN_QUICKACTION, ICON_NAME_EDITABLE} +     +    // data positions in the pies ListStore +    private enum PiePos {NAME, ID} +     +    // data positions in the groups ListStore +    private enum GroupPos {NAME, TYPE, ICON} + +    public PieList() { +        GLib.Object(); +         +        Gtk.TreeIter last; +         +        // group choices +        this.groups = new Gtk.ListStore(3, typeof(string),     // group name +                                           typeof(string),     // group type +                                           typeof(string));    // group icon +         +        // add all registered group types +        foreach (var type in GroupRegistry.types) { +            this.groups.append(out last);  +            this.groups.set(last, GroupPos.NAME, GroupRegistry.names[type],  +                                  GroupPos.TYPE, type.name(),  +                                  GroupPos.ICON, GroupRegistry.icons[type]);  +        } +          +        // pie choices +        this.pies = new Gtk.ListStore(2,  typeof(string),      // pie name  +                                          typeof(string));     // pie id +         +        // action type choices                                                               +        this.actions = new Gtk.ListStore(4, typeof(string),    // type name +                                            typeof(string),    // action type  +                                            typeof(bool),      // can be quickaction +                                            typeof(bool));     // icon/name editable    +         +        // add all registered action types +        foreach (var type in ActionRegistry.types) { +            this.actions.append(out last);  +            this.actions.set(last, ActionPos.NAME, ActionRegistry.names[type],  +                                   ActionPos.TYPE, type.name(),  +                        ActionPos.CAN_QUICKACTION, true,  +                     ActionPos.ICON_NAME_EDITABLE, ActionRegistry.icon_name_editables[type]);  +        } +        // and one type for groups +        this.actions.append(out last);  +        this.actions.set(last, ActionPos.NAME, _("Slice group"),  +                               ActionPos.TYPE, typeof(ActionGroup).name(),  +                    ActionPos.CAN_QUICKACTION, false,  +                 ActionPos.ICON_NAME_EDITABLE, false);  +         +        // main data model +        this.data = new Gtk.TreeStore(24, typeof(bool),       // is quickaction +                                          typeof(string),     // icon +                                          typeof(string),     // name    +                                          typeof(string),     // slice: type label, pie: "ID: %id" +                                          typeof(string),     // typeof(action), typeof(ActionGroup).name() if group action, pie_id if Pie  +                                           +                                          typeof(Gdk.Pixbuf), // icon pixbuf +                                          typeof(int),        // font weight +                                           +                                          typeof(bool),       // icon/name editable +                                           +                                          typeof(bool),       // quickaction visible +                                          typeof(bool),       // quickaction activatable +                                          typeof(bool),       // type visible +                                          typeof(bool),       // group renderer visible +                                          typeof(bool),       // app renderer visible +                                          typeof(bool),       // key renderer visible +                                          typeof(bool),       // pie renderer visible +                                          typeof(bool),       // uri renderer visible +                                           +                                          typeof(string),     // display command group +                                          typeof(string),     // display command app +                                          typeof(string),     // display command key +                                          typeof(string),     // display command pie +                                          typeof(string),     // display command uri +                                           +                                          typeof(string),     // real command group +                                          typeof(string),     // real command pie +                                          typeof(string));    // real command key +                                           +             +        this.set_model(this.data); +        this.set_grid_lines(Gtk.TreeViewGridLines.NONE); +        this.set_enable_tree_lines(false); +        this.set_reorderable(false); +        this.set_level_indentation(-10); +         +        // create the gui +        // icon column +        var icon_column = new Gtk.TreeViewColumn(); +            icon_column.title = _("Icon"); +            icon_column.expand = false; +             +            // quickaction checkbox +            var check_render = new Gtk.CellRendererToggle(); +                check_render.activatable = true; +                check_render.radio = true; +                check_render.width = 15; + +                check_render.toggled.connect((path) => { +                    Gtk.TreeIter toggled; +                    this.data.get_iter_from_string(out toggled, path); +                     +                    bool current = false; +                    this.data.get(toggled, DataPos.IS_QUICKACTION, out current); +                     +                    // set all others off +                    Gtk.TreeIter parent; +                    this.data.iter_parent(out parent, toggled); +                    string parent_pos = this.data.get_string_from_iter(parent); +                    int child_count = this.data.iter_n_children(parent); +                     +                    for (int i=0; i<child_count; ++i) { +                        Gtk.TreeIter child; +                        this.data.get_iter_from_string(out child, "%s:%d".printf(parent_pos, i)); +                        this.data.set(child, DataPos.IS_QUICKACTION, false); +                    } +                     +                    // toggle selected +                    this.data.set(toggled, DataPos.IS_QUICKACTION, !current); +                     +                    this.update_pie(toggled); +                }); +                 +                icon_column.pack_start(check_render, false); +                icon_column.add_attribute(check_render, "activatable", DataPos.QUICKACTION_ACTIVATABLE); +                icon_column.add_attribute(check_render, "sensitive", DataPos.QUICKACTION_ACTIVATABLE); +                icon_column.add_attribute(check_render, "visible", DataPos.QUICKACTION_VISIBLE); +                icon_column.add_attribute(check_render, "active", DataPos.IS_QUICKACTION); +                 +         +            // icon  +            var icon_render = new GnomePie.CellRendererIcon(); +                icon_render.editable = true; + +                icon_render.on_select.connect((path, icon_name) => { +                    Gtk.TreeIter iter; +                    this.data.get_iter_from_string(out iter, path); +                    int icon_size =  this.data.iter_depth(iter) == 0 ? this.large_icon : this.small_icon; +                     +                    this.data.set(iter, DataPos.ICON, icon_name); +                    this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(icon_name, icon_size)); +                     +                    this.update_pie(iter); +                    this.update_linked(); +                }); +                 +                icon_column.pack_start(icon_render, false); +                icon_column.add_attribute(icon_render, "icon_name", DataPos.ICON); +                icon_column.add_attribute(icon_render, "pixbuf", DataPos.ICON_PIXBUF); +                icon_column.add_attribute(icon_render, "editable", DataPos.ICON_NAME_EDITABLE); +                icon_column.add_attribute(icon_render, "icon_sensitive", DataPos.ICON_NAME_EDITABLE); +                   +        // command column     +        var command_column = new Gtk.TreeViewColumn(); +            command_column.title = _("Command"); +            command_column.resizable = true; +             +            // slice group  +            var command_renderer_group = new Gtk.CellRendererCombo(); +                command_renderer_group.editable = true; +                command_renderer_group.has_entry = false; +                command_renderer_group.text_column = 0; +                command_renderer_group.ellipsize = Pango.EllipsizeMode.END; +                command_renderer_group.model = this.groups; + +                command_renderer_group.changed.connect((path, iter) => { +                    string display_name; +                    string type; +                    string icon; +                     +                    this.groups.get(iter, GroupPos.NAME, out display_name); +                    this.groups.get(iter, GroupPos.TYPE, out type); +                    this.groups.get(iter, GroupPos.ICON, out icon); +                                      +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_GROUP, display_name); +                    this.data.set(data_iter, DataPos.REAL_COMMAND_GROUP, type); +                    this.data.set(data_iter, DataPos.NAME, display_name); +                    this.data.set(data_iter, DataPos.ICON, icon); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_column.pack_end(command_renderer_group, true); +                command_column.add_attribute(command_renderer_group, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_group, "text", DataPos.DISPLAY_COMMAND_GROUP); +                command_column.add_attribute(command_renderer_group, "visible", DataPos.GROUP_VISIBLE); +                 +                 +            // app action  +            var command_renderer_app = new Gtk.CellRendererText(); +                command_renderer_app.editable = true; +                command_renderer_app.ellipsize = Pango.EllipsizeMode.END; + +                command_renderer_app.edited.connect((path, command) => {                  +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_APP, command); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_column.pack_end(command_renderer_app, true); +                command_column.add_attribute(command_renderer_app, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_app, "text", DataPos.DISPLAY_COMMAND_APP); +                command_column.add_attribute(command_renderer_app, "visible", DataPos.APP_VISIBLE); +                 +                 +            // key action  +            var command_renderer_key = new Gtk.CellRendererAccel(); +                command_renderer_key.editable = true; +                command_renderer_key.ellipsize = Pango.EllipsizeMode.END; + +                command_renderer_key.accel_edited.connect((path, key, mods) => {                  +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    string label = Gtk.accelerator_get_label(key, mods); +                    string accelerator = Gtk.accelerator_name(key, mods); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, label); +                    this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, accelerator); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_renderer_key.accel_cleared.connect((path) => {                  +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, _("Not bound")); +                    this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, ""); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_column.pack_end(command_renderer_key, true); +                command_column.add_attribute(command_renderer_key, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_key, "text", DataPos.DISPLAY_COMMAND_KEY); +                command_column.add_attribute(command_renderer_key, "visible", DataPos.KEY_VISIBLE); +                 +                 +            // pie action  +            var command_renderer_pie = new Gtk.CellRendererCombo(); +                command_renderer_pie.editable = true; +                command_renderer_pie.has_entry = false; +                command_renderer_pie.text_column = 0; +                command_renderer_pie.ellipsize = Pango.EllipsizeMode.END; +                command_renderer_pie.model = this.pies; + +                command_renderer_pie.changed.connect((path, iter) => { +                    string name; +                    string id; +                     +                    this.pies.get(iter, PiePos.NAME, out name); +                    this.pies.get(iter, PiePos.ID, out id); +                                      +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_PIE, name); +                    this.data.set(data_iter, DataPos.REAL_COMMAND_PIE, id); +                     +                    this.update_pie(data_iter); +                    this.update_linked(); +                }); +                 +                command_column.pack_end(command_renderer_pie, true); +                command_column.add_attribute(command_renderer_pie, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_pie, "text", DataPos.DISPLAY_COMMAND_PIE); +                command_column.add_attribute(command_renderer_pie, "visible", DataPos.PIE_VISIBLE); +                 +                 +            // uri action  +            var command_renderer_uri = new Gtk.CellRendererText(); +                command_renderer_uri.editable = true; +                command_renderer_uri.ellipsize = Pango.EllipsizeMode.END; + +                command_renderer_uri.edited.connect((path, uri) => {                  +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_URI, uri); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_column.pack_end(command_renderer_uri, true); +                command_column.add_attribute(command_renderer_uri, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_uri, "text", DataPos.DISPLAY_COMMAND_URI); +                command_column.add_attribute(command_renderer_uri, "visible", DataPos.URI_VISIBLE); +                 +         +        // type column    +        var type_column = new Gtk.TreeViewColumn(); +            type_column.title = _("Pie-ID / Action type"); +            type_column.resizable = true; +                 +            var type_render = new Gtk.CellRendererCombo(); +                type_render.editable = true; +                type_render.has_entry = false; +                type_render.model = actions; +                type_render.text_column = 0; +                type_render.ellipsize = Pango.EllipsizeMode.END; + +                // change command_render's visibility accordingly +                type_render.changed.connect((path, iter) => { +                    string text = ""; +                    string type; +                    bool can_quickaction; +                    bool icon_name_editable; +                     +                    this.actions.get(iter, ActionPos.NAME, out text); +                    this.actions.get(iter, ActionPos.TYPE, out type); +                    this.actions.get(iter, ActionPos.CAN_QUICKACTION, out can_quickaction); +                    this.actions.get(iter, ActionPos.ICON_NAME_EDITABLE, out icon_name_editable); +                 +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.TYPE_ID, text); +                    this.data.set(data_iter, DataPos.ACTION_TYPE, type); +                    this.data.set(data_iter, DataPos.QUICKACTION_ACTIVATABLE, can_quickaction); +                    this.data.set(data_iter, DataPos.ICON_NAME_EDITABLE, icon_name_editable); +                     +                    // set all command renderes invisible +                    this.data.set(data_iter, DataPos.GROUP_VISIBLE, false); +                    this.data.set(data_iter, DataPos.APP_VISIBLE, false); +                    this.data.set(data_iter, DataPos.KEY_VISIBLE, false); +                    this.data.set(data_iter, DataPos.PIE_VISIBLE, false); +                    this.data.set(data_iter, DataPos.URI_VISIBLE, false); +                     +                    // set one visible +                    int type_id = 0; +                    if(type == typeof(AppAction).name()) type_id = 1;  +                    else if(type == typeof(KeyAction).name()) type_id = 2;  +                    else if(type == typeof(PieAction).name()) type_id = 3;  +                    else if(type == typeof(UriAction).name()) type_id = 4;  +                    else type_id = 0; +                     +                    this.data.set(data_iter, DataPos.GROUP_VISIBLE + type_id, true); +                     +                    this.update_linked(); +                    this.update_pie(data_iter); +                     +                    //this.set_cursor(new Gtk.TreePath.from_string(path), command_column, true); +                }); +                 +                type_column.pack_start(type_render, true); +                type_column.add_attribute(type_render, "sensitive", DataPos.TYPE_VISIBLE); +                type_column.add_attribute(type_render, "editable", DataPos.TYPE_VISIBLE); +                type_column.add_attribute(type_render, "text", DataPos.TYPE_ID); +         +        // name column     +        var name_column = new Gtk.TreeViewColumn(); +            name_column.title = _("Name"); +            name_column.expand = true; +            name_column.resizable = true; +         +            var name_render = new Gtk.CellRendererText(); +                name_render.editable = true; +                name_render.ellipsize = Pango.EllipsizeMode.END; + +                name_render.edited.connect((path, text) => {                         +                    Gtk.TreeIter iter; +                    this.data.get_iter_from_string(out iter, path); +                     +                    this.data.set(iter, DataPos.NAME, text); +                     +                    // try to change icon to a fitting one +                    string icon; +                    this.data.get(iter, DataPos.ICON, out icon); +                    if (icon == "application-default-icon" && Gtk.IconTheme.get_default().has_icon(text.down())) { +                        this.data.set(iter, DataPos.ICON, text.down()); +                    } +                     +                    this.update_pie(iter); +                    this.update_linked(); +                     +                    //this.set_cursor(new Gtk.TreePath.from_string(path), type_column, true); +                }); +                 +                name_column.pack_start(name_render, true); +                name_column.add_attribute(name_render, "weight", DataPos.FONT_WEIGHT); +                name_column.add_attribute(name_render, "text", DataPos.NAME); +                name_column.add_attribute(name_render, "sensitive", DataPos.ICON_NAME_EDITABLE); +                name_column.add_attribute(name_render, "editable", DataPos.ICON_NAME_EDITABLE); +         +        this.append_column(icon_column); +        this.append_column(name_column); +        this.append_column(type_column); +        this.append_column(command_column); +         +        this.realize.connect(this.load); +         +        // context menu +        var menu = new Gtk.Menu(); + +        var item = new Gtk.ImageMenuItem.with_label(_("Add new Pie")); +        item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); +        item.activate.connect(this.add_empty_pie); +        menu.append(item); + +        item = new Gtk.ImageMenuItem.with_label(_("Add new Slice")); +        item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); +        item.activate.connect(this.add_empty_slice); +        menu.append(item); +         +        var sepa = new Gtk.SeparatorMenuItem(); +        menu.append(sepa); + +        item = new Gtk.ImageMenuItem.with_label(_("Delete")); +        item.set_image(new Gtk.Image.from_stock(Gtk.Stock.DELETE, Gtk.IconSize.MENU)); +        item.activate.connect(this.delete_selection); +        menu.append(item); +         +        menu.show_all(); +         +        this.button_press_event.connect((event) => { +            if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) { +                menu.popup(null, null, null, event.button, event.time); +            } +            return false; +        }); +         +        // setup drag'n'drop +        Gtk.TargetEntry uri_source = {"text/uri-list", 0, 0}; +        Gtk.TargetEntry[] entries = { uri_source }; +         +        this.drag_data_received.connect(this.on_dnd_received); +        this.drag_data_get.connect(this.on_dnd_source); +        this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); +         +        this.get_selection().changed.connect(() => { +            Gtk.TreeIter selected; +            if (this.get_selection().get_selected(null, out selected)) { +                if (this.data.iter_depth(selected) == 0) { +                     this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK);    +                } else { +                    this.unset_rows_drag_source(); +                } +            } +        }); +         +        this.drag_begin.connect(() => { +            this.unset_rows_drag_dest(); +        }); +         +        this.drag_end.connect(() => { +            this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); +        }); +    } +     +    // moves the selected slice up +    public void selection_up() { +        Gtk.TreeIter selected; +        if (this.get_selection().get_selected(null, out selected)) { +            Gtk.TreePath path = this.data.get_path(selected); +            Gtk.TreeIter? before = null;; +            if (path.prev() && this.data.get_iter(out before, path)) { +                this.data.swap(selected, before); +                this.get_selection().changed(); +                this.update_pie(selected); +            } +        } +    } +     +    // moves the selected slice down +    public void selection_down() { +        Gtk.TreeIter selected; +        if (this.get_selection().get_selected(null, out selected)) { +            Gtk.TreePath path = this.data.get_path(selected); +            Gtk.TreeIter? after = null; +            path.next(); +            if (this.data.get_iter(out after, path)) { +                this.data.swap(selected, after); +                this.get_selection().changed(); +                this.update_pie(selected); +            } +        } +    } +     +    // updates the entire list, checking for changed cross-references via PieActions +    // updates their names and icons if needed +    private void update_linked() { +        this.data.foreach((model, path, iter) => { +            string action_type; +            this.data.get(iter, DataPos.ACTION_TYPE, out action_type); +             +            if (action_type == typeof(PieAction).name()) { +                string command; +                this.data.get(iter, DataPos.REAL_COMMAND_PIE, out command); +                 +                var referee = PieManager.all_pies[command]; +                 +                if (referee != null) { +                    this.data.set(iter, DataPos.ICON, referee.icon); +                    this.data.set(iter, DataPos.NAME, referee.name); +                    this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(referee.icon, this.small_icon)); +                    this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, referee.name); +                } else { +                    // referenced Pie does not exist anymore or no is selected; +                    // select the first one... +                    Gtk.TreeIter first_pie; +                    this.pies.get_iter_first(out first_pie); +                     +                    string name; +                    string id; +                     +                    this.pies.get(first_pie, PiePos.NAME, out name); +                    this.pies.get(first_pie, PiePos.ID, out id); +                     +                    this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, name); +                    this.data.set(iter, DataPos.REAL_COMMAND_PIE, id); +                     +                    update_linked(); +                } +            } else if (action_type == typeof(ActionGroup).name()) { +                string command; +                this.data.get(iter, DataPos.REAL_COMMAND_GROUP, out command); +                                +                if (command == "") { +                    // no group is selected, select the first one... +                    Gtk.TreeIter first_group; +                    this.groups.get_iter_first(out first_group); +                     +                    string name; +                    string type; +                    string icon; +                     +                    this.groups.get(first_group, GroupPos.NAME, out name); +                    this.groups.get(first_group, GroupPos.TYPE, out type); +                    this.groups.get(first_group, GroupPos.ICON, out icon); +                     +                    this.data.set(iter, DataPos.DISPLAY_COMMAND_GROUP, name); +                    this.data.set(iter, DataPos.NAME, name); +                    this.data.set(iter, DataPos.REAL_COMMAND_GROUP, type); +                    this.data.set(iter, DataPos.ICON, icon); +                } +            } +             +            return false; +        }); +    } +     +    // adds a new, empty pie to the list +    private void add_empty_pie() { +        var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", ""); +         +        Gtk.TreeIter last; +        this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id);  +     +        Gtk.TreeIter parent; +        this.data.append(out parent, null); +        this.data.set(parent, DataPos.IS_QUICKACTION, false, +                                        DataPos.ICON, new_one.icon, +                                        DataPos.NAME, new_one.name, +                                     DataPos.TYPE_ID, "ID: " + new_one.id, +                                 DataPos.ACTION_TYPE, new_one.id, +                                 DataPos.ICON_PIXBUF, this.load_icon(new_one.icon, this.large_icon), +                                 DataPos.FONT_WEIGHT, 800, +                          DataPos.ICON_NAME_EDITABLE, true, +                         DataPos.QUICKACTION_VISIBLE, false, +                     DataPos.QUICKACTION_ACTIVATABLE, false, +                                DataPos.TYPE_VISIBLE, false, +                               DataPos.GROUP_VISIBLE, false, +                                 DataPos.APP_VISIBLE, false, +                                 DataPos.KEY_VISIBLE, true, +                                 DataPos.PIE_VISIBLE, false, +                                 DataPos.URI_VISIBLE, false, +                       DataPos.DISPLAY_COMMAND_GROUP, "", +                         DataPos.DISPLAY_COMMAND_APP, "", +                         DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), +                         DataPos.DISPLAY_COMMAND_PIE, "", +                         DataPos.DISPLAY_COMMAND_URI, "", +                          DataPos.REAL_COMMAND_GROUP, "", +                            DataPos.REAL_COMMAND_PIE, "", +                            DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(new_one.id)); +                           +         +        this.get_selection().select_iter(parent); +        this.scroll_to_cell(this.data.get_path(parent), null, true, 0.5f, 0.0f); +    } +     +    // adds a new empty slice to the list +    private void add_empty_slice() { +        Gtk.TreeIter selected; +        if (this.get_selection().get_selected(null, out selected)) { +            var path = this.data.get_path(selected); +            if (path != null) { +                if (path.get_depth() == 2) +                    this.data.iter_parent(out selected, selected); +                 +                this.load_action(selected, new AppAction(_("New Action"), "application-default-icon", "")); +                 +                Gtk.TreeIter new_one; +                this.data.iter_nth_child(out new_one, selected, this.data.iter_n_children(selected)-1); +                this.expand_to_path(this.data.get_path(new_one)); +                this.get_selection().select_iter(new_one); +                this.scroll_to_cell(this.data.get_path(new_one), null, true, 0.5f, 0.0f); +                 +                this.update_pie(selected); +            }  +        } else { +            var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL,  +                                                     Gtk.MessageType.INFO,  +                                                     Gtk.ButtonsType.CLOSE,  +                                                     _("You have to select a Pie to add a Slice to!")); +            dialog.run(); +            dialog.destroy(); +        } +    } +     +    // writes the contents of action to the position pointed by slice +    private void write_action(Action action, Gtk.TreeIter slice) { +        this.data.set(slice, DataPos.IS_QUICKACTION, action.is_quick_action, +                                       DataPos.ICON, action.icon, +                                       DataPos.NAME, action.name, +                                    DataPos.TYPE_ID, ActionRegistry.names[action.get_type()], +                                DataPos.ACTION_TYPE, action.get_type().name(), +                                DataPos.ICON_PIXBUF, this.load_icon(action.icon, this.small_icon), +                                DataPos.FONT_WEIGHT, 400, +                         DataPos.ICON_NAME_EDITABLE, !(action is PieAction), +                        DataPos.QUICKACTION_VISIBLE, true, +                    DataPos.QUICKACTION_ACTIVATABLE, true, +                               DataPos.TYPE_VISIBLE, true, +                              DataPos.GROUP_VISIBLE, false, +                                DataPos.APP_VISIBLE, action is AppAction, +                                DataPos.KEY_VISIBLE, action is KeyAction, +                                DataPos.PIE_VISIBLE, action is PieAction, +                                DataPos.URI_VISIBLE, action is UriAction, +                      DataPos.DISPLAY_COMMAND_GROUP, "", +                        DataPos.DISPLAY_COMMAND_APP, (action is AppAction) ? action.display_command : "", +                        DataPos.DISPLAY_COMMAND_KEY, (action is KeyAction) ? action.display_command : _("Not bound"), +                        DataPos.DISPLAY_COMMAND_PIE, (action is PieAction) ? action.display_command : "", +                        DataPos.DISPLAY_COMMAND_URI, (action is UriAction) ? action.display_command : "", +                         DataPos.REAL_COMMAND_GROUP, "", +                           DataPos.REAL_COMMAND_PIE, (action is PieAction) ? action.real_command : "", +                           DataPos.REAL_COMMAND_KEY, (action is KeyAction) ? action.real_command : ""); +    } +     +    // deletes the currently selected pie or slice +    private void delete_selection() { +        Gtk.TreeIter selected; +        if (this.get_selection().get_selected(null, out selected)) { +            var path = this.data.get_path(selected); +            if (path != null) { +                if (path.get_depth() == 1) +                    this.delete_pie(selected); +                else +                    this.delete_slice(selected); +            }  +        } else { +            var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL,  +                                                     Gtk.MessageType.INFO,  +                                                     Gtk.ButtonsType.CLOSE,  +                                                     _("You have to select a Pie or a Slice to delete!")); +            dialog.run(); +            dialog.destroy(); +        } +    } + +    // deletes the given pie +    private void delete_pie(Gtk.TreeIter pie) { +        var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL,  +                                                 Gtk.MessageType.QUESTION,  +                                                 Gtk.ButtonsType.YES_NO,  +                                                 _("Do you really want to delete the selected Pie with all contained Slices?")); +                                                  +        dialog.response.connect((response) => { +            if (response == Gtk.ResponseType.YES) { +                string id; +                this.data.get(pie, DataPos.ACTION_TYPE, out id); +                this.data.remove(pie); +                PieManager.remove_pie(id); +                 +                this.pies.foreach((model, path, iter) => { +                    string pies_id; +                    this.pies.get(iter, PiePos.ID, out pies_id); +                     +                    if (id == pies_id) { +                        this.pies.remove(iter); +                        return true; +                    } +                     +                    return false; +                }); +                 +                this.update_linked(); +            } +        }); +         +        dialog.run(); +        dialog.destroy(); +    } + +    // deletes the given slice +    private void delete_slice(Gtk.TreeIter slice) { +        var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL,  +                                                 Gtk.MessageType.QUESTION,  +                                                 Gtk.ButtonsType.YES_NO,  +                                                 _("Do you really want to delete the selected Slice?")); +                                                  +        dialog.response.connect((response) => { +            if (response == Gtk.ResponseType.YES) { +                Gtk.TreeIter parent; +                this.data.iter_parent(out parent, slice); +                this.data.remove(slice); +                this.update_pie(parent); +            } +        }); +         +        dialog.run(); +        dialog.destroy(); +    } +     +    // loads all pies to the list +    private void load() { +        foreach (var pie in PieManager.all_pies.entries) { +            this.load_pie(pie.value); +        } +    } +     +    // loads one given pie to the list +    private void load_pie(Pie pie) { +        if (pie.id.length == 3) { +         +            Gtk.TreeIter last; +            this.pies.append(out last); this.pies.set(last, PiePos.NAME, pie.name,  +                                                              PiePos.ID, pie.id);  +         +            Gtk.TreeIter parent; +            this.data.append(out parent, null); +            this.data.set(parent, DataPos.IS_QUICKACTION, false, +                                            DataPos.ICON, pie.icon, +                                            DataPos.NAME, pie.name, +                                         DataPos.TYPE_ID, "ID: " + pie.id, +                                     DataPos.ACTION_TYPE, pie.id, +                                     DataPos.ICON_PIXBUF, this.load_icon(pie.icon, this.large_icon), +                                     DataPos.FONT_WEIGHT, 800, +                              DataPos.ICON_NAME_EDITABLE, true, +                             DataPos.QUICKACTION_VISIBLE, false, +                         DataPos.QUICKACTION_ACTIVATABLE, false, +                                    DataPos.TYPE_VISIBLE, false, +                                   DataPos.GROUP_VISIBLE, false, +                                     DataPos.APP_VISIBLE, false, +                                     DataPos.KEY_VISIBLE, true, +                                     DataPos.PIE_VISIBLE, false, +                                     DataPos.URI_VISIBLE, false, +                           DataPos.DISPLAY_COMMAND_GROUP, "", +                             DataPos.DISPLAY_COMMAND_APP, "", +                             DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), +                             DataPos.DISPLAY_COMMAND_PIE, "", +                             DataPos.DISPLAY_COMMAND_URI, "", +                              DataPos.REAL_COMMAND_GROUP, "", +                                DataPos.REAL_COMMAND_PIE, "", +                                DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(pie.id)); +                              +            foreach (var group in pie.action_groups) { +                this.load_group(parent, group); +            } +        } +    } +     +    // loads a given group +    private void load_group(Gtk.TreeIter parent, ActionGroup group) { +        if (group.get_type() == typeof(ActionGroup)) { +            foreach (var action in group.actions) { +                this.load_action(parent, action); +            } +        } else { +            Gtk.TreeIter child; +            this.data.append(out child, parent); +            this.data.set(child, DataPos.IS_QUICKACTION, false, +                                           DataPos.ICON, GroupRegistry.icons[group.get_type()], +                                           DataPos.NAME, GroupRegistry.names[group.get_type()], +                                        DataPos.TYPE_ID, _("Slice group"), +                                    DataPos.ACTION_TYPE, typeof(ActionGroup).name(), +                                    DataPos.ICON_PIXBUF, this.load_icon(GroupRegistry.icons[group.get_type()], this.small_icon), +                                    DataPos.FONT_WEIGHT, 400, +                             DataPos.ICON_NAME_EDITABLE, false, +                            DataPos.QUICKACTION_VISIBLE, true, +                        DataPos.QUICKACTION_ACTIVATABLE, false, +                                   DataPos.TYPE_VISIBLE, true, +                                  DataPos.GROUP_VISIBLE, true, +                                    DataPos.APP_VISIBLE, false, +                                    DataPos.KEY_VISIBLE, false, +                                    DataPos.PIE_VISIBLE, false, +                                    DataPos.URI_VISIBLE, false, +                          DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()], +                            DataPos.DISPLAY_COMMAND_APP, "", +                            DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), +                            DataPos.DISPLAY_COMMAND_PIE, "", +                            DataPos.DISPLAY_COMMAND_URI, "", +                             DataPos.REAL_COMMAND_GROUP, group.get_type().name(), +                               DataPos.REAL_COMMAND_PIE, "", +                               DataPos.REAL_COMMAND_KEY, ""); +        } +    } +     +    // loads a given slice +    private void load_action(Gtk.TreeIter parent, Action action) { +        Gtk.TreeIter child; +        this.data.append(out child, parent); +        this.write_action(action, child); +    } +     +    // applies all changes done to the given pie +    private void update_pie(Gtk.TreeIter slice_or_pie) { +        // get pie iter +        var path = this.data.get_path(slice_or_pie); +        if (path != null) { +            var pie = slice_or_pie; +            if (path.get_depth() == 2) +                this.data.iter_parent(out pie, slice_or_pie); +             +            // get information on pie +            string id; +            string icon; +            string name; +            string hotkey; +             +            this.data.get(pie, DataPos.ICON, out icon); +            this.data.get(pie, DataPos.NAME, out name); +            this.data.get(pie, DataPos.ACTION_TYPE, out id); +            this.data.get(pie, DataPos.REAL_COMMAND_KEY, out hotkey); +             +            // remove pie +            PieManager.remove_pie(id); +              +            this.pies.foreach((model, path, iter) => { +                string pies_id; +                this.pies.get(iter, PiePos.ID, out pies_id); +                 +                if (id == pies_id) { +                    this.pies.set(iter, PiePos.NAME, name); +                    return true; +                } +                 +                return false; +            }); +                 +            // create new pie +            var new_pie = PieManager.create_persistent_pie(name, icon, hotkey, id); +             +            // add actions accordingly +            if (this.data.iter_has_child(pie)) { +                Gtk.TreeIter child; +                this.data.iter_children(out child, pie); +                 +                do { +                    // get slice information +                    string slice_type; +                    string slice_icon; +                    string slice_name; +                    bool is_quick_action; +                     +                    this.data.get(child, DataPos.ICON, out slice_icon); +                    this.data.get(child, DataPos.NAME, out slice_name); +                    this.data.get(child, DataPos.ACTION_TYPE, out slice_type); +                    this.data.get(child, DataPos.IS_QUICKACTION, out is_quick_action); +                     +                    if (slice_type == typeof(AppAction).name()) { +                        string slice_command; +                        this.data.get(child, DataPos.DISPLAY_COMMAND_APP, out slice_command); +                        var group = new ActionGroup(new_pie.id); +                        group.add_action(new AppAction(slice_name, slice_icon, slice_command, is_quick_action)); +                        new_pie.add_group(group); +                    } else if (slice_type == typeof(KeyAction).name()) { +                        string slice_command; +                        this.data.get(child, DataPos.REAL_COMMAND_KEY, out slice_command); +                        var group = new ActionGroup(new_pie.id); +                        group.add_action(new KeyAction(slice_name, slice_icon, slice_command, is_quick_action)); +                        new_pie.add_group(group); +                    } else if (slice_type == typeof(PieAction).name()) { +                        string slice_command; +                        this.data.get(child, DataPos.REAL_COMMAND_PIE, out slice_command); +                        var group = new ActionGroup(new_pie.id); +                        group.add_action(new PieAction(slice_command, is_quick_action)); +                        new_pie.add_group(group); +                    } else if (slice_type == typeof(UriAction).name()) { +                        string slice_command; +                        this.data.get(child, DataPos.DISPLAY_COMMAND_URI, out slice_command); +                        var group = new ActionGroup(new_pie.id); +                        group.add_action(new UriAction(slice_name, slice_icon, slice_command, is_quick_action)); +                        new_pie.add_group(group); +                    } else if (slice_type == typeof(ActionGroup).name()) { +                        string slice_command; +                        this.data.get(child, DataPos.REAL_COMMAND_GROUP, out slice_command); +                         +                        var group = GLib.Object.new(GLib.Type.from_name(slice_command), "parent_id", new_pie.id); +                        new_pie.add_group(group as ActionGroup); +                    }  +                     +                } while (this.data.iter_next(ref child)); +            } +        }          +    } +     +    // creates new action when the list receives a drag'n'drop event +    private void on_dnd_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time_) { +        string[] uris = selection_data.get_uris(); +         +        Gtk.TreePath path; +        Gtk.TreeViewDropPosition pos; +         +        // check for valid position +        if (!this.get_dest_row_at_pos(x, y, out path, out pos) +            || (path.to_string() == "0" && pos == Gtk.TreeViewDropPosition.BEFORE)) { +             +            warning("Failed to insert Slice: Invalid location!"); +            return; +        } +         +        // get position to insert (when child: after, when parent: as first child) +        Gtk.TreeIter parent; +        int insert_pos = 0; +        if (path.get_depth() == 1) { +            if (pos == Gtk.TreeViewDropPosition.BEFORE) { +                path.prev(); +                this.data.get_iter(out parent, path); +                insert_pos = this.data.iter_n_children(parent); +            } else { +                this.data.get_iter(out parent, path); +            } +        } else { +            if (pos == Gtk.TreeViewDropPosition.BEFORE) { +                insert_pos = path.get_indices()[1]; +            } else { +                insert_pos = path.get_indices()[1]+1; +            } +             +            path.up(); +            this.data.get_iter(out parent, path); +        } +         +        foreach (var uri in uris) { +            Gtk.TreeIter new_child; +            this.data.insert(out new_child, parent, insert_pos); +            this.write_action(ActionRegistry.new_for_uri(uri), new_child); +        } +         +        this.update_pie(parent); +    } +     +    private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) { +        Gtk.TreeIter selected; +        if (this.get_selection().get_selected(null, out selected)) { +            string id = ""; +            this.data.get(selected, DataPos.ACTION_TYPE, out id); +            selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"}); +        } +    } +     +    private Gdk.Pixbuf load_icon(string name, int size) { +        Gdk.Pixbuf pixbuf = null; +         +        try { +            if (name.contains("/")) +                pixbuf = new Gdk.Pixbuf.from_file_at_size(name, size, size); +            else +                pixbuf = new Gdk.Pixbuf.from_file_at_size(Icon.get_icon_file(name, size), size, size); +        } catch (GLib.Error e) { +            warning(e.message); +        } +         +        return pixbuf; +    } +} + +} diff --git a/src/gui/preferences.vala b/src/gui/preferences.vala new file mode 100644 index 0000000..f43fd4a --- /dev/null +++ b/src/gui/preferences.vala @@ -0,0 +1,338 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// The Gtk settings menu of Gnome-Pie. +///////////////////////////////////////////////////////////////////////// + +public class Preferences : Gtk.Window { +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, constructs the whole dialog. Many thanks to the +    /// synapse-project, since some of this code is taken from there!  +    ///////////////////////////////////////////////////////////////////// +     +    public Preferences() { +        this.title = _("Gnome-Pie - Settings"); +        this.set_position(Gtk.WindowPosition.CENTER); +        this.set_size_request(550, 550); +        this.resizable = false; +        this.icon_name = "gnome-pie"; +        this.delete_event.connect(hide_on_delete); +         +        // main container +        var main_vbox = new Gtk.VBox(false, 12); +            main_vbox.border_width = 12; +            add(main_vbox); + +            // tab container +            var tabs = new Gtk.Notebook(); +             +                // general tab +                var general_tab = new Gtk.VBox(false, 6); +                    general_tab.border_width = 12; +                     +                    // behavior frame +                    var behavior_frame = new Gtk.Frame(null); +                        behavior_frame.set_shadow_type(Gtk.ShadowType.NONE); +                        var behavior_frame_label = new Gtk.Label(null); +                        behavior_frame_label.set_markup(Markup.printf_escaped ("<b>%s</b>", _("Behavior"))); +                        behavior_frame.set_label_widget(behavior_frame_label); + +                        var behavior_vbox = new Gtk.VBox (false, 6); +                        var align = new Gtk.Alignment (0.5f, 0.5f, 1.0f, 1.0f); +                        align.set_padding (6, 12, 12, 12); +                        align.add (behavior_vbox); +                        behavior_frame.add (align); + +                        // Autostart checkbox +                        var autostart = new Gtk.CheckButton.with_label (_("Startup on Login")); +                            autostart.tooltip_text = _("If checked, Gnome-Pie will start when you log in."); +                            autostart.active = Config.global.auto_start; +                            autostart.toggled.connect(autostart_toggled); +                            behavior_vbox.pack_start(autostart, false); + +                        // Indicator icon  +                        var indicator = new Gtk.CheckButton.with_label (_("Show Indicator")); +                            indicator.tooltip_text = _("If checked, an indicator for easy access of the settings menu is shown in your panel."); +                            indicator.active = Config.global.show_indicator; +                            indicator.toggled.connect(indicator_toggled); +                            behavior_vbox.pack_start(indicator, false); +                             +                        // Open Pies at Mouse +                        var open_at_mouse = new Gtk.CheckButton.with_label (_("Open Pies at Mouse")); +                            open_at_mouse.tooltip_text = _("If checked, pies will open at your pointer. Otherwise they'll pop up in the middle of the screen."); +                            open_at_mouse.active = Config.global.open_at_mouse; +                            open_at_mouse.toggled.connect(open_at_mouse_toggled); +                            behavior_vbox.pack_start(open_at_mouse, false); +                             +                        // Click to activate +                        var click_to_activate = new Gtk.CheckButton.with_label (_("Turbo mode")); +                            click_to_activate.tooltip_text = _("If checked, the pie closes when its keystroke is released. The currently hovered slice gets executed. This allows very fast selection but disables keyboard navigation."); +                            click_to_activate.active = Config.global.turbo_mode; +                            click_to_activate.toggled.connect(turbo_mode_toggled); +                            behavior_vbox.pack_start(click_to_activate, false); +                             +                        // Slider +                        var slider_hbox = new Gtk.HBox (false, 6); +                            behavior_vbox.pack_start(slider_hbox); +                             +                            var scale_label = new Gtk.Label(_("Global Scale")); +                                slider_hbox.pack_start(scale_label, false, false); +                             +                            var scale_slider = new Gtk.HScale.with_range(0.5, 2.0, 0.05); +                                scale_slider.set_value(Config.global.global_scale); +                                scale_slider.value_pos = Gtk.PositionType.RIGHT; +                                 +                                bool changing = false; +                                bool changed_again = false; + +                                scale_slider.value_changed.connect(() => { +                                    if (!changing) { +                                        changing = true; +                                        Timeout.add(300, () => { +                                            if (changed_again) { +                                                changed_again = false; +                                                return true; +                                            } + +                                            Config.global.global_scale = scale_slider.get_value(); +                                            Config.global.load_themes(Config.global.theme.name); +                                            changing = false; +                                            return false; +                                        }); +                                    } else { +                                        changed_again = true; +                                    } +                                }); +                                 +                                slider_hbox.pack_end(scale_slider, true, true); + +                    general_tab.pack_start (behavior_frame, false); +                     +                    // theme frame +                    var theme_frame = new Gtk.Frame(null); +                        theme_frame.set_shadow_type(Gtk.ShadowType.NONE); +                        var theme_frame_label = new Gtk.Label(null); +                        theme_frame_label.set_markup(Markup.printf_escaped("<b>%s</b>", _("Themes"))); +                        theme_frame.set_label_widget(theme_frame_label); +                         +                        // scrollable frame +                        var scroll = new Gtk.ScrolledWindow (null, null); +                            align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); +                            align.set_padding(6, 12, 12, 12); +                            align.add(scroll); +                            theme_frame.add(align); + +                            scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); +                            scroll.set_shadow_type (Gtk.ShadowType.IN); +                             +                            // themes list +                            var theme_list = new ThemeList(); +                                scroll.add(theme_list); + +                general_tab.pack_start (theme_frame, true, true); +                tabs.append_page(general_tab, new Gtk.Label(_("General"))); +                 +                // pies tab +                var pies_tab = new Gtk.VBox(false, 6); +                    pies_tab.border_width = 12; +                    tabs.append_page(pies_tab, new Gtk.Label(_("Pies"))); +                         +                    // scrollable frame +                    scroll = new Gtk.ScrolledWindow (null, null); +                        align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); +                        align.add(scroll); +                        pies_tab.add(align); + +                        scroll.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); +                        scroll.set_shadow_type (Gtk.ShadowType.IN); +                         +                        // pies list +                        var pie_list = new PieList(); +                            scroll.add(pie_list); +                         +                    // bottom box +                    var info_box = new Gtk.HBox (false, 6); +                     +                        // info image +                        var info_image = new Gtk.Image.from_stock (Gtk.Stock.INFO, Gtk.IconSize.MENU); +                            info_box.pack_start (info_image, false); + +                        // info label +                        var info_label = new TipViewer({ +                                _("You can right-click in the list for adding or removing entries."), +                                _("You can reset Gnome-Pie to its default options with the terminal command \"gnome-pie --reset\"."), +                                _("The radiobutton at the beginning of each slice-line indicates the QuickAction of the pie."), +                                _("Pies can be opened with the terminal command \"gnome-pie --open=ID\"."), +                                _("Feel free to visit Gnome-Pie's homepage at %s!").printf("<a href='http://gnome-pie.simonschneegans.de'>gnome-pie.simonschneegans.de</a>"), +                                _("You can drag'n'drop applications from your main menu to the list above."), +                                _("If you want to give some feedback, please write an e-mail to %s!").printf("<a href='mailto:code@simonschneegans.de'>code@simonschneegans.de</a>"), +                                _("You may drag'n'drop URLs and bookmarks from your internet browser to the list above."), +                                _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"), +                                _("It's possible to drag'n'drop files and folders from your file browser to the list above."), +                                _("In order to create a launcher for a Pie, drag the Pie from the list to your desktop!") +                            }); +                            this.show.connect(info_label.start_slide_show); +                            this.hide.connect(info_label.stop_slide_show); +                             +                            info_box.pack_start (info_label); +                         +                        // down Button +                        var down_button = new Gtk.Button(); +                            down_button.tooltip_text = _("Moves the selected Slice down"); +                            down_button.sensitive = false; +                            var down_image = new Gtk.Image.from_stock (Gtk.Stock.GO_DOWN, Gtk.IconSize.LARGE_TOOLBAR); +                            down_button.add(down_image); +                            down_button.clicked.connect (() => { +                                pie_list.selection_down(); +                            }); + +                            info_box.pack_end(down_button, false, false); +                         +                        // up Button +                        var up_button = new Gtk.Button(); +                            up_button.tooltip_text = _("Moves the selected Slice up"); +                            up_button.sensitive = false; +                            var up_image = new Gtk.Image.from_stock (Gtk.Stock.GO_UP, Gtk.IconSize.LARGE_TOOLBAR); +                            up_button.add(up_image); +                            up_button.clicked.connect (() => { +                                pie_list.selection_up(); +                            }); +                             +                            info_box.pack_end(up_button, false, false); +                             +                        pie_list.get_selection().changed.connect(() => { +                            Gtk.TreeIter selected; +                            if (pie_list.get_selection().get_selected(null, out selected)) { +                                Gtk.TreePath path = pie_list.model.get_path(selected); +                                if (path.get_depth() == 1) { +                                    up_button.sensitive = false; +                                    down_button.sensitive = false; +                                } else { +                                    up_button.sensitive = true; +                                    down_button.sensitive = true; +                                     +                                    int child_pos = path.get_indices()[1]; + +                                    if (child_pos == 0) +                                        up_button.sensitive = false; +                                     +                                    path.up(); +                                    Gtk.TreeIter parent_iter; +                                    pie_list.model.get_iter(out parent_iter, path); +                                    if (child_pos == pie_list.model.iter_n_children(parent_iter)-1) +                                        down_button.sensitive = false; +                                     +                                } +                            } +                        }); +                         +                        pies_tab.pack_start (info_box, false); +                 +                main_vbox.pack_start(tabs); + +            // close button  +            var bbox = new Gtk.HButtonBox (); +                bbox.set_layout (Gtk.ButtonBoxStyle.END); +                var close_button = new Gtk.Button.from_stock (Gtk.Stock.CLOSE); +                close_button.clicked.connect (() => {  +                    hide(); +                    // save settings on close +                    Config.global.save(); +                    Pies.save(); +                }); +                bbox.pack_start (close_button); + +                main_vbox.pack_start(bbox, false); +                 +            main_vbox.show_all(); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Creates or deletes the autostart file. This code is inspired +    /// by project synapse as well. +    ///////////////////////////////////////////////////////////////////// +     +    private void autostart_toggled(Gtk.ToggleButton check_box) { +        bool active = check_box.active; +        if (!active && FileUtils.test(Paths.autostart, FileTest.EXISTS)) { +            // delete the autostart file +            FileUtils.remove (Paths.autostart); +        } +        else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) { +            string autostart_entry =  +                "#!/usr/bin/env xdg-open\n" +  +                "[Desktop Entry]\n" + +                "Name=Gnome-Pie\n" + +                "Exec=gnome-pie\n" + +                "Encoding=UTF-8\n" + +                "Type=Application\n" + +                "X-GNOME-Autostart-enabled=true\n" + +                "Icon=gnome-pie\n"; + +            // create the autostart file +            string autostart_dir = GLib.Path.get_dirname(Paths.autostart); +            if (!FileUtils.test(autostart_dir, FileTest.EXISTS | FileTest.IS_DIR)) { +                DirUtils.create_with_parents(autostart_dir, 0755); +            } +             +            try { +                FileUtils.set_contents(Paths.autostart, autostart_entry); +                FileUtils.chmod(Paths.autostart, 0755); +            } catch (Error e) { +                var d = new Gtk.MessageDialog (this, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, +                                           "%s", e.message); +                d.run (); +                d.destroy (); +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Shows or hides the indicator. +    ///////////////////////////////////////////////////////////////////// +     +    private void indicator_toggled(Gtk.ToggleButton check_box) { +        var check = check_box as Gtk.CheckButton; +        Config.global.show_indicator = check.active; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Toggles whether the Pies are shown at the mouse or in the middle +    /// of the screen. +    ///////////////////////////////////////////////////////////////////// +     +    private void open_at_mouse_toggled(Gtk.ToggleButton check_box) { +        var check = check_box as Gtk.CheckButton; +        Config.global.open_at_mouse = check.active; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Toggles whether the user has to click with the mouse in order to +    /// activate a slice. +    ///////////////////////////////////////////////////////////////////// +     +    private void turbo_mode_toggled(Gtk.ToggleButton check_box) { +        var check = check_box as Gtk.CheckButton; +        Config.global.turbo_mode = check.active; +    } +} + +} diff --git a/src/gui/themeList.vala b/src/gui/themeList.vala new file mode 100644 index 0000000..7eadcdb --- /dev/null +++ b/src/gui/themeList.vala @@ -0,0 +1,95 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A widget displaying all available themes of Gnome-Pie. +///////////////////////////////////////////////////////////////////////// + +class ThemeList : Gtk.TreeView { + +    ///////////////////////////////////////////////////////////////////// +    /// The currently selected row. +    ///////////////////////////////////////////////////////////////////// + +    private Gtk.TreeIter active { private get; private set; } + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, constructs the Widget. +    ///////////////////////////////////////////////////////////////////// + +    public ThemeList() { +        GLib.Object(); +         +        var data = new Gtk.ListStore(2, typeof(bool),    // selected +                                        typeof(string)); // description +        base.set_model(data); +        base.set_headers_visible(false); +        base.set_rules_hint(true); +        base.set_grid_lines(Gtk.TreeViewGridLines.NONE); +         +        var main_column = new Gtk.TreeViewColumn(); +            var check_render = new Gtk.CellRendererToggle(); +                check_render.set_radio(true); +                check_render.set_activatable(true); +                main_column.pack_start(check_render, false); +                 +                // switch the theme if the entry has been toggled +                check_render.toggled.connect((r, path) => { +                    Gtk.TreeIter toggled; +                    data.get_iter(out toggled, new Gtk.TreePath.from_string(path)); +                     +                    if (toggled != this.active) { +                        Timeout.add(10, () => { +                            int index = int.parse(path); +                            Config.global.theme = Config.global.themes[index]; +                            Config.global.theme.load(); +                            Config.global.theme.load_images(); +                            return false; +                        }); +                         +                        data.set(this.active, 0, false);  +                        data.set(toggled, 0, true); +                         +                        this.active = toggled; +                    } +                }); +         +            var theme_render = new Gtk.CellRendererText(); +                main_column.pack_start(theme_render, true); +         +        base.append_column(main_column); +         +        main_column.add_attribute(check_render, "active", 0); +        main_column.add_attribute(theme_render, "markup", 1); +         +        // load all themes into the list +        var themes = Config.global.themes; +        foreach(var theme in themes) { +            Gtk.TreeIter current; +            data.append(out current); +            data.set(current, 0, theme == Config.global.theme);  +            data.set(current, 1, "<b>" + theme.name + "</b>\n" + theme.description +                                 + "  <small> - " + _("by") + " " + theme.author + "</small>");  +            if(theme == Config.global.theme) +                this.active = current; +        }   +    } +} + +} diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala new file mode 100644 index 0000000..c653dd9 --- /dev/null +++ b/src/gui/tipViewer.vala @@ -0,0 +1,172 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A widget showing tips. The tips are beautifully faded in and out. +///////////////////////////////////////////////////////////////////////// + +public class TipViewer : Gtk.Label { + +    ///////////////////////////////////////////////////////////////////// +    /// Some settings tweaking the behavior of the TipViewer. +    ///////////////////////////////////////////////////////////////////// + +    private const double fade_time = 0.5; +    private const double frame_rate = 20.0; +    private const double delay = 7.0; + +    ///////////////////////////////////////////////////////////////////// +    /// False, if the playback of tips is stopped. +    ///////////////////////////////////////////////////////////////////// +     +    private bool playing = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// An array containing all tips. +    ///////////////////////////////////////////////////////////////////// +     +    private string[] tips; +     +    ///////////////////////////////////////////////////////////////////// +    /// The index of the currently displayed tip. +    ///////////////////////////////////////////////////////////////////// +     +    private int index = -1; +     +    ///////////////////////////////////////////////////////////////////// +    /// Colors of the font and the background. Used for fading effects. +    ///////////////////////////////////////////////////////////////////// +     +    private Gdk.Color fg; +    private Gdk.Color bg; +     +    ///////////////////////////////////////////////////////////////////// +    /// The fading value. +    ///////////////////////////////////////////////////////////////////// +     +    private AnimatedValue alpha; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members and sets the basic layout. +    ///////////////////////////////////////////////////////////////////// +     +    public TipViewer(string[] tips) { +        this.tips = tips; +        this.fg = this.get_style().fg[0]; +        this.bg = this.get_style().bg[0]; +         +        this.alpha = new AnimatedValue.linear(1.0, 0.0, this.fade_time); +         +        this.set_alignment (0.0f, 0.5f); +        this.wrap = true; +        this.set_use_markup(true); +        this.modify_font(Pango.FontDescription.from_string("9")); +         +        this.set_random_tip(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Starts the playback of tips. +    ///////////////////////////////////////////////////////////////////// +     +    public void start_slide_show() { +        if (!this.playing && tips.length > 1) { +            this.playing = true; +            GLib.Timeout.add((uint)(this.delay*1000.0), () => { +                this.fade_out(); +                 +                GLib.Timeout.add((uint)(1000.0*this.fade_time), () => { +                    this.set_random_tip(); +                    this.fade_in(); +                    return false; +                }); +                 +                return this.playing; +            }); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Stops the playback of tips. +    ///////////////////////////////////////////////////////////////////// +     +    public void stop_slide_show() { +        this.playing = false; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Starts the fading in. +    ///////////////////////////////////////////////////////////////////// +     +    private void fade_in() { +        this.alpha = new AnimatedValue.linear(this.alpha.val, 1.0, this.fade_time); +         +        GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { +            this.alpha.update(1.0/this.frame_rate); +            this.update_label(); +             +            return (this.alpha.val != 1.0); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Starts the fading out. +    ///////////////////////////////////////////////////////////////////// +     +    private void fade_out() { +        this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, this.fade_time); +         +        GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { +            this.alpha.update(1.0/this.frame_rate); +            this.update_label(); +             +            return (this.alpha.val != 0.0); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Updates the color of the label. Called every frame while fading. +    ///////////////////////////////////////////////////////////////////// +     +    private void update_label() { +        Gdk.Color color = {(uint32)(fg.pixel*this.alpha.val + bg.pixel*(1.0 - this.alpha.val)), +                           (uint16)(fg.red*this.alpha.val + bg.red*(1.0 - this.alpha.val)), +                           (uint16)(fg.green*this.alpha.val + bg.green*(1.0 - this.alpha.val)), +                           (uint16)(fg.blue*this.alpha.val + bg.blue*(1.0 - this.alpha.val))}; +         +        this.modify_fg(Gtk.StateType.NORMAL, color); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Chooses the next random tip. +    ///////////////////////////////////////////////////////////////////// +     +    private void set_random_tip() { +        if (tips.length > 1) { +            int next_index = -1; +            do { +                next_index = GLib.Random.int_range(0, tips.length); +            } while (next_index == this.index); +            this.index = next_index; +            this.label = tips[this.index]; +        } +    } +} + +} diff --git a/src/pies/defaultConfig.vala b/src/pies/defaultConfig.vala new file mode 100644 index 0000000..bd981b5 --- /dev/null +++ b/src/pies/defaultConfig.vala @@ -0,0 +1,70 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A helper class which creates a user-specific default configuration. +///////////////////////////////////////////////////////////////////////// + +namespace Pies { + +    public void create_default_config() { +         +        // add a pie with playback controls +        var multimedia = PieManager.create_persistent_pie(_("Multimedia"), "stock_media-play", "<Control><Alt>m"); +            multimedia.add_action(new KeyAction(_("Next Track"), "stock_media-next", "XF86AudioNext", true)); +            multimedia.add_action(new KeyAction(_("Stop"), "stock_media-stop", "XF86AudioStop")); +            multimedia.add_action(new KeyAction(_("Previous Track"), "stock_media-prev", "XF86AudioPrev")); +            multimedia.add_action(new KeyAction(_("Play/Pause"), "stock_media-play", "XF86AudioPlay")); +         +        // add a pie with the users default applications +        var apps = PieManager.create_persistent_pie(_("Applications"), "applications-accessories", "<Control><Alt>a"); +            apps.add_action(ActionRegistry.default_for_mime_type("text/plain")); +            apps.add_action(ActionRegistry.default_for_mime_type("audio/ogg")); +            apps.add_action(ActionRegistry.default_for_mime_type("video/ogg")); +            apps.add_action(ActionRegistry.default_for_mime_type("image/jpg")); +            apps.add_action(ActionRegistry.default_for_uri("http")); +            apps.add_action(ActionRegistry.default_for_uri("mailto")); +         +        // add a pie with the users bookmarks and devices +        var bookmarks = PieManager.create_persistent_pie(_("Bookmarks"), "user-bookmarks", "<Control><Alt>b"); +            bookmarks.add_group(new BookmarkGroup(bookmarks.id)); +            bookmarks.add_group(new DevicesGroup(bookmarks.id)); +         +        // add a pie with session controls +        var session = PieManager.create_persistent_pie(_("Session"), "gnome-session-halt", "<Control><Alt>q"); +            session.add_group(new SessionGroup(session.id)); +         +        // add a pie with a main menu +        var menu = PieManager.create_persistent_pie(_("Main Menu"), "alacarte", "<Control><Alt>space"); +            menu.add_group(new MenuGroup(menu.id)); +         +        // add a pie with window controls +        var window = PieManager.create_persistent_pie(_("Window"), "gnome-window-manager", "<Control><Alt>w"); +            window.add_action(new KeyAction(_("Scale"), "top", "<Control><Alt>s")); +            window.add_action(new KeyAction(_("Minimize"), "bottom", "<Alt>F9", true)); +            window.add_action(new KeyAction(_("Close"), "window-close", "<Alt>F4")); +            window.add_action(new KeyAction(_("Maximize"), "window_fullscreen", "<Alt>F10")); +            window.add_action(new KeyAction(_("Restore"), "window_nofullscreen", "<Alt>F5")); +     +        // save the configuration to file +        Pies.save(); +    } +} + +} diff --git a/src/pies/load.vala b/src/pies/load.vala new file mode 100644 index 0000000..912ddf0 --- /dev/null +++ b/src/pies/load.vala @@ -0,0 +1,230 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/         + +using GLib.Math; + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A helper method which loads pies according to the configuration file. +///////////////////////////////////////////////////////////////////////// + +namespace Pies { + +    ///////////////////////////////////////////////////////////////////// +    /// Loads all Pies from the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    public void load() { +        // check whether the config file exists +        if (!GLib.File.new_for_path(Paths.pie_config).query_exists()) { +            message("Creating new configuration file in \"" + Paths.pie_config + "\"."); +            Pies.create_default_config(); +            return; +        } +     +        // load the settings file +        Xml.Parser.init(); +        Xml.Doc* piesXML = Xml.Parser.parse_file(Paths.pie_config); +         +        if (piesXML != null) { +            // begin parsing at the root element +            Xml.Node* root = piesXML->get_root_element(); +            if (root != null) { +                for (Xml.Node* node = root->children; node != null; node = node->next) { +                    if (node->type == Xml.ElementType.ELEMENT_NODE) { +                        string node_name = node->name.down(); +                        switch (node_name) { +                            case "pie": +                                parse_pie(node); +                                break; +                            default: +                                warning("Invalid child element <" + node_name + "> in <pies> element pies.conf!"); +                                break; +                        }  +                    } +                } +                Xml.Parser.cleanup(); +                 +            } else { +                warning("Error loading pies: pies.conf is empty! The cake is a lie..."); +            } +             +            delete piesXML; +             +        } else { +            warning("Error loading pies: pies.conf not found! The cake is a lie..."); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <pie> element from the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    private static void parse_pie(Xml.Node* node) { +        string hotkey = ""; +        string name = ""; +        string icon = ""; +        string id = ""; +        int quick_action = -1; +         +        // parse all attributes of this node +        for (Xml.Attr* attribute = node->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "hotkey": +                    hotkey = attr_content; +                    break; +                case "quickaction": +                    quick_action = int.parse(attr_content); +                    break; +                case "name": +                    name = attr_content; +                    break; +                case "icon": +                    icon = attr_content; +                    break; +                case "id": +                    id = attr_content; +                    break; +                default: +                    warning("Invalid setting \"" + attr_name + "\" in pies.conf!"); +                    break; +            } +        } +         +        if (name == "") { +            warning("Invalid <pie> element in pies.conf: No name specified!"); +            return; +        } +         +        // add a new Pie with the loaded properties +        var pie = PieManager.create_persistent_pie(name, icon, hotkey, id); +         +        // and parse all child elements +        for (Xml.Node* slice = node->children; slice != null; slice = slice->next) { +            if (slice->type == Xml.ElementType.ELEMENT_NODE) { +                string node_name = slice->name.down(); +                switch (node_name) { +                    case "slice": +                        parse_slice(slice, pie); +                        break; +                    case "group": +                        parse_group(slice, pie); +                        break; +                    default: +                        warning("Invalid child element <" + node_name + "> in <pie> element in pies.conf!"); +                        break; +                }  +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <slice> element from the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    private static void parse_slice(Xml.Node* slice, Pie pie) { +        string name=""; +        string icon=""; +        string command=""; +        string type=""; +        bool quick_action = false; +         +        // parse all attributes of this node +        for (Xml.Attr* attribute = slice->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; + +            switch (attr_name) { +                case "name": +                    name = attr_content; +                    break; +                case "icon": +                    icon = attr_content; +                    break; +                case "command": +                    command = attr_content; +                    break; +                case "type": +                    type = attr_content; +                    break; +                case "quickaction": +                    quick_action = bool.parse(attr_content); +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <slice> element in pies.conf!"); +                    break; +            } +        } +         +        Action action = null; +         +        // create a new Action according to the loaded type +        foreach (var action_type in ActionRegistry.types) { +            if (ActionRegistry.settings_names[action_type] == type) { +             +                action = GLib.Object.new(action_type, "name", name,  +                                                      "icon", icon,  +                                              "real_command", command,  +                                           "is_quick_action", quick_action) as Action; +                break; +            }  +        } +         +        if (action != null) pie.add_action(action); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <group> element from the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    private static void parse_group(Xml.Node* slice, Pie pie) { +        string type=""; +         +        // parse all attributes of this node +        for (Xml.Attr* attribute = slice->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; + +            switch (attr_name) { +                case "type": +                    type = attr_content; +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <group> element in pies.conf!"); +                    break; +            } +        } +         +        ActionGroup group = null; +         +        // create a new ActionGroup according to the loaded type +        foreach (var group_type in GroupRegistry.types) { +            if (GroupRegistry.settings_names[group_type] == type) { +                group = GLib.Object.new(group_type, "parent_id", pie.id) as ActionGroup; +                break; +            }  +        } + +        if (group != null) pie.add_group(group); +    } +} + +} diff --git a/src/pies/pie.vala b/src/pies/pie.vala new file mode 100644 index 0000000..0f87d8f --- /dev/null +++ b/src/pies/pie.vala @@ -0,0 +1,96 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This class stores information on a pie. A pie consists of a name, an  +/// icon name and an unique ID. Furthermore it has an arbitrary amount +/// of ActionGroups storing Actions. +///////////////////////////////////////////////////////////////////////// + +public class Pie : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// The name of this Pie. It has not to be unique. +    ///////////////////////////////////////////////////////////////////// +     +    public string name { get; construct; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The name of the icon to be used for this Pie. It should exist in +    /// the users current icon theme, else a standard icon will be used. +    ///////////////////////////////////////////////////////////////////// +     +    public string icon { get; construct; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The ID of this Pie. It has to be unique among all Pies. This ID +    /// consists of three digits when the Pie was created by the user,  +    /// of four digits when it was created dynamically by another class,  +    /// for example by an ActionGroup. +    ///////////////////////////////////////////////////////////////////// +     +    public string id { get; construct; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Stores all ActionGroups of this Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public Gee.ArrayList<ActionGroup?> action_groups { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all given members. +    ///////////////////////////////////////////////////////////////////// +     +    public Pie(string id, string name, string icon) { +        GLib.Object(id: id, name: name, icon:icon); +         +        this.action_groups = new Gee.ArrayList<ActionGroup?>(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Should be called when this Pie is deleted, in order to clean up +    /// stuff created by contained ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public virtual void on_remove() { +        foreach (var action_group in action_groups) +            action_group.on_remove(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Adds an Action to this Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public void add_action(Action action) { +        var group = new ActionGroup(this.id); +            group.add_action(action); +        this.add_group(group); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Adds an ActionGroup to this Pie. +    ///////////////////////////////////////////////////////////////////// +     +    public void add_group(ActionGroup group) { +        this.action_groups.add(group); +    } +} + +} + diff --git a/src/pies/pieManager.vala b/src/pies/pieManager.vala new file mode 100644 index 0000000..eb031d0 --- /dev/null +++ b/src/pies/pieManager.vala @@ -0,0 +1,231 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A static class which stores all Pies. It can be used to add, delete  +/// and open Pies. +///////////////////////////////////////////////////////////////////////// + +public class PieManager : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// A map of all Pies. It contains both, dynamic and persistent Pies. +    /// They are associated to their ID's. +    ///////////////////////////////////////////////////////////////////// + +    public static Gee.HashMap<string, Pie?> all_pies { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Stores all global hotkeys. +    ///////////////////////////////////////////////////////////////////// +     +    private static BindingManager bindings; +     +    ///////////////////////////////////////////////////////////////////// +    /// True, if any pie has the current focus. If it is closing this +    /// will be false already. +    ///////////////////////////////////////////////////////////////////// +     +    private static bool a_pie_is_opened = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes all Pies. They are loaded from the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        all_pies = new Gee.HashMap<string, Pie?>(); +        bindings = new BindingManager(); +         +        // load all Pies from th pies.conf file +        Pies.load(); +         +        // open the according pie if it's hotkey is pressed +        bindings.on_press.connect((id) => { +            open_pie(id); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Opens the Pie with the given ID, if it exists. +    ///////////////////////////////////////////////////////////////////// +     +    public static void open_pie(string id) { +        if (!a_pie_is_opened) { +            Pie? pie = all_pies[id]; +             +            if (pie != null) { +                a_pie_is_opened = true; +                 +                var window = new PieWindow(); +                window.load_pie(pie); +                window.open(); +                 +                window.on_closing.connect(() => { +                    a_pie_is_opened = false; +                }); +            } else { +                warning("Failed to open pie with ID \"" + id + "\": ID does not exist!"); +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the hotkey which the Pie with the given ID is bound to. +    ///////////////////////////////////////////////////////////////////// +     +    public static string get_accelerator_of(string id) { +        return bindings.get_accelerator_of(id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns a human-readable version of the hotkey which the Pie +    /// with the given ID is bound to. +    ///////////////////////////////////////////////////////////////////// +     +    public static string get_accelerator_label_of(string id) { +        return bindings.get_accelerator_label_of(id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the name of the Pie with the given ID. +    ///////////////////////////////////////////////////////////////////// +     +    public static string get_name_of(string id) { +        Pie? pie = all_pies[id]; +        if (pie == null) return ""; +        else             return pie.name; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a new Pie which is displayed in the configuration dialog +    /// and gets saved. +    ///////////////////////////////////////////////////////////////////// +     +    public static Pie create_persistent_pie(string name, string icon_name, string hotkey, string? desired_id = null) { +        Pie pie = create_pie(name, icon_name, 100, 999, desired_id); + +        if (hotkey != "") bindings.bind(hotkey, pie.id); +         +        create_launcher(pie.id); +         +        return pie; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a new Pie which is not displayed in the configuration +    /// dialog and is not saved. +    ///////////////////////////////////////////////////////////////////// +     +    public static Pie create_dynamic_pie(string name, string icon_name, string? desired_id = null) { +        return create_pie(name, icon_name, 1000, 9999, desired_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Adds a new Pie. Can't be accesd from outer scope. Use +    /// create_persistent_pie or create_dynamic_pie instead. +    ///////////////////////////////////////////////////////////////////// +     +    private static Pie create_pie(string name, string icon_name, int min_id, int max_id, string? desired_id = null) { +         var random = new GLib.Rand(); +         +        string final_id; +         +        if (desired_id == null)  +            final_id = random.int_range(min_id, max_id).to_string(); +        else { +            final_id = desired_id; +            final_id.canon("0123456789", '_'); +            final_id = final_id.replace("_", ""); +             +            int id = int.parse(final_id); +             +            if (id < min_id || id > max_id) { +                final_id = random.int_range(min_id, max_id).to_string(); +                warning("The ID for pie \"" + name + "\" should be in range %u - %u! Using \"" + final_id + "\" instead of \"" + desired_id + "\"...", min_id, max_id); +            } +        } + +        if (all_pies.has_key(final_id)) { +            var tmp = final_id; +            var id_number = int.parse(final_id) + 1; +            if (id_number == max_id+1) id_number = min_id; +            final_id = id_number.to_string(); +            warning("Trying to add pie \"" + name + "\": ID \"" + tmp + "\" already exists! Using \"" + final_id + "\" instead..."); +            return create_pie(name, icon_name, min_id, max_id, final_id); +        } + +        Pie pie = new Pie(final_id, name, icon_name); +        all_pies.set(final_id, pie); +         +        return pie; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Removes the Pie with the given ID if it exists. Additionally it +    /// unbinds it's global hotkey. +    ///////////////////////////////////////////////////////////////////// +     +    public static void remove_pie(string id) { +        if (all_pies.has_key(id)) { +            all_pies[id].on_remove(); +            all_pies.unset(id); +            bindings.unbind(id); +             +            if (id.length == 3) +                remove_launcher(id); +        } +        else { +            warning("Failed to remove pie with ID \"" + id + "\": ID does not exist!"); +        } +    } +     +    private static void create_launcher(string id) { +        if (all_pies.has_key(id)) { +            Pie? pie = all_pies[id]; +             +            string launcher_entry =  +                "#!/usr/bin/env xdg-open\n" +  +                "[Desktop Entry]\n" + +                "Name=%s\n".printf(pie.name) + +                "Exec=gnome-pie -o %s\n".printf(pie.id) + +                "Encoding=UTF-8\n" + +                "Type=Application\n" + +                "Icon=%s\n".printf(pie.icon); + +            // create the launcher file +            string launcher = Paths.launchers + "/%s.desktop".printf(pie.id); +             +            try { +                FileUtils.set_contents(launcher, launcher_entry); +                FileUtils.chmod(launcher, 0755); +            } catch (Error e) { +                warning(e.message); +            } +        } +    } +     +    private static void remove_launcher(string id) { +        string launcher = Paths.launchers + "/%s.desktop".printf(id); +        if (FileUtils.test(launcher, FileTest.EXISTS)) { +            FileUtils.remove(launcher); +        } +    } +} + +} diff --git a/src/pies/save.vala b/src/pies/save.vala new file mode 100644 index 0000000..d691a95 --- /dev/null +++ b/src/pies/save.vala @@ -0,0 +1,81 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/         + +using GLib.Math; + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A helper method which saves pies in a configuration file. +///////////////////////////////////////////////////////////////////////// + +namespace Pies { + +    ///////////////////////////////////////////////////////////////////// +    /// Saves all Pies of the PieManager to the pies.conf file. +    ///////////////////////////////////////////////////////////////////// +     +    public void save() { +        // initializes the XML-Writer +        var writer = new Xml.TextWriter.filename(Paths.pie_config); +        writer.set_indent(true); +        writer.start_document("1.0"); +        writer.start_element("pies"); +         +        // iterate through all Pies +        foreach (var pie_entry in PieManager.all_pies.entries) { +            var pie = pie_entry.value; +             +            // if it's no dynamically created Pie +            if (pie.id.length == 3) { +                // write all attributes of the Pie +                writer.start_element("pie"); +                writer.write_attribute("name", pie.name); +                writer.write_attribute("id", pie.id); +                writer.write_attribute("icon", pie.icon); +                writer.write_attribute("hotkey", PieManager.get_accelerator_of(pie.id)); +                 +                // and all of it's Actions +                foreach (var group in pie.action_groups) { +                    // if it's a custom ActionGroup +                    if (group.get_type().depth() == 2) { +                        foreach (var action in group.actions) { +                            writer.start_element("slice"); +                            writer.write_attribute("type", ActionRegistry.settings_names[action.get_type()]); +                            if (ActionRegistry.icon_name_editables[action.get_type()]) { +                                writer.write_attribute("name", action.name); +                                writer.write_attribute("icon", action.icon); +                            } +                            writer.write_attribute("command", action.real_command); +                            writer.write_attribute("quickAction", action.is_quick_action ? "true" : "false"); +                            writer.end_element(); +                        } +                    } else { +                        writer.start_element("group"); +                            writer.write_attribute("type", GroupRegistry.settings_names[group.get_type()]); +                        writer.end_element(); +                    } +                } +                writer.end_element(); +            } +        } +        writer.end_element(); +        writer.end_document(); +    } +} + +} diff --git a/src/renderers/centerRenderer.vala b/src/renderers/centerRenderer.vala new file mode 100644 index 0000000..c30e9ce --- /dev/null +++ b/src/renderers/centerRenderer.vala @@ -0,0 +1,146 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +using GLib.Math; + +namespace GnomePie { + +// Renders the center of a Pie. + +public class CenterRenderer : GLib.Object { + +    private unowned PieRenderer parent; +    private unowned Image? caption; +    private Color color; +     +    private AnimatedValue activity; +    private AnimatedValue alpha; + +    public CenterRenderer(PieRenderer parent) { +        this.parent = parent; +        this.activity = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time); +        this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time); +        this.color = new Color(); +        this.caption = null; +    } +     +    public void fade_out() { +        this.activity.reset_target(0.0, Config.global.theme.fade_out_time); +        this.alpha.reset_target(0.0, Config.global.theme.fade_out_time); +    } +     +    public void set_active_slice(SliceRenderer? active_slice) { +        if (active_slice == null) { +            this.activity.reset_target(0.0, Config.global.theme.transition_time); +        } else { +            this.activity.reset_target(1.0, Config.global.theme.transition_time); +            this.caption = active_slice.caption; +            this.color   = active_slice.color; +        } +    } +     +    public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) { + +	    var layers = Config.global.theme.center_layers; +         +        this.activity.update(frame_time); +        this.alpha.update(frame_time); +	 +	    foreach (var layer in layers) { +	     +	        ctx.save(); + +            double active_speed = (layer.active_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?  +                0.0 : layer.active_rotation_speed; +            double inactive_speed = (layer.inactive_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?  +                0.0 : layer.inactive_rotation_speed; +	        double max_scale = layer.active_scale*this.activity.val  +	            + layer.inactive_scale*(1.0-this.activity.val); +            double max_alpha = layer.active_alpha*this.activity.val  +                + layer.inactive_alpha*(1.0-this.activity.val); +            double colorize = ((layer.active_colorize == true) ? this.activity.val : 0.0)  +                + ((layer.inactive_colorize == true) ? 1.0 - this.activity.val : 0.0); +            double max_rotation_speed = active_speed*this.activity.val  +                + inactive_speed*(1.0-this.activity.val); +            CenterLayer.RotationMode rotation_mode = ((this.activity.val > 0.5) ?  +                layer.active_rotation_mode : layer.inactive_rotation_mode); +	         +	        if (rotation_mode == CenterLayer.RotationMode.TO_MOUSE) { +	            double diff = angle-layer.rotation; +	            max_rotation_speed = layer.active_rotation_speed*this.activity.val  +	                + layer.inactive_rotation_speed*(1.0-this.activity.val); +	            double smoothy = fabs(diff) < 0.9 ? fabs(diff) + 0.1 : 1.0;  +	            double step = max_rotation_speed*frame_time*smoothy; +	             +                if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step) +		            layer.rotation = angle; +	            else { +	                if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step; +		            else            		                   layer.rotation -= step; +                } +                 +	        } else if (rotation_mode == CenterLayer.RotationMode.TO_ACTIVE) { +	            max_rotation_speed *= this.activity.val; +	             +	            double slice_angle = parent.slice_count() > 0 ? 2*PI/parent.slice_count() : 0; +	            double direction = (int)((angle+0.5*slice_angle) / (slice_angle))*slice_angle; +	            double diff = direction-layer.rotation; +	            double step = max_rotation_speed*frame_time; +	             +                if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step) +		            layer.rotation = direction; +	            else { +	                if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step; +		            else            		                   layer.rotation -= step; +                } +	             +	        } else layer.rotation += max_rotation_speed*frame_time; +	         +	        layer.rotation = fmod(layer.rotation+2*PI, 2*PI); +	         +	        if (colorize > 0.0) ctx.push_group(); +	         +	        ctx.rotate(layer.rotation); +	        ctx.scale(max_scale, max_scale); +	        layer.image.paint_on(ctx, this.alpha.val*max_alpha); +             +            if (colorize > 0.0) { +                ctx.set_operator(Cairo.Operator.ATOP); +                ctx.set_source_rgb(this.color.r, this.color.g, this.color.b); +                ctx.paint_with_alpha(colorize); +                 +                ctx.set_operator(Cairo.Operator.OVER); +                ctx.pop_group_to_source(); +	            ctx.paint(); +	        } +             +            ctx.restore(); +        } +         +        // draw caption +        if (Config.global.theme.caption && caption != null && this.activity.val > 0) { +		    ctx.save(); +            ctx.identity_matrix(); +            int pos = this.parent.get_size()/2; +            ctx.translate(pos, (int)(Config.global.theme.caption_position) + pos); +            caption.paint_on(ctx, this.activity.val*this.alpha.val); +            ctx.restore(); +        } +    } +} + +} diff --git a/src/renderers/pieRenderer.vala b/src/renderers/pieRenderer.vala new file mode 100644 index 0000000..5b706f4 --- /dev/null +++ b/src/renderers/pieRenderer.vala @@ -0,0 +1,208 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +using GLib.Math; + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This class renders a Pie. In order to accomplish that, it owns a +/// CenterRenderer and some SliceRenderers. +///////////////////////////////////////////////////////////////////////// + +public class PieRenderer : GLib.Object { + +    public int quick_action { get; private set; } +    public int active_slice { get; private set; } +    public bool show_hotkeys { get; set; } + +    private int size; +    private Gee.ArrayList<SliceRenderer?> slices; +    private CenterRenderer center; +    private bool key_board_control = false; +     +    public PieRenderer() { +        this.slices = new Gee.ArrayList<SliceRenderer?>();  +        this.center = new CenterRenderer(this); +        this.quick_action = -1; +        this.active_slice = -2; +        this.size = 0; +    } +     +    public void load_pie(Pie pie) { +        this.slices.clear(); +     +        int count = 0; +        foreach (var group in pie.action_groups) { +            foreach (var action in group.actions) { +                var renderer = new SliceRenderer(this); +                this.slices.add(renderer); +                renderer.load(action, slices.size-1); +                 +                if (action.is_quick_action) { +                    this.quick_action = count; +                } +                 +                ++count; +            } +        } +         +        this.set_highlighted_slice(this.quick_action); +         +        this.size = (int)fmax(2*Config.global.theme.radius + 2*Config.global.theme.slice_radius*Config.global.theme.max_zoom, +                              2*Config.global.theme.center_radius); +         +        // increase size if there are many slices +        if (slices.size > 0) { +            this.size = (int)fmax(this.size, +                (((Config.global.theme.slice_radius + Config.global.theme.slice_gap)/tan(PI/slices.size))  +                + Config.global.theme.slice_radius)*2*Config.global.theme.max_zoom); +        } +    } +     +    public void activate() { +        if (this.active_slice >= 0 && this.active_slice < this.slices.size) +            slices[active_slice].activate(); +        this.cancel(); +    } +     +    public void cancel() { +        foreach (var slice in this.slices) +            slice.fade_out(); +             +        center.fade_out(); +    } +     +    public void select_up() { +        int bottom = this.slice_count()/4; +        int top = this.slice_count()*3/4; +     +        if (this.active_slice == -1 || this.active_slice == bottom) +           this.set_highlighted_slice(top); +        else if (this.active_slice > bottom && this.active_slice < top) +           this.set_highlighted_slice(this.active_slice+1); +        else if (this.active_slice != top) +           this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count()); +    } +     +    public void select_down() { +        int bottom = this.slice_count()/4; +        int top = this.slice_count()*3/4; +     +        if (this.active_slice == -1 || this.active_slice == top) +           this.set_highlighted_slice(bottom); +        else if (this.active_slice > bottom && this.active_slice < top) +           this.set_highlighted_slice(this.active_slice-1); +        else if (this.active_slice != bottom) +           this.set_highlighted_slice((this.active_slice+1)%this.slice_count()); +    } +     +    public void select_left() { +        int left = this.slice_count()/2; +        int right = 0; +     +        if (this.active_slice == -1 || this.active_slice == right) +           this.set_highlighted_slice(left); +        else if (this.active_slice > left) +           this.set_highlighted_slice(this.active_slice-1); +        else if (this.active_slice < left) +           this.set_highlighted_slice(this.active_slice+1); +    } +     +    public void select_right() { +        int left = this.slice_count()/2; +        int right = 0; +     +        if (this.active_slice == -1 || this.active_slice == left) +           this.set_highlighted_slice(right); +        else if (this.active_slice > left) +           this.set_highlighted_slice((this.active_slice+1)%this.slice_count()); +        else if (this.active_slice < left && this.active_slice != right) +           this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count()); +    } +     +    public int slice_count() { +        return slices.size; +    } +     +    public int get_size() { +        return size; +    } +     +    public void draw(double frame_time, Cairo.Context ctx, int mouse_x, int mouse_y) { +	    double distance = sqrt(mouse_x*mouse_x + mouse_y*mouse_y); +	    double angle = 0.0; + +	    if (this.key_board_control) { +	        angle = 2.0*PI*this.active_slice/(double)slice_count(); +	    } else { +	     +	        if (distance > 0) { +	            angle = acos(mouse_x/distance); +		        if (mouse_y < 0)  +		            angle = 2*PI - angle; +	        } +	         +	        int next_active_slice = this.active_slice; +	         +	        if (distance < Config.global.theme.active_radius +	            && this.quick_action >= 0 && this.quick_action < this.slices.size) { +	          +	            next_active_slice = this.quick_action;    +	            angle = 2.0*PI*quick_action/(double)slice_count(); +	        } else if (distance > Config.global.theme.active_radius && this.slice_count() > 0) { +	            next_active_slice = (int)(angle*slices.size/(2*PI) + 0.5) % this.slice_count(); +	        } else { +	            next_active_slice = -1; +	        } +	     +	        this.set_highlighted_slice(next_active_slice); +	    } + +        center.draw(frame_time, ctx, angle, distance); +	     +	    foreach (var slice in this.slices) +		    slice.draw(frame_time, ctx, angle, distance); +    } +     +    public void on_mouse_move() { +        this.key_board_control = false; +    } +     +    public void set_highlighted_slice(int index) { +        if (index != this.active_slice) { +            if (index >= 0 && index < this.slice_count())  +                this.active_slice = index; +            else if (this.quick_action >= 0) +                this.active_slice = this.quick_action; +            else +                this.active_slice = -1; +             +            SliceRenderer? active = (this.active_slice >= 0 && this.active_slice < this.slice_count()) ? +                                     this.slices[this.active_slice] : null; +	                     +            center.set_active_slice(active); +             +            foreach (var slice in this.slices) +                slice.set_active_slice(active); +             +            this.key_board_control = true; +        } +    } +} + +} diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala new file mode 100644 index 0000000..c4ac2ec --- /dev/null +++ b/src/renderers/pieWindow.vala @@ -0,0 +1,263 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/         + +using GLib.Math; + +namespace GnomePie { + +// An invisible window. Used to draw Pies onto. + +public class PieWindow : Gtk.Window { +     +    public signal void on_closing(); + +    private PieRenderer renderer; +    private bool closing = false; +    private GLib.Timer timer; +     +    private bool has_compositing = false; +     +    private Image background = null; + +    public PieWindow() { +        this.renderer = new PieRenderer(); +     +        this.set_title("Gnome-Pie"); +        this.set_skip_taskbar_hint(true); +        this.set_skip_pager_hint(true); +        this.set_keep_above(true); +        this.set_type_hint(Gdk.WindowTypeHint.SPLASHSCREEN); +        this.set_decorated(false); +        this.set_resizable(false); +        this.icon_name = "gnome-pie"; +        this.set_accept_focus(false); +         +        if (this.screen.is_composited()) { +            this.set_colormap(this.screen.get_rgba_colormap()); +            this.has_compositing = true; +        } +         +        this.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK | +                        Gdk.EventMask.KEY_RELEASE_MASK | +                        Gdk.EventMask.KEY_PRESS_MASK | +                        Gdk.EventMask.POINTER_MOTION_MASK); + +        this.button_release_event.connect ((e) => { +            if (e.button == 1) this.activate_slice(); +            else               this.cancel(); +            return true; +        }); +         +        // remember last pressed key in order to disable key repeat +        uint last_key = 0; +        this.key_press_event.connect((e) => { +            if (e.keyval != last_key) { +                last_key = e.keyval; +                this.handle_key_press(e.keyval); +            } +            return true; +        }); +         +        this.key_release_event.connect((e) => { +            last_key = 0; +            if (Config.global.turbo_mode) +                this.activate_slice(); +            else +                this.handle_key_release(e.keyval); +            return true; +        }); +         +        this.motion_notify_event.connect((e) => { +            this.renderer.on_mouse_move(); +            return true; +        }); + +        this.expose_event.connect(this.draw); +    } + +    public void load_pie(Pie pie) { +        this.renderer.load_pie(pie); +        this.set_window_position(); +        this.set_size_request(renderer.get_size(), renderer.get_size()); +    } +     +    public void open() { +        this.realize(); +         +        if (!this.has_compositing) { +            int x, y, width, height; +            this.get_position(out x, out y); +            this.get_size(out width, out height); +            this.background = new Image.capture_screen(x, y, width+1, height+1); +        } +     +        this.show(); +        this.fix_focus(); + +        this.timer = new GLib.Timer(); +        this.timer.start(); +        this.queue_draw(); +         +        Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => { +            this.queue_draw(); +            return this.visible; +        });  +    } + +    private bool draw(Gtk.Widget da, Gdk.EventExpose event) {     +        // clear the window +        var ctx = Gdk.cairo_create(this.window); + +        if (this.has_compositing) { +            ctx.set_operator (Cairo.Operator.CLEAR); +            ctx.paint(); +            ctx.set_operator (Cairo.Operator.OVER); +        } else { +            ctx.set_operator (Cairo.Operator.OVER); +            ctx.set_source_surface(background.surface, -1, -1); +            ctx.paint(); +        } +         +        ctx.translate(this.width_request*0.5, this.height_request*0.5); + +        double mouse_x = 0.0, mouse_y = 0.0; +        this.get_pointer(out mouse_x, out mouse_y); +         +        double frame_time = this.timer.elapsed(); +        this.timer.reset(); +         +        this.renderer.draw(frame_time, ctx, (int)(mouse_x - this.width_request*0.5), +                                            (int)(mouse_y - this.height_request*0.5)); +         +        return true; +    } +     +    private void activate_slice() { +        if (!this.closing) { +            this.closing = true; +            this.on_closing(); +            this.unfix_focus(); +            this.renderer.activate(); +             +            Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => { +                this.destroy(); +                //ThemedIcon.clear_cache(); +                return false; +            }); +        } +    } +     +    private void cancel() { +        if (!this.closing) { +            this.closing = true; +            this.on_closing(); +            this.unfix_focus(); +            this.renderer.cancel(); +             +            Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => { +                this.destroy(); +                //ThemedIcon.clear_cache(); +                return false; +            }); +        } +    } +     +    private void set_window_position() { +        if(Config.global.open_at_mouse) this.set_position(Gtk.WindowPosition.MOUSE); +        else                            this.set_position(Gtk.WindowPosition.CENTER); +    } +     +    private void handle_key_press(uint key) { +        if      (Gdk.keyval_name(key) == "Escape") this.cancel(); +        else if (Gdk.keyval_name(key) == "Return") this.activate_slice(); +        else if (!Config.global.turbo_mode) { +            if (Gdk.keyval_name(key) == "Up") this.renderer.select_up(); +            else if (Gdk.keyval_name(key) == "Down") this.renderer.select_down(); +            else if (Gdk.keyval_name(key) == "Left") this.renderer.select_left(); +            else if (Gdk.keyval_name(key) == "Right") this.renderer.select_right(); +            else if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = true; +            else { +                int index = -1; +                 +                if (key >= 48 && key <= 57)        index = (int)key - 48; +                else if (key >= 97 && key <= 122)  index = (int)key - 87; +                else if (key >= 65 && key <= 90)   index = (int)key - 55; +                 +                if (index >= 0 && index < this.renderer.slice_count()) { +                    this.renderer.set_highlighted_slice(index); +                 +                    if (this.renderer.active_slice == index) { +                        GLib.Timeout.add((uint)(Config.global.theme.transition_time*1000.0), ()=> { +                            this.activate_slice(); +                            return false; +                        }); +                    } +                         +                } +            } +        } +    } +     +    private void handle_key_release(uint key) { +        if (!Config.global.turbo_mode) { +            if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = false; +        } +    } +     +    // utilities for grabbing focus +    // Code from Gnome-Do/Synapse  +    private void fix_focus() { +        uint32 timestamp = Gtk.get_current_event_time(); +        this.present_with_time(timestamp); +        this.get_window().raise(); +        this.get_window().focus(timestamp); + +        int i = 0; +        Timeout.add(100, () => { +            if (++i >= 100) return false; +            return !try_grab_window(); +        }); +    } +     +    // Code from Gnome-Do/Synapse  +    private void unfix_focus() { +        uint32 time = Gtk.get_current_event_time(); +        Gdk.pointer_ungrab(time); +        Gdk.keyboard_ungrab(time); +        Gtk.grab_remove(this); +    } +     +    // Code from Gnome-Do/Synapse  +    private bool try_grab_window() { +        uint time = Gtk.get_current_event_time(); +        if (Gdk.pointer_grab(this.get_window(), true, Gdk.EventMask.BUTTON_PRESS_MASK |  +                             Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK, +                             null, null, time) == Gdk.GrabStatus.SUCCESS) { +             +            if (Gdk.keyboard_grab(this.get_window(), true, time) == Gdk.GrabStatus.SUCCESS) { +                Gtk.grab_add(this); +                return true; +            } else { +                Gdk.pointer_ungrab(time); +                return false; +            } +        } +        return false; +    }   +} + +} diff --git a/src/renderers/sliceRenderer.vala b/src/renderers/sliceRenderer.vala new file mode 100644 index 0000000..08c880f --- /dev/null +++ b/src/renderers/sliceRenderer.vala @@ -0,0 +1,182 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +using GLib.Math; + +namespace GnomePie { + +// Renders a Slice of a Pie. According to the current theme. + +public class SliceRenderer : GLib.Object { + +    public bool active {get; private set; default = false;} +    public Image caption {get; private set;} +    public Color color {get; private set;} +     +    private Image active_icon; +    private Image inactive_icon; +    private Image hotkey; +     +    private Action action; + +    private unowned PieRenderer parent;     +    private int position; +     +    private AnimatedValue fade; +    private AnimatedValue scale; +    private AnimatedValue alpha; +    private AnimatedValue fade_rotation; +    private AnimatedValue fade_scale; + +    public SliceRenderer(PieRenderer parent) { +        this.parent = parent; +        +        this.fade =  new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time); +        this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time); +        this.scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,  +                                                 1.0/Config.global.theme.max_zoom,  +                                                 1.0/Config.global.theme.max_zoom,  +                                                 Config.global.theme.transition_time,  +                                                 Config.global.theme.springiness); +        this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,  +                                                 Config.global.theme.fade_in_zoom, 1.0,  +                                                 Config.global.theme.fade_in_time,  +                                                 Config.global.theme.springiness); +        this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,  +                                                 Config.global.theme.fade_in_rotation, 0.0,  +                                                 Config.global.theme.fade_in_time); +    } + +    public void load(Action action, int position) { +        this.position = position; +        this.action = action; +         +     +        if (Config.global.theme.caption) +            this.caption = new RenderedText(action.name,  +                                            Config.global.theme.caption_width, +                                            Config.global.theme.caption_height, +                                            Config.global.theme.caption_font); +             +        this.active_icon = new ThemedIcon(action.icon, true); +        this.inactive_icon = new ThemedIcon(action.icon, false); +         +        this.color = new Color.from_icon(this.active_icon); +         +        string hotkey_label = ""; +        if (position < 10) { +            hotkey_label = "%u".printf(position); +        } else if (position < 36) { +            hotkey_label = "%c".printf((char)(55 + position)); +        } +         +        this.hotkey = new RenderedText(hotkey_label, (int)Config.global.theme.slice_radius*2, +                         (int)Config.global.theme.slice_radius*2, "sans 20"); +    } +     +    public void activate() { +        action.activate(); +    } +     +    public void fade_out() { +        this.alpha.reset_target(0.0, Config.global.theme.fade_out_time); +        this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.IN,  +                                             this.fade_scale.val,  +                                             Config.global.theme.fade_out_zoom,  +                                             Config.global.theme.fade_out_time,  +                                             Config.global.theme.springiness); +        this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.IN,  +                                             this.fade_rotation.val,  +                                             Config.global.theme.fade_out_rotation,  +                                             Config.global.theme.fade_out_time); +    } +     +    public void set_active_slice(SliceRenderer? active_slice) { +       if (active_slice == this) { +            this.fade.reset_target(1.0, Config.global.theme.transition_time); +        } else { +            this.fade.reset_target(0.0, Config.global.theme.transition_time); +        } +    } + +    public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) { +	     +	    double direction = 2.0 * PI * position/parent.slice_count() + this.fade_rotation.val; +	    double max_scale = 1.0/Config.global.theme.max_zoom; +        double diff = fabs(angle-direction); +         +        if (diff > PI) +	        diff = 2 * PI - diff; + +        if (diff < 2 * PI * Config.global.theme.zoom_range) +            max_scale = (Config.global.theme.max_zoom/(diff * (Config.global.theme.max_zoom - 1) +                        /(2 * PI * Config.global.theme.zoom_range) + 1)) +                        /Config.global.theme.max_zoom; +	     +	    active = ((parent.active_slice >= 0) && (diff < PI/parent.slice_count())); +         +        max_scale = (parent.active_slice >= 0 ? max_scale : 1.0/Config.global.theme.max_zoom); +         +        if (fabs(this.scale.end - max_scale) > Config.global.theme.max_zoom*0.005) +            this.scale.reset_target(max_scale, Config.global.theme.transition_time); +         +        this.scale.update(frame_time); +        this.alpha.update(frame_time); +        this.fade.update(frame_time); +        this.fade_scale.update(frame_time); +        this.fade_rotation.update(frame_time); +	     +        ctx.save(); +         +        double radius = Config.global.theme.radius; +         +        if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap) +          /(radius/Config.global.theme.max_zoom)) > PI/parent.slice_count()) { +            radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap) +                     /tan(PI/parent.slice_count())*Config.global.theme.max_zoom; +        } +         +        ctx.scale(scale.val*fade_scale.val, scale.val*fade_scale.val); +        ctx.translate(cos(direction)*radius, sin(direction)*radius); +         +        ctx.push_group(); +         +        ctx.set_operator(Cairo.Operator.ADD); +     +        if (fade.val > 0.0) active_icon.paint_on(ctx, this.alpha.val*this.fade.val); +        if (fade.val < 1.0) inactive_icon.paint_on(ctx, this.alpha.val*(1.0 - fade.val)); +         +        if (this.parent.show_hotkeys) { +            ctx.set_operator(Cairo.Operator.ATOP); +            ctx.set_source_rgba(0, 0, 0, 0.5); +            ctx.paint(); +        } +         +        ctx.set_operator(Cairo.Operator.OVER); +         +         +        ctx.pop_group_to_source(); +        ctx.paint(); +         +        if (this.parent.show_hotkeys) +            this.hotkey.paint_on(ctx, 1.0); +             +        ctx.restore(); +    } +} + +} diff --git a/src/themes/centerLayer.vala b/src/themes/centerLayer.vala new file mode 100644 index 0000000..3469fd0 --- /dev/null +++ b/src/themes/centerLayer.vala @@ -0,0 +1,111 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This class representing a layer of the center of a pie. Each theme +/// may have plenty of them. +///////////////////////////////////////////////////////////////////////// + +public class CenterLayer : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Possible rotation modes. +    /// AUTO:      Turns the layer continously. +    /// TO_MOUSE:  Turns the layer always to the pointer. +    /// TO_ACTIVE: Turns the layer to the active slice. +    ///////////////////////////////////////////////////////////////////// + +    public enum RotationMode {AUTO, TO_MOUSE, TO_ACTIVE} +     +    ///////////////////////////////////////////////////////////////////// +    /// Information on the contained image. +    ///////////////////////////////////////////////////////////////////// +     +    public Image image {get; private set;} +    public string image_file; +     +    ///////////////////////////////////////////////////////////////////// +    /// Properties for the active state of this layer. +    ///////////////////////////////////////////////////////////////////// +     +    public double active_scale {get; private set;} +    public double active_rotation_speed {get; private set;} +    public double active_alpha {get; private set;} +    public bool active_colorize {get; private set;} +    public RotationMode active_rotation_mode {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// Properties for the inactive state of this layer. +    ///////////////////////////////////////////////////////////////////// +     +    public double inactive_scale {get; private set;} +    public double inactive_rotation_speed {get; private set;} +    public double inactive_alpha {get; private set;} +    public bool inactive_colorize {get; private set;} +    public RotationMode inactive_rotation_mode {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// The current rotation of this layer. TODO: Remove this. +    ///////////////////////////////////////////////////////////////////// +     +    public double rotation {get; set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// Helper variable for image loading. +    ///////////////////////////////////////////////////////////////////// +     +    private int center_radius; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members of the layer. +    ///////////////////////////////////////////////////////////////////// +     +    public CenterLayer(string image_file, int center_radius, double active_scale, double active_rotation_speed,    +                                    double active_alpha,   bool active_colorize,   RotationMode active_rotation_mode, +                                    double inactive_scale, double inactive_rotation_speed,  +                                    double inactive_alpha, bool inactive_colorize, RotationMode inactive_rotation_mode) { +         +        this.image_file = image_file; +        this.center_radius = center_radius; +         +        this.active_scale = active_scale; +        this.active_rotation_speed = active_rotation_speed; +        this.active_alpha = active_alpha; +        this.active_colorize = active_colorize; +        this.active_rotation_mode = active_rotation_mode; +         +        this.inactive_scale = inactive_scale; +        this.inactive_rotation_speed = inactive_rotation_speed; +        this.inactive_alpha = inactive_alpha; +        this.inactive_colorize = inactive_colorize; +        this.inactive_rotation_mode = inactive_rotation_mode; +         +        this.rotation = 0.0; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads the contained image. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_image() { +        this.image = new Image.from_file_at_size(image_file, 2*center_radius, 2*center_radius); +    } +} + +} diff --git a/src/themes/sliceLayer.vala b/src/themes/sliceLayer.vala new file mode 100644 index 0000000..2620912 --- /dev/null +++ b/src/themes/sliceLayer.vala @@ -0,0 +1,65 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// This class representing a layer of a slice of a pie. Each theme may +/// have plenty of them. +///////////////////////////////////////////////////////////////////////// + +public class SliceLayer : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// Information on the contained image. +    ///////////////////////////////////////////////////////////////////// +     +    public Image image {get; set;} +    public string icon_file {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// Properties of this layer. +    ///////////////////////////////////////////////////////////////////// +     +    public bool colorize {get; private set; } +    public bool is_icon {get; private set;} +    public int icon_size {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members of the layer. +    ///////////////////////////////////////////////////////////////////// +     +    public SliceLayer(string icon_file, int icon_size, bool colorize, bool is_icon) { +        this.icon_file = icon_file; +        this.colorize = colorize; +        this.is_icon = is_icon; +        this.icon_size = icon_size; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads the contained image. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_image() { +        if (this.icon_file == "" && this.is_icon == true)  +            this.image = new Image.empty(this.icon_size, this.icon_size, new Color.from_rgb(1, 1, 1)); +        else +            this.image = new Image.from_file_at_size(this.icon_file, this.icon_size, this.icon_size); +    } +} + +} diff --git a/src/themes/theme.vala b/src/themes/theme.vala new file mode 100644 index 0000000..fa6f55a --- /dev/null +++ b/src/themes/theme.vala @@ -0,0 +1,489 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http:www.gnu.org/licenses/>.  +*/ + +using GLib.Math; + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A theme of Gnome-Pie. Can be loaded from XML-Files. +///////////////////////////////////////////////////////////////////////// + +public class Theme : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// Properties of a theme. +    ///////////////////////////////////////////////////////////////////// +     +    public string directory        {get; private set; default="";} +    public string name             {get; private set; default="";} +    public string description      {get; private set; default="";} +    public string author           {get; private set; default="";} +    public string email            {get; private set; default="";} +    public double radius           {get; private set; default=150;} +    public double max_zoom         {get; private set; default=1.2;} +    public double zoom_range       {get; private set; default=0.2;} +    public double transition_time  {get; private set; default=0.5;} +    public double fade_in_time     {get; private set; default=0.2;} +    public double fade_out_time    {get; private set; default=0.1;} +    public double fade_in_zoom     {get; private set; default=1.0;} +    public double fade_out_zoom    {get; private set; default=1.0;} +    public double fade_in_rotation {get; private set; default=0.0;} +    public double fade_out_rotation{get; private set; default=0.0;} +    public double springiness      {get; private set; default=0.0;} +    public double center_radius    {get; private set; default=90.0;} +    public double active_radius    {get; private set; default=45.0;} +    public double slice_radius     {get; private set; default=32.0;} +    public double slice_gap        {get; private set; default=14.0;} +    public bool   caption          {get; private set; default=false;} +    public string caption_font     {get; private set; default="sans 12";} +    public int    caption_width    {get; private set; default=100;} +    public int    caption_height   {get; private set; default=100;} +    public double caption_position {get; private set; default=0.0;} +    public Color  caption_color    {get; private set; default=new Color();} +     +    public Gee.ArrayList<CenterLayer?> center_layers         {get; private set;} +    public Gee.ArrayList<SliceLayer?>  active_slice_layers   {get; private set;} +    public Gee.ArrayList<SliceLayer?>  inactive_slice_layers {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a theme object for a given theme directory. This +    /// directory should contain a theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    public Theme(string dir) { +        this.center_layers =         new Gee.ArrayList<CenterLayer?>(); +        this.active_slice_layers =   new Gee.ArrayList<SliceLayer?>(); +        this.inactive_slice_layers = new Gee.ArrayList<SliceLayer?>(); +         +        this.directory = dir; +         +        this.load(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads the theme from its directory. Images have to be loaded  +    /// explicitly. +    ///////////////////////////////////////////////////////////////////// +     +    public void load() { +        this.center_layers.clear(); +        this.active_slice_layers.clear(); +        this.inactive_slice_layers.clear(); +     +        Xml.Parser.init(); +        string path = this.directory + "/theme.xml"; +         +        Xml.Doc* themeXML = Xml.Parser.parse_file(path); +        if (themeXML == null) { +            warning("Error parsing theme: \"" + path + "\" not found!"); +            return; +        } + +        Xml.Node* root = themeXML->get_root_element(); +        if (root == null) { +            delete themeXML; +            warning("Invalid theme \"" + this.directory + "\": theme.xml is empty!"); +            return; +        } +         +        this.parse_root(root); +         +        delete themeXML; +        Xml.Parser.cleanup(); +         +        this.radius *= max_zoom; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads all images of the theme. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_images() { +        foreach (var layer in this.center_layers) +            layer.load_image(); +        foreach (var layer in this.active_slice_layers) +            layer.load_image(); +        foreach (var layer in this.inactive_slice_layers) +            layer.load_image(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The following methods parse specific parts of the theme file. +    /// Nothing special here, just some boring code. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_root(Xml.Node* root) { +        for (Xml.Attr* attribute = root->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "name": +                    name = attr_content; +                    break; +                case "description": +                    description = attr_content; +                    break; +                case "email": +                    email = attr_content; +                    break; +                case "author": +                    author = attr_content; +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <theme> element!"); +                    break; +            } +        } +        for (Xml.Node* node = root->children; node != null; node = node->next) { +            if (node->type == Xml.ElementType.ELEMENT_NODE) { +                parse_pie(node); +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <pie> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_pie(Xml.Node* pie) { +        for (Xml.Attr* attribute = pie->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "radius": +                    radius = double.parse(attr_content) * Config.global.global_scale; +                    break; +                case "maxzoom": +                    max_zoom = double.parse(attr_content); +                    break; +                case "zoomrange": +                    zoom_range = double.parse(attr_content); +                    break; +                case "transitiontime": +                    transition_time = double.parse(attr_content); +                    break; +                case "fadeintime": +                    fade_in_time = double.parse(attr_content); +                    break; +                case "fadeouttime": +                    fade_out_time = double.parse(attr_content); +                    break; +                case "fadeinzoom": +                    fade_in_zoom = double.parse(attr_content); +                    break; +                case "fadeoutzoom": +                    fade_out_zoom = double.parse(attr_content); +                    break; +                case "fadeinrotation": +                    fade_in_rotation = double.parse(attr_content); +                    break; +                case "fadeoutrotation": +                    fade_out_rotation = double.parse(attr_content); +                    break; +                case "springiness": +                    springiness = double.parse(attr_content); +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <pie> element!"); +                    break; +            } +        } +        for (Xml.Node* node = pie->children; node != null; node = node->next) { +            if (node->type == Xml.ElementType.ELEMENT_NODE) { +                string element_name = node->name.down(); +                switch (element_name) { +                    case "center": +                        parse_center(node); +                        break; +                    case "slices": +                        parse_slices(node); +                        break; +                    case "caption": +                        caption = true; +                        parse_caption(node); +                        break; +                    default: +                        warning("Invalid child element \"" + element_name + "\" in <pie> element!"); +                        break; +                } +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <center> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_center(Xml.Node* center) { +        for (Xml.Attr* attribute = center->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "radius": +                    center_radius = double.parse(attr_content) * Config.global.global_scale; +                    break; +                case "activeradius": +                    active_radius = double.parse(attr_content) * Config.global.global_scale; +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <center> element!"); +                    break; +            } +        } +        for (Xml.Node* node = center->children; node != null; node = node->next) { +            if (node->type == Xml.ElementType.ELEMENT_NODE) { +                string element_name = node->name.down(); +                 +                if (element_name == "center_layer") { +                    parse_center_layer(node); +                } else { +                    warning("Invalid child element \"" + element_name + "\" in <center> element!"); +                } +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <slices> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_slices(Xml.Node* slices) { +        for (Xml.Attr* attribute = slices->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                 case "radius": +                    slice_radius = double.parse(attr_content) * Config.global.global_scale; +                    break; +                case "mingap": +                    slice_gap = double.parse(attr_content) * Config.global.global_scale; +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <slices> element!"); +                    break; +            } +        } +        for (Xml.Node* node = slices->children; node != null; node = node->next) { +            if (node->type == Xml.ElementType.ELEMENT_NODE) { +                string element_name = node->name.down(); +                 +                if (element_name == "activeslice" || element_name == "inactiveslice") { +                    parse_slice_layers(node); +                } else { +                    warning("Invalid child element \"" + element_name + "\" in <slices> element!"); +                } +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <center_layer> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_center_layer(Xml.Node* layer) { + +        string file = ""; +        double active_rotation_speed = 0.0; +        double inactive_rotation_speed = 0.0; +        double active_scale = 1.0; +        double inactive_scale = 1.0; +        double active_alpha = 1.0; +        double inactive_alpha = 1.0; +        bool   active_colorize = false; +        bool   inactive_colorize = false; +        CenterLayer.RotationMode active_rotation_mode = CenterLayer.RotationMode.AUTO; +        CenterLayer.RotationMode inactive_rotation_mode = CenterLayer.RotationMode.AUTO; +         +        for (Xml.Attr* attribute = layer->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "file": +                    file = attr_content; +                    break; +                case "active_scale": +                    active_scale = double.parse(attr_content); +                    break; +                case "active_alpha": +                    active_alpha = double.parse(attr_content); +                    break; +                case "active_rotationmode": +                    switch (attr_content.down()) { +                        case "auto": +                           active_rotation_mode = CenterLayer.RotationMode.AUTO; +                           break; +                        case "turn_to_active": +                           active_rotation_mode = CenterLayer.RotationMode.TO_ACTIVE; +                           break;  +                        case "turn_to_mouse": +                           active_rotation_mode = CenterLayer.RotationMode.TO_MOUSE; +                           break;  +                        default: +                           warning("Invalid value \"" + attr_content + "\" for attribute \"" + attr_name + "\" in <center_layer> element!"); +                           break; +                    } +                    break; +                case "active_rotationspeed": +                    active_rotation_speed = double.parse(attr_content); +                    break; +                case "active_colorize": +                    active_colorize = bool.parse(attr_content); +                    break; +                case "inactive_scale": +                    inactive_scale = double.parse(attr_content); +                    break; +                case "inactive_alpha": +                    inactive_alpha = double.parse(attr_content); +                    break; +                case "inactive_rotationmode": +                    switch (attr_content.down()) { +                        case "auto": +                           inactive_rotation_mode = CenterLayer.RotationMode.AUTO; +                           break; +                        case "turn_to_active": +                           inactive_rotation_mode = CenterLayer.RotationMode.TO_ACTIVE; +                           break;  +                        case "turn_to_mouse": +                           inactive_rotation_mode = CenterLayer.RotationMode.TO_MOUSE; +                           break;  +                        default: +                           warning("Invalid value \"" + attr_content + "\" for attribute \"" + attr_name + "\" in <center_layer> element!"); +                           break; +                    } +                    break; +                case "inactive_rotationspeed": +                    inactive_rotation_speed = double.parse(attr_content); +                    break; +                case "inactive_colorize": +                    inactive_colorize = bool.parse(attr_content); +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <center_layer> element!"); +                    break; +            } +        } + +        double max_scale = GLib.Math.fmax(active_scale, inactive_scale); +        center_layers.add(new CenterLayer(directory + "/" + file, (int)(center_radius*max_scale), active_scale/max_scale,   active_rotation_speed,   active_alpha,   active_colorize,   active_rotation_mode,  +                                                 inactive_scale/max_scale, inactive_rotation_speed, inactive_alpha, inactive_colorize, inactive_rotation_mode)); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <slice_layer> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_slice_layers(Xml.Node* slice) { +        for (Xml.Node* layer = slice->children; layer != null; layer = layer->next) { +            if (layer->type == Xml.ElementType.ELEMENT_NODE) { +                string element_name = layer->name.down(); +                 +                if (element_name == "slice_layer") { +                    string file = ""; +                    double scale = 1.0; +                    bool is_icon = false; +                    bool colorize = false; +                     +                    for (Xml.Attr* attribute = layer->properties; attribute != null; attribute = attribute->next) { +                        string attr_name = attribute->name.down(); +                        string attr_content = attribute->children->content; +                         +                        switch (attr_name) { +                            case "file": +                                file = attr_content; +                                break; +                            case "scale": +                                scale = double.parse(attr_content); +                                break; +                            case "type": +                                if (attr_content == "icon") +                                    is_icon = true; +                                else if (attr_content != "file") +                                    warning("Invalid attribute content " + attr_content + " for attribute " + attr_name + " in <slice_layer> element!"); +                                break; +                            case "colorize": +                                colorize = bool.parse(attr_content); +                                break; +                            default: +                                warning("Invalid attribute \"" + attr_name + "\" in <slice_layer> element!"); +                                break; +                        } +                    } +                     +                    if (file != "") +                        file = directory + "/" + file; +                     +                    int size = 2*(int)(slice_radius*scale*max_zoom); + +                    if (slice->name.down() == "activeslice") { +                        active_slice_layers.add(new SliceLayer(file, size, colorize, is_icon)); +                    } else { +                        inactive_slice_layers.add(new SliceLayer(file, size, colorize, is_icon)); +                    } + +                } else { +                    warning("Invalid child element \"" + element_name + "\" in <" + slice->name + "> element!"); +                } +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a <caption> element from the theme.xml file. +    ///////////////////////////////////////////////////////////////////// +     +    private void parse_caption(Xml.Node* caption) { +        for (Xml.Attr* attribute = caption->properties; attribute != null; attribute = attribute->next) { +            string attr_name = attribute->name.down(); +            string attr_content = attribute->children->content; +             +            switch (attr_name) { +                case "font": +                    caption_font = attr_content; +                    break; +                case "width": +                    caption_width = (int)(int.parse(attr_content) * Config.global.global_scale); +                    if (caption_width % 2 == 1) +                        --caption_width; +                    break; +                case "height": +                    caption_height = (int)(int.parse(attr_content) * Config.global.global_scale); +                    if (caption_height % 2 == 1) +                        --caption_height; +                    break; +                case "position": +                    caption_position = double.parse(attr_content) * Config.global.global_scale; +                    break; +                case "color": +                    caption_color = new Color.from_string(attr_content); +                    break; +                default: +                    warning("Invalid attribute \"" + attr_name + "\" in <caption> element!"); +                    break; +            } +        } + +    } + +} + +} diff --git a/src/utilities/animatedValue.vala b/src/utilities/animatedValue.vala new file mode 100644 index 0000000..32ab889 --- /dev/null +++ b/src/utilities/animatedValue.vala @@ -0,0 +1,190 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie {	 + +/////////////////////////////////////////////////////////////////////////     +/// A class which interpolates smoothly between to given values. +/// Duration and interpolation mode can be specified. +///////////////////////////////////////////////////////////////////////// + +public class AnimatedValue : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// The direction of the interpolation. +    ///////////////////////////////////////////////////////////////////// + +    public enum Direction { IN, OUT, IN_OUT, OUT_IN } +     +    ///////////////////////////////////////////////////////////////////// +    /// Type of the interpolation, linear or cubic. +    ///////////////////////////////////////////////////////////////////// +     +    private enum Type { LINEAR, CUBIC } +     +    ///////////////////////////////////////////////////////////////////// +    /// Current value, interpolated. +    ///////////////////////////////////////////////////////////////////// +     +    public double val { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Starting value of the interpolation. +    ///////////////////////////////////////////////////////////////////// +     +    public double start { get; private set; default=0.0; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Final value of the interpolation. +    ///////////////////////////////////////////////////////////////////// +     +    public double end { get; private set; default=0.0; }  +     +    ///////////////////////////////////////////////////////////////////// +    /// The current state. In range 0 .. 1 +    ///////////////////////////////////////////////////////////////////// +     +    private double state = 0.0; +     +    ///////////////////////////////////////////////////////////////////// +    /// Duration of the interpolation. Should be in the same unit as  +    /// taken for the update() method. +    ///////////////////////////////////////////////////////////////////// +     +    private double duration = 0.0; +     +    ///////////////////////////////////////////////////////////////////// +    /// The amount of over-shooting of the cubicly interpolated value. +    ///////////////////////////////////////////////////////////////////// +     +    private double multiplier = 0.0; +     +    ///////////////////////////////////////////////////////////////////// +    /// Type of the interpolation, linear or cubic. +    ///////////////////////////////////////////////////////////////////// +     +    private Type type = Type.LINEAR; +     +    ///////////////////////////////////////////////////////////////////// +    /// The direction of the interpolation. +    ///////////////////////////////////////////////////////////////////// +     +    private Direction direction = Direction.IN; +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a new linearly interpolated value. +    ///////////////////////////////////////////////////////////////////// +     +    public AnimatedValue.linear(double start, double end, double duration) { +        this.val = start; +        this.start = start; +        this.end = end; +        this.duration = duration; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a new cubicly interpolated value. +    ///////////////////////////////////////////////////////////////////// +     +    public AnimatedValue.cubic(Direction direction, double start, double end, double duration, double multiplier = 0) { +        this.val = start; +        this.start = start; +        this.end = end; +        this.duration = duration; +        this.direction = direction; +        this.type = Type.CUBIC; +        this.multiplier = multiplier; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Resets the final value of the interpolation to a new value. The +    /// current state is taken for the beginning from now. +    ///////////////////////////////////////////////////////////////////// +     +    public void reset_target(double end, double duration) { +        this.start = this.val; +        this.end = end; +        this.duration = duration; +        this.state = 0.0; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Updates the interpolated value according to it's type. +    ///////////////////////////////////////////////////////////////////// +     +    public void update(double time) { +        this.state += time/this.duration; +         +        if (state < 1) { +         +            switch (this.type) { +                case Type.LINEAR: +                    this.val = update_linear(); +                    break; +                case Type.CUBIC: +                    switch (this.direction) { +                        case Direction.IN: +                            this.val = update_ease_in(); +                            return; +                        case Direction.OUT: +                            this.val = update_ease_out(); +                            return; +                        case Direction.IN_OUT: +                            this.val = update_ease_in_out(); +                            return; +                        case Direction.OUT_IN: +                            this.val = update_ease_out_in(); +                            return; +                } +                break; +            } +        } else if (this.val != this.end) { +             this.val = this.end; +        }   +    } + +    ///////////////////////////////////////////////////////////////////// +    /// The following equations are based on Robert Penner's easing  +    /// equations. See (http://www.robertpenner.com/easing/) and their  +    /// adaption by Zeh Fernando, Nate Chatellier and Arthur Debert for +    /// the Tweener class. See (http://code.google.com/p/tweener/). +    ///////////////////////////////////////////////////////////////////// +     +    private double update_linear(double t = this.state, double s = this.start, double e = this.end) { +        return (s + t*(e - s)); +    } +     +    private double update_ease_in(double t = this.state, double s = this.start, double e = this.end) { +        return (s + (t*t*((multiplier+1)*t-multiplier))*(e - s)); +    } +     +    private double update_ease_out(double t = this.state, double s = this.start, double e = this.end) { +        return (s + ((t-1) * (t-1) * ((multiplier+1)*(t-1)+multiplier) + 1) * (e - s)); +    } +     +    private double update_ease_in_out(double t = this.state, double s = this.start, double e = this.end) { +        if (this.state < 0.5) return update_ease_in(t*2, s, e - (e-s)*0.5); +        else                  return update_ease_out(t*2-1, s + (e-s)*0.5, e); +    } +     +    private double update_ease_out_in(double t = this.state, double s = this.start, double e = this.end) { +        if (this.state < 0.5) return update_ease_out(t*2, s, e - (e-s)*0.5); +        else                  return update_ease_in(t*2-1, s + (e-s)*0.5, e); +    } +} + +} diff --git a/src/utilities/bindingManager.vala b/src/utilities/bindingManager.vala new file mode 100644 index 0000000..8795124 --- /dev/null +++ b/src/utilities/bindingManager.vala @@ -0,0 +1,196 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// Globally binds key stroke to given ID's. When one of the bound  +/// strokes is invoked, a signal with the according ID is emitted. +///////////////////////////////////////////////////////////////////////// + +public class BindingManager : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Called when a stored binding is invoked. The according ID is +    /// passed as argument. +    ///////////////////////////////////////////////////////////////////// + +    public signal void on_press(string id); +     +    ///////////////////////////////////////////////////////////////////// +    /// A list storing bindings, which are invoked even if Gnome-Pie +    /// doesn't have the current focus +    ///////////////////////////////////////////////////////////////////// +     +    private Gee.List<Keybinding> bindings = new Gee.ArrayList<Keybinding>(); + +    ///////////////////////////////////////////////////////////////////// +    /// Ignored modifier masks, used to grab all keys even if these locks +    /// are active. +    ///////////////////////////////////////////////////////////////////// +     +    private static uint[] lock_modifiers = { +        0, +        Gdk.ModifierType.MOD2_MASK, +        Gdk.ModifierType.LOCK_MASK, +        Gdk.ModifierType.MOD5_MASK, +        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK, +        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.MOD5_MASK, +        Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK, +        Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK +    }; +  +    ///////////////////////////////////////////////////////////////////// +    /// Helper class to store keybinding +    ///////////////////////////////////////////////////////////////////// +     +    private class Keybinding { +     +        public Keybinding(string accelerator, int keycode, Gdk.ModifierType modifiers, string id) { +            this.accelerator = accelerator; +            this.keycode = keycode; +            this.modifiers = modifiers; +            this.id = id; +        } +  +        public string accelerator { get; set; } +        public int keycode { get; set; } +        public Gdk.ModifierType modifiers { get; set; } +        public string id { get; set; } +    } +  +    ///////////////////////////////////////////////////////////////////// +    /// C'tor adds the event filter to the root window. +    ///////////////////////////////////////////////////////////////////// +     +    public BindingManager() { +        // init filter to retrieve X.Events +        Gdk.Window rootwin = Gdk.get_default_root_window(); +        if(rootwin != null) { +            rootwin.add_filter(event_filter); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Binds the ID to the given accelerator. +    ///////////////////////////////////////////////////////////////////// +      +    public void bind(string accelerator, string id) { +        uint keysym; +        Gdk.ModifierType modifiers; +        Gtk.accelerator_parse(accelerator, out keysym, out modifiers); +         +        if (keysym == 0) { +            warning("Invalid keystroke: " + accelerator); +            return; +        } +  +        Gdk.Window rootwin = Gdk.get_default_root_window(); +        X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin); +        X.ID xid = Gdk.x11_drawable_get_xid(rootwin); +        int keycode = display.keysym_to_keycode(keysym); +  +        if(keycode != 0) { +            Gdk.error_trap_push(); +  +            foreach(uint lock_modifier in lock_modifiers) { +                display.grab_key(keycode, modifiers|lock_modifier, xid, false, X.GrabMode.Async, X.GrabMode.Async); +            } +  +            Gdk.flush(); +  +            Keybinding binding = new Keybinding(accelerator, keycode, modifiers, id); +            bindings.add(binding); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Unbinds the accelerator of the given ID. +    ///////////////////////////////////////////////////////////////////// +  +    public void unbind(string id) { +        Gdk.Window rootwin = Gdk.get_default_root_window(); +        X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin); +        X.ID xid = Gdk.x11_drawable_get_xid(rootwin); +        Gee.List<Keybinding> remove_bindings = new Gee.ArrayList<Keybinding>(); +        foreach(var binding in bindings) { +            if(id == binding.id) { +                foreach(uint lock_modifier in lock_modifiers) { +                    display.ungrab_key(binding.keycode, binding.modifiers, xid); +                } +                remove_bindings.add(binding); +            } +        } + +        bindings.remove_all(remove_bindings); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns a human readable accelerator for the given ID. +    ///////////////////////////////////////////////////////////////////// +     +    public string get_accelerator_label_of(string id) { +        string accelerator = this.get_accelerator_of(id); +         +        if (accelerator == "") +            return _("Not bound"); +         +        uint key = 0; +        Gdk.ModifierType mods; +        Gtk.accelerator_parse(accelerator, out key, out mods); +        return Gtk.accelerator_get_label(key, mods); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the accelerator to which the given ID is bound. +    ///////////////////////////////////////////////////////////////////// +     +    public string get_accelerator_of(string id) { +        foreach (var binding in bindings) { +            if (binding.id == id) { +                return binding.accelerator; +            } +        } +         +        return ""; +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Event filter method needed to fetch X.Events +    ///////////////////////////////////////////////////////////////////// +     +    private Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) { +        Gdk.FilterReturn filter_return = Gdk.FilterReturn.CONTINUE; +  +        void* pointer = &gdk_xevent; +        X.Event* xevent = (X.Event*) pointer; +  +        if(xevent->type == X.EventType.KeyPress) { +            foreach(var binding in bindings) { +                // remove NumLock, CapsLock and ScrollLock from key state +                uint event_mods = xevent.xkey.state & ~ (lock_modifiers[7]); +                if(xevent->xkey.keycode == binding.keycode && event_mods == binding.modifiers) { +                    on_press(binding.id); +                } +            } +         }  +  +        return filter_return; +    } +} + +} diff --git a/src/utilities/color.vala b/src/utilities/color.vala new file mode 100644 index 0000000..836411e --- /dev/null +++ b/src/utilities/color.vala @@ -0,0 +1,305 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +using GLib.Math; + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A Color class with full rgb/hsv support +/// and some useful utility methods. +///////////////////////////////////////////////////////////////////////// + +public class Color: GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Private members, storing the actual color information. +    /// In range 0 .. 1 +    ///////////////////////////////////////////////////////////////////// + +    private float _r; +    private float _g; +    private float _b; +    private float _a; +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a white Color. +    ///////////////////////////////////////////////////////////////////// +     +    public Color() { +        Color.from_rgb(1.0f, 1.0f, 1.0f); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a solid color with the given RGB values. +    ///////////////////////////////////////////////////////////////////// + +    public Color.from_rgb(float red, float green, float blue) { +        Color.from_rgba(red, green, blue, 1.0f); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a translucient color with the given RGBA values. +    ///////////////////////////////////////////////////////////////////// +     +    public Color.from_rgba(float red, float green, float blue, float alpha) { +        r = red; +        g = green; +        b = blue; +        a = alpha; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a color from the given Gdk.Color +    ///////////////////////////////////////////////////////////////////// +     +    public Color.from_gdk(Gdk.Color color) { +        Color.from_rgb( +            (float)color.red/65535.0f, +            (float)color.green/65535.0f, +            (float)color.blue/65535.0f +        ); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a color, parsed from a string, such as #22EE33 +    ///////////////////////////////////////////////////////////////////// +     +    public Color.from_string(string hex_string) { +        Gdk.Color color; +        Gdk.Color.parse(hex_string, out color); +        Color.from_gdk(color); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Gets the main color from an Image. Code from Unity. +    ///////////////////////////////////////////////////////////////////// +     +    public Color.from_icon(Image icon) { +        unowned uchar[] data = icon.surface.get_data(); +     +        uint width = icon.surface.get_width(); +        uint height = icon.surface.get_height(); +        uint row_bytes = icon.surface.get_stride(); + +        double total = 0.0; +        double rtotal = 0.0; +        double gtotal = 0.0; +        double btotal = 0.0;  + +        for (uint i = 0; i < width; ++i) { +            for (uint j = 0; j < height; ++j) { +                uint pixel = j * row_bytes + i * 4; +                double b = data[pixel + 0]/255.0; +                double g = data[pixel + 1]/255.0; +                double r = data[pixel + 2]/255.0; +                double a = data[pixel + 3]/255.0; + +                double saturation = (fmax (r, fmax (g, b)) - fmin (r, fmin (g, b))); +                double relevance = 0.1 + 0.9 * a * saturation; + +                rtotal +=  (r * relevance); +                gtotal +=  (g * relevance); +                btotal +=  (b * relevance); + +                total += relevance; +            } +        } + +        Color.from_rgb((float)(rtotal/total), (float)(gtotal/total), (float)(btotal/total)); + +        if (s > 0.15f) s = 0.65f; + +        v = 1.0f; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The reddish part of the color. +    ///////////////////////////////////////////////////////////////////// + +    public float r { +        get { +            return _r; +        } +        set { +            if (value > 1.0f) _r = 1.0f; +            else if (value < 0.0f) _r = 0.0f; +            else _r = value;  +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The greenish part of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float g { +        get { +            return _g; +        } +        set { +            if (value > 1.0f) _g = 1.0f; +            else if (value < 0.0f) _g = 0.0f; +            else _g = value;  +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The blueish part of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float b { +        get { +            return _b; +        } +        set { +            if (value > 1.0f) _b = 1.0f; +            else if (value < 0.0f) _b = 0.0f; +            else _b = value;  +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The transparency of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float a { +        get { +            return _a; +        } +        set { +            if (value > 1.0f) _a = 1.0f; +            else if (value < 0.0f) _a = 0.0f; +            else _a = value;  +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The hue of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float h { +        get { +            if (s > 0.0f) { +                float maxi = fmaxf(fmaxf(r, g), b); +                float mini = fminf(fminf(r, g), b); + +                if (maxi == r) +                    return fmodf(60.0f*((g-b)/(maxi-mini))+360.0f, 360.0f); +                else if (maxi == g) +                    return fmodf(60.0f*(2.0f + (b-r)/(maxi-mini))+360.0f, 360.0f); +                else +                    return fmodf(60.0f*(4.0f + (r-g)/(maxi-mini))+360.0f, 360.0f); +            } +            else return 0.0f; +        } +        set { +            setHSV(value, s, v); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The saturation of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float s { +        get { +            if (v == 0.0f) return 0.0f; +            else return ((v-fminf(fminf(r, g), b)) / v); +        } +        set { +            if (value > 1.0f) setHSV(h, 1.0f, v); +            else if (value < 0.0f) setHSV(h, 0.0f, v); +            else setHSV(h, value, v); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// The value of the color. +    ///////////////////////////////////////////////////////////////////// +     +    public float v { +        get { +            return fmaxf(fmaxf(r, g), b); +        } +        set { +            if (value > 1) setHSV(h, s, 1.0f); +            else if (value < 0) setHSV(h, s, 0.0f); +            else setHSV(h, s, value); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Inverts the color. +    ///////////////////////////////////////////////////////////////////// +     +    public void invert() { +        h += 180.0f; +        v = 1.0f - v; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Private member, used to apply color changes. +    ///////////////////////////////////////////////////////////////////// + +    private void setHSV(float hue, float saturation, float val) { +        if(saturation == 0) { +	        r = val; +	        g = val; +	        b = val; +	        return; +        } +        hue = fmodf(hue, 360); +        hue /= 60; +        int i = (int) floorf(hue); +        float f = hue - i; + +        switch(i) { +	        case 0: +		        r = val; +		        g = val * (1.0f - saturation * (1.0f - f)); +		        b = val * (1.0f - saturation); +		        break; +	        case 1: +		        r = val * (1.0f - saturation * f); +		        g = val; +		        b = val * (1.0f - saturation); +		        break; +	        case 2: +		        r = val * (1.0f - saturation); +		        g = val; +		        b = val * (1.0f - saturation * (1.0f - f)); +		        break; +	        case 3: +		        r = val * (1.0f - saturation); +		        g = val * (1.0f - saturation * f); +		        b = val; +		        break; +	        case 4: +		        r = val * (1.0f - saturation * (1.0f - f)); +		        g = val * (1.0f - saturation); +		        b = val; +		        break; +	        default: +		        r = val; +		        g = val * (1.0f - saturation); +		        b = val * (1.0f - saturation * f); +		        break; +        } +    } +} + +} diff --git a/src/utilities/config.vala b/src/utilities/config.vala new file mode 100644 index 0000000..c5dedd5 --- /dev/null +++ b/src/utilities/config.vala @@ -0,0 +1,202 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A singleton class for storing global settings. These settings can +/// be loaded from and saved to an XML file. +///////////////////////////////////////////////////////////////////////// + +public class Config : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// The singleton instance of this class. +    ///////////////////////////////////////////////////////////////////// + +    private static Config _instance = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the singleton instance. +    ///////////////////////////////////////////////////////////////////// +     +    public static Config global { +        get { +            if (_instance == null) { +                _instance = new Config(); +                _instance.load(); +            } +            return _instance; +        } +        private set { +            _instance = value; +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// All settings variables. +    ///////////////////////////////////////////////////////////////////// + +    public Theme theme { get; set; } +    public double refresh_rate { get; set; default = 60.0; } +    public double global_scale { get; set; default = 1.0; } +    public bool show_indicator { get; set; default = true; } +    public bool open_at_mouse { get; set; default = true; } +    public bool turbo_mode { get; set; default = false; } +    public bool auto_start { get; set; default = false; } +    public Gee.ArrayList<Theme?> themes { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Saves all above variables to a file. +    ///////////////////////////////////////////////////////////////////// +     +    public void save() { +        var writer = new Xml.TextWriter.filename(Paths.settings); +        writer.start_document("1.0"); +            writer.start_element("settings"); +                writer.write_attribute("theme", theme.name); +                writer.write_attribute("refresh_rate", refresh_rate.to_string()); +                writer.write_attribute("global_scale", global_scale.to_string()); +                writer.write_attribute("show_indicator", show_indicator ? "true" : "false"); +                writer.write_attribute("open_at_mouse", open_at_mouse ? "true" : "false"); +                writer.write_attribute("turbo_mode", turbo_mode ? "true" : "false"); +            writer.end_element(); +        writer.end_document(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads all settings variables from a file. +    ///////////////////////////////////////////////////////////////////// +     +    private void load() { +     +        // check for auto_start filename +        this.auto_start = FileUtils.test(Paths.autostart, FileTest.EXISTS); +     +        // parse the settings file +        Xml.Parser.init(); +        Xml.Doc* settingsXML = Xml.Parser.parse_file(Paths.settings); +        bool   error_occrured = false; +        string theme_name = ""; +         +        if (settingsXML != null) { + +            Xml.Node* root = settingsXML->get_root_element(); +            if (root != null) { + +                for (Xml.Attr* attribute = root->properties; attribute != null; attribute = attribute->next) { +                    string attr_name = attribute->name.down(); +                    string attr_content = attribute->children->content; +                     +                    switch (attr_name) { +                        case "theme": +                            theme_name = attr_content; +                            break; +                        case "refresh_rate": +                            refresh_rate = double.parse(attr_content); +                            break; +                        case "global_scale": +                            global_scale = double.parse(attr_content); +                            global_scale.clamp(0.5, 2.0); +                            break; +                        case "show_indicator": +                            show_indicator = bool.parse(attr_content); +                            break; +                        case "open_at_mouse": +                            open_at_mouse = bool.parse(attr_content); +                            break; +                        case "turbo_mode": +                            turbo_mode = bool.parse(attr_content); +                            break; +                        default: +                            warning("Invalid setting \"" + attr_name + "\" in gnome-pie.conf!"); +                            break; +                    } +                } +                +                Xml.Parser.cleanup(); +                 +            } else { +                warning("Error loading settings: gnome-pie.conf is empty! Using defaults..."); +                error_occrured = true; +            } +             +            delete settingsXML; +             +        } else { +            warning("Error loading settings: gnome-pie.conf not found! Using defaults..."); +            error_occrured = true; +        } +         +        load_themes(theme_name); + +        if (error_occrured) save(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Registers all themes in the user's and in the global +    /// theme directory. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_themes(string current) { +        themes = new Gee.ArrayList<Theme?>(); +        try { +            string name; +             +            // load global themes +            var d = Dir.open(Paths.global_themes); +            while ((name = d.read_name()) != null) { +                var theme = new Theme(Paths.global_themes + "/" + name); +                if (theme != null) +                    themes.add(theme); +            } +             +            // load local themes +            d = Dir.open(Paths.local_themes); +            while ((name = d.read_name()) != null) { +                var theme = new Theme(Paths.local_themes + "/" + name); +                if (theme != null) +                    themes.add(theme); +            } +             +        } catch (Error e) { +            warning (e.message); +        }  +         +        if (themes.size > 0) { +            if (current == "") { +                current = "Unity"; +                warning("No theme specified! Using default..."); +            } +            foreach (var t in themes) { +                if (t.name == current) { +                    theme = t; +                    theme.load_images(); +                    break; +                } +            } +            if (theme == null) { +                theme = themes[0]; +                warning("Theme \"" + current + "\" not found! Using fallback..."); +            } +        } +        else error("No theme found!"); +    } +     +} + +} diff --git a/src/utilities/icon.vala b/src/utilities/icon.vala new file mode 100644 index 0000000..1c8a9f4 --- /dev/null +++ b/src/utilities/icon.vala @@ -0,0 +1,102 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +///  A class representing a square-shaped icon, loaded from the users +/// icon theme. +///////////////////////////////////////////////////////////////////////// + +public class Icon : Image { + +    ///////////////////////////////////////////////////////////////////// +    /// A cache which stores loaded icon. It is cleared when the icon +    /// theme of the user changes. The key is in form <filename>@<size>. +    ///////////////////////////////////////////////////////////////////// + +    private static Gee.HashMap<string, Cairo.ImageSurface?> cache { private get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes the cache. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        clear_cache(); +         +        Gtk.IconTheme.get_default().changed.connect(() => { +            clear_cache(); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Clears the cache. +    ///////////////////////////////////////////////////////////////////// +     +    public static void clear_cache() { +        cache = new Gee.HashMap<string, Cairo.ImageSurface?>(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads an icon from the current icon theme of the user. +    ///////////////////////////////////////////////////////////////////// +     +    public Icon(string icon_name, int size) { +        var cached = this.cache.get("%s@%u".printf(icon_name, size)); +         +        if (cached == null) { +            this.load_file_at_size(this.get_icon_file(icon_name, size), size, size); +            this.cache.set("%s@%u".printf(icon_name, size), this.surface); +        } else { +            this.surface = cached; +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the size of the icon in pixels. Greetings to Liskov. +    ///////////////////////////////////////////////////////////////////// +     +    public int size() { +        return base.width(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the filename for a given system icon. +    ///////////////////////////////////////////////////////////////////// +     +    public static string get_icon_file(string icon_name, int size) { +        string result = ""; +     +        var icon_theme = Gtk.IconTheme.get_default(); +        var file = icon_theme.lookup_icon(icon_name, size, 0); +        if (file != null) result = file.get_filename(); +         +        if (result == "") { +            warning("Icon \"" + icon_name + "\" not found! Using default icon..."); +            icon_name = "application-default-icon"; +            file = icon_theme.lookup_icon(icon_name, size, 0); +            if (file != null) result = file.get_filename(); +        } +         +        if (result == "") +            warning("Icon \"" + icon_name + "\" not found! Will be ugly..."); +             +        return result; +    } +} + +} diff --git a/src/utilities/image.vala b/src/utilities/image.vala new file mode 100644 index 0000000..836e4e2 --- /dev/null +++ b/src/utilities/image.vala @@ -0,0 +1,163 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A class which loads image files. It can load image files in various +/// formats, including jpeg, png and svg.  +///////////////////////////////////////////////////////////////////////// + +public class Image : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// The internally used surface. +    ///////////////////////////////////////////////////////////////////// +     +    public Cairo.ImageSurface surface { public get; protected set; default=null; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates an empty Image. +    ///////////////////////////////////////////////////////////////////// +     +    public Image.empty(int width, int height, Color? color = null) { +        this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); +         +        if (color != null) { +            var ctx = this.context(); +            ctx.set_source_rgb(color.r, color.g, color.b); +            ctx.paint(); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates an image from the the given filename. +    ///////////////////////////////////////////////////////////////////// +     +    public Image.from_file(string filename) { +        this.load_file(filename); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates an image from the the given filename at a given size. +    ///////////////////////////////////////////////////////////////////// +     +    public Image.from_file_at_size(string filename, int width, int height) { +        this.load_file_at_size(filename, width, height); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates an image from the the given Gdk.Pixbuf. +    ///////////////////////////////////////////////////////////////////// +     +    public Image.from_pixbuf(Gdk.Pixbuf pixbuf) { +        this.load_pixbuf(pixbuf); +    } +     +    public Image.capture_screen(int posx, int posy, int width, int height) { +        Gdk.Window root = Gdk.get_default_root_window(); +        Gdk.Pixbuf pixbuf = Gdk.pixbuf_get_from_drawable(null, root, null, posx, posy, 0, 0, width, height); + +        this.load_pixbuf(pixbuf); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads an image from the the given filename. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_file(string filename) { +        try { +            var pixbuf = new Gdk.Pixbuf.from_file(filename); +             +            if (pixbuf != null) { +                this.load_pixbuf(pixbuf); +            } else { +                warning("Failed to load " + filename + "!"); +            } +        } catch (GLib.Error e) { +            message("Error loading image file: %s", e.message); +            this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads an image from the the given filename at a given size. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_file_at_size(string filename, int width, int height) { +        try { +            var pixbuf = new Gdk.Pixbuf.from_file_at_size(filename, width, height); +             +            if (pixbuf != null) { +                this.load_pixbuf(pixbuf); +            } else { +                warning("Failed to load " + filename + "!"); +            } +        } catch (GLib.Error e) { +            message("Error loading image file: %s", e.message); +            this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads an image from the the given Gdk.Pixbuf. +    ///////////////////////////////////////////////////////////////////// +     +    public void load_pixbuf(Gdk.Pixbuf pixbuf) { +        this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, pixbuf.width, pixbuf.height); +     +        var ctx = this.context(); +        Gdk.cairo_set_source_pixbuf(ctx, pixbuf, 1.0, 1.0); +        ctx.paint(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Paints the image onto the given Cairo.Context +    ///////////////////////////////////////////////////////////////////// +     +    public void paint_on(Cairo.Context ctx, double alpha = 1.0) { +        ctx.set_source_surface(this.surface, -0.5*this.width()-1, -0.5*this.height()-1); +        if (alpha >= 1.0) ctx.paint(); +        else              ctx.paint_with_alpha(alpha); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns a Cairo.Context for the Image. +    ///////////////////////////////////////////////////////////////////// +     +    public Cairo.Context context() { +        return new Cairo.Context(this.surface);; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the width of the image in pixels. +    ///////////////////////////////////////////////////////////////////// +     +    public int width() { +        return this.surface.get_width(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the height of the image in pixels. +    ///////////////////////////////////////////////////////////////////// +     +    public int height() { +        return this.surface.get_height(); +    } +} + +} diff --git a/src/utilities/key.vala b/src/utilities/key.vala new file mode 100644 index 0000000..6700b16 --- /dev/null +++ b/src/utilities/key.vala @@ -0,0 +1,139 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A class which represents a key stroke. It can be used to "press"  +/// the associated keys. +///////////////////////////////////////////////////////////////////////// + +public class Key : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Some static members, which are often used by this class. +    /////////////////////////////////////////////////////////////////////     + +    private static X.Display display; + +    private static int shift_code; +    private static int ctrl_code; +    private static int alt_code; +    private static int super_code; + +    ///////////////////////////////////////////////////////////////////// +    /// A human readable form of the Key's accelerator. +    ///////////////////////////////////////////////////////////////////// + +    public string label { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The accelerator of the Key. +    ///////////////////////////////////////////////////////////////////// +     +    public string accelerator { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Keycode and modifiers of this stroke. +    ///////////////////////////////////////////////////////////////////// +     +    private int key_code; +    private Gdk.ModifierType modifiers; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public Key(string stroke) { +        this.accelerator = stroke; +         +        uint keysym; +        Gtk.accelerator_parse(stroke, out keysym, out this.modifiers); +        this.key_code = display.keysym_to_keycode(keysym); +        this.label = Gtk.accelerator_get_label(keysym, this.modifiers); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes static members. +    ///////////////////////////////////////////////////////////////////// +     +    static construct { +        display = new X.Display(); +     +        shift_code = display.keysym_to_keycode(Gdk.keyval_from_name("Shift_L")); +        ctrl_code =  display.keysym_to_keycode(Gdk.keyval_from_name("Control_L")); +        alt_code =   display.keysym_to_keycode(Gdk.keyval_from_name("Alt_L")); +        super_code = display.keysym_to_keycode(Gdk.keyval_from_name("Super_L")); +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Simulates the pressing of the Key . +    ///////////////////////////////////////////////////////////////////// + +    public void press() { +        // store currently pressed modifier keys +        Gdk.ModifierType current_modifiers = get_modifiers(); + +        // release them and press the desired ones +        press_modifiers(current_modifiers, false); +        press_modifiers(this.modifiers, true); +         +        // send events to X +        display.flush(); + +        // press and release the actual key +        X.Test.fake_key_event(this.display, this.key_code, true, 0); +        X.Test.fake_key_event(this.display, this.key_code, false, 0); + +        // release the pressed modifiers and re-press the keys hold down by the user +        press_modifiers(this.modifiers, false); +        press_modifiers(current_modifiers, true); + +        // send events to X +        display.flush(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Helper method returning currently hold down modifier keys. +    ///////////////////////////////////////////////////////////////////// +     +    private Gdk.ModifierType get_modifiers() { +        Gdk.ModifierType modifiers; +        Gdk.Display.get_default().get_pointer(null, null, null, out modifiers); +        return modifiers; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Helper method which 'presses' the desired modifier keys. +    ///////////////////////////////////////////////////////////////////// +     +    private void press_modifiers(Gdk.ModifierType modifiers, bool down) { +        if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) +            X.Test.fake_key_event(this.display, this.ctrl_code, down, 0); + +        if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0) +            X.Test.fake_key_event(this.display, this.shift_code, down, 0); +             +        if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0) +            X.Test.fake_key_event(this.display, this.alt_code, down, 0); + +        if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0) +            X.Test.fake_key_event(this.display, this.super_code, down, 0); +    } +} + +} diff --git a/src/utilities/logger.vala b/src/utilities/logger.vala new file mode 100644 index 0000000..3108ba3 --- /dev/null +++ b/src/utilities/logger.vala @@ -0,0 +1,194 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A static class which beautifies the messages of the default logger. +/// Some of this code is inspired by plank's written by Robert Dyer.  +/// Thanks a lot for this project!  +///////////////////////////////////////////////////////////////////////// + +public class Logger { + +    ///////////////////////////////////////////////////////////////////// +    /// If these are set to false, the according messages are not shown +    ///////////////////////////////////////////////////////////////////// +     +    public static bool display_info { get; set; default = true; } +    public static bool display_debug { get; set; default = true; } +    public static bool display_warning { get; set; default = true; } +    public static bool display_error { get; set; default = true; } +     +    ///////////////////////////////////////////////////////////////////// +    /// If true, a time stamp is shown in each message. +    ///////////////////////////////////////////////////////////////////// +     +    public static bool display_time { get; set; default = true; } +     +    ///////////////////////////////////////////////////////////////////// +    /// If true, the origin of the message is shown. In form file:line +    ///////////////////////////////////////////////////////////////////// +     +    public static bool display_file { get; set; default = false; } +     +    ///////////////////////////////////////////////////////////////////// +    /// A regex, used to format the standard message. +    ///////////////////////////////////////////////////////////////////// +     +    private static Regex regex = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// Possible terminal colors. +    ///////////////////////////////////////////////////////////////////// +     +    private enum Color { +        BLACK, +        RED, +        GREEN, +        YELLOW, +        BLUE, +        PURPLE, +        TURQUOISE, +        WHITE +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates the regex and binds the handler. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        try { +			regex = new Regex("""(.*)\.vala(:\d+): (.*)"""); +		} catch {} +		 +        GLib.Log.set_default_handler(log_func); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Displays an Info message. +    ///////////////////////////////////////////////////////////////////// +     +    private static void info(string message) { +        if (display_info) { +            stdout.printf(set_color(Color.GREEN, false) + "[" + get_time() + "MESSAGE]" + message); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Displays a Debug message. +    ///////////////////////////////////////////////////////////////////// +     +    private static void debug(string message) { +        if (display_debug) { +            stdout.printf(set_color(Color.BLUE, false) + "[" + get_time() + " DEBUG ]" + message); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Displays a Warning message. +    ///////////////////////////////////////////////////////////////////// +     +    private static void warning(string message) { +        if (display_warning) { +            stdout.printf(set_color(Color.YELLOW, false) + "[" + get_time() + "WARNING]" + message); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Displays a Error message. +    ///////////////////////////////////////////////////////////////////// +     +    private static void error(string message) { +        if (display_error) { +            stdout.printf(set_color(Color.RED, false) + "[" + get_time() + " ERROR ]" + message); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Helper method which resets the terminal color. +    ///////////////////////////////////////////////////////////////////// +     +    private static string reset_color() { +		return "\x001b[0m"; +	} +	 +	///////////////////////////////////////////////////////////////////// +	/// Helper method which sets the terminal color. +	///////////////////////////////////////////////////////////////////// +	 +	private static string set_color(Color color, bool bold) { +	    if (bold) return "\x001b[1;%dm".printf((int)color + 30); +	    else      return "\x001b[0;%dm".printf((int)color + 30); +	} +	 +	///////////////////////////////////////////////////////////////////// +	/// Returns the current time in hh:mm:ss:mmmmmm +	///////////////////////////////////////////////////////////////////// +	 +	private static string get_time() { +	    if (display_time) {   +            var now = new DateTime.now_local (); +		    return "%.2d:%.2d:%.2d:%.6d ".printf (now.get_hour (), now.get_minute (), now.get_second (), now.get_microsecond ()); +		} else { +		    return ""; +		} +	} +	 +	///////////////////////////////////////////////////////////////////// +    /// Helper method to format the message. +    ///////////////////////////////////////////////////////////////////// +	 +	private static string create_message(string message) { +	    if (display_file && regex != null && regex.match(message)) { +			var parts = regex.split(message); +			return " [%s%s]%s %s\n".printf(parts[1], parts[2], reset_color(), parts[3]); +		} else if (regex != null && regex.match(message)) { +		    var parts = regex.split(message); +			return "%s %s\n".printf(reset_color(), parts[3]); +		} else { +		    return reset_color() + " " + message + "\n"; +		} +	} +	 +	///////////////////////////////////////////////////////////////////// +	/// The handler function. +	///////////////////////////////////////////////////////////////////// +	 +	private static void log_func(string? d, LogLevelFlags flags, string message) { +			 +		switch (flags) { +		    case LogLevelFlags.LEVEL_ERROR: +		    case LogLevelFlags.LEVEL_CRITICAL: +			    error(create_message(message)); +			    break; +		    case LogLevelFlags.LEVEL_INFO: +		    case LogLevelFlags.LEVEL_MESSAGE: +			    info(create_message(message)); +			    break; +		    case LogLevelFlags.LEVEL_DEBUG: +			    debug(create_message(message)); +			    break; +		    case LogLevelFlags.LEVEL_WARNING: +		    default: +			    warning(create_message(message)); +			    break; +		} +	} +} + +} diff --git a/src/utilities/paths.vala b/src/utilities/paths.vala new file mode 100644 index 0000000..1c42176 --- /dev/null +++ b/src/utilities/paths.vala @@ -0,0 +1,211 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A static class which stores all relevant paths used by Gnome-Pie. +/// These depend upon the location from which the program was launched. +///////////////////////////////////////////////////////////////////////// + +public class Paths : GLib.Object { +     +    ///////////////////////////////////////////////////////////////////// +    /// The file settings file, +    /// usually ~/.config/gnome-pie/gnome-pie.conf. +    ///////////////////////////////////////////////////////////////////// +     +    public static string settings { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The file pie configuration file +    /// usually ~/.config/gnome-pie/pies.conf. +    ///////////////////////////////////////////////////////////////////// +     +    public static string pie_config { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The directory containing themes installed by the user +    /// usually ~/.config/gnome-pie/themes. +    ///////////////////////////////////////////////////////////////////// +     +    public static string local_themes { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The directory containing pre-installed themes +    /// usually /usr/share/gnome-pie/themes. +    ///////////////////////////////////////////////////////////////////// +     +    public static string global_themes { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The directory containing locale files +    /// usually /usr/share/locale. +    ///////////////////////////////////////////////////////////////////// +     +    public static string locales { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The autostart file of gnome-pie_config +    /// usually ~/.config/autostart/gnome-pie.desktop. +    ///////////////////////////////////////////////////////////////////// +     +    public static string autostart { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The path where all pie-launchers are stored +    /// usually ~/.config/gnome-pie/launchers. +    ///////////////////////////////////////////////////////////////////// +     +    public static string launchers { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes all values above. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +     +        // append resources to icon search path to icon theme, if neccasary +        try { +            var icon_dir = GLib.File.new_for_path(GLib.Path.get_dirname( +                        GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources"); +                         +            if (icon_dir.query_exists()) { +                string path = icon_dir.get_path(); +                Gtk.IconTheme.get_default().append_search_path(path); +            } +             +            Gtk.IconTheme.get_default().append_search_path("/usr/share/pixmaps/"); +             +        } catch (GLib.FileError e) { +            warning("Failed to get path of executable!"); +        } +     +        // get global paths +        var default_dir = GLib.File.new_for_path("/usr/share/gnome-pie/"); +        if(!default_dir.query_exists()) { +            default_dir = GLib.File.new_for_path("/usr/local/share/gnome-pie/"); +             +            if(!default_dir.query_exists()) { +                try { +                    default_dir = GLib.File.new_for_path(GLib.Path.get_dirname( +                        GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources"); +                } catch (GLib.FileError e) { +                    warning("Failed to get path of executable!"); +                } +            } +        } +         +        global_themes = default_dir.get_path() + "/themes"; +         +        // get locales path +        var locale_dir = GLib.File.new_for_path("/usr/share/locale/de/LC_MESSAGES/gnomepie.mo"); +        if(locale_dir.query_exists()) { +            locale_dir = GLib.File.new_for_path("/usr/share/locale"); +        } else { +            locale_dir = GLib.File.new_for_path("/usr/local/share/locale/de/LC_MESSAGES/gnomepie.mo"); +            if(locale_dir.query_exists()) { +                locale_dir = GLib.File.new_for_path("/usr/local/share/locale"); +            } else { +             +                try { +                    locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname( +                        GLib.FileUtils.read_link("/proc/self/exe"))).get_child( +                        "resources/locale/de/LC_MESSAGES/gnomepie.mo"); +                } catch (GLib.FileError e) { +                    warning("Failed to get path of executable!"); +                } +                 +                if(locale_dir.query_exists()) { +                    try { +                        locale_dir = GLib.File.new_for_path(GLib.Path.get_dirname( +                            GLib.FileUtils.read_link("/proc/self/exe"))).get_child("resources/locale"); +                    } catch (GLib.FileError e) { +                        warning("Failed to get path of executable!"); +                    } +                } +            } +        } +         +        locales = locale_dir.get_path(); +     +        // get local paths +        var config_dir = GLib.File.new_for_path( +            GLib.Environment.get_user_config_dir()).get_child("gnome-pie"); + +        // create config_dir if neccasary +        if(!config_dir.query_exists()) { +            try { +                config_dir.make_directory(); +            } catch (GLib.Error e) { +                error(e.message); +            } +        } +         +        // create local themes directory if neccasary +        var themes_dir = config_dir.get_child("themes"); +        if(!themes_dir.query_exists()) { +            try { +                themes_dir.make_directory(); +            } catch (GLib.Error e) { +                error(e.message); +            } +        } +         +        local_themes = themes_dir.get_path(); +         +        // create launchers directory if neccasary +        var launchers_dir = config_dir.get_child("launchers"); +        if(!launchers_dir.query_exists()) { +            try { +                launchers_dir.make_directory(); +            } catch (GLib.Error e) { +                error(e.message); +            } +        } +         +        launchers = launchers_dir.get_path(); +         +        // check for config file +        var config_file = config_dir.get_child("pies.conf"); +         +        pie_config = config_file.get_path(); +        settings = config_dir.get_path() + "/gnome-pie.conf"; +         +        // autostart file name +        autostart = GLib.Path.build_filename(GLib.Environment.get_user_config_dir(),  +                                             "autostart", "gnome-pie.desktop", null); +         +        // print results +        if (!GLib.File.new_for_path(pie_config).query_exists())                                                   +            warning("Failed to find pie configuration file \"pies.conf\"! (This should only happen when Gnome-Pie is started for the first time...)"); +             +        if (!GLib.File.new_for_path(settings).query_exists())                                                   +            warning("Failed to find settings file \"gnome-pie.conf\"!"); +             +        if (!GLib.File.new_for_path(local_themes).query_exists())                                                   +            warning("Failed to find local themes directory!"); +             +        if (!GLib.File.new_for_path(launchers).query_exists())                                                   +            warning("Failed to find launchers directory!"); +             +        if (!GLib.File.new_for_path(global_themes).query_exists())  +            warning("Failed to find global themes directory!");      +    }     +} + +} diff --git a/src/utilities/renderedText.vala b/src/utilities/renderedText.vala new file mode 100644 index 0000000..924742a --- /dev/null +++ b/src/utilities/renderedText.vala @@ -0,0 +1,110 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +///  A class representing string, rendered on an Image. +///////////////////////////////////////////////////////////////////////// + +public class RenderedText : Image { + +    ///////////////////////////////////////////////////////////////////// +    /// A cache which stores images. It is cleared when the theme of +    /// Gnome-Pie changes. +    /// The key is in form <string>@<width>x<height>:<font>. +    ///////////////////////////////////////////////////////////////////// + +    private static Gee.HashMap<string, Cairo.ImageSurface?> cache { private get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes the cache. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        clear_cache(); +         +        Config.global.notify["theme"].connect(() => { +            clear_cache(); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Clears the cache. +    ///////////////////////////////////////////////////////////////////// +     +    static void clear_cache() { +        cache = new Gee.HashMap<string, Cairo.ImageSurface?>(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new image representation of a string. +    ///////////////////////////////////////////////////////////////////// +     +    public RenderedText(string text, int width, int height, string font) { +        var cached = this.cache.get("%s@%ux%u:%s".printf(text, width, height, font)); +         +        if (cached == null) { +            this.render_text(text, width, height, font); +            this.cache.set("%s@%ux%u:%s".printf(text, width, height, font), this.surface); +        } else { +            this.surface = cached; +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Creates a new transparent image, with text written onto. +    ///////////////////////////////////////////////////////////////////// +     +    public void render_text(string text, int width, int height, string font) { +        this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); + +        var ctx = this.context(); +         +        // set the color as specified in the current theme +        Color color = Config.global.theme.caption_color; +        ctx.set_source_rgb(color.r, color.g, color.g); +         +        var layout = Pango.cairo_create_layout(ctx);         +        layout.set_width(Pango.units_from_double(width)); +         +        var font_description = Pango.FontDescription.from_string(font); +        font_description.set_size((int)(font_description.get_size() * Config.global.global_scale)); +         +        layout.set_font_description(font_description); +        layout.set_text(text, -1); +         +        // add newlines at the end of each line, in order to allow ellipsizing +        string broken_string = ""; +        foreach (var line in layout.get_lines()) { +            broken_string = broken_string.concat(text.substring(line.start_index, line.length), "\n"); +        } +        layout.set_text(broken_string, broken_string.length-1); +         +        layout.set_ellipsize(Pango.EllipsizeMode.END); +        layout.set_alignment(Pango.Alignment.CENTER); +         +        Pango.Rectangle extents; +        layout.get_pixel_extents(null, out extents); +        ctx.move_to(0, (int)(0.5*(height - extents.height))); +         +        Pango.cairo_update_layout(ctx, layout); +        Pango.cairo_show_layout(ctx, layout); +    } +} + +} diff --git a/src/utilities/themedIcon.vala b/src/utilities/themedIcon.vala new file mode 100644 index 0000000..29ae380 --- /dev/null +++ b/src/utilities/themedIcon.vala @@ -0,0 +1,161 @@ +/*  +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program.  If not, see <http://www.gnu.org/licenses/>.  +*/ + +namespace GnomePie { + +/////////////////////////////////////////////////////////////////////////     +/// A class representing a square-shaped icon, themed according to the +/// current theme of Gnome-Pie. +///////////////////////////////////////////////////////////////////////// + +public class ThemedIcon : Image { + +    ///////////////////////////////////////////////////////////////////// +    /// A cache which stores loaded icon. The key is the icon name. When +    /// the users icon theme or the theme of Gnome-Pie changes, these +    /// cahces are cleared. +    ///////////////////////////////////////////////////////////////////// + +    private static Gee.HashMap<string, Cairo.ImageSurface?> active_cache { private get; private set; } +    private static Gee.HashMap<string, Cairo.ImageSurface?> inactive_cache { private get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Initializes the caches. +    ///////////////////////////////////////////////////////////////////// +     +    public static void init() { +        clear_cache(); +         +        Config.global.notify["theme"].connect(() => { +            clear_cache(); +        }); +         +        Gtk.IconTheme.get_default().changed.connect(() => { +            clear_cache(); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Clears the cache. +    ///////////////////////////////////////////////////////////////////// +     +    public static void clear_cache() { +        active_cache = new Gee.HashMap<string, Cairo.ImageSurface?>(); +        inactive_cache = new Gee.HashMap<string, Cairo.ImageSurface?>(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Paint a slice icon according to the current theme. +    ///////////////////////////////////////////////////////////////////// +     +    public ThemedIcon(string icon_name, bool active) { +        // check cache +        var current_cache = active ? active_cache : inactive_cache; +        var cached = current_cache.get(icon_name); +         +        if (cached != null) { +            this.surface = cached; +            return; +        } +     +        // get layers for the desired slice type +        var layers = active ? Config.global.theme.active_slice_layers : Config.global.theme.inactive_slice_layers; +         +        // get max size +        int size = 0; +        foreach (var layer in layers) { +            if (layer.image.width() > size) size = layer.image.width(); +        } +         +        this.surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, size, size); +         +        // get size of icon layer +        int icon_size = size; +        foreach (var layer in layers) { +            if (layer.is_icon) icon_size = layer.image.width(); +        } +     +        Image icon; +        if (icon_name.contains("/")) +            icon = new Image.from_file_at_size(icon_name, icon_size, icon_size); +        else +            icon = new Icon(icon_name, icon_size); +         +        var color = new Color.from_icon(icon); +        var ctx = this.context(); +         +        ctx.translate(size/2, size/2); +        ctx.set_operator(Cairo.Operator.OVER); +         +        // now render all layers on top of each other +        foreach (var layer in layers) { +         +            if (layer.colorize) { +                ctx.push_group(); +            } +                     +            if (layer.is_icon) { +             +                ctx.push_group(); +                 +                layer.image.paint_on(ctx); +                 +                ctx.set_operator(Cairo.Operator.IN); +                 +                if (layer.image.width() != icon_size) { +                    if (icon_name.contains("/")) +                        icon = new Image.from_file_at_size(icon_name, layer.image.width(), layer.image.width()); +                    else +                        icon = new Icon(icon_name,layer.image.width()); +                } +                 +                icon.paint_on(ctx); + +                ctx.pop_group_to_source(); +                ctx.paint(); +                ctx.set_operator(Cairo.Operator.OVER); +                 +            } else { +                layer.image.paint_on(ctx); +            } +             +            // colorize the whole layer if neccasary +            if (layer.colorize) { +                ctx.set_operator(Cairo.Operator.ATOP); +                ctx.set_source_rgb(color.r, color.g, color.b); +                ctx.paint(); +                 +                ctx.set_operator(Cairo.Operator.OVER); +                ctx.pop_group_to_source(); +                ctx.paint(); +            } +        } +         +        // store the surface in cache +        current_cache.set(icon_name, this.surface); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the size of the icon in pixels. Greetings to Liskov. +    ///////////////////////////////////////////////////////////////////// +     +    public int size() { +        return base.width(); +    } +} + +} | 
