diff options
Diffstat (limited to 'src')
30 files changed, 1589 insertions, 216 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3b8ed3..23b9474 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,3 +80,11 @@ install(  		${CMAKE_INSTALL_PREFIX}/share/applications  ) +# install manpage +install( +	FILES +		${CMAKE_SOURCE_DIR}/resources/gnome-pie.1 +	DESTINATION +		${CMAKE_INSTALL_PREFIX}/share/man/man1 +) + diff --git a/src/actionGroups/bookmarkGroup.vala b/src/actionGroups/bookmarkGroup.vala index f4ba66e..389b14a 100644 --- a/src/actionGroups/bookmarkGroup.vala +++ b/src/actionGroups/bookmarkGroup.vala @@ -110,7 +110,7 @@ public class BookmarkGroup : ActionGroup {          }          // add trash -        this.add_action(ActionRegistry.new_for_uri("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))); diff --git a/src/actionGroups/clipboardGroup.vala b/src/actionGroups/clipboardGroup.vala index 0e95b65..cd1da36 100644 --- a/src/actionGroups/clipboardGroup.vala +++ b/src/actionGroups/clipboardGroup.vala @@ -19,6 +19,7 @@ namespace GnomePie {  /////////////////////////////////////////////////////////////////////////      /// This Group keeps a history of the last used Clipboard entries. +/// Experimental. Not enabled.  /////////////////////////////////////////////////////////////////////////  public class ClipboardGroup : ActionGroup { diff --git a/src/actionGroups/groupRegistry.vala b/src/actionGroups/groupRegistry.vala index 94169d5..a9f8d06 100644 --- a/src/actionGroups/groupRegistry.vala +++ b/src/actionGroups/groupRegistry.vala @@ -38,7 +38,6 @@ public class GroupRegistry : GLib.Object {      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.      ///////////////////////////////////////////////////////////////////// @@ -78,6 +77,12 @@ public class GroupRegistry : GLib.Object {          icons.set(typeof(SessionGroup), icon);          settings_names.set(typeof(SessionGroup), settings_name); +        WindowListGroup.register(out name, out icon, out settings_name); +        types.add(typeof(WindowListGroup)); +        names.set(typeof(WindowListGroup), name); +        icons.set(typeof(WindowListGroup), icon); +        settings_names.set(typeof(WindowListGroup), settings_name); +          //        ClipboardGroup.register(out name, out icon, out settings_name);  //        types.add(typeof(ClipboardGroup));  //        names.set(typeof(ClipboardGroup), name); diff --git a/src/actionGroups/sessionGroup.vala b/src/actionGroups/sessionGroup.vala index 9fcab1d..0b3f249 100644 --- a/src/actionGroups/sessionGroup.vala +++ b/src/actionGroups/sessionGroup.vala @@ -49,6 +49,11 @@ public class SessionGroup : ActionGroup {      /////////////////////////////////////////////////////////////////////      construct { +//    	string iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.gnome.SessionManager", "/org/gnome/SessionManager"); +//		iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer"); +//    	iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.kde.ksmserver", "/KSMServer"); +//    	iface = GLib.Bus.get_proxy_sync(GLib.BusType.SESSION, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager"); +              this.add_action(new AppAction(_("Shutdown"), "gnome-shutdown",               "dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestShutdown")); @@ -60,9 +65,9 @@ public class SessionGroup : ActionGroup {      }      // 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.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 +    // dbus-send --print-reply --dest="org.freedesktop.ConsoleKit" /org/freedesktop/ConsoleKit/Manager org.freedesktop.ConsoleKit.Manager.Stop  }  } diff --git a/src/actionGroups/windowListGroup.vala b/src/actionGroups/windowListGroup.vala new file mode 100644 index 0000000..b12f188 --- /dev/null +++ b/src/actionGroups/windowListGroup.vala @@ -0,0 +1,142 @@ +/*  +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 displays a list of all running application windows. +///////////////////////////////////////////////////////////////////// + +public class WindowListGroup : ActionGroup { +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to register this type of ActionGroup. It sets the display +    /// name for this ActionGroup, it's icon name and the string used in  +    /// the pies.conf file for this kind of ActionGroups. +    ///////////////////////////////////////////////////////////////////// +     +    public static void register(out string name, out string icon, out string settings_name) { +        name = _("Window List"); +        icon = "window-manager"; +        settings_name = "window_list"; +    } + +    ///////////////////////////////////////////////////////////////////// +    /// Two members needed to avoid useless, frequent changes of the  +    /// stored Actions. +    ///////////////////////////////////////////////////////////////////// + +    private bool changing = false; +    private bool changed_again = false; +     +    private Wnck.Screen screen; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +     +    public WindowListGroup(string parent_id) { +        GLib.Object(parent_id : parent_id); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads all windows. +    ///////////////////////////////////////////////////////////////////// +     +    construct { +        this.screen = Wnck.Screen.get_default(); +     +        this.screen.window_opened.connect(reload); +        this.screen.window_closed.connect(reload); +         +        this.load(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads all currently opened windows and creates actions for them. +    ///////////////////////////////////////////////////////////////////// +     +    private void load() { +        unowned GLib.List<Wnck.Window?> windows = this.screen.get_windows(); +         +        var matcher = Bamf.Matcher.get_default(); + +        foreach (var window in windows) { +            if (window.get_window_type() == Wnck.WindowType.NORMAL +            	&& !window.is_skip_pager() && !window.is_skip_tasklist()) { +                var application = window.get_application(); +                var bamf_app = matcher.get_application_for_xid((uint32)window.get_xid()); +                 +                string name = window.get_name(); +                 +                if (name.length > 30) +                    name = name.substring(0, 30) + "..."; +                 +                var action = new SigAction( +                    name, +                    (bamf_app == null) ? application.get_icon_name().down() : bamf_app.get_icon(), +                    "%lu".printf(window.get_xid())  +                ); +                action.activated.connect(() => { +                    Wnck.Screen.get_default().force_update(); +                 +                    var xid = (X.Window)uint64.parse(action.real_command); +                    var win = Wnck.Window.get(xid); +                    var time = Gtk.get_current_event_time();  +                     +                    if (win.get_workspace() != null  +                        && win.get_workspace() != win.get_screen().get_active_workspace())  +				        win.get_workspace().activate(time); +			 +			        if (win.is_minimized())  +				        win.unminimize(time); +			 +			        win.activate_transient(time); +                }); +                this.add_action(action); +            } +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Reloads all running applications. +    ///////////////////////////////////////////////////////////////////// +     +    private void reload() { +        // avoid too frequent changes... +        if (!this.changing) { +            this.changing = true; +            Timeout.add(500, () => { +                if (this.changed_again) { +                    this.changed_again = false; +                    return true; +                } + +                // reload +                this.delete_all(); +                this.load(); +                 +                this.changing = false; +                return false; +            }); +        } else { +            this.changed_again = true; +        }     +    } +} + +} diff --git a/src/actions/keyAction.vala b/src/actions/keyAction.vala index 0f6d094..ddeebb5 100644 --- a/src/actions/keyAction.vala +++ b/src/actions/keyAction.vala @@ -30,7 +30,7 @@ public class KeyAction : Action {      /////////////////////////////////////////////////////////////////////      public static void register(out string name, out bool icon_name_editable, out string settings_name) { -        name = _("Press key stroke"); +        name = _("Press hotkey");          icon_name_editable = true;          settings_name = "key";      }    diff --git a/src/deamon.vala b/src/deamon.vala index af232eb..0cdb4c2 100644 --- a/src/deamon.vala +++ b/src/deamon.vala @@ -16,13 +16,8 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */  ///////////////////////////////////////////////////////////////////// -/// TODO-List: -/// IconSelectWindow +/// TODO-List (need comments):  /// PieList -/// PieWindow -/// CenterRenderer -/// SliceRenderer -/// PieRenderer  /////////////////////////////////////////////////////////////////////  namespace GnomePie { @@ -108,12 +103,14 @@ public class Deamon : GLib.Object {          if (app.is_running) {              // inform the running instance of the pie to be opened              if (open_pie != null) { +            	message("Gnome-Pie is already running. Sending request to open pie " + open_pie + ".");                  var data = new Unique.MessageData();                  data.set_text(open_pie, open_pie.length);                  app.send_message(Unique.Command.ACTIVATE, data);                  return;              }  +            message("Gnome-Pie is already running. Sending request to open config menu.");              app.send_message(Unique.Command.ACTIVATE, null);              return;          } diff --git a/src/gui/about.vala b/src/gui/about.vala index 1ace9cb..ce4256e 100644 --- a/src/gui/about.vala +++ b/src/gui/about.vala @@ -24,18 +24,44 @@ namespace GnomePie {  public class GnomePieAboutDialog: Gtk.AboutDialog {      public GnomePieAboutDialog () { -        string[] devs = {"Simon Schneegans <code@simonschneegans.de>",  -                         "Francesco Piccinno"}; -        string[] artists = {"Simon Schneegans <code@simonschneegans.de>"}; +    	string[] devs = { +			"Simon Schneegans <code@simonschneegans.de>",  +            "Francesco Piccinno <stack.box@gmail.com>" +        }; +        string[] artists = { +			"Simon Schneegans <code@simonschneegans.de>" +        }; +    	string[] translators = { +    		"DE\t\t Simon Schneegans <code@simonschneegans.de>", +    		"IT\t\t Riccardo Traverso <gr3yfox.fw@gmail.com>", +    		"PT-BR\t Magnun Leno <magnun@codecommunity.org>", +    		"EN\t\t Simon Schneegans <code@simonschneegans.de>", +    		"KO\t\t Kim Boram <Boramism@gmail.com>" +    	}; +    	 +    	// sort translators +    	GLib.List<string> translator_list = new GLib.List<string>(); +    	foreach (var translator in translators) +    		translator_list.append(translator); +    		 +    	translator_list.sort((a, b) => { +    		return a.ascii_casecmp(b); +    	}); +    	 +    	string translator_string = ""; +    	foreach (var translator in translator_list) +	   		translator_string += translator + "\n"; +                  GLib.Object (              artists : artists,              authors : devs, +            translator_credits : translator_string,              copyright : "Copyright (C) 2011 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" +            version: "0.3.1"          );      }  } diff --git a/src/gui/cellRendererTrigger.vala b/src/gui/cellRendererTrigger.vala new file mode 100644 index 0000000..a825c32 --- /dev/null +++ b/src/gui/cellRendererTrigger.vala @@ -0,0 +1,84 @@ +/*  +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 opens a TriggerSelectWindow. +///////////////////////////////////////////////////////////////////////// + +public class CellRendererTrigger : Gtk.CellRendererText { +     +    ///////////////////////////////////////////////////////////////////// +    /// This signal is emitted when the user selects another trigger. +    ///////////////////////////////////////////////////////////////////// +     +    public signal void on_select(string path, Trigger trigger); +     +    ///////////////////////////////////////////////////////////////////// +    /// The trigger which can be set with this window. +    ///////////////////////////////////////////////////////////////////// +     +    public string trigger { get; set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The IconSelectWindow which is shown on click. +    ///////////////////////////////////////////////////////////////////// + +    private TriggerSelectWindow select_window = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// A helper variable, needed to emit the current path. +    ///////////////////////////////////////////////////////////////////// +     +    private string current_path = ""; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new CellRendererIcon. +    ///////////////////////////////////////////////////////////////////// +     +    public CellRendererTrigger() { +        this.select_window = new TriggerSelectWindow();   +     +        this.select_window.on_select.connect((trigger) => { +            this.trigger = trigger.name; +            this.on_select(current_path, trigger); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Open the TriggerSelectWindow 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.current_path = path; +         +        this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); +        this.select_window.set_modal(true); +        this.select_window.set_trigger(new Trigger.from_string(this.trigger)); +                   +        this.select_window.show(); +             +        return base.start_editing(event, widget, path, bg_area, cell_area, flags); +    } +} + +} + diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala index 2274ec5..01a4a40 100644 --- a/src/gui/iconSelectWindow.vala +++ b/src/gui/iconSelectWindow.vala @@ -19,45 +19,17 @@ 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. +/// theme. Custom icons/images can be selested as well. Loading of icons +/// happens in an extra thread and a spinner is displayed while loading.  /////////////////////////////////////////////////////////////////////////  public class IconSelectWindow : 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;} +    ///////////////////////////////////////////////////////////////////// +    /// The currently selected icon. If set, this icon gets focused. +    ///////////////////////////////////////////////////////////////////// -    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"; +    private string _active_icon = "application-default-icon";      public string active_icon {          get { @@ -85,7 +57,104 @@ public class IconSelectWindow : Gtk.Dialog {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// This signal gets emitted when the user selects a new icon. +    ///////////////////////////////////////////////////////////////////// +          public signal void on_select(string icon_name); + +    ///////////////////////////////////////////////////////////////////// +    /// The ListStore storing all theme-icons. +    ///////////////////////////////////////////////////////////////////// + +    private static Gtk.ListStore icon_list = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// True, if the icon theme is currently reloaded. +    ///////////////////////////////////////////////////////////////////// +     +    private static bool loading = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// If set to true, the icon list will be reloaded next time the +    /// window opens. +    ///////////////////////////////////////////////////////////////////// +     +    private static bool need_reload = true; +     +    ///////////////////////////////////////////////////////////////////// +    /// Icons of these contexts won't appear in the list. +    ///////////////////////////////////////////////////////////////////// +     +    private const string disabled_contexts = "Animations, FileSystems"; +     +    ///////////////////////////////////////////////////////////////////// +    /// The list of icons, filtered according to the chosen type and +    /// filter string. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.TreeModelFilter icon_list_filtered = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// The Gtk widget displaying the icons. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.IconView icon_view = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// This spinner is displayed when the icons are loaded. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.Spinner spinner = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// A Gtk widget used for custom icon/image selection. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.FileChooserWidget file_chooser = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// The notebook containing the different icon choice possibilities: +    /// from the theme or custom. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.Notebook tabs = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// A little structure containing data for one icon in the icon_view. +    ///////////////////////////////////////////////////////////////////// + +    private class ListEntry { +        public string name; +        public IconContext context; +        public Gdk.Pixbuf pixbuf; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// This queue is used for icon loading. A loading thread pushes +    /// icons into it --- the main thread updates the icon_view +    /// accordingly. +    ///////////////////////////////////////////////////////////////////// +     +    private GLib.AsyncQueue<ListEntry?> load_queue; +     +    ///////////////////////////////////////////////////////////////////// +    /// Possible icon types. +    ///////////////////////////////////////////////////////////////////// +     +    private enum IconContext { +        ALL, +        APPS, +        ACTIONS, +        PLACES, +        FILES, +        EMOTES, +        OTHER +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new IconSelectWindow. +    /////////////////////////////////////////////////////////////////////      public IconSelectWindow() {          this.title = _("Choose an Icon"); @@ -94,15 +163,22 @@ public class IconSelectWindow : Gtk.Dialog {          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 = new Gtk.ListStore(3, typeof(string),      // icon name +                                                  typeof(IconContext), // icon type +                                                  typeof(Gdk.Pixbuf)); // the icon itself +                                                   +            // disable sorting until all icons are loaded +            // else loading becomes horribly slow                                                  this.icon_list.set_default_sort_func(() => {return 0;}); +            // reload if icon theme changes              Gtk.IconTheme.get_default().changed.connect(() => {                  if (this.visible) load_icons();                  else              need_reload = true;              });          }  +        // make the icon_view filterable          this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null);          var container = new Gtk.VBox(false, 12); @@ -111,9 +187,11 @@ public class IconSelectWindow : Gtk.Dialog {              // tab container              this.tabs = new Gtk.Notebook(); +                // icon theme tab                  var theme_tab = new Gtk.VBox(false, 12);                      theme_tab.set_border_width(12); +                    // type chooser combo-box                      var context_combo = new Gtk.ComboBox.text();                          context_combo.append_text(_("All icons"));                          context_combo.append_text(_("Applications")); @@ -130,13 +208,16 @@ public class IconSelectWindow : Gtk.Dialog {                          });                          theme_tab.pack_start(context_combo, false, false); -                         +                     +                    // string filter entry                      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); +                        // only display items which have the selected type +                        // and whose name contains the text entered in the entry                          this.icon_list_filtered.set_visible_func((model, iter) => {                              string name = "";                              IconContext context = IconContext.ALL; @@ -150,33 +231,39 @@ public class IconSelectWindow : Gtk.Dialog {                                      name.down().contains(filter.text.down());                          }); +                        // clear when the users clicks on the "clear" icon                          filter.icon_release.connect((pos, event) => {                              if (pos == Gtk.EntryIconPosition.SECONDARY)                                  filter.text = "";                          }); +                        // refilter on input                          filter.notify["text"].connect(() => {                              this.icon_list_filtered.refilter();                          }); +                    // container for the icon_view                      var scroll = new Gtk.ScrolledWindow (null, null);                          scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);                          scroll.set_shadow_type (Gtk.ShadowType.IN); +                        // displays the filtered icons                          this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered);                              this.icon_view.item_width = 32;                              this.icon_view.item_padding = 3;                              this.icon_view.pixbuf_column = 2;                              this.icon_view.tooltip_column = 0; +                            // set _active_icon if selection changes                              this.icon_view.selection_changed.connect(() => {                                  foreach (var path in this.icon_view.get_selected_items()) {                                      Gtk.TreeIter iter;                                      this.icon_list_filtered.get_iter(out iter, path); -                                    icon_list_filtered.get(iter, 0, out this._active_icon); +                                    this.icon_list_filtered.get(iter, 0, out this._active_icon);                                  }                              }); +                            // hide this window when the user activates an icon                              this.icon_view.item_activated.connect((path) => {                                  Gtk.TreeIter iter;                                  this.icon_list_filtered.get_iter(out iter, path); @@ -191,20 +278,27 @@ public class IconSelectWindow : Gtk.Dialog {                      tabs.append_page(theme_tab, new Gtk.Label(_("Icon Theme"))); +                // tab containing the possibility to choose a custom icon                  var custom_tab = new Gtk.VBox(false, 6);                      custom_tab.border_width = 12; +                    // file chooser widget                      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); +                        // set _active_icon if the user selected a file                          file_chooser.selection_changed.connect(() => { -                            if (file_chooser.get_filename() != null && GLib.FileUtils.test(file_chooser.get_filename(), GLib.FileTest.IS_REGULAR)) +                            if (file_chooser.get_filename() != null &&  +                                GLib.FileUtils.test(file_chooser.get_filename(),  +                                                    GLib.FileTest.IS_REGULAR)) +                                                                  this._active_icon = file_chooser.get_filename();                          }); +                        // hide this window when the user activates a file                          file_chooser.file_activated.connect(() => {                              this._active_icon = file_chooser.get_filename();                              this.on_select(this._active_icon); @@ -218,7 +312,9 @@ public class IconSelectWindow : Gtk.Dialog {              container.pack_start(tabs, true, true); -            // button box  +            // button box --- this dialog has a custom button box at the bottom because it +            // should have a spinner there. Sadly that's impossible with the "normal" +            // action_area of Gtk.Dialog's               var bottom_box = new Gtk.HBox(false, 0);                  var bbox = new Gtk.HButtonBox(); @@ -255,8 +351,16 @@ public class IconSelectWindow : Gtk.Dialog {          this.set_focus(this.icon_view);      } +    ///////////////////////////////////////////////////////////////////// +    /// Hide the "normal" action_area when this window is shown. Reload +    /// all icons if necessary. +    ///////////////////////////////////////////////////////////////////// +          public override void show() {          base.show(); +         +        // hide the "normal" action_area --- this Dialog has a custom set of +        // buttons containg the spinner          this.action_area.hide();          if (this.need_reload) { @@ -265,23 +369,32 @@ public class IconSelectWindow : Gtk.Dialog {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// (Re)load all icons. +    ///////////////////////////////////////////////////////////////////// +          private void load_icons() { +        // only if it's not loading currently          if (!this.loading) {              this.loading = true;              this.icon_list.clear(); +            // display the spinner              if (spinner != null)                  this.spinner.visible = true; +            // disable sorting of the icon_view - else it's horribly slow              this.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING);              try { +                // start loading in another thread                  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!");              } +            // insert loaded icons every 200 ms              Timeout.add(200, () => {                  while (this.load_queue.length() > 0) {                      var new_entry = this.load_queue.pop(); @@ -292,6 +405,7 @@ public class IconSelectWindow : Gtk.Dialog {                                                  2, new_entry.pixbuf);                  } +                // enable sorting of the icon_view if loading finished                  if (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING);                    return loading; @@ -299,6 +413,11 @@ public class IconSelectWindow : Gtk.Dialog {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Loads all icons of an icon theme and pushes them into the +    /// load_queue. +    ///////////////////////////////////////////////////////////////////// +          private void* load_thread() {          var icon_theme = Gtk.IconTheme.get_default(); @@ -321,6 +440,7 @@ public class IconSelectWindow : Gtk.Dialog {                      }                      try {       +                        // create a new entry for the queue                          var new_entry = new ListEntry();                          new_entry.name = icon;                          new_entry.context = icon_context; @@ -337,8 +457,10 @@ public class IconSelectWindow : Gtk.Dialog {              }          } +        // finished loading          this.loading = false; +        // hide the spinner          if (spinner != null)              spinner.visible = this.loading; diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala index df6135a..46970d5 100644 --- a/src/gui/pieList.vala +++ b/src/gui/pieList.vala @@ -34,7 +34,7 @@ class PieList : Gtk.TreeView {      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,  +                          URI_VISIBLE, TRIGGER_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} @@ -91,7 +91,7 @@ class PieList : Gtk.TreeView {                   ActionPos.ICON_NAME_EDITABLE, false);           // main data model -        this.data = new Gtk.TreeStore(24, typeof(bool),       // is quickaction +        this.data = new Gtk.TreeStore(25, typeof(bool),       // is quickaction                                            typeof(string),     // icon                                            typeof(string),     // name                                               typeof(string),     // slice: type label, pie: "ID: %id" @@ -110,6 +110,7 @@ class PieList : Gtk.TreeView {                                            typeof(bool),       // key renderer visible                                            typeof(bool),       // pie renderer visible                                            typeof(bool),       // uri renderer visible +                                          typeof(bool),       // trigger renderer visible                                            typeof(string),     // display command group                                            typeof(string),     // display command app @@ -198,6 +199,28 @@ class PieList : Gtk.TreeView {          var command_column = new Gtk.TreeViewColumn();              command_column.title = _("Command");              command_column.resizable = true; +            command_column.expand = true; +             +            // trigger  +            var command_renderer_trigger = new CellRendererTrigger(); +                command_renderer_trigger.editable = true; +                command_renderer_trigger.ellipsize = Pango.EllipsizeMode.END; + +                command_renderer_trigger.on_select.connect((path, trigger) => {                  +                    Gtk.TreeIter data_iter; +                    this.data.get_iter_from_string(out data_iter, path); +                     +                    this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, trigger.label_with_specials); +                    this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, trigger.name); +                     +                    this.update_pie(data_iter); +                }); +                 +                command_column.pack_end(command_renderer_trigger, true); +                command_column.add_attribute(command_renderer_trigger, "weight", DataPos.FONT_WEIGHT); +                command_column.add_attribute(command_renderer_trigger, "markup", DataPos.DISPLAY_COMMAND_KEY); +                command_column.add_attribute(command_renderer_trigger, "visible", DataPos.TRIGGER_VISIBLE); +                command_column.add_attribute(command_renderer_trigger, "trigger", DataPos.REAL_COMMAND_KEY);              // slice group               var command_renderer_group = new Gtk.CellRendererCombo(); @@ -342,6 +365,7 @@ class PieList : Gtk.TreeView {          var type_column = new Gtk.TreeViewColumn();              type_column.title = _("Pie-ID / Action type");              type_column.resizable = true; +            type_column.expand = false;              var type_render = new Gtk.CellRendererCombo();                  type_render.editable = true; @@ -592,7 +616,7 @@ class PieList : Gtk.TreeView {      // 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", ""); +        var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", null);          Gtk.TreeIter last;          this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id);  @@ -612,9 +636,10 @@ class PieList : Gtk.TreeView {                                  DataPos.TYPE_VISIBLE, false,                                 DataPos.GROUP_VISIBLE, false,                                   DataPos.APP_VISIBLE, false, -                                 DataPos.KEY_VISIBLE, true, +                                 DataPos.KEY_VISIBLE, false,                                   DataPos.PIE_VISIBLE, false,                                   DataPos.URI_VISIBLE, false, +                             DataPos.TRIGGER_VISIBLE, true,                         DataPos.DISPLAY_COMMAND_GROUP, "",                           DataPos.DISPLAY_COMMAND_APP, "",                           DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), @@ -672,6 +697,7 @@ class PieList : Gtk.TreeView {                      DataPos.QUICKACTION_ACTIVATABLE, true,                                 DataPos.TYPE_VISIBLE, true,                                DataPos.GROUP_VISIBLE, false, +                            DataPos.TRIGGER_VISIBLE, false,                                  DataPos.APP_VISIBLE, action is AppAction,                                  DataPos.KEY_VISIBLE, action is KeyAction,                                  DataPos.PIE_VISIBLE, action is PieAction, @@ -791,9 +817,10 @@ class PieList : Gtk.TreeView {                                      DataPos.TYPE_VISIBLE, false,                                     DataPos.GROUP_VISIBLE, false,                                       DataPos.APP_VISIBLE, false, -                                     DataPos.KEY_VISIBLE, true, +                                     DataPos.KEY_VISIBLE, false,                                       DataPos.PIE_VISIBLE, false,                                       DataPos.URI_VISIBLE, false, +                                 DataPos.TRIGGER_VISIBLE, true,                             DataPos.DISPLAY_COMMAND_GROUP, "",                               DataPos.DISPLAY_COMMAND_APP, "",                               DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), @@ -834,6 +861,7 @@ class PieList : Gtk.TreeView {                                      DataPos.KEY_VISIBLE, false,                                      DataPos.PIE_VISIBLE, false,                                      DataPos.URI_VISIBLE, false, +                                DataPos.TRIGGER_VISIBLE, false,                            DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()],                              DataPos.DISPLAY_COMMAND_APP, "",                              DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), @@ -888,7 +916,7 @@ class PieList : Gtk.TreeView {              });              // create new pie -            var new_pie = PieManager.create_persistent_pie(name, icon, hotkey, id); +            var new_pie = PieManager.create_persistent_pie(name, icon, new Trigger.from_string(hotkey), id);              // add actions accordingly              if (this.data.iter_has_child(pie)) { diff --git a/src/gui/preferences.vala b/src/gui/preferences.vala index f43fd4a..9444fac 100644 --- a/src/gui/preferences.vala +++ b/src/gui/preferences.vala @@ -82,13 +82,6 @@ public class Preferences : Gtk.Window {                              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); @@ -187,6 +180,7 @@ public class Preferences : Gtk.Window {                                  _("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."), +                                _("It's recommended to keep your Pies small (at most 6-8 Slices). Else they will become hard to navigate."),                                  _("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); @@ -251,18 +245,21 @@ public class Preferences : Gtk.Window {              // close button               var bbox = new Gtk.HButtonBox ();                  bbox.set_layout (Gtk.ButtonBoxStyle.END); -                var close_button = new Gtk.Button.from_stock (Gtk.Stock.CLOSE); +                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(); +             +        this.hide.connect(() => { +            // save settings on close +            Config.global.save(); +            Pies.save(); +        });      }      ///////////////////////////////////////////////////////////////////// @@ -323,16 +320,6 @@ public class Preferences : Gtk.Window {          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/triggerSelectWindow.vala b/src/gui/triggerSelectWindow.vala new file mode 100644 index 0000000..e003a84 --- /dev/null +++ b/src/gui/triggerSelectWindow.vala @@ -0,0 +1,257 @@ +/*  +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 window allows the selection of a hotkey. It is returned in form +/// of a Trigger. Therefore it can be either a keyboard driven hotkey or +/// a mouse based hotkey. +///////////////////////////////////////////////////////////////////////// + +public class TriggerSelectWindow : Gtk.Dialog { +     +    ///////////////////////////////////////////////////////////////////// +    /// This signal is emitted when the user selects a new hot key. +    ///////////////////////////////////////////////////////////////////// +     +    public signal void on_select(Trigger trigger); +     +    ///////////////////////////////////////////////////////////////////// +    /// Some private members which are needed by other methods. +    ///////////////////////////////////////////////////////////////////// +     +    private Gtk.CheckButton turbo; +    private Gtk.CheckButton delayed; +    private Gtk.Label preview; +     +    ///////////////////////////////////////////////////////////////////// +    /// The currently configured trigger. +    ///////////////////////////////////////////////////////////////////// +     +    private Trigger trigger = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// The trigger which was active when this window was opened. It is +    /// stored in order to check whether anything has changed when the +    /// user clicks on OK. +    ///////////////////////////////////////////////////////////////////// +     +    private Trigger original_trigger = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// These modifiers are ignored. +    ///////////////////////////////////////////////////////////////////// +     +    private Gdk.ModifierType lock_modifiers = Gdk.ModifierType.MOD2_MASK +                                             |Gdk.ModifierType.LOCK_MASK +                                             |Gdk.ModifierType.MOD5_MASK; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, constructs a new TriggerSelectWindow. +    ///////////////////////////////////////////////////////////////////// +     +    public TriggerSelectWindow() { +        this.title = _("Define an open-command"); +        this.resizable = false; +        this.delete_event.connect(hide_on_delete); +        this.key_press_event.connect(on_key_press); +        this.button_press_event.connect(on_button_press); +         +        this.show.connect_after(() => { +            FocusGrabber.grab(this); +        }); +         +        this.hide.connect(() => { +            FocusGrabber.ungrab(this); +        }); + +        var container = new Gtk.VBox(false, 6); +            container.set_border_width(6); +              +             // click area +             var click_frame = new Gtk.Frame(_("Click here if you want to bind a mouse button!")); +             +                var click_box = new Gtk.EventBox(); +                    click_box.height_request = 100; +                    click_box.button_press_event.connect(on_area_clicked); +                     +                    this.preview = new Gtk.Label(null); +                     +                    click_box.add(this.preview); +                     +                click_frame.add(click_box); +                 +            container.pack_start(click_frame, false); +             +            // turbo checkbox +            this.turbo = new Gtk.CheckButton.with_label (_("Turbo mode")); +                this.turbo.tooltip_text = _("If checked, the Pie will close when you " +  +                                            "release the chosen hot key."); +                this.turbo.active = false; +                this.turbo.toggled.connect(() => { +                	if (this.trigger != null) +		            	this.update_trigger(new Trigger.from_values( +		            		this.trigger.key_sym, this.trigger.modifiers, +							this.trigger.with_mouse, this.turbo.active, +							this.delayed.active)); +                }); +                 +            container.pack_start(turbo, false); +             +            // delayed checkbox +            this.delayed = new Gtk.CheckButton.with_label (_("Long press for activation")); +                this.delayed.tooltip_text = _("If checked, the Pie will only open if you " +  +                                              "press this hot key a bit longer."); +                this.delayed.active = false; +                this.delayed.toggled.connect(() => { +                	if (this.trigger != null) +		            	this.update_trigger(new Trigger.from_values( +		            		this.trigger.key_sym, this.trigger.modifiers, +							this.trigger.with_mouse, this.turbo.active, +							this.delayed.active)); +                }); +                 +            container.pack_start(delayed, false); + +        container.show_all(); +         +        this.vbox.pack_start(container, true, true); +         +        this.add_button(Gtk.Stock.CANCEL, 1); +        this.add_button(Gtk.Stock.OK, 0); +         +        // select a new trigger on OK, hide on CANCEL +        this.response.connect((id) => { +        	if (id == 1) +        		this.hide(); +        	else if (id == 0) { +        		var assigned_id = PieManager.get_assigned_id(this.trigger); +     +				if (this.trigger == this.original_trigger) { +					// nothing did change +					this.hide(); +				} else if (this.trigger.key_code == this.original_trigger.key_code +						   && this.trigger.modifiers == this.original_trigger.modifiers +						   && this.trigger.with_mouse == this.original_trigger.with_mouse) { +					// only turbo and/or delayed mode changed, no need to check for double assignment +					this.on_select(this.trigger); +				    this.hide(); +				} else if (assigned_id != "") { +					// it's already assigned +				    var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " + +				                  "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id)); +				    var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(),  +				    									Gtk.DialogFlags.MODAL,  +				                                        Gtk.MessageType.ERROR,  +				                                        Gtk.ButtonsType.CANCEL,  +				                                        error); +				    dialog.run(); +				    dialog.destroy(); +				} else { +					// a unused hot key has been chosen, great! +				    this.on_select(this.trigger); +				    this.hide(); +				} +        	} +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Used to set the currently selected trigger on opening. +    ///////////////////////////////////////////////////////////////////// +     +    public void set_trigger(Trigger trigger) { +        this.turbo.active = trigger.turbo; +        this.delayed.active = trigger.delayed; +        this.original_trigger = trigger; +        this.update_trigger(trigger); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Called when the user clicks in the click area. +    ///////////////////////////////////////////////////////////////////// +     +    private bool on_area_clicked(Gdk.EventButton event) { +        Gdk.ModifierType state = event.state & ~ this.lock_modifiers; +         +        var new_trigger = new Trigger.from_values((int)event.button, state, true,  +                                                  this.turbo.active, this.delayed.active); +        if (new_trigger.key_code == 1) { +            var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL,  +                                                Gtk.MessageType.WARNING,  +                                                Gtk.ButtonsType.YES_NO,  +                                                _("It possible to make your system unusable if " + +                                                  "you bind a Pie to your left mouse button. Do " + +                                                  "you really want to do this?")); +                                                  +            dialog.response.connect((response) => { +                if (response == Gtk.ResponseType.YES) { +                    this.update_trigger(new_trigger); +                } +            }); +             +            dialog.run(); +            dialog.destroy(); +        } else { +            this.update_trigger(new_trigger); +        } +         +        return true; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Called when the user presses a keyboard key. +    ///////////////////////////////////////////////////////////////////// +     +    private bool on_key_press(Gdk.EventKey event) { +        if (Gdk.keyval_name(event.keyval) == "Escape") { +            this.hide(); +        } else if (Gdk.keyval_name(event.keyval) == "BackSpace") { +            this.update_trigger(new Trigger()); +        } else if (event.is_modifier == 0) { +            Gdk.ModifierType state = event.state & ~ this.lock_modifiers; +            this.update_trigger(new Trigger.from_values((int)event.keyval, state, false,  +                                                   this.turbo.active, this.delayed.active)); +        } +         +        return true; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Called when the user presses a mouse button. +    ///////////////////////////////////////////////////////////////////// +     +    private bool on_button_press(Gdk.EventButton event) { +        int width = 0, height = 0; +        this.window.get_geometry(null, null, out width, out height, null); +        if (event.x < 0 || event.x > width || event.y < 0 || event.y > height) +            this.hide(); +        return true; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Helper method to update the content of the trigger preview label. +    ///////////////////////////////////////////////////////////////////// +     +    private void update_trigger(Trigger new_trigger) { +    	this.trigger = new_trigger; +        this.preview.set_markup("<big><b>" + this.trigger.label + "</b></big>"); +    }      +} + +} diff --git a/src/utilities/icon.vala b/src/images/icon.vala index 1c8a9f4..1c8a9f4 100644 --- a/src/utilities/icon.vala +++ b/src/images/icon.vala diff --git a/src/utilities/image.vala b/src/images/image.vala index 836e4e2..836e4e2 100644 --- a/src/utilities/image.vala +++ b/src/images/image.vala diff --git a/src/utilities/renderedText.vala b/src/images/renderedText.vala index 924742a..924742a 100644 --- a/src/utilities/renderedText.vala +++ b/src/images/renderedText.vala diff --git a/src/utilities/themedIcon.vala b/src/images/themedIcon.vala index 29ae380..29ae380 100644 --- a/src/utilities/themedIcon.vala +++ b/src/images/themedIcon.vala diff --git a/src/pies/defaultConfig.vala b/src/pies/defaultConfig.vala index bd981b5..87fd30d 100644 --- a/src/pies/defaultConfig.vala +++ b/src/pies/defaultConfig.vala @@ -26,14 +26,14 @@ 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"); +        var multimedia = PieManager.create_persistent_pie(_("Multimedia"), "stock_media-play", new Trigger.from_string("<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"); +        var apps = PieManager.create_persistent_pie(_("Applications"), "applications-accessories", new Trigger.from_string("<Control><Alt>a"));              apps.add_action(ActionRegistry.default_for_mime_type("text/plain"));              apps.add_action(ActionRegistry.default_for_mime_type("audio/ogg"));              apps.add_action(ActionRegistry.default_for_mime_type("video/ogg")); @@ -42,20 +42,20 @@ namespace Pies {              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"); +        var bookmarks = PieManager.create_persistent_pie(_("Bookmarks"), "user-bookmarks", new Trigger.from_string("<Control><Alt>b"));              bookmarks.add_group(new BookmarkGroup(bookmarks.id));              bookmarks.add_group(new DevicesGroup(bookmarks.id));          // add a pie with session controls -        var session = PieManager.create_persistent_pie(_("Session"), "gnome-session-halt", "<Control><Alt>q"); +        var session = PieManager.create_persistent_pie(_("Session"), "gnome-session-halt", new Trigger.from_string("<Control><Alt>q"));              session.add_group(new SessionGroup(session.id));          // add a pie with a main menu -        var menu = PieManager.create_persistent_pie(_("Main Menu"), "alacarte", "<Control><Alt>space"); +        var menu = PieManager.create_persistent_pie(_("Main Menu"), "alacarte", new Trigger.from_string("<Control><Alt>space"));              menu.add_group(new MenuGroup(menu.id));          // add a pie with window controls -        var window = PieManager.create_persistent_pie(_("Window"), "gnome-window-manager", "<Control><Alt>w"); +        var window = PieManager.create_persistent_pie(_("Window"), "gnome-window-manager", new Trigger.from_string("<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")); diff --git a/src/pies/load.vala b/src/pies/load.vala index 912ddf0..98fd72f 100644 --- a/src/pies/load.vala +++ b/src/pies/load.vala @@ -115,7 +115,7 @@ namespace Pies {          }          // add a new Pie with the loaded properties -        var pie = PieManager.create_persistent_pie(name, icon, hotkey, id); +        var pie = PieManager.create_persistent_pie(name, icon, new Trigger.from_string(hotkey), id);          // and parse all child elements          for (Xml.Node* slice = node->children; slice != null; slice = slice->next) { diff --git a/src/pies/pieManager.vala b/src/pies/pieManager.vala index eb031d0..5f84ea0 100644 --- a/src/pies/pieManager.vala +++ b/src/pies/pieManager.vala @@ -103,6 +103,14 @@ public class PieManager : GLib.Object {      }      ///////////////////////////////////////////////////////////////////// +    /// Returns true if the pie with the given id is in turbo mode. +    ///////////////////////////////////////////////////////////////////// +     +    public static bool get_is_turbo(string id) { +        return bindings.get_is_turbo(id); +    } +     +    /////////////////////////////////////////////////////////////////////      /// Returns the name of the Pie with the given ID.      ///////////////////////////////////////////////////////////////////// @@ -113,14 +121,23 @@ public class PieManager : GLib.Object {      }      ///////////////////////////////////////////////////////////////////// +    /// Returns the name ID of the Pie bound to the given Trigger. +    /// Returns "" if there is nothing bound to this trigger. +    ///////////////////////////////////////////////////////////////////// +     +    public static string get_assigned_id(Trigger trigger) { +        return bindings.get_assigned_id(trigger); +    } +     +    /////////////////////////////////////////////////////////////////////      /// Creates a new Pie which is displayed in the configuration dialog      /// and gets saved.      ///////////////////////////////////////////////////////////////////// -    public static Pie create_persistent_pie(string name, string icon_name, string hotkey, string? desired_id = null) { +    public static Pie create_persistent_pie(string name, string icon_name, Trigger? hotkey, string? desired_id = null) {          Pie pie = create_pie(name, icon_name, 100, 999, desired_id); -        if (hotkey != "") bindings.bind(hotkey, pie.id); +        if (hotkey != null) bindings.bind(hotkey, pie.id);          create_launcher(pie.id); diff --git a/src/renderers/centerRenderer.vala b/src/renderers/centerRenderer.vala index c30e9ce..fab633e 100644 --- a/src/renderers/centerRenderer.vala +++ b/src/renderers/centerRenderer.vala @@ -19,17 +19,44 @@ using GLib.Math;  namespace GnomePie { -// Renders the center of a Pie. +/////////////////////////////////////////////////////////////////////////     +///  Renders the center of a Pie. +/////////////////////////////////////////////////////////////////////////  public class CenterRenderer : GLib.Object { +    ///////////////////////////////////////////////////////////////////// +    /// The PieRenderer which owns this CenterRenderer. +    ///////////////////////////////////////////////////////////////////// +      private unowned PieRenderer parent; +     +    ///////////////////////////////////////////////////////////////////// +    /// The caption drawn in the center. Changes when the active slice +    /// changes. +    ///////////////////////////////////////////////////////////////////// +          private unowned Image? caption; +     +    ///////////////////////////////////////////////////////////////////// +    /// The color of the currently active slice. Used to colorize layers. +    ///////////////////////////////////////////////////////////////////// +          private Color color; +    ///////////////////////////////////////////////////////////////////// +    /// Two AnimatedValues: alpha is for global transparency (when +    /// fading in/out), activity is 1.0 if there is an active slice and +    /// 0.0 if there is no active slice. +    ///////////////////////////////////////////////////////////////////// +          private AnimatedValue activity;      private AnimatedValue alpha; +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all members. +    ///////////////////////////////////////////////////////////////////// +      public CenterRenderer(PieRenderer parent) {          this.parent = parent;          this.activity = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time); @@ -38,11 +65,21 @@ public class CenterRenderer : GLib.Object {          this.caption = null;      } +    ///////////////////////////////////////////////////////////////////// +    /// Initiates the fade-out animation by resetting the targets of the +    /// AnimatedValues to 0.0. +    ///////////////////////////////////////////////////////////////////// +          public void fade_out() {          this.activity.reset_target(0.0, Config.global.theme.fade_out_time);          this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);      } +    ///////////////////////////////////////////////////////////////////// +    /// Should be called if the active slice of the PieRenderer changes. +    /// The members activity, caption and color are set accordingly. +    ///////////////////////////////////////////////////////////////////// +          public void set_active_slice(SliceRenderer? active_slice) {          if (active_slice == null) {              this.activity.reset_target(0.0, Config.global.theme.transition_time); @@ -53,17 +90,23 @@ public class CenterRenderer : GLib.Object {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Draws all center layers and the caption. +    ///////////////////////////////////////////////////////////////////// +          public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) { - +        // get all center_layers  	    var layers = Config.global.theme.center_layers; +        // update the AnimatedValues          this.activity.update(frame_time);          this.alpha.update(frame_time); +	    // draw each layer  	    foreach (var layer in layers) { -	      	        ctx.save(); +            // calculate all values needed for animation/drawing              double 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) ?  @@ -114,10 +157,14 @@ public class CenterRenderer : GLib.Object {  	        if (colorize > 0.0) ctx.push_group(); +	        // transform the context  	        ctx.rotate(layer.rotation);  	        ctx.scale(max_scale, max_scale); +	         +	        // paint the layer  	        layer.image.paint_on(ctx, this.alpha.val*max_alpha); +            // colorize it, if necessary              if (colorize > 0.0) {                  ctx.set_operator(Cairo.Operator.ATOP);                  ctx.set_source_rgb(this.color.r, this.color.g, this.color.b); @@ -135,7 +182,7 @@ public class CenterRenderer : GLib.Object {          if (Config.global.theme.caption && caption != null && this.activity.val > 0) {  		    ctx.save();              ctx.identity_matrix(); -            int pos = this.parent.get_size()/2; +            int pos = this.parent.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 index 5b706f4..ffaf776 100644 --- a/src/renderers/pieRenderer.vala +++ b/src/renderers/pieRenderer.vala @@ -26,15 +26,60 @@ namespace GnomePie {  public class PieRenderer : GLib.Object { +    ///////////////////////////////////////////////////////////////////// +    /// The index of the slice used for quick action. (The action which +    /// gets executed when the user clicks on the middle of the pie) +    ///////////////////////////////////////////////////////////////////// +      public int quick_action { get; private set; } + +    ///////////////////////////////////////////////////////////////////// +    /// The index of the currently active slice. +    /////////////////////////////////////////////////////////////////////     +          public int active_slice { get; private set; } + +    ///////////////////////////////////////////////////////////////////// +    /// True, if the hot keys are currently displayed. +    ///////////////////////////////////////////////////////////////////// +          public bool show_hotkeys { get; set; } -    private int size; +    ///////////////////////////////////////////////////////////////////// +    /// The width and height of the Pie in pixels. +    ///////////////////////////////////////////////////////////////////// + +    public int size { get; private set; } +     +    ///////////////////////////////////////////////////////////////////// +    /// True if the pie should close when it's trigger is released. +    ///////////////////////////////////////////////////////////////////// +     +    public bool turbo_mode { get; private set; default=false; } +     +    ///////////////////////////////////////////////////////////////////// +    /// All SliceRenderers used to draw this Pie. +    ///////////////////////////////////////////////////////////////////// +          private Gee.ArrayList<SliceRenderer?> slices; +     +    ///////////////////////////////////////////////////////////////////// +    /// The renderer for the center of this pie. +    ///////////////////////////////////////////////////////////////////// +          private CenterRenderer center; +     +    ///////////////////////////////////////////////////////////////////// +    /// True if the pie is currently navigated with the keyboard. This is +    /// set to false as soon as the mouse moves. +    ///////////////////////////////////////////////////////////////////// +          private bool key_board_control = false; +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes members. +    ///////////////////////////////////////////////////////////////////// +          public PieRenderer() {          this.slices = new Gee.ArrayList<SliceRenderer?>();           this.center = new CenterRenderer(this); @@ -43,6 +88,10 @@ public class PieRenderer : GLib.Object {          this.size = 0;      } +    ///////////////////////////////////////////////////////////////////// +    /// Loads an Pie. All members are initialized accordingly. +    ///////////////////////////////////////////////////////////////////// +          public void load_pie(Pie pie) {          this.slices.clear(); @@ -61,6 +110,8 @@ public class PieRenderer : GLib.Object {              }          } +        this.turbo_mode = PieManager.get_is_turbo(pie.id); +                  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, @@ -74,12 +125,20 @@ public class PieRenderer : GLib.Object {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Activates the currently active slice. +    ///////////////////////////////////////////////////////////////////// +          public void activate() {          if (this.active_slice >= 0 && this.active_slice < this.slices.size)              slices[active_slice].activate();          this.cancel();      } +    ///////////////////////////////////////////////////////////////////// +    /// Asks all renders to fade out. +    ///////////////////////////////////////////////////////////////////// +          public void cancel() {          foreach (var slice in this.slices)              slice.fade_out(); @@ -87,6 +146,11 @@ public class PieRenderer : GLib.Object {          center.fade_out();      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the up-key is pressed. Selects the next slice towards +    /// the top.  +    ///////////////////////////////////////////////////////////////////// +          public void select_up() {          int bottom = this.slice_count()/4;          int top = this.slice_count()*3/4; @@ -99,6 +163,11 @@ public class PieRenderer : GLib.Object {             this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the down-key is pressed. Selects the next slice +    /// towards the bottom.  +    ///////////////////////////////////////////////////////////////////// +          public void select_down() {          int bottom = this.slice_count()/4;          int top = this.slice_count()*3/4; @@ -111,6 +180,11 @@ public class PieRenderer : GLib.Object {             this.set_highlighted_slice((this.active_slice+1)%this.slice_count());      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the left-key is pressed. Selects the next slice +    /// towards the left.  +    ///////////////////////////////////////////////////////////////////// +          public void select_left() {          int left = this.slice_count()/2;          int right = 0; @@ -123,6 +197,11 @@ public class PieRenderer : GLib.Object {             this.set_highlighted_slice(this.active_slice+1);      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the right-key is pressed. Selects the next slice +    /// towards the right.  +    ///////////////////////////////////////////////////////////////////// +          public void select_right() {          int left = this.slice_count()/2;          int right = 0; @@ -135,13 +214,17 @@ public class PieRenderer : GLib.Object {             this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());      } +    ///////////////////////////////////////////////////////////////////// +    /// Returns the amount of slices in this pie. +    ///////////////////////////////////////////////////////////////////// +          public int slice_count() {          return slices.size;      } -    public int get_size() { -        return size; -    } +    ///////////////////////////////////////////////////////////////////// +    /// Draws the entire pie. +    /////////////////////////////////////////////////////////////////////      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); @@ -179,10 +262,18 @@ public class PieRenderer : GLib.Object {  		    slice.draw(frame_time, ctx, angle, distance);      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the user moves the mouse. +    ///////////////////////////////////////////////////////////////////// +          public void on_mouse_move() {          this.key_board_control = false;      } +    ///////////////////////////////////////////////////////////////////// +    /// Called when the currently active slice changes. +    ///////////////////////////////////////////////////////////////////// +          public void set_highlighted_slice(int index) {          if (index != this.active_slice) {              if (index >= 0 && index < this.slice_count())  diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala index c4ac2ec..59117df 100644 --- a/src/renderers/pieWindow.vala +++ b/src/renderers/pieWindow.vala @@ -19,19 +19,52 @@ using GLib.Math;  namespace GnomePie { -// An invisible window. Used to draw Pies onto. +/////////////////////////////////////////////////////////////////////////     +///  An invisible window. Used to draw Pies onto. +/////////////////////////////////////////////////////////////////////////  public class PieWindow : Gtk.Window { + +    ///////////////////////////////////////////////////////////////////// +    /// Signal which gets emitted when the PieWindow is about to close. +    /////////////////////////////////////////////////////////////////////      public signal void on_closing(); +     +    ///////////////////////////////////////////////////////////////////// +    /// The owned renderer. +    /////////////////////////////////////////////////////////////////////      private PieRenderer renderer; +     +    ///////////////////////////////////////////////////////////////////// +    /// True, if the Pie is currently fading out. +    ///////////////////////////////////////////////////////////////////// +          private bool closing = false; +     +    ///////////////////////////////////////////////////////////////////// +    /// A timer used for calculating the frame time. +    ///////////////////////////////////////////////////////////////////// +          private GLib.Timer timer; +    ///////////////////////////////////////////////////////////////////// +    /// True, if the screen supports compositing. +    ///////////////////////////////////////////////////////////////////// +          private bool has_compositing = false; +    ///////////////////////////////////////////////////////////////////// +    /// The background image used for fake transparency if +    /// has_compositing is false. +    ///////////////////////////////////////////////////////////////////// +          private Image background = null; +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, sets up the window. +    /////////////////////////////////////////////////////////////////////      public PieWindow() {          this.renderer = new PieRenderer(); @@ -46,19 +79,27 @@ public class PieWindow : Gtk.Window {          this.icon_name = "gnome-pie";          this.set_accept_focus(false); +        // check for compositing          if (this.screen.is_composited()) {              this.set_colormap(this.screen.get_rgba_colormap());              this.has_compositing = true;          } +        // set up event filter          this.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |                          Gdk.EventMask.KEY_RELEASE_MASK |                          Gdk.EventMask.KEY_PRESS_MASK |                          Gdk.EventMask.POINTER_MOTION_MASK); +        // activate on left click          this.button_release_event.connect ((e) => { -            if (e.button == 1) this.activate_slice(); -            else               this.cancel(); +            if (e.button == 1 || this.renderer.turbo_mode) this.activate_slice(); +            return true; +        }); +         +         // cancel on right click +        this.button_press_event.connect ((e) => { +            if (e.button == 3) this.cancel();              return true;          }); @@ -72,32 +113,44 @@ public class PieWindow : Gtk.Window {              return true;          }); +        // activate on key release if turbo_mode is enabled          this.key_release_event.connect((e) => {              last_key = 0; -            if (Config.global.turbo_mode) +            if (this.renderer.turbo_mode)                  this.activate_slice();              else                  this.handle_key_release(e.keyval);              return true;          }); +        // notify the renderer of mouse move events          this.motion_notify_event.connect((e) => {              this.renderer.on_mouse_move();              return true;          }); +        // draw the pie on expose          this.expose_event.connect(this.draw);      } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads a Pie to be rendered. +    /////////////////////////////////////////////////////////////////////      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()); +        this.set_size_request(renderer.size, renderer.size);      } +    ///////////////////////////////////////////////////////////////////// +    /// Opens the window. load_pie should have been called before. +    ///////////////////////////////////////////////////////////////////// +          public void open() {          this.realize(); +        // capture the background image if there is no compositing          if (!this.has_compositing) {              int x, y, width, height;              this.get_position(out x, out y); @@ -105,23 +158,31 @@ public class PieWindow : Gtk.Window {              this.background = new Image.capture_screen(x, y, width+1, height+1);          } +        // capture the input focus          this.show(); -        this.fix_focus(); +        FocusGrabber.grab(this); +        // start the timer          this.timer = new GLib.Timer();          this.timer.start();          this.queue_draw(); +        // the main draw loop          Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {              this.queue_draw();              return this.visible;          });       } +     +    ///////////////////////////////////////////////////////////////////// +    /// Draw the Pie. +    /////////////////////////////////////////////////////////////////////      private bool draw(Gtk.Widget da, Gdk.EventExpose event) {              // clear the window          var ctx = Gdk.cairo_create(this.window); +        // paint the background image if there is no compositing          if (this.has_compositing) {              ctx.set_operator (Cairo.Operator.CLEAR);              ctx.paint(); @@ -132,59 +193,80 @@ public class PieWindow : Gtk.Window {              ctx.paint();          } +        // align the context to the center of the PieWindow          ctx.translate(this.width_request*0.5, this.height_request*0.5); - +         +        // get the mouse position          double mouse_x = 0.0, mouse_y = 0.0;          this.get_pointer(out mouse_x, out mouse_y); +        // store the frame time          double frame_time = this.timer.elapsed();          this.timer.reset(); +        // render the Pie          this.renderer.draw(frame_time, ctx, (int)(mouse_x - this.width_request*0.5),                                              (int)(mouse_y - this.height_request*0.5));          return true;      } +    ///////////////////////////////////////////////////////////////////// +    /// Activates the currently activate slice. +    ///////////////////////////////////////////////////////////////////// +          private void activate_slice() {          if (!this.closing) {              this.closing = true;              this.on_closing(); -            this.unfix_focus(); +            FocusGrabber.ungrab(this);              this.renderer.activate();              Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {                  this.destroy(); -                //ThemedIcon.clear_cache(); +                ThemedIcon.clear_cache();                  return false;              });          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Activates no slice and closes the PieWindow. +    ///////////////////////////////////////////////////////////////////// +          private void cancel() {          if (!this.closing) {              this.closing = true;              this.on_closing(); -            this.unfix_focus(); +            FocusGrabber.ungrab(this);              this.renderer.cancel();              Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {                  this.destroy(); -                //ThemedIcon.clear_cache(); +                ThemedIcon.clear_cache();                  return false;              });          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Sets the position of the window to the center of the screen or to +    /// the mouse. +    ///////////////////////////////////////////////////////////////////// +          private void set_window_position() {          if(Config.global.open_at_mouse) this.set_position(Gtk.WindowPosition.MOUSE);          else                            this.set_position(Gtk.WindowPosition.CENTER);      } +    ///////////////////////////////////////////////////////////////////// +    /// Do some useful stuff when keys are pressed. +    ///////////////////////////////////////////////////////////////////// +          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) { +        else if (!this.renderer.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(); @@ -212,52 +294,15 @@ public class PieWindow : Gtk.Window {          }      } +    ///////////////////////////////////////////////////////////////////// +    /// Do some useful stuff when keys are released. +    ///////////////////////////////////////////////////////////////////// +          private void handle_key_release(uint key) { -        if (!Config.global.turbo_mode) { +        if (!this.renderer.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 index 08c880f..61c50b1 100644 --- a/src/renderers/sliceRenderer.vala +++ b/src/renderers/sliceRenderer.vala @@ -19,28 +19,77 @@ using GLib.Math;  namespace GnomePie { -// Renders a Slice of a Pie. According to the current theme. +/////////////////////////////////////////////////////////////////////////     +///  Renders a Slice of a Pie. According to the current theme. +/////////////////////////////////////////////////////////////////////////  public class SliceRenderer : GLib.Object { +    ///////////////////////////////////////////////////////////////////// +    /// Whether this slice is active (hovered) or not. +    ///////////////////////////////////////////////////////////////////// +      public bool active {get; private set; default = false;} +     +    ///////////////////////////////////////////////////////////////////// +    /// The Image which should be displayed as center caption when this +    /// slice is active. +    ///////////////////////////////////////////////////////////////////// +          public Image caption {get; private set;} +     +    ///////////////////////////////////////////////////////////////////// +    /// The color which should be used for colorizing center layers when +    /// this slice is active. +    ///////////////////////////////////////////////////////////////////// +          public Color color {get; private set;} +    ///////////////////////////////////////////////////////////////////// +    /// The two Images used, when this slice is active or not. +    ///////////////////////////////////////////////////////////////////// +          private Image active_icon;      private Image inactive_icon; +     +    ///////////////////////////////////////////////////////////////////// +    /// The Image displaying the associated hot key of this slice. +    ///////////////////////////////////////////////////////////////////// +          private Image hotkey; +    ///////////////////////////////////////////////////////////////////// +    /// The Action which is rendered by this SliceRenderer. +    ///////////////////////////////////////////////////////////////////// +          private Action action; +     +    ///////////////////////////////////////////////////////////////////// +    /// The PieRenderer which owns this SliceRenderer. +    /////////////////////////////////////////////////////////////////////      private unowned PieRenderer parent;     +     +    ///////////////////////////////////////////////////////////////////// +    /// The index of this slice in a pie. Clockwise assigned, starting +    /// from the right-most slice. +    ///////////////////////////////////////////////////////////////////// +          private int position; -    private AnimatedValue fade; -    private AnimatedValue scale; -    private AnimatedValue alpha; -    private AnimatedValue fade_rotation; -    private AnimatedValue fade_scale; +    ///////////////////////////////////////////////////////////////////// +    /// AnimatedValues needed for a slice. +    ///////////////////////////////////////////////////////////////////// +     +    private AnimatedValue fade;             // for transitions from active to inactive +    private AnimatedValue scale;            // for zoom effect +    private AnimatedValue alpha;            // for fading in/out +    private AnimatedValue fade_rotation;    // for fading in/out +    private AnimatedValue fade_scale;       // for fading in/out + +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, initializes all AnimatedValues. +    /////////////////////////////////////////////////////////////////////      public SliceRenderer(PieRenderer parent) {          this.parent = parent; @@ -60,6 +109,10 @@ public class SliceRenderer : GLib.Object {                                                   Config.global.theme.fade_in_rotation, 0.0,                                                    Config.global.theme.fade_in_time);      } +     +    ///////////////////////////////////////////////////////////////////// +    /// Loads an Action. All members are initialized accordingly. +    /////////////////////////////////////////////////////////////////////      public void load(Action action, int position) {          this.position = position; @@ -88,10 +141,19 @@ public class SliceRenderer : GLib.Object {                           (int)Config.global.theme.slice_radius*2, "sans 20");      } +    ///////////////////////////////////////////////////////////////////// +    /// Activaes the Action of this slice. +    ///////////////////////////////////////////////////////////////////// +          public void activate() {          action.activate();      } +    ///////////////////////////////////////////////////////////////////// +    /// Initiates the fade-out animation by resetting the targets of the +    /// AnimatedValues to 0.0. +    ///////////////////////////////////////////////////////////////////// +          public void fade_out() {          this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);          this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.IN,  @@ -105,6 +167,11 @@ public class SliceRenderer : GLib.Object {                                               Config.global.theme.fade_out_time);      } +    ///////////////////////////////////////////////////////////////////// +    /// Should be called if the active slice of the PieRenderer changes. +    /// The members activity, caption and color are set accordingly. +    ///////////////////////////////////////////////////////////////////// +          public void set_active_slice(SliceRenderer? active_slice) {         if (active_slice == this) {              this.fade.reset_target(1.0, Config.global.theme.transition_time); @@ -112,6 +179,10 @@ public class SliceRenderer : GLib.Object {              this.fade.reset_target(0.0, Config.global.theme.transition_time);          }      } +     +    ///////////////////////////////////////////////////////////////////// +    /// Draws all layers of the slice. +    /////////////////////////////////////////////////////////////////////      public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) { @@ -134,6 +205,7 @@ public class SliceRenderer : GLib.Object {          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); +        // update the AnimatedValues          this.scale.update(frame_time);          this.alpha.update(frame_time);          this.fade.update(frame_time); @@ -142,14 +214,17 @@ public class SliceRenderer : GLib.Object {          ctx.save(); +        // distance from the center          double radius = Config.global.theme.radius; +        // increase radius if there are many slices in a pie          if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap)            /(radius/Config.global.theme.max_zoom)) > PI/parent.slice_count()) {              radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap)                       /tan(PI/parent.slice_count())*Config.global.theme.max_zoom;          } +        // transform the context          ctx.scale(scale.val*fade_scale.val, scale.val*fade_scale.val);          ctx.translate(cos(direction)*radius, sin(direction)*radius); @@ -157,6 +232,7 @@ public class SliceRenderer : GLib.Object {          ctx.set_operator(Cairo.Operator.ADD); +        // paint the images          if (fade.val > 0.0) active_icon.paint_on(ctx, this.alpha.val*this.fade.val);          if (fade.val < 1.0) inactive_icon.paint_on(ctx, this.alpha.val*(1.0 - fade.val)); @@ -172,6 +248,7 @@ public class SliceRenderer : GLib.Object {          ctx.pop_group_to_source();          ctx.paint(); +        // draw hotkeys if necassary          if (this.parent.show_hotkeys)              this.hotkey.paint_on(ctx, 1.0); diff --git a/src/themes/theme.vala b/src/themes/theme.vala index fa6f55a..284e1ef 100644 --- a/src/themes/theme.vala +++ b/src/themes/theme.vala @@ -71,8 +71,6 @@ public class Theme : GLib.Object {          this.inactive_slice_layers = new Gee.ArrayList<SliceLayer?>();          this.directory = dir; -         -        this.load();      }      ///////////////////////////////////////////////////////////////////// @@ -80,7 +78,7 @@ public class Theme : GLib.Object {      /// explicitly.      ///////////////////////////////////////////////////////////////////// -    public void load() { +    public bool load() {          this.center_layers.clear();          this.active_slice_layers.clear();          this.inactive_slice_layers.clear(); @@ -90,15 +88,15 @@ public class Theme : GLib.Object {          Xml.Doc* themeXML = Xml.Parser.parse_file(path);          if (themeXML == null) { -            warning("Error parsing theme: \"" + path + "\" not found!"); -            return; +            warning("Failed to add theme: \"" + path + "\" not found!"); +            return false;          }          Xml.Node* root = themeXML->get_root_element();          if (root == null) {              delete themeXML; -            warning("Invalid theme \"" + this.directory + "\": theme.xml is empty!"); -            return; +            warning("Failed to add theme: \"theme.xml\" is empty!"); +            return false;          }          this.parse_root(root); @@ -107,6 +105,8 @@ public class Theme : GLib.Object {          Xml.Parser.cleanup();          this.radius *= max_zoom; +         +        return true;      }      ///////////////////////////////////////////////////////////////////// diff --git a/src/utilities/bindingManager.vala b/src/utilities/bindingManager.vala index 8795124..437f4c1 100644 --- a/src/utilities/bindingManager.vala +++ b/src/utilities/bindingManager.vala @@ -53,6 +53,10 @@ public class BindingManager : GLib.Object {          Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK,          Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK      }; +     +    private uint32 delayed_count = 0; +    private X.Event? delayed_event = null; +    private Keybinding? delayed_binding = null;      /////////////////////////////////////////////////////////////////////      /// Helper class to store keybinding @@ -60,16 +64,12 @@ public class BindingManager : GLib.Object {      private class Keybinding { -        public Keybinding(string accelerator, int keycode, Gdk.ModifierType modifiers, string id) { -            this.accelerator = accelerator; -            this.keycode = keycode; -            this.modifiers = modifiers; +        public Keybinding(Trigger trigger, string id) { +            this.trigger = trigger;              this.id = id;          } -        public string accelerator { get; set; } -        public int keycode { get; set; } -        public Gdk.ModifierType modifiers { get; set; } +        public Trigger trigger { get; set; }          public string id { get; set; }      } @@ -89,32 +89,30 @@ public class BindingManager : GLib.Object {      /// 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); +    public void bind(Trigger trigger, string id) { +        if(trigger.key_code != 0) { +            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); -        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); +                if (trigger.with_mouse) { +                    display.grab_button(trigger.key_code, trigger.modifiers|lock_modifier, xid, false, +                                        X.EventMask.ButtonPressMask | X.EventMask.ButtonReleaseMask,  +                                        X.GrabMode.Async, X.GrabMode.Async, xid, 0); +                } else { +                    display.grab_key(trigger.key_code, trigger.modifiers|lock_modifier,  +                                     xid, false, X.GrabMode.Async, X.GrabMode.Async); +                }              }              Gdk.flush(); -            Keybinding binding = new Keybinding(accelerator, keycode, modifiers, id); +            Keybinding binding = new Keybinding(trigger, id);              bindings.add(binding); +            display.flush();          }      } @@ -130,13 +128,18 @@ public class BindingManager : GLib.Object {          foreach(var binding in bindings) {              if(id == binding.id) {                  foreach(uint lock_modifier in lock_modifiers) { -                    display.ungrab_key(binding.keycode, binding.modifiers, xid); +                    if (binding.trigger.with_mouse) { +                        display.ungrab_button(binding.trigger.key_code, binding.trigger.modifiers|lock_modifier, xid); +                    } else { +                        display.ungrab_key(binding.trigger.key_code, binding.trigger.modifiers|lock_modifier, xid); +                    }                   }                  remove_bindings.add(binding);              }          }          bindings.remove_all(remove_bindings); +        display.flush();      }      ///////////////////////////////////////////////////////////////////// @@ -144,15 +147,13 @@ public class BindingManager : GLib.Object {      /////////////////////////////////////////////////////////////////////      public string get_accelerator_label_of(string id) { -        string accelerator = this.get_accelerator_of(id); -         -        if (accelerator == "") -            return _("Not bound"); +        foreach (var binding in bindings) { +            if (binding.id == id) { +                return binding.trigger.label_with_specials; +            } +        } -        uint key = 0; -        Gdk.ModifierType mods; -        Gtk.accelerator_parse(accelerator, out key, out mods); -        return Gtk.accelerator_get_label(key, mods); +        return _("Not bound");      }      ///////////////////////////////////////////////////////////////////// @@ -162,7 +163,38 @@ public class BindingManager : GLib.Object {      public string get_accelerator_of(string id) {          foreach (var binding in bindings) {              if (binding.id == id) { -                return binding.accelerator; +                return binding.trigger.name; +            } +        } +         +        return ""; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns whether the pie with the given ID is in turbo mode. +    ///////////////////////////////////////////////////////////////////// +     +    public bool get_is_turbo(string id) { +        foreach (var binding in bindings) { +            if (binding.id == id) { +                return binding.trigger.turbo; +            } +        } +         +        return false; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the name ID of the Pie bound to the given Trigger. +    /// Returns "" if there is nothing bound to this trigger. +    ///////////////////////////////////////////////////////////////////// +     +    public string get_assigned_id(Trigger trigger) { +        foreach (var binding in bindings) { +            var first = binding.trigger.name.replace("[turbo]", "").replace("[delayed]", ""); +            var second = trigger.name.replace("[turbo]", "").replace("[delayed]", ""); +            if (first == second) { +                return binding.id;              }          } @@ -170,12 +202,10 @@ public class BindingManager : GLib.Object {      }      ///////////////////////////////////////////////////////////////////// -    /// Event filter method needed to fetch X.Events +    /// 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; -  +    private Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event) {           void* pointer = &gdk_xevent;          X.Event* xevent = (X.Event*) pointer; @@ -183,13 +213,92 @@ public class BindingManager : GLib.Object {              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); +                if(xevent->xkey.keycode == binding.trigger.key_code && event_mods == binding.trigger.modifiers) { +                    if (binding.trigger.delayed) { +                        this.activate_delayed(binding, *xevent); +                    } else { +                        on_press(binding.id); +                    }                  }              }           }  +         else if(xevent->type == X.EventType.ButtonPress) { +            foreach(var binding in bindings) { +                // remove NumLock, CapsLock and ScrollLock from key state +                uint event_mods = xevent.xbutton.state & ~ (lock_modifiers[7]); +                if(xevent->xbutton.button == binding.trigger.key_code && event_mods == binding.trigger.modifiers) { +                    if (binding.trigger.delayed) { +                        this.activate_delayed(binding, *xevent); +                    } else { +                        on_press(binding.id); +                    } +                } +            } +         } +         else if(xevent->type == X.EventType.ButtonRelease || xevent->type == X.EventType.KeyRelease) { +            this.activate_delayed(null, *xevent); +         }  -        return filter_return; +        return Gdk.FilterReturn.CONTINUE; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// This method is always called when a trigger is activated which is +    /// delayed. Therefore on_press() is only emitted, when this method +    /// is not called again within 300 milliseconds. Else a fake event is +    /// sent in order to simulate the actual key which has been pressed. +    ///////////////////////////////////////////////////////////////////// +     +    private void activate_delayed(Keybinding? binding , X.Event event) { +    	// increase event count, so any waiting event will realize that +    	// something happened in the meantime +        var current_count = ++this.delayed_count; +         +        if (binding == null && this.delayed_event != null) { +        	// if the trigger is released and an event is currently waiting +		    // simulate that the trigger has been pressed without any inter- +		    // ference of Gnome-Pie +            Gdk.Window rootwin = Gdk.get_default_root_window(); +       		X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin); +       		 +       		// unbind the trigger, else we'll capture that event again ;) +       		unbind(delayed_binding.id); +       		 +       		if (this.delayed_binding.trigger.with_mouse) { +       			// simulate mouse click +       			X.Test.fake_button_event(display, this.delayed_event.xbutton.button, true, 0); +       			display.flush(); +       			 +            	X.Test.fake_button_event(display, this.delayed_event.xbutton.button, false, 0); +            	display.flush(); +            	 +       		} else { +       			// simulate key press +       			X.Test.fake_key_event(display, this.delayed_event.xkey.keycode, true, 0); +       			display.flush(); +       			 +       			X.Test.fake_key_event(display, this.delayed_event.xkey.keycode, false, 0); +       			display.flush(); +       		} +             +            // bind it again +            bind(delayed_binding.trigger, delayed_binding.id); +        } else if (binding != null) { +        	// if the trigger has been pressed, store it and wait for any interuption +        	// within the next 300 milliseconds +            this.delayed_event = event; +            this.delayed_binding = binding; + +            Timeout.add(300, () => { +            	// if nothing has been pressed in the meantime +                if (current_count == this.delayed_count) { +                	this.delayed_binding = null; +                    this.delayed_event = null; +                    on_press(binding.id); +                } +                return false; +            }); +        }      }  } diff --git a/src/utilities/config.vala b/src/utilities/config.vala index c5dedd5..cf4311d 100644 --- a/src/utilities/config.vala +++ b/src/utilities/config.vala @@ -56,7 +56,6 @@ public class Config : GLib.Object {      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; } @@ -73,7 +72,6 @@ public class Config : GLib.Object {                  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();      } @@ -119,9 +117,6 @@ public class Config : GLib.Object {                          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; @@ -160,16 +155,17 @@ public class Config : GLib.Object {              // 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); +	            var theme = new Theme(Paths.global_themes + "/" + name); +	             +	            if (theme.load()) +	            	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) +                if (theme.load())                      themes.add(theme);              } @@ -185,7 +181,6 @@ public class Config : GLib.Object {              foreach (var t in themes) {                  if (t.name == current) {                      theme = t; -                    theme.load_images();                      break;                  }              } @@ -193,6 +188,7 @@ public class Config : GLib.Object {                  theme = themes[0];                  warning("Theme \"" + current + "\" not found! Using fallback...");              } +            theme.load_images();          }          else error("No theme found!");      } diff --git a/src/utilities/focusGrabber.vala b/src/utilities/focusGrabber.vala new file mode 100644 index 0000000..0e07b39 --- /dev/null +++ b/src/utilities/focusGrabber.vala @@ -0,0 +1,74 @@ +/*  +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 { + +/////////////////////////////////////////////////////////////////////////     +/// Some helper methods which focus the input on a given Gtk.Window. +///////////////////////////////////////////////////////////////////////// + +public class FocusGrabber : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Utilities for grabbing focus. +    /// Code from Gnome-Do/Synapse. +    ///////////////////////////////////////////////////////////////////// +     +    public static void grab(Gtk.Window window) { +        window.present_with_time(Gdk.CURRENT_TIME); +        window.get_window().raise(); +        window.get_window().focus(Gdk.CURRENT_TIME); + +        int i = 0; +        Timeout.add(100, () => { +            if (++i >= 100) return false; +            return !try_grab_window(window); +        }); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Code from Gnome-Do/Synapse. +    ///////////////////////////////////////////////////////////////////// +     +    public static void ungrab(Gtk.Window window) { +        Gdk.pointer_ungrab(Gdk.CURRENT_TIME); +        Gdk.keyboard_ungrab(Gdk.CURRENT_TIME); +        Gtk.grab_remove(window); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Code from Gnome-Do/Synapse. +    ///////////////////////////////////////////////////////////////////// +     +    private static bool try_grab_window(Gtk.Window window) { +        if (Gdk.pointer_grab(window.get_window(), true, Gdk.EventMask.BUTTON_PRESS_MASK |  +                             Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK, +                             null, null, Gdk.CURRENT_TIME) == Gdk.GrabStatus.SUCCESS) { +             +            if (Gdk.keyboard_grab(window.get_window(), true, Gdk.CURRENT_TIME) == Gdk.GrabStatus.SUCCESS) { +                Gtk.grab_add(window); +                return true; +            } else { +                Gdk.pointer_ungrab(Gdk.CURRENT_TIME); +                return false; +            } +        } +        return false; +    }   +} + +} diff --git a/src/utilities/trigger.vala b/src/utilities/trigger.vala new file mode 100644 index 0000000..1f6fcfe --- /dev/null +++ b/src/utilities/trigger.vala @@ -0,0 +1,255 @@ +/*  +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 represents a hotkey, used to open pies. It supports any +/// combination of modifier keys with keyboard and mouse buttons. +///////////////////////////////////////////////////////////////////////// + +public class Trigger : GLib.Object { + +    ///////////////////////////////////////////////////////////////////// +    /// Returns a human-readable version of this Trigger. +    ///////////////////////////////////////////////////////////////////// + +    public string label { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns a human-readable version of this Trigger. Small +    /// identifiers for turbo mode and delayed mode are added. +    ///////////////////////////////////////////////////////////////////// + +    public string label_with_specials { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The Trigger string. Like [delayed]<Control>button3 +    ///////////////////////////////////////////////////////////////////// +     +    public string name { get; private set; default=""; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The key code of the hotkey or the button number of the mouse. +    ///////////////////////////////////////////////////////////////////// +     +    public int key_code { get; private set; default=0; } +     +    ///////////////////////////////////////////////////////////////////// +    /// The keysym of the hotkey or the button number of the mouse. +    ///////////////////////////////////////////////////////////////////// +     +    public uint key_sym { get; private set; default=0; } +     +    ///////////////////////////////////////////////////////////////////// +    /// Modifier keys pressed for this hotkey. +    ///////////////////////////////////////////////////////////////////// +     +    public Gdk.ModifierType modifiers { get; private set; default=0; } +     +    ///////////////////////////////////////////////////////////////////// +    /// True if this hotkey involves the mouse. +    ///////////////////////////////////////////////////////////////////// +     +    public bool with_mouse { get; private set; default=false; } +     +    ///////////////////////////////////////////////////////////////////// +    /// True if the pie closes when the trigger hotkey is released. +    ///////////////////////////////////////////////////////////////////// +     +    public bool turbo { get; private set; default=false; } +     +    ///////////////////////////////////////////////////////////////////// +    /// True if the trigger should wait a short delay before being +    /// triggered. +    ///////////////////////////////////////////////////////////////////// +     +    public bool delayed { get; private set; default=false; } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new, "unbound" Trigger. +    ///////////////////////////////////////////////////////////////////// +     +    public Trigger() { +        this.set_unbound(); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new Trigger from a given Trigger string. This is +    /// in this format: "[option(s)]<modifier(s)>button" where +    /// "<modifier>" is something like "<Alt>" or "<Control>", "button" +    /// something like "s", "F4" or "button0" and "[option]" is either +    /// "[turbo]" or "["delayed"]". +    ///////////////////////////////////////////////////////////////////// +     +    public Trigger.from_string(string trigger) { +        this.parse_string(trigger); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// C'tor, creates a new Trigger from the key values. +    ///////////////////////////////////////////////////////////////////// +     +    public Trigger.from_values(uint key_sym, Gdk.ModifierType modifiers,  +                               bool with_mouse, bool turbo, bool delayed ) { +         +        string trigger = (turbo ? "[turbo]" : "") + (delayed ? "[delayed]" : ""); +         +        if (with_mouse) { +            trigger += Gtk.accelerator_name(0, modifiers) + "button%u".printf(key_sym); +        } else { +            trigger += Gtk.accelerator_name(key_sym, modifiers); +        } +         +        this.parse_string(trigger); +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Parses a Trigger string. This is +    /// in this format: "[option(s)]<modifier(s)>button" where +    /// "<modifier>" is something like "<Alt>" or "<Control>", "button" +    /// something like "s", "F4" or "button0" and "[option]" is either +    /// "[turbo]" or "["delayed"]". +    ///////////////////////////////////////////////////////////////////// +     +    public void parse_string(string trigger) { +        if (this.is_valid(trigger)) { +            // copy string +            string check_string = trigger; +         +            this.name = check_string; +             +            this.turbo = check_string.contains("[turbo]"); +            this.delayed = check_string.contains("[delayed]"); +             +            // remove optional arguments +            check_string = check_string.replace("[turbo]", ""); +            check_string = check_string.replace("[delayed]", ""); +             +            int button = this.get_mouse_button(check_string); +            if (button > 0) { +                this.with_mouse = true; +                this.key_code = button; +                this.key_sym = button; +                 +                Gtk.accelerator_parse(check_string, null, out this._modifiers); +                this.label = Gtk.accelerator_get_label(0, this.modifiers); +                 +                string button_text = _("Button %i").printf(this.key_code); +                 +                if (this.key_code == 1) +                    button_text = _("LeftButton"); +                else if (this.key_code == 3) +                    button_text = _("RightButton"); +                else if (this.key_code == 2) +                    button_text = _("MiddleButton"); +                 +                this.label += button_text; +            } else { +                this.with_mouse = false; +                 +                var display = new X.Display(); +                 +                uint keysym = 0; +                Gtk.accelerator_parse(check_string, out keysym, out this._modifiers); +                this.key_code = display.keysym_to_keycode(keysym); +                this.key_sym = keysym; +                this.label = Gtk.accelerator_get_label(keysym, this.modifiers); +            } +             +            this.label = this.label.replace("<", "<"); +            this.label = this.label.replace(">", ">"); +            this.label = this.label.replace("&", "&"); +             +            this.label_with_specials = this.label; +             +            if (this.turbo && this.delayed) +                this.label_with_specials += ("\n<small><span weight='light'>" + _("Turbo") + " | " + _("Delayed") + "</span></small>"); +            else if (this.turbo) +                this.label_with_specials += ("\n<small><span weight='light'>" + _("Turbo") + "</span></small>"); +            else if (this.delayed) +                this.label_with_specials += ("\n<small><span weight='light'>" + _("Delayed") + "</span></small>"); +             +        } else { +            this.set_unbound(); +        } +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Resets all member variables to their defaults. +    ///////////////////////////////////////////////////////////////////// +     +    private void set_unbound() { +        this.label = _("Not bound"); +        this.label_with_specials = _("Not bound"); +        this.name = ""; +        this.key_code = 0; +        this.key_sym = 0; +        this.modifiers = 0; +        this.turbo = false; +        this.delayed = false; +        this.with_mouse = false; +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns true, if the trigger string is in a valid format. +    ///////////////////////////////////////////////////////////////////// +     +    private bool is_valid(string trigger) { +        // copy string +        string check_string = trigger; +         +        // remove optional arguments +        check_string = check_string.replace("[turbo]", ""); +        check_string = check_string.replace("[delayed]", ""); +          +        if (this.get_mouse_button(check_string) > 0) { +            // it seems to be a valid mouse-trigger so replace button part, +            // with something accepted by gtk, and check it with gtk +            int button_index = check_string.index_of("button"); +            check_string = check_string.slice(0, button_index) + "a"; +        }  +         +        // now it shouls be a normal gtk accelerator +        uint keysym = 0; +        Gdk.ModifierType modifiers = 0; +        Gtk.accelerator_parse(check_string, out keysym, out modifiers); +        if (keysym == 0) +            return false; +         +        return true;  +    } +     +    ///////////////////////////////////////////////////////////////////// +    /// Returns the mouse button number of the given trigger string.  +    /// Returns -1 if it is not a mouse trigger. +    ///////////////////////////////////////////////////////////////////// +     +    private int get_mouse_button(string trigger) { +        if (trigger.contains("button")) { +            // it seems to be a mouse-trigger so check the button part. +            int button_index = trigger.index_of("button"); +            int number = int.parse(trigger.slice(button_index + 6, trigger.length));   +            if (number > 0)       +                return number; +        } +         +        return -1; +    } +} + +}  | 
