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; + } +} + +} |