summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlessandro Ghedini <al3xbio@gmail.com>2011-11-20 15:50:38 +0100
committerAlessandro Ghedini <al3xbio@gmail.com>2011-11-20 15:51:50 +0100
commit3997b71e281a5f43cc8c1892e85de30728df7b2d (patch)
tree96f2be7c48af03b7c540eba9dd6680eb270f23b8 /src
parent902829c62f552f35517783570025b479745de3c5 (diff)
Imported Upstream version 0.3.1
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/actionGroups/bookmarkGroup.vala2
-rw-r--r--src/actionGroups/clipboardGroup.vala1
-rw-r--r--src/actionGroups/groupRegistry.vala7
-rw-r--r--src/actionGroups/sessionGroup.vala9
-rw-r--r--src/actionGroups/windowListGroup.vala142
-rw-r--r--src/actions/keyAction.vala2
-rw-r--r--src/deamon.vala9
-rw-r--r--src/gui/about.vala34
-rw-r--r--src/gui/cellRendererTrigger.vala84
-rw-r--r--src/gui/iconSelectWindow.vala200
-rw-r--r--src/gui/pieList.vala40
-rw-r--r--src/gui/preferences.vala29
-rw-r--r--src/gui/triggerSelectWindow.vala257
-rw-r--r--src/images/icon.vala (renamed from src/utilities/icon.vala)0
-rw-r--r--src/images/image.vala (renamed from src/utilities/image.vala)0
-rw-r--r--src/images/renderedText.vala (renamed from src/utilities/renderedText.vala)0
-rw-r--r--src/images/themedIcon.vala (renamed from src/utilities/themedIcon.vala)0
-rw-r--r--src/pies/defaultConfig.vala12
-rw-r--r--src/pies/load.vala2
-rw-r--r--src/pies/pieManager.vala21
-rw-r--r--src/renderers/centerRenderer.vala55
-rw-r--r--src/renderers/pieRenderer.vala99
-rw-r--r--src/renderers/pieWindow.vala153
-rw-r--r--src/renderers/sliceRenderer.vala89
-rw-r--r--src/themes/theme.vala14
-rw-r--r--src/utilities/bindingManager.vala191
-rw-r--r--src/utilities/config.vala16
-rw-r--r--src/utilities/focusGrabber.vala74
-rw-r--r--src/utilities/trigger.vala255
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("<", "&lt;");
+ this.label = this.label.replace(">", "&gt;");
+ this.label = this.label.replace("&", "&amp;");
+
+ 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;
+ }
+}
+
+}