summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/aboutWindow.vala84
-rw-r--r--src/gui/iconSelectWindow.vala450
-rw-r--r--src/gui/indicator.vala180
-rw-r--r--src/gui/newSliceWindow.vala433
-rw-r--r--src/gui/newsWindow.vala73
-rw-r--r--src/gui/pieComboList.vala155
-rw-r--r--src/gui/pieList.vala275
-rw-r--r--src/gui/pieOptionsWindow.vala315
-rw-r--r--src/gui/piePreview.vala387
-rw-r--r--src/gui/piePreviewAddSign.vala224
-rw-r--r--src/gui/piePreviewCenter.vala109
-rw-r--r--src/gui/piePreviewDeleteSign.vala195
-rw-r--r--src/gui/piePreviewRenderer.vala443
-rw-r--r--src/gui/piePreviewSliceRenderer.vala276
-rw-r--r--src/gui/preferencesWindow.vala604
-rw-r--r--src/gui/sliceTypeList.vala173
-rw-r--r--src/gui/themeList.vala118
-rw-r--r--src/gui/tipViewer.vala163
-rw-r--r--src/gui/triggerSelectButton.vala163
19 files changed, 4820 insertions, 0 deletions
diff --git a/src/gui/aboutWindow.vala b/src/gui/aboutWindow.vala
new file mode 100644
index 0000000..73fb1be
--- /dev/null
+++ b/src/gui/aboutWindow.vala
@@ -0,0 +1,84 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A simple about dialog.
+/////////////////////////////////////////////////////////////////////////
+
+public class AboutWindow: Gtk.AboutDialog {
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new about dialog. The entries are sorted alpha-
+ /// betically.
+ /////////////////////////////////////////////////////////////////////
+
+ public AboutWindow () {
+ string[] devs = {
+ "Simon Schneegans <code@simonschneegans.de>",
+ "Gabriel Dubatti <gdubatti@gmail.com>",
+ "Francesco Piccinno <stack.box@gmail.com>",
+ "György Balló <ballogyor@gmail.com>"
+ };
+ string[] artists = {
+ "Simon Schneegans <code@simonschneegans.de>"
+ };
+ string[] translators = {
+ "Simon Schneegans <code@simonschneegans.de> (DE, EN)",
+ "Riccardo Traverso <gr3yfox.fw@gmail.com> (IT)",
+ "Magnun Leno <magnun@codecommunity.org> (PT-BR)",
+ "Kim Boram <Boramism@gmail.com> (KO)",
+ "Eduardo Anabalon <lalo1412@gmail.com> (ES)",
+ "Moo <hazap@hotmail.com> (LT)",
+ "Gabriel Dubatti <gdubatti@gmail.com> (ES)",
+ "Grégoire Bellon-Gervais <greggbg@gmail.com> (FR)",
+ "Raphaël Rochet <raphael@rri.fr> (FR)",
+ "Alex Maxime <cad.maxime@gmail.com> (FR)",
+ "Eugene Roskin <pams@imail.ru> (RU)",
+ "Ting Zhou <tzhou@haverford.edu> (ZH-CN)",
+ "Martin Dinov <martindinov@yahoo.com> (BG)"
+ };
+
+ // sort translators
+ GLib.List<string> translator_list = new GLib.List<string>();
+ foreach (var translator in translators)
+ translator_list.append(translator);
+
+ translator_list.sort((a, b) => {
+ return a.ascii_casecmp(b);
+ });
+
+ string translator_string = "";
+ foreach (var translator in translator_list)
+ translator_string += translator + "\n";
+
+ GLib.Object (
+ artists : artists,
+ authors : devs,
+ translator_credits : translator_string,
+ copyright : "Copyright (C) 2011-2015 Simon Schneegans <code@simonschneegans.de>",
+ program_name: "Gnome-Pie",
+ logo_icon_name: "gnome-pie",
+ website: "http://simmesimme.github.io/gnome-pie.html",
+ website_label: "Homepage",
+ version: Daemon.version
+ );
+ }
+}
+
+}
diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala
new file mode 100644
index 0000000..ce610ea
--- /dev/null
+++ b/src/gui/iconSelectWindow.vala
@@ -0,0 +1,450 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A window which allows selection of an Icon of the user's current icon
+/// theme. Custom icons/images can be selested as well. Loading of icons
+/// happens in an extra thread and a spinner is displayed while loading.
+/////////////////////////////////////////////////////////////////////////
+
+public class IconSelectWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new icon.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_ok(string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the currently selected icon.
+ /////////////////////////////////////////////////////////////////////
+
+ private string active_icon = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ListStore storing all theme-icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private static Gtk.ListStore icon_list = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the icon theme is currently reloaded.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool loading = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// If set to true, the icon list will be reloaded next time the
+ /// window opens.
+ /////////////////////////////////////////////////////////////////////
+
+ private static bool need_reload = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Icons of these contexts won't appear in the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private const string disabled_contexts = "Animations, FileSystems";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The list of icons, filtered according to the chosen type and
+ /// filter string.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeModelFilter icon_list_filtered = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Gtk widget displaying the icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.IconView icon_view = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// This spinner is displayed when the icons are loaded.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Spinner spinner = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A Gtk widget used for custom icon/image selection.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.FileChooserWidget file_chooser = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The notebook containing the different icon choice possibilities:
+ /// from the theme or custom.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Notebook tabs = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The main window.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Window window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A little structure containing data for one icon in the icon_view.
+ /////////////////////////////////////////////////////////////////////
+
+ private class ListEntry {
+ public string name;
+ public IconContext context;
+ public Gdk.Pixbuf pixbuf;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// This queue is used for icon loading. A loading thread pushes
+ /// icons into it --- the main thread updates the icon_view
+ /// accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.AsyncQueue<ListEntry?> load_queue;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Possible icon types.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum IconContext {
+ ALL,
+ APPS,
+ ACTIONS,
+ PLACES,
+ FILES,
+ EMOTES,
+ OTHER
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates a new IconSelectWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public IconSelectWindow(Gtk.Window parent) {
+ try {
+ this.load_queue = new GLib.AsyncQueue<ListEntry?>();
+
+ if (IconSelectWindow.icon_list == null) {
+ IconSelectWindow.icon_list = new Gtk.ListStore(3, typeof(string), // icon name
+ typeof(IconContext), // icon type
+ typeof(Gdk.Pixbuf)); // the icon itself
+
+ // disable sorting until all icons are loaded
+ // else loading becomes horribly slow
+ IconSelectWindow.icon_list.set_default_sort_func(() => {return 0;});
+
+ // reload if icon theme changes
+ Gtk.IconTheme.get_default().changed.connect(() => {
+ if (this.window.visible) load_icons();
+ else IconSelectWindow.need_reload = true;
+ });
+ }
+
+ // make the icon_view filterable
+ this.icon_list_filtered = new Gtk.TreeModelFilter(IconSelectWindow.icon_list, null);
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/icon_select.ui");
+
+ this.window = builder.get_object("window") as Gtk.Window;
+ this.window.set_transient_for(parent);
+ this.window.set_modal(true);
+
+ this.tabs = builder.get_object("tabs") as Gtk.Notebook;
+
+ this.spinner = builder.get_object("spinner") as Gtk.Spinner;
+ this.spinner.start();
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(on_cancel_button_clicked);
+
+ var combo_box = builder.get_object("combo-box") as Gtk.Box;
+
+ // context combo
+ var context_combo = new Gtk.ComboBoxText();
+ context_combo.append_text(_("All icons"));
+ context_combo.append_text(_("Applications"));
+ context_combo.append_text(_("Actions"));
+ context_combo.append_text(_("Places"));
+ context_combo.append_text(_("File types"));
+ context_combo.append_text(_("Emotes"));
+ context_combo.append_text(_("Miscellaneous"));
+
+ context_combo.set_active(0);
+
+ context_combo.changed.connect(() => {
+ this.icon_list_filtered.refilter();
+ });
+
+ combo_box.pack_start(context_combo, false, false);
+
+ // string filter entry
+ var filter = builder.get_object("filter-entry") as Gtk.Entry;
+
+ // only display items which have the selected type
+ // and whose name contains the text entered in the entry
+ this.icon_list_filtered.set_visible_func((model, iter) => {
+ string name = "";
+ IconContext context = IconContext.ALL;
+ model.get(iter, 0, out name);
+ model.get(iter, 1, out context);
+
+ if (name == null) return false;
+
+ return (context_combo.get_active() == context ||
+ context_combo.get_active() == IconContext.ALL) &&
+ name.down().contains(filter.text.down());
+ });
+
+ // clear when the users clicks on the "clear" icon
+ filter.icon_release.connect((pos, event) => {
+ if (pos == Gtk.EntryIconPosition.SECONDARY)
+ filter.text = "";
+ });
+
+ // refilter on input
+ filter.notify["text"].connect(() => {
+ this.icon_list_filtered.refilter();
+ });
+
+ // container for the icon_view
+ var scroll = builder.get_object("icon-scrolledwindow") as Gtk.ScrolledWindow;
+
+ // displays the filtered icons
+ this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered);
+ this.icon_view.item_width = 32;
+ this.icon_view.item_padding = 2;
+ this.icon_view.pixbuf_column = 2;
+ this.icon_view.tooltip_column = 0;
+
+ // set active_icon if selection changes
+ this.icon_view.selection_changed.connect(() => {
+ foreach (var path in this.icon_view.get_selected_items()) {
+ Gtk.TreeIter iter;
+ this.icon_list_filtered.get_iter(out iter, path);
+ this.icon_list_filtered.get(iter, 0, out this.active_icon);
+ }
+ });
+
+ // hide this window when the user activates an icon
+ this.icon_view.item_activated.connect((path) => {
+ Gtk.TreeIter iter;
+ this.icon_list_filtered.get_iter(out iter, path);
+ this.icon_list_filtered.get(iter, 0, out this.active_icon);
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ });
+
+ scroll.add(this.icon_view);
+
+ // file chooser widget
+ this.file_chooser = builder.get_object("filechooser") as Gtk.FileChooserWidget;
+ var file_filter = new Gtk.FileFilter();
+ file_filter.add_pixbuf_formats();
+ file_filter.set_filter_name(_("All supported image formats"));
+ file_chooser.add_filter(file_filter);
+
+ // set active_icon if the user selected a file
+ file_chooser.selection_changed.connect(() => {
+ if (file_chooser.get_filename() != null &&
+ GLib.FileUtils.test(file_chooser.get_filename(),
+ GLib.FileTest.IS_REGULAR))
+
+ this.active_icon = file_chooser.get_filename();
+ });
+
+ // hide this window when the user activates a file
+ file_chooser.file_activated.connect(() => {
+ this.active_icon = file_chooser.get_filename();
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ });
+
+ this.window.set_focus(this.icon_view);
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays the window. The icons are reloaded if neccessary.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.window.show_all();
+ this.spinner.hide();
+
+ if (IconSelectWindow.need_reload) {
+ IconSelectWindow.need_reload = false;
+ this.load_icons();
+ }
+ }
+
+ public static void clear_icons() {
+ if (IconSelectWindow.icon_list != null) {
+ IconSelectWindow.need_reload = true;
+ IconSelectWindow.icon_list.clear();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the window select the icon of the given Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_icon(string icon) {
+ this.active_icon = icon;
+
+ if (icon.contains("/")) {
+ this.file_chooser.set_filename(icon);
+ this.tabs.set_current_page(1);
+ } else {
+ this.icon_list_filtered.foreach((model, path, iter) => {
+ string name = "";
+ model.get(iter, 0, out name);
+
+ if (name == icon) {
+ this.icon_view.select_path(path);
+ this.icon_view.scroll_to_path(path, true, 0.5f, 0.0f);
+ this.icon_view.set_cursor(path, null, false);
+ }
+ return (name == icon);
+ });
+
+ this.tabs.set_current_page(0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicks the ok button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ this.on_ok(this.active_icon);
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicks the cancel button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// (Re)load all icons.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_icons() {
+ // only if it's not loading currently
+ if (!IconSelectWindow.loading) {
+ IconSelectWindow.loading = true;
+ IconSelectWindow.icon_list.clear();
+
+ // display the spinner
+ if (spinner != null)
+ this.spinner.visible = true;
+
+ // disable sorting of the icon_view - else it's horribly slow
+ IconSelectWindow.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING);
+
+ this.load_all.begin();
+
+ // insert loaded icons every 200 ms
+ Timeout.add(200, () => {
+ while (this.load_queue.length() > 0) {
+ var new_entry = this.load_queue.pop();
+ Gtk.TreeIter current;
+ IconSelectWindow.icon_list.append(out current);
+ IconSelectWindow.icon_list.set(current, 0, new_entry.name,
+ 1, new_entry.context,
+ 2, new_entry.pixbuf);
+ }
+
+ // enable sorting of the icon_view if loading finished
+ if (!IconSelectWindow.loading) {
+ IconSelectWindow.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING);
+ }
+
+ return loading;
+ });
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all icons of an icon theme and pushes them into the
+ /// load_queue.
+ /////////////////////////////////////////////////////////////////////
+
+ private async void load_all() {
+ var icon_theme = Gtk.IconTheme.get_default();
+
+ foreach (var context in icon_theme.list_contexts()) {
+ if (!disabled_contexts.contains(context)) {
+ foreach (var icon in icon_theme.list_icons(context)) {
+
+ IconContext icon_context = IconContext.OTHER;
+ switch(context) {
+ case "Apps": case "Applications":
+ icon_context = IconContext.APPS; break;
+ case "Emotes":
+ icon_context = IconContext.EMOTES; break;
+ case "Places": case "Devices":
+ icon_context = IconContext.PLACES; break;
+ case "Mimetypes":
+ icon_context = IconContext.FILES; break;
+ case "Actions":
+ icon_context = IconContext.ACTIONS; break;
+ default: break;
+ }
+
+ Idle.add(load_all.callback);
+ yield;
+
+ try {
+ // create a new entry for the queue
+ var new_entry = new ListEntry();
+ new_entry.name = icon;
+ new_entry.context = icon_context;
+ new_entry.pixbuf = icon_theme.load_icon(icon, 32, 0);
+
+ // some icons have only weird sizes... do not include them
+ if (new_entry.pixbuf.width == 32)
+ this.load_queue.push(new_entry);
+
+ } catch (GLib.Error e) {
+ warning("Failed to load image " + icon);
+ }
+ }
+ }
+ }
+
+ // finished loading
+ IconSelectWindow.loading = false;
+
+ // hide the spinner
+ if (spinner != null)
+ spinner.visible = false;
+ }
+}
+
+}
diff --git a/src/gui/indicator.vala b/src/gui/indicator.vala
new file mode 100644
index 0000000..ddb85e6
--- /dev/null
+++ b/src/gui/indicator.vala
@@ -0,0 +1,180 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// An appindicator sitting in the panel. It owns the settings menu.
+/////////////////////////////////////////////////////////////////////////
+
+public class Indicator : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The internally used indicator.
+ /////////////////////////////////////////////////////////////////////
+
+ #if HAVE_APPINDICATOR
+ private AppIndicator.Indicator indicator { private get; private set; }
+ #else
+ private Gtk.StatusIcon indicator {private get; private set; }
+ private Gtk.Menu menu {private get; private set; }
+ #endif
+
+ /////////////////////////////////////////////////////////////////////
+ /// The Preferences Menu of Gnome-Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private PreferencesWindow prefs { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns true, when the indicator is currently visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool active {
+ get {
+ #if HAVE_APPINDICATOR
+ return indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE;
+ #else
+ return indicator.get_visible();
+ #endif
+ }
+ set {
+ #if HAVE_APPINDICATOR
+ if (value) indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
+ else indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE);
+ #else
+ indicator.set_visible(value);
+ #endif
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs a new Indicator, residing in the user's panel.
+ /////////////////////////////////////////////////////////////////////
+
+ public Indicator() {
+ string icon = "gnome-pie-symbolic";
+ var screen = (Gdk.X11.Screen)Gdk.Screen.get_default();
+ bool gnome_shell = false;
+
+ if (screen.get_window_manager_name() == "GNOME Shell") {
+ icon = "gnome-pie";
+ gnome_shell = true;
+ }
+
+ #if HAVE_APPINDICATOR
+
+ string path = "";
+ try {
+ path = GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources";
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ }
+
+ if (gnome_shell) {
+
+ if (GLib.File.new_for_path(path).query_exists()) {
+ this.indicator = new AppIndicator.Indicator("Gnome-Pie", path + "/" + icon + ".svg",
+ AppIndicator.IndicatorCategory.APPLICATION_STATUS);
+ } else {
+ this.indicator = new AppIndicator.Indicator("Gnome-Pie", icon,
+ AppIndicator.IndicatorCategory.APPLICATION_STATUS);
+ }
+ } else {
+ this.indicator = new AppIndicator.Indicator.with_path("Gnome-Pie", icon,
+ AppIndicator.IndicatorCategory.APPLICATION_STATUS, path);
+ }
+ var menu = new Gtk.Menu();
+ #else
+ this.indicator = new Gtk.StatusIcon();
+ try {
+ var file = GLib.File.new_for_path(GLib.Path.build_filename(
+ GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources",
+ icon + ".svg"
+ ));
+
+ if (!file.query_exists())
+ this.indicator.set_from_icon_name(icon);
+ else
+ this.indicator.set_from_file(file.get_path());
+ } catch (GLib.FileError e) {
+ warning("Failed to get path of executable!");
+ this.indicator.set_from_icon_name(icon);
+ }
+
+ this.menu = new Gtk.Menu();
+ var menu = this.menu;
+ #endif
+
+ this.prefs = new PreferencesWindow();
+
+ // preferences item
+ var item = new Gtk.ImageMenuItem.with_mnemonic(_("_Preferences"));
+ item.activate.connect(() => {
+ this.prefs.show();
+ });
+
+ item.show();
+ menu.append(item);
+
+ // about item
+ item = new Gtk.ImageMenuItem.with_mnemonic(_("_About"));
+ item.show();
+ item.activate.connect(() => {
+ var about = new AboutWindow();
+ about.run();
+ about.destroy();
+ });
+ menu.append(item);
+
+ // separator
+ var sepa = new Gtk.SeparatorMenuItem();
+ sepa.show();
+ menu.append(sepa);
+
+ // quit item
+ item = new Gtk.ImageMenuItem.with_mnemonic(_("_Quit"));
+ item.activate.connect(()=>{
+ GLib.Application.get_default().release();
+ });
+ item.show();
+ menu.append(item);
+
+ #if HAVE_APPINDICATOR
+ this.indicator.set_menu(menu);
+ #else
+ this.indicator.popup_menu.connect((btn, time) => {
+ this.menu.popup(null, null, null, btn, time);
+ });
+ #endif
+
+ this.active = Config.global.show_indicator;
+ Config.global.notify["show-indicator"].connect((s, p) => {
+ this.active = Config.global.show_indicator;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows the preferences menu.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show_preferences() {
+ this.prefs.show();
+ }
+}
+
+}
diff --git a/src/gui/newSliceWindow.vala b/src/gui/newSliceWindow.vala
new file mode 100644
index 0000000..89294b5
--- /dev/null
+++ b/src/gui/newSliceWindow.vala
@@ -0,0 +1,433 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A window which allows selection of a new Slice which is about to be
+/// added to a Pie. It can be also used to edit an existing Slice
+/////////////////////////////////////////////////////////////////////////
+
+public class NewSliceWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user confirms his selection.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(ActionGroup action, bool as_new_slice, int at_position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The contained list of slice types. It contains both: Groups and
+ /// single actions.
+ /////////////////////////////////////////////////////////////////////
+
+ private SliceTypeList slice_type_list = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The IconSelectWindow used for icon selection for a Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private IconSelectWindow? icon_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some widgets of this window. Loaded by a ui-builder and stored
+ /// for later access.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Dialog window = null;
+ private Gtk.Box name_box = null;
+ private Gtk.Box command_box = null;
+ private Gtk.Button icon_button = null;
+ private Gtk.Box no_options_box = null;
+ private Gtk.Box pie_box = null;
+ private Gtk.Box hotkey_box = null;
+ private Gtk.Box uri_box = null;
+ private Gtk.Box quickaction_box = null;
+ private Gtk.Box clipboard_box = null;
+ private Gtk.Box workspace_only_box = null;
+ private Gtk.Image icon = null;
+ private Gtk.Entry name_entry = null;
+ private Gtk.Entry command_entry = null;
+ private Gtk.Entry uri_entry = null;
+ private Gtk.Switch quickaction_checkbutton = null;
+ private Gtk.Switch workspace_only_checkbutton = null;
+ private Gtk.Scale clipboard_slider = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Two custom widgets. For Pie and hotkey selection respectively.
+ /////////////////////////////////////////////////////////////////////
+
+ private PieComboList pie_select = null;
+ private TriggerSelectButton key_select = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// These members store information on the currently selected Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_type = "";
+ private string current_icon = "";
+ private string current_id = "";
+ private string current_custom_icon = "";
+ private string current_hotkey = "";
+ private string current_pie_to_open = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position of the edited Slice in its parent Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private int slice_position = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if the Slice i going to be added as a new Slice. Else it
+ /// will edit the Slice at slice_position in its parent Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool add_as_new_slice = true;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor creates a new window.
+ /////////////////////////////////////////////////////////////////////
+
+ public NewSliceWindow() {
+ try {
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/slice_select.ui");
+
+ this.slice_type_list = new SliceTypeList();
+ this.slice_type_list.on_select.connect((type, icon) => {
+
+ this.name_box.hide();
+ this.command_box.hide();
+ this.icon_button.sensitive = false;
+ this.no_options_box.hide();
+ this.pie_box.hide();
+ this.hotkey_box.hide();
+ this.uri_box.hide();
+ this.quickaction_box.hide();
+ this.workspace_only_box.hide();
+ this.clipboard_box.hide();
+
+ this.current_type = type;
+
+ switch (type) {
+ case "bookmarks": case "devices":
+ case "menu": case "session":
+ this.no_options_box.show();
+ this.set_icon(icon);
+ break;
+ case "window_list":
+ this.workspace_only_box.show();
+ this.set_icon(icon);
+ break;
+ case "clipboard":
+ this.clipboard_box.show();
+ this.set_icon(icon);
+ break;
+ case "app":
+ this.name_box.show();
+ this.command_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ case "key":
+ this.name_box.show();
+ this.hotkey_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ case "pie":
+ this.pie_box.show();
+ this.quickaction_box.show();
+ this.set_icon(PieManager.all_pies[this.pie_select.current_id].icon);
+ break;
+ case "uri":
+ this.name_box.show();
+ this.uri_box.show();
+ this.quickaction_box.show();
+ this.icon_button.sensitive = true;
+ if (this.current_custom_icon == "") this.set_icon(icon);
+ else this.set_icon(this.current_custom_icon);
+ break;
+ }
+ });
+
+ this.name_box = builder.get_object("name-box") as Gtk.Box;
+ this.command_box = builder.get_object("command-box") as Gtk.Box;
+ this.icon_button = builder.get_object("icon-button") as Gtk.Button;
+ this.no_options_box = builder.get_object("no-options-box") as Gtk.Box;
+ this.pie_box = builder.get_object("pie-box") as Gtk.Box;
+ this.pie_select = new PieComboList();
+ this.pie_select.on_select.connect((id) => {
+ this.current_pie_to_open = id;
+ this.set_icon(PieManager.all_pies[id].icon);
+ });
+
+ this.pie_box.pack_start(this.pie_select, true, true);
+
+ this.hotkey_box = builder.get_object("hotkey-box") as Gtk.Box;
+ this.key_select = new TriggerSelectButton(false);
+ this.hotkey_box.pack_start(this.key_select, false, true);
+ this.key_select.on_select.connect((trigger) => {
+ this.current_hotkey = trigger.name;
+ });
+
+ this.uri_box = builder.get_object("uri-box") as Gtk.Box;
+
+ this.name_entry = builder.get_object("name-entry") as Gtk.Entry;
+ this.uri_entry = builder.get_object("uri-entry") as Gtk.Entry;
+ this.command_entry = builder.get_object("command-entry") as Gtk.Entry;
+ this.quickaction_checkbutton = builder.get_object("quick-action-checkbutton") as Gtk.Switch;
+ this.quickaction_box = builder.get_object("quickaction-box") as Gtk.Box;
+ this.icon = builder.get_object("icon") as Gtk.Image;
+
+ this.workspace_only_checkbutton = builder.get_object("workspace-only-checkbutton") as Gtk.Switch;
+ this.workspace_only_box = builder.get_object("workspace-only-box") as Gtk.Box;
+
+ this.clipboard_box = builder.get_object("clipboard-box") as Gtk.Box;
+ this.clipboard_slider = (builder.get_object("clipboard-scale") as Gtk.Scale);
+ clipboard_slider.set_range(2, 24);
+ clipboard_slider.set_value(8);
+
+ this.icon_button.clicked.connect(on_icon_button_clicked);
+
+ var scroll_area = builder.get_object("slice-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.slice_type_list);
+
+ this.window = builder.get_object("window") as Gtk.Dialog;
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(on_cancel_button_clicked);
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the parent window, in order to make this window stay in
+ /// front.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_parent(Gtk.Window parent) {
+ this.window.set_transient_for(parent);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sows the window on the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.slice_type_list.select_first();
+ this.pie_select.select_first();
+ this.key_select.set_trigger(new Trigger());
+ this.window.show_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload() {
+ this.pie_select.reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes all widgets display stuff according to the given action.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_action(ActionGroup group, int position) {
+ this.set_default(group.parent_id, position);
+
+ this.add_as_new_slice = false;
+ string type = "";
+
+ if (group.get_type().depth() == 2) {
+ var action = group.actions[0];
+ type = ActionRegistry.descriptions[action.get_type().name()].id;
+ this.select_type(type);
+
+ this.set_icon(action.icon);
+ this.quickaction_checkbutton.active = action.is_quickaction;
+ this.name_entry.text = action.name;
+
+ switch (type) {
+ case "app":
+ this.current_custom_icon = action.icon;
+ this.command_entry.text = action.real_command;
+ break;
+ case "key":
+ this.current_custom_icon = action.icon;
+ this.current_hotkey = action.real_command;
+ this.key_select.set_trigger(new Trigger.from_string(action.real_command));
+ break;
+ case "pie":
+ this.pie_select.select(action.real_command);
+ break;
+ case "uri":
+ this.current_custom_icon = action.icon;
+ this.uri_entry.text = action.real_command;
+ break;
+ }
+
+ } else {
+ type = GroupRegistry.descriptions[group.get_type().name()].id;
+ switch (type) {
+ case "clipboard":
+ this.clipboard_slider.set_value((group as ClipboardGroup).max_items);
+ break;
+ case "window_list":
+ this.workspace_only_checkbutton.active = (group as WindowListGroup).current_workspace_only;
+ break;
+
+ }
+ this.select_type(type);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects a default action.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_default(string pie_id, int position) {
+ this.slice_position = position;
+ this.add_as_new_slice = true;
+ this.current_custom_icon = "";
+ this.select_type("app");
+ this.current_id = pie_id;
+ this.key_select.set_trigger(new Trigger());
+ this.pie_select.select_first();
+ this.name_entry.text = _("Rename me!");
+ this.command_entry.text = "";
+ this.uri_entry.text = "";
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects a specific action type.
+ /////////////////////////////////////////////////////////////////////
+
+ private void select_type(string type) {
+ this.current_type = type;
+ this.slice_type_list.select(type);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called, when the user presses the ok button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ this.window.hide();
+
+ ActionGroup group = null;
+
+ switch (this.current_type) {
+ case "bookmarks": group = new BookmarkGroup(this.current_id); break;
+ case "devices": group = new DevicesGroup(this.current_id); break;
+ case "menu": group = new MenuGroup(this.current_id); break;
+ case "session": group = new SessionGroup(this.current_id); break;
+ case "clipboard":
+ var g = new ClipboardGroup(this.current_id);
+ g.max_items = (int)this.clipboard_slider.get_value();
+ group = g;
+ break;
+ case "window_list":
+ var g = new WindowListGroup(this.current_id);
+ g.current_workspace_only = this.workspace_only_checkbutton.active;
+ group = g;
+ break;
+ case "app":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new AppAction(this.name_entry.text, this.current_icon,
+ this.command_entry.text,
+ this.quickaction_checkbutton.active));
+ break;
+ case "key":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new KeyAction(this.name_entry.text, this.current_icon,
+ this.current_hotkey,
+ this.quickaction_checkbutton.active));
+ break;
+ case "pie":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new PieAction(this.current_pie_to_open,
+ this.quickaction_checkbutton.active));
+ break;
+ case "uri":
+ group = new ActionGroup(this.current_id);
+ group.add_action(new UriAction(this.name_entry.text, this.current_icon,
+ this.uri_entry.text,
+ this.quickaction_checkbutton.active));
+ break;
+ }
+
+ this.on_select(group, this.add_as_new_slice, this.slice_position);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses the cancel button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses the icon select button.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_icon_button_clicked(Gtk.Button button) {
+ if (this.icon_window == null) {
+ this.icon_window = new IconSelectWindow(this.window);
+ this.icon_window.on_ok.connect((icon) => {
+ this.current_custom_icon = icon;
+ this.set_icon(icon);
+ });
+ }
+
+ this.icon_window.show();
+ this.icon_window.set_icon(this.current_icon);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which sets the icon of the icon select button.
+ /// It assures that both can be displayed: A customly chosen image
+ /// from or an icon from the current theme.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_icon(string icon) {
+ if (icon.contains("/"))
+ try {
+ this.icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale(icon, this.icon.get_pixel_size(),
+ this.icon.get_pixel_size(), true);
+ } catch (GLib.Error error) {
+ warning(error.message);
+ }
+ else
+ this.icon.icon_name = icon;
+
+ this.current_icon = icon;
+ }
+}
+
+}
diff --git a/src/gui/newsWindow.vala b/src/gui/newsWindow.vala
new file mode 100644
index 0000000..cc1a77d
--- /dev/null
+++ b/src/gui/newsWindow.vala
@@ -0,0 +1,73 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////
+
+public class NewsWindow: Gtk.Dialog {
+
+ public static const int news_count = 2;
+
+ /////////////////////////////////////////////////////////////////////
+ ///
+ /////////////////////////////////////////////////////////////////////
+
+ public NewsWindow () {
+ this.title = "Gnome-Pie";
+
+ this.set_border_width(5);
+
+ var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
+
+ var image = new Gtk.Image.from_icon_name("gnome-pie", Gtk.IconSize.DIALOG);
+ box.pack_start(image);
+
+ var news = new Gtk.Label("");
+ news.wrap = true;
+ news.set_width_chars(75);
+ news.set_markup("<b>Thank you!</b>\n\n");
+
+ box.pack_start(news, false, false);
+
+ var check = new Gtk.CheckButton.with_label("Don't show this window again.");
+ check.toggled.connect((check_box) => {
+ var checky = check_box as Gtk.CheckButton;
+
+ if (checky.active) Config.global.showed_news = news_count;
+ else Config.global.showed_news = news_count-1;
+
+ Config.global.save();
+ });
+
+ box.pack_end(check);
+
+ (this.get_content_area() as Gtk.VBox).pack_start(box);
+ this.get_content_area().show_all();
+
+ this.add_button(_("_Close"), 0);
+
+ this.response.connect((id) => {
+ if (id == 0)
+ this.hide();
+ });
+ }
+}
+
+}
diff --git a/src/gui/pieComboList.vala b/src/gui/pieComboList.vala
new file mode 100644
index 0000000..f0fd22f
--- /dev/null
+++ b/src/gui/pieComboList.vala
@@ -0,0 +1,155 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A drop-down list, containing one entry for each existing Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class PieComboList : Gtk.ComboBox {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently selected row.
+ /////////////////////////////////////////////////////////////////////
+
+ public string current_id { get; private set; default=""; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the data internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieComboList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(3, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(1, Gtk.SortType.ASCENDING);
+
+ base.set_model(this.data);
+
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ this.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ this.pack_start(name_render, true);
+
+ this.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ this.add_attribute(name_render, "text", DataPos.NAME);
+
+ this.changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_active_iter(out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ this.current_id = id;
+ }
+ });
+
+ reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all existing Pies to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload() {
+ Gtk.TreeIter active;
+ string id = "";
+ if (this.get_active_iter(out active))
+ this.data.get(active, DataPos.ID, out id);
+
+ data.clear();
+ foreach (var pie in PieManager.all_pies.entries) {
+ this.load_pie(pie.value);
+ }
+
+ select_first();
+ select(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.set_active_iter(active);
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ this.current_id = id;
+ } else {
+ this.on_select("");
+ this.current_id = "";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.set_active_iter(iter);
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads one given pie to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_pie(Pie pie) {
+ if (pie.id.length == 3) {
+ Gtk.TreeIter last;
+ this.data.append(out last);
+ var icon = new Icon(pie.icon, 24);
+ this.data.set(last, DataPos.ICON, icon.to_pixbuf(),
+ DataPos.NAME, pie.name,
+ DataPos.ID, pie.id);
+ }
+ }
+}
+
+}
diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala
new file mode 100644
index 0000000..77f833b
--- /dev/null
+++ b/src/gui/pieList.vala
@@ -0,0 +1,275 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A list, containing one entry for each existing Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class PieList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id);
+ public signal void on_activate();
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the data internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, ICON_NAME, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores where a drag startet.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeIter? drag_start = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Rembers the time when a last drag move event was reported. Used
+ /// to avoid frequent changes of selected Pie when a Pie is dragged
+ /// over this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private uint last_hover = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(4, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(DataPos.NAME, Gtk.SortType.ASCENDING);
+
+ this.set_model(this.data);
+ this.set_headers_visible(true);
+ this.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.width_request = 170;
+ this.set_enable_search(false);
+
+ this.set_events(Gdk.EventMask.POINTER_MOTION_MASK);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.title = _("Pies");
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ icon_render.ypad = 4;
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ name_render.ellipsize = Pango.EllipsizeMode.END;
+ name_render.ellipsize_set = true;
+ main_column.pack_start(name_render, true);
+
+ base.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ // setup drag'n'drop
+ Gtk.TargetEntry uri_source = {"text/uri-list", 0, 0};
+ Gtk.TargetEntry[] entries = { uri_source };
+ this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK);
+ this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK);
+ this.drag_data_get.connect(this.on_dnd_source);
+ this.drag_data_received.connect(this.on_dnd_received);
+ this.drag_begin.connect_after(this.on_start_drag);
+ this.drag_motion.connect(this.on_drag_move);
+ this.drag_leave.connect(() => {
+ this.last_hover = 0;
+ });
+
+ this.row_activated.connect(() => {
+ this.on_activate();
+ });
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ }
+ });
+
+ reload_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads all existing Pies to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload_all() {
+ Gtk.TreeIter active;
+ string id = "";
+ if (this.get_selection().get_selected(null, out active))
+ this.data.get(active, DataPos.ID, out id);
+
+ data.clear();
+ foreach (var pie in PieManager.all_pies.entries) {
+ this.load_pie(pie.value);
+ }
+
+ select(id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.get_selection().select_iter(active);
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.on_select(id);
+ } else {
+ this.on_select("");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.get_selection().select_iter(iter);
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads one given pie to the list.
+ /////////////////////////////////////////////////////////////////////
+
+ private void load_pie(Pie pie) {
+ if (pie.id.length == 3) {
+ Gtk.TreeIter last;
+ this.data.append(out last);
+ var icon = new Icon(pie.icon, 24);
+ this.data.set(last, DataPos.ICON, icon.to_pixbuf(),
+ DataPos.ICON_NAME, pie.icon,
+ DataPos.NAME,GLib.Markup.escape_text(pie.name) + "\n" +
+ "<span font-size='x-small'>" + PieManager.get_accelerator_label_of(pie.id) + "</span>",
+ DataPos.ID, pie.id);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a drag which started on this Widget was successfull.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) {
+ if (this.drag_start != null) {
+ string id = "";
+ this.data.get(this.drag_start, DataPos.ID, out id);
+ selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"});
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a drag operation is started on this Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_start_drag(Gdk.DragContext ctx) {
+ if (this.get_selection().get_selected(null, out this.drag_start)) {
+ string icon_name = "";
+ this.data.get(this.drag_start, DataPos.ICON_NAME, out icon_name);
+
+ var icon = new Icon(icon_name, 48);
+ var pixbuf = icon.to_pixbuf();
+ Gtk.drag_set_icon_pixbuf(ctx, pixbuf, icon.size()/2, icon.size()/2);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when something is dragged over this Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_drag_move(Gdk.DragContext context, int x, int y, uint time) {
+
+ Gtk.TreeViewDropPosition position;
+ Gtk.TreePath path;
+
+ if (!this.get_dest_row_at_pos(x, y, out path, out position))
+ return false;
+
+ if (position == Gtk.TreeViewDropPosition.BEFORE)
+ this.set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_BEFORE);
+ else if (position == Gtk.TreeViewDropPosition.AFTER)
+ this.set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_AFTER);
+
+ Gdk.drag_status(context, context.get_suggested_action(), time);
+
+ // avoid too frequent selection...
+ this.last_hover = time;
+
+ GLib.Timeout.add(150, () => {
+ if (this.last_hover == time)
+ this.get_selection().select_path(path);
+ return false;
+ });
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for external drags.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData selection_data, uint info, uint time_) {
+
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ this.data.get(active, DataPos.ID, out id);
+
+ var pie = PieManager.all_pies[id];
+
+ foreach (var uri in selection_data.get_uris()) {
+ pie.add_action(ActionRegistry.new_for_uri(uri), 0);
+ }
+
+ this.on_select(id);
+ }
+ }
+}
+
+}
diff --git a/src/gui/pieOptionsWindow.vala b/src/gui/pieOptionsWindow.vala
new file mode 100644
index 0000000..2f9cadf
--- /dev/null
+++ b/src/gui/pieOptionsWindow.vala
@@ -0,0 +1,315 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This window allows the selection of a hotkey. It is returned in form
+/// of a Trigger. Therefore it can be either a keyboard driven hotkey or
+/// a mouse based hotkey.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieOptionsWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal is emitted when the user selects a new hot key.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_ok(Trigger trigger, string pie_name, string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some private members which are needed by other methods.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Dialog window;
+ private Gtk.CheckButton turbo;
+ private Gtk.CheckButton delayed;
+ private Gtk.CheckButton centered;
+ private Gtk.CheckButton warp;
+ private Gtk.RadioButton rshape[10];
+ private TriggerSelectButton trigger_button;
+ private Gtk.Entry name_entry = null;
+ private Gtk.Button? icon_button = null;
+ private Gtk.Image? icon = null;
+ private Gtk.Label? pie_id = null;
+
+ private IconSelectWindow? icon_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently configured trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The trigger which was active when this window was opened. It is
+ /// stored in order to check whether anything has changed when the
+ /// user clicks on OK.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger original_trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the current icon name of the pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string icon_name = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the id of the current pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Radioboxes call toggled() twice per selection change.
+ /// This flag is used to discard one of the two notifications.
+ /////////////////////////////////////////////////////////////////////
+
+ private static int notify_toggle = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs a new PieOptionsWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public PieOptionsWindow() {
+ try {
+
+ Gtk.Builder builder = new Gtk.Builder();
+
+ builder.add_from_file (Paths.ui_files + "/pie_options.ui");
+
+ this.window = builder.get_object("window") as Gtk.Dialog;
+ this.trigger_button = new TriggerSelectButton(true);
+ this.trigger_button.show();
+
+ this.trigger_button.on_select.connect((trigger) => {
+ this.trigger = new Trigger.from_values(
+ trigger.key_sym,
+ trigger.modifiers,
+ trigger.with_mouse,
+ this.turbo.active,
+ this.delayed.active,
+ this.centered.active,
+ this.warp.active,
+ this.get_radio_shape()
+ );
+ });
+
+ (builder.get_object("trigger-box") as Gtk.Box).pack_start(this.trigger_button, true, true);
+
+ (builder.get_object("ok-button") as Gtk.Button).clicked.connect(this.on_ok_button_clicked);
+ (builder.get_object("cancel-button") as Gtk.Button).clicked.connect(this.on_cancel_button_clicked);
+
+ this.turbo = builder.get_object("turbo-check") as Gtk.CheckButton;
+ this.turbo.toggled.connect(this.on_check_toggled);
+
+ this.delayed = builder.get_object("delay-check") as Gtk.CheckButton;
+ this.delayed.toggled.connect(this.on_check_toggled);
+
+ this.centered = builder.get_object("center-check") as Gtk.CheckButton;
+ this.centered.toggled.connect(this.on_check_toggled);
+
+ this.warp = builder.get_object("warp-check") as Gtk.CheckButton;
+ this.warp.toggled.connect(this.on_check_toggled);
+
+ for (int i= 0; i < 10; i++) {
+ this.rshape[i] = builder.get_object("rshape%d".printf(i)) as Gtk.RadioButton;
+ this.rshape[i].toggled.connect(this.on_radio_toggled);
+ }
+
+ this.name_entry = builder.get_object("name-entry") as Gtk.Entry;
+ this.name_entry.activate.connect(this.on_ok_button_clicked);
+
+ this.pie_id = builder.get_object("pie-id") as Gtk.Label;
+
+ this.icon = builder.get_object("icon") as Gtk.Image;
+ this.icon_button = builder.get_object("icon-button") as Gtk.Button;
+ this.icon_button.clicked.connect(on_icon_button_clicked);
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+
+ } catch (GLib.Error e) {
+ error("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the parent window, in order to make this window stay in
+ /// front.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_parent(Gtk.Window parent) {
+ this.window.set_transient_for(parent);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays the window on the screen.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.window.show_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Initilizes all members to match the Trigger of the Pie with the
+ /// given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_pie(string id) {
+ var trigger = new Trigger.from_string(PieManager.get_accelerator_of(id));
+ var pie = PieManager.all_pies[id];
+
+ this.id = id;
+
+ this.turbo.active = trigger.turbo;
+ this.delayed.active = trigger.delayed;
+ this.centered.active = trigger.centered;
+ this.warp.active = trigger.warp;
+ this.set_radio_shape( trigger.shape );
+ this.original_trigger = trigger;
+ this.trigger = trigger;
+ this.name_entry.text = PieManager.get_name_of(id);
+ this.pie_id.label = "Pie-ID: " + id;
+ this.trigger_button.set_trigger(trigger);
+ this.set_icon(pie.icon);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when one of the checkboxes is toggled.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_check_toggled() {
+ if (this.trigger != null) {
+ this.trigger = new Trigger.from_values(
+ this.trigger.key_sym, this.trigger.modifiers,
+ this.trigger.with_mouse, this.turbo.active,
+ this.delayed.active, this.centered.active,
+ this.warp.active,
+ this.get_radio_shape()
+ );
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the current selected radio-button shape: 0= automatic
+ /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves
+ /// 1 | 4 | 7
+ /// 2 | 5 | 8
+ /// 3 | 6 | 9
+ /////////////////////////////////////////////////////////////////////
+
+ private int get_radio_shape() {
+ int rs;
+ for (rs= 0; rs < 10; rs++) {
+ if (this.rshape[rs].active) {
+ break;
+ }
+ }
+ return rs;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the current selected radio-button shape: 0= automatic
+ /// 5= full pie; 1,3,7,8= quarters; 2,4,6,8=halves
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_radio_shape(int rs) {
+ if (rs < 0 || rs > 9) {
+ rs= 5; //replace invalid value with default= full pie
+ }
+ this.rshape[rs].active= true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called twice when one of the radioboxes is toggled.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_radio_toggled() {
+ notify_toggle= 1 - notify_toggle;
+ if (notify_toggle == 1) {
+ on_check_toggled(); //just call once
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the icon button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_icon_button_clicked(Gtk.Button button) {
+ if (this.icon_window == null) {
+ this.icon_window = new IconSelectWindow(this.window);
+ this.icon_window.on_ok.connect((icon) => {
+ set_icon(icon);
+ });
+ }
+
+ this.icon_window.show();
+ this.icon_window.set_icon(this.icon_name);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the OK-button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_ok_button_clicked() {
+ var assigned_id = PieManager.get_assigned_id(this.trigger);
+
+ if (assigned_id != "" && assigned_id != this.id) {
+ // it's already assigned
+ var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " +
+ "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id));
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.get_toplevel(), Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.CANCEL, error);
+ dialog.run();
+ dialog.destroy();
+ } else {
+ // a unused hot key has been chosen, great!
+ this.on_ok(this.trigger, this.name_entry.text, this.icon_name);
+ this.window.hide();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the icon of the icon_button
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_icon(string name) {
+ this.icon_name = name;
+
+ if (name.contains("/")) {
+ try {
+ this.icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale(name,
+ this.icon.get_pixel_size(), this.icon.get_pixel_size(), true);
+ } catch (GLib.Error error) {
+ warning(error.message);
+ }
+ } else {
+ this.icon.icon_name = name;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the cancel button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_cancel_button_clicked() {
+ this.window.hide();
+ }
+}
+
+}
diff --git a/src/gui/piePreview.vala b/src/gui/piePreview.vala
new file mode 100644
index 0000000..ce1ba96
--- /dev/null
+++ b/src/gui/piePreview.vala
@@ -0,0 +1,387 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A custom widget displaying the preview of a Pie. It can be used to
+/// configure the displayed Pie in various aspects.
+/////////////////////////////////////////////////////////////////////////
+
+class PiePreview : Gtk.DrawingArea {
+
+ /////////////////////////////////////////////////////////////////////
+ /// These get called when the last Slice is removed and when the
+ /// first Slice is added respectively.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_last_slice_removed();
+ public signal void on_first_slice_added();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The internally used renderer to draw the Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewRenderer renderer = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The window which pops up, when a Slice is added or edited.
+ /////////////////////////////////////////////////////////////////////
+
+ private NewSliceWindow? new_slice_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// A timer used for calculating the frame time.
+ /////////////////////////////////////////////////////////////////////
+
+ private GLib.Timer timer;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when it is possible to drag a slice from this widget.
+ /// False, when the user currently hovers over the add sign.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool drag_enabled = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the currently displayed Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position from where a Slice-drag started.
+ /////////////////////////////////////////////////////////////////////
+
+ private int drag_start_index = -1;
+ private string drag_start_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates the widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreview() {
+ this.renderer = new PiePreviewRenderer(this);
+
+ this.draw.connect(this.on_draw);
+ this.timer = new GLib.Timer();
+ this.set_events(Gdk.EventMask.POINTER_MOTION_MASK
+ | Gdk.EventMask.LEAVE_NOTIFY_MASK
+ | Gdk.EventMask.ENTER_NOTIFY_MASK);
+
+ // setup drag and drop
+ this.enable_drag_source();
+
+ Gtk.TargetEntry uri_dest = {"text/uri-list", 0, 0};
+ Gtk.TargetEntry slice_dest = {"text/plain", Gtk.TargetFlags.SAME_WIDGET, 0};
+ Gtk.TargetEntry[] destinations = { uri_dest, slice_dest };
+ Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, destinations, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK);
+
+ this.drag_begin.connect(this.on_start_drag);
+ this.drag_end.connect(this.on_end_drag);
+ this.drag_data_received.connect(this.on_dnd_received);
+
+ // connect mouse events
+ this.drag_motion.connect(this.on_drag_move);
+ this.leave_notify_event.connect(this.on_mouse_leave);
+ this.enter_notify_event.connect(this.on_mouse_enter);
+ this.motion_notify_event.connect_after(this.on_mouse_move);
+ this.button_release_event.connect_after(this.on_button_release);
+ this.button_press_event.connect_after(this.on_button_press);
+
+ this.new_slice_window = new NewSliceWindow();
+ this.new_slice_window.on_select.connect((new_action, as_new_slice, at_position) => {
+ var pie = PieManager.all_pies[this.current_id];
+
+ if (new_action.has_quickaction())
+ renderer.disable_quickactions();
+
+ if (as_new_slice) {
+ pie.add_group(new_action, at_position+1);
+ this.renderer.add_group(new_action, at_position+1);
+
+ if (this.renderer.slice_count() == 1)
+ this.on_first_slice_added();
+ } else {
+ pie.update_group(new_action, at_position);
+ this.renderer.update_group(new_action, at_position);
+ }
+ });
+
+ this.renderer.on_edit_slice.connect((pos) => {
+ this.new_slice_window.reload();
+
+ this.new_slice_window.set_parent(this.get_toplevel() as Gtk.Window);
+ this.new_slice_window.show();
+
+ var pie = PieManager.all_pies[this.current_id];
+ this.new_slice_window.set_action(pie.action_groups[pos], pos);
+ });
+
+ this.renderer.on_add_slice.connect((pos) => {
+ this.new_slice_window.reload();
+
+ this.new_slice_window.set_parent(this.get_toplevel() as Gtk.Window);
+ this.new_slice_window.show();
+
+ this.new_slice_window.set_default(this.current_id, pos);
+ });
+
+ this.renderer.on_remove_slice.connect((pos) => {
+
+ var dialog = new Gtk.MessageDialog(this.get_toplevel() as Gtk.Window, Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("Do you really want to delete this Slice?"));
+
+ dialog.response.connect((response) => {
+ if (response == Gtk.ResponseType.YES) {
+ var pie = PieManager.all_pies[this.current_id];
+
+ pie.remove_group(pos);
+ this.renderer.remove_group(pos);
+
+ if (this.renderer.slice_count() == 0)
+ this.on_last_slice_removed();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Sets the currently displayed Pie to the Pie with the given ID.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_pie(string id) {
+ var style = this.get_style_context();
+
+ this.current_id = id;
+ this.override_background_color(Gtk.StateFlags.NORMAL, style.get_background_color(Gtk.StateFlags.NORMAL));
+ this.renderer.load_pie(PieManager.all_pies[id]);
+
+ if (id == this.drag_start_id) {
+ this.renderer.hide_group(this.drag_start_index);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Begins the draw loop. It automatically ends, when the containing
+ /// window becomes invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw_loop() {
+ this.timer.start();
+ this.queue_draw();
+
+ GLib.Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {
+ this.queue_draw();
+ return this.get_toplevel().visible;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called every frame.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_draw(Cairo.Context ctx) {
+ // store the frame time
+ double frame_time = this.timer.elapsed();
+ this.timer.reset();
+
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+
+ ctx.translate((int)(allocation.width*0.5), (int)(allocation.height*0.5));
+
+ this.renderer.draw(frame_time, ctx);
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_leave(Gdk.EventCrossing event) {
+ this.renderer.on_mouse_leave();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse enters the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_enter(Gdk.EventCrossing event) {
+ this.renderer.on_mouse_enter();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves in the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_mouse_move(Gdk.EventMotion event) {
+ this.renderer.set_dnd_mode(false);
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+ this.renderer.on_mouse_move(event.x-allocation.width*0.5, event.y-allocation.height*0.5);
+
+ if (this.renderer.get_active_slice() < 0) this.disable_drag_source();
+ else this.enable_drag_source();
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_press() {
+ this.renderer.on_button_press();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is released.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_release() {
+ if (!this.renderer.drag_n_drop_mode)
+ this.renderer.on_button_release();
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse is moved over this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_drag_move(Gdk.DragContext ctx, int x, int y, uint time) {
+ this.renderer.set_dnd_mode(true);
+ Gtk.Allocation allocation;
+ this.get_allocation(out allocation);
+ this.renderer.on_mouse_move(x-allocation.width*0.5, y-allocation.height*0.5);
+
+ return true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user tries to drag something from this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_start_drag(Gdk.DragContext ctx) {
+ this.drag_start_index = this.renderer.get_active_slice();
+ this.drag_start_id = this.current_id;
+ var icon = this.renderer.get_active_icon();
+ var pixbuf = icon.to_pixbuf();
+
+ this.renderer.hide_group(this.drag_start_index);
+ Gtk.drag_set_icon_pixbuf(ctx, pixbuf, icon.size()/2, icon.size()/2);
+
+ this.renderer.set_dnd_mode(true);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for Slice-movement.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_end_drag(Gdk.DragContext context) {
+
+ if (context.list_targets() != null) {
+
+ int target_index = this.renderer.get_active_slice();
+ this.renderer.set_dnd_mode(false);
+
+ context.list_targets().foreach((target) => {
+ Gdk.Atom target_type = (Gdk.Atom)target;
+ if (target_type.name() == "text/plain") {
+ if (this.current_id == this.drag_start_id) {
+ var pie = PieManager.all_pies[this.current_id];
+ pie.move_group(this.drag_start_index, target_index);
+ this.renderer.show_hidden_group_at(target_index);
+ } else {
+ var src_pie = PieManager.all_pies[this.drag_start_id];
+ var dst_pie = PieManager.all_pies[this.current_id];
+ dst_pie.add_group(src_pie.action_groups[this.drag_start_index], target_index);
+ this.renderer.add_group(dst_pie.action_groups[target_index], target_index);
+
+ if (this.renderer.slices.size == 1)
+ this.on_first_slice_added();
+
+ if ((context.get_actions() & Gdk.DragAction.COPY) == 0)
+ src_pie.remove_group(this.drag_start_index);
+ }
+
+
+ }
+ });
+
+ this.drag_start_index = -1;
+ this.drag_start_id = "";
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user finishes a drag operation on this widget.
+ /// Only used for external drags.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_dnd_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData selection_data, uint info, uint time_) {
+
+ var pie = PieManager.all_pies[this.current_id];
+ int position = this.renderer.get_active_slice();
+ this.renderer.set_dnd_mode(false);
+
+ foreach (var uri in selection_data.get_uris()) {
+ pie.add_action(ActionRegistry.new_for_uri(uri), position);
+ this.renderer.add_group(pie.action_groups[position], position);
+
+ if (this.renderer.slices.size == 1)
+ this.on_first_slice_added();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables this widget to be a source for drag operations.
+ /////////////////////////////////////////////////////////////////////
+
+ private void enable_drag_source() {
+ if (!this.drag_enabled) {
+ this.drag_enabled = true;
+ Gtk.TargetEntry slice_source = {"text/plain", Gtk.TargetFlags.SAME_WIDGET | Gtk.TargetFlags.SAME_APP, 0};
+ Gtk.TargetEntry[] sources = { slice_source };
+ Gtk.drag_source_set(this, Gdk.ModifierType.BUTTON1_MASK, sources, Gdk.DragAction.MOVE | Gdk.DragAction.COPY);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Disables this widget to be a source for drag operations.
+ /////////////////////////////////////////////////////////////////////
+
+ private void disable_drag_source() {
+ if (this.drag_enabled) {
+ this.drag_enabled = false;
+ Gtk.drag_source_unset(this);
+ }
+ }
+
+}
+
+}
diff --git a/src/gui/piePreviewAddSign.vala b/src/gui/piePreviewAddSign.vala
new file mode 100644
index 0000000..b3f6f7b
--- /dev/null
+++ b/src/gui/piePreviewAddSign.vala
@@ -0,0 +1,224 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A liitle plus-sign displayed on the preview widget to indicate where
+/// the user may add a new Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewAddSign : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Gets emitted, when the users clicks on this object.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image icon { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when the add sign is currently visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool visible { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The position of the sign in its parent Pie. May be 2.5 for
+ /// example.
+ /////////////////////////////////////////////////////////////////////
+
+ private double position = 0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some values used for displaying this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ private double time = 0;
+ private double max_size = 0;
+ private double angle = 0;
+ private AnimatedValue size;
+ private AnimatedValue alpha;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewAddSign(PiePreviewRenderer parent) {
+ this.parent = parent;
+
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 2.0);
+ this.alpha = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, -3, -3, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 0.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads the desired icon for this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ this.icon = new Icon("list-add", 36);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the position where this object should be displayed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_position(int position) {
+ double new_position = position;
+
+ if (!this.parent.drag_n_drop_mode)
+ new_position += 0.5;
+
+ this.position = new_position;
+ this.angle = 2.0 * PI * new_position/parent.slice_count();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.visible = true;
+ this.size.reset_target(this.max_size, 0.3);
+ this.alpha.reset_target(1.0, 0.3);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide() {
+ this.visible = false;
+ this.size.reset_target(0.0, 0.3);
+ this.alpha.reset_target(0.0, 0.3);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.max_size = size;
+ this.size.reset_target(size, 0.5);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the sign to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+
+ this.time += frame_time;
+
+ this.size.update(frame_time);
+ this.alpha.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ if (this.parent.slice_count() == 0) {
+ ctx.save();
+
+ double scale = this.clicked.val
+ + GLib.Math.sin(this.time*10)*0.02*this.alpha.val
+ + this.alpha.val*0.08 - 0.1;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx);
+
+ ctx.restore();
+
+ } else if (this.alpha.val*this.activity.val > 0) {
+ ctx.save();
+
+ // distance from the center
+ double radius = 120;
+
+ // transform the context
+ ctx.translate(cos(this.angle)*radius, sin(this.angle)*radius);
+ double scale = this.size.val*this.clicked.val
+ + this.activity.val*0.07
+ + GLib.Math.sin(this.time*10)*0.03*this.activity.val
+ - 0.1;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx, this.alpha.val*this.activity.val);
+
+ ctx.restore();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_move(double angle) {
+ if (parent.slice_count() > 0) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ if (diff < 0.5*PI/parent.slice_count()) this.activity.reset_target(1.0, 1.0);
+ else this.activity.reset_target(-3.0, 1.5);
+ } else {
+ this.activity.reset_target(1.0, 1.0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press(double x, double y) {
+ if (this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ this.on_clicked((int)this.position);
+ this.clicked.reset_target(1.0, 0.1);
+ }
+ }
+}
+
+}
diff --git a/src/gui/piePreviewCenter.vala b/src/gui/piePreviewCenter.vala
new file mode 100644
index 0000000..2a163b6
--- /dev/null
+++ b/src/gui/piePreviewCenter.vala
@@ -0,0 +1,109 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewCenter : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// THe Images displayed. When the displayed text changes the
+ /// currently displayed text becomes the old_text. So it's possible
+ /// to create a smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private RenderedText text = null;
+ private RenderedText old_text = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stores the currently displayed text in order to avoid frequent
+ /// and useless updates.
+ /////////////////////////////////////////////////////////////////////
+
+ private string current_text = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// An AnimatedValue for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue blend;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewCenter(PiePreviewRenderer parent) {
+ this.parent = parent;
+ this.blend = new AnimatedValue.linear(0, 0, 0);
+
+ this.text = new RenderedText("", 1, 1, "", new Color(), 1.0);
+ this.old_text = text;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the currently displayed text. It will be smoothly
+ /// blended and may contain pango markup.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_text(string text) {
+ if (text != this.current_text) {
+
+ var style = parent.parent.get_style_context();
+
+ this.old_text = this.text;
+ this.text = new RenderedText.with_markup(
+ text, 180, 180, style.get_font(Gtk.StateFlags.NORMAL).get_family()+" 10",
+ new Color.from_gdk(style.get_color(Gtk.StateFlags.NORMAL)), 1.0);
+ this.current_text = text;
+
+ this.blend.reset_target(0.0, 0.0);
+ this.blend.reset_target(1.0, 0.1);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the center to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+
+ this.blend.update(frame_time);
+
+ ctx.save();
+
+ if (this.parent.slice_count() == 0)
+ ctx.translate(0, 40);
+
+ this.old_text.paint_on(ctx, 1-this.blend.val);
+ this.text.paint_on(ctx, this.blend.val);
+
+ ctx.restore();
+ }
+}
+
+}
diff --git a/src/gui/piePreviewDeleteSign.vala b/src/gui/piePreviewDeleteSign.vala
new file mode 100644
index 0000000..a830002
--- /dev/null
+++ b/src/gui/piePreviewDeleteSign.vala
@@ -0,0 +1,195 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// The delete sign, displayed in the upper right corner of each
+/// Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewDeleteSign : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on this sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Image icon { get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some constants determining the look and behaviour of this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const int radius = 18;
+ private static const double globale_scale = 0.8;
+ private static const double click_cancel_treshold = 5;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when the add sign is currently visible.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool visible = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some AnimatedValues for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue size;
+ private AnimatedValue alpha;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Storing the position where a mouse click was executed. Useful for
+ /// canceling the click when the mouse moves some pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ private double clicked_x = 0.0;
+ private double clicked_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewDeleteSign() {
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 2.0);
+ this.alpha = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, -3, -3, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 0.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Action. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load() {
+ this.icon = new Icon("edit-delete", PiePreviewDeleteSign.radius*2);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object visible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ if (!this.visible) {
+ this.visible = true;
+ this.alpha.reset_target(1.0, 0.3);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes this object invisible.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide() {
+ if (this.visible) {
+ this.visible = false;
+ this.alpha.reset_target(0.0, 0.3);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.size.reset_target(size, 0.2);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the sign to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.size.update(frame_time);
+ this.alpha.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ if (this.alpha.val > 0) {
+ ctx.save();
+
+ // transform the context
+ double scale = (this.size.val*this.clicked.val
+ + this.activity.val*0.2 - 0.2)*PiePreviewDeleteSign.globale_scale;
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx, this.alpha.val);
+
+ ctx.restore();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_move(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ double dist = GLib.Math.pow(x-this.clicked_x, 2) + GLib.Math.pow(y-this.clicked_y, 2);
+ if (dist > PiePreviewDeleteSign.click_cancel_treshold*PiePreviewDeleteSign.click_cancel_treshold)
+ this.clicked.reset_target(1.0, 0.1);
+ }
+
+ if (GLib.Math.fabs(x) <= PiePreviewDeleteSign.radius*PiePreviewDeleteSign.globale_scale && GLib.Math.fabs(y) <= PiePreviewDeleteSign.radius*PiePreviewDeleteSign.globale_scale) {
+ this.activity.reset_target(1.0, 0.2);
+ return true;
+ }
+
+ this.activity.reset_target(0.0, 0.2);
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_button_press(double x, double y) {
+ if (this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ this.clicked_x = x;
+ this.clicked_y = y;
+ return true;
+ }
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_button_release(double x, double y) {
+ if (this.clicked.end == 0.9) {
+ this.clicked.reset_target(1.0, 0.1);
+ this.on_clicked();
+
+ return true;
+ }
+ return false;
+ }
+}
+
+}
diff --git a/src/gui/piePreviewRenderer.vala b/src/gui/piePreviewRenderer.vala
new file mode 100644
index 0000000..53dd2fb
--- /dev/null
+++ b/src/gui/piePreviewRenderer.vala
@@ -0,0 +1,443 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A complex class which is able to draw the preview of a Pie. It can
+/// manipulate the displayed Pie as well.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// These signals get emitted when a slice is added, removed or
+ /// manipulated.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_add_slice(int position);
+ public signal void on_remove_slice(int position);
+ public signal void on_edit_slice(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, when there is currently a drag going on.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool drag_n_drop_mode { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// A list containing all SliceRenderers of this Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ public Gee.ArrayList<PiePreviewSliceRenderer?> slices;
+
+ /////////////////////////////////////////////////////////////////////
+ /// When a Slice is moved within a Pie it is temporarily removed.
+ /// If so, it is stored in this member.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewSliceRenderer hidden_group { get; private set; default=null; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The add sign which indicates that a new Slice could be added.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewAddSign add_sign = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The object which renders the name of the currently selected Slice
+ /// in the middle.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewCenter center_renderer = null;
+ private enum CenterDisplay { NONE, ACTIVE_SLICE, DROP, ADD, DELETE }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some members storing some inter-frame-information.
+ /////////////////////////////////////////////////////////////////////
+
+ private int active_slice = -1;
+ private double angle = 0.0;
+ private double mouse_x = 0.0;
+ private double mouse_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent DrawingArea.
+ /////////////////////////////////////////////////////////////////////
+
+ public unowned Gtk.DrawingArea parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes members.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewRenderer(Gtk.DrawingArea parent) {
+ this.parent = parent;
+ this.slices = new Gee.ArrayList<PiePreviewSliceRenderer?>();
+ this.center_renderer = new PiePreviewCenter(this);
+ this.add_sign = new PiePreviewAddSign(this);
+ this.add_sign.load();
+
+ this.add_sign.on_clicked.connect((pos) => {
+ this.on_add_slice(pos);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Pie. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load_pie(Pie pie) {
+ this.slices.clear();
+
+ foreach (var group in pie.action_groups) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ renderer.load(group);
+
+ this.add_slice_renderer(renderer);
+ this.connect_siganls(renderer);
+ }
+
+ this.active_slice = -1;
+ this.update_sizes();
+ this.update_positions(false);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables or disables the drag n dropn mode.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_dnd_mode(bool dnd) {
+ if (this.drag_n_drop_mode != dnd) {
+ this.drag_n_drop_mode = dnd;
+ this.update_positions();
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the number of Slices.
+ /////////////////////////////////////////////////////////////////////
+
+ public int slice_count() {
+ if (this.drag_n_drop_mode && !(this.slices.size == 0))
+ return slices.size+1;
+
+ return slices.size;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the index of the currently hovered Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public int get_active_slice() {
+ if (this.slices.size == 0)
+ return 0;
+
+ if (this.drag_n_drop_mode)
+ return (int)(this.angle/(2*PI)*this.slice_count() + 0.5) % this.slice_count();
+
+ return this.active_slice;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Returns the Icon of the currently hovered Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public Icon get_active_icon() {
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ return this.slices[this.active_slice].icon;
+ else
+ return new Icon("", 24);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the entire Pie to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.add_sign.draw(frame_time, ctx);
+ this.center_renderer.draw(frame_time, ctx);
+
+ foreach (var slice in this.slices)
+ slice.draw(frame_time, ctx);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_leave() {
+ this.add_sign.hide();
+ this.update_positions();
+ this.update_center(CenterDisplay.NONE);
+
+ foreach (var slice in this.slices)
+ slice.on_mouse_leave();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse enters the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_enter() {
+ this.add_sign.show();
+ this.update_positions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves in the drawing area of this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_move(double x, double y) {
+ this.mouse_x = x;
+ this.mouse_y = y;
+
+ this.angle = acos(x/sqrt(x*x + y*y));
+ if (y < 0) this.angle = 2*PI - this.angle;
+
+ if (!this.drag_n_drop_mode)
+ this.active_slice = -1;
+
+ bool delete_hovered = false;
+
+ for (int i=0; i<this.slices.size; ++i)
+ if (slices[i].on_mouse_move(this.angle, x, y) && !this.drag_n_drop_mode) {
+ this.active_slice = i;
+ delete_hovered = slices[i].delete_hovered;
+ }
+
+ if (this.drag_n_drop_mode) this.update_center(CenterDisplay.DROP);
+ else if (this.active_slice < 0) this.update_center(CenterDisplay.ADD);
+ else if (delete_hovered) this.update_center(CenterDisplay.DELETE);
+ else this.update_center(CenterDisplay.ACTIVE_SLICE);
+
+ this.add_sign.on_mouse_move(this.angle);
+
+ this.update_positions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is pressed over this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press() {
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].on_button_press(this.mouse_x, this.mouse_y);
+ this.add_sign.on_button_press(this.mouse_x, this.mouse_y);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a mouse button is released over this renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release() {
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].on_button_release(this.mouse_x, this.mouse_y);
+ this.add_sign.on_button_release(this.mouse_x, this.mouse_y);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Adds a new Slice to the renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void add_group(ActionGroup group, int at_position = -1) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ renderer.load(group);
+ this.add_slice_renderer(renderer, at_position);
+ this.connect_siganls(renderer);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Removes a Slice from the renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ public void remove_group(int index) {
+ if (this.slices.size > index) {
+ this.slices.remove_at(index);
+ this.update_positions();
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Hides the Slice at the given position temporarily.
+ /////////////////////////////////////////////////////////////////////
+
+ public void hide_group(int index) {
+ if (this.slices.size > index) {
+ this.hidden_group = this.slices[index];
+ this.remove_group(index);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Re-shows a Slice which has been hidden before.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show_hidden_group_at(int index) {
+ if (this.slices.size >= index && this.hidden_group != null) {
+ this.hidden_group.set_position(index, false);
+ this.add_slice_renderer(this.hidden_group, index);
+ this.hidden_group = null;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates a Slice at the given position.
+ /////////////////////////////////////////////////////////////////////
+
+ public void update_group(ActionGroup group, int index) {
+ if (this.slices.size > index) {
+ var renderer = new PiePreviewSliceRenderer(this);
+ this.slices.set(index, renderer);
+ renderer.load(group);
+
+ this.connect_siganls(renderer);
+
+ this.update_positions(false);
+ this.update_sizes();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Disables all quickactions of this pie preview.
+ /////////////////////////////////////////////////////////////////////
+
+ public void disable_quickactions() {
+ foreach (var slice in this.slices)
+ slice.disable_quickactions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which adds a new Slice to the given position.
+ /////////////////////////////////////////////////////////////////////
+
+ private void add_slice_renderer(PiePreviewSliceRenderer renderer, int at_position = -1) {
+ if (at_position < 0 || at_position >= this.slices.size)
+ this.slices.add(renderer);
+ else
+ this.slices.insert(at_position, renderer);
+
+ this.update_positions(false);
+ this.update_sizes();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Helper method which connects all neccessary signals of a newly
+ /// added Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private void connect_siganls(PiePreviewSliceRenderer renderer) {
+ renderer.on_clicked.connect((pos) => {
+ this.on_edit_slice(pos);
+ });
+
+ renderer.on_remove.connect((pos) => {
+ this.on_remove_slice(pos);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Moves all slices to their positions. This may happen smoothly if
+ /// desired.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_positions(bool smoothly = true) {
+ if (this.slices.size > 0) {
+ if (this.add_sign.visible) {
+ int add_position = 0;
+ add_position = (int)(this.angle/(2*PI)*this.slice_count()) % this.slice_count();
+ this.add_sign.set_position(add_position);
+
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i, smoothly);
+ }
+
+ } else if (this.drag_n_drop_mode) {
+ int add_position = 0;
+ add_position = (int)(this.angle/(2*PI)*this.slice_count() + 0.5) % this.slice_count();
+
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i >= add_position ? i+1 : i, smoothly);
+ }
+
+ this.update_center(CenterDisplay.DROP);
+
+ } else {
+ for (int i=0; i<this.slices.size; ++i) {
+ this.slices[i].set_position(i, smoothly);
+ }
+
+ if (this.active_slice < 0) this.update_center(CenterDisplay.NONE);
+ else this.update_center(CenterDisplay.ACTIVE_SLICE);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Resizes all slices to their new sizes. This may happen smoothly
+ /// if desired.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_sizes() {
+ double size = 1.0;
+ if (this.slice_count() > 20) size = 0.5;
+ else if (this.slice_count() > 8) size = 1.0 - (double)(this.slice_count() - 8)/24.0;
+
+ this.add_sign.set_size(size);
+
+ for (int i=0; i<this.slices.size; ++i)
+ this.slices[i].set_size(size);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Displays a new text in the middle of the preview.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_center(CenterDisplay display) {
+ switch (display) {
+ case CenterDisplay.ACTIVE_SLICE:
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(slices[this.active_slice].name) + "</b>\n<small>"
+ + _("Click to edit") + "\n" + _("Drag to move") + "</small>");
+ break;
+ case CenterDisplay.ADD:
+ this.center_renderer.set_text("<small>" + _("Click to add a new Slice") + "</small>");
+ break;
+ case CenterDisplay.DROP:
+ if (hidden_group == null)
+ this.center_renderer.set_text("<small>" + _("Drop to add as new Slice") + "</small>");
+ else
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(this.hidden_group.name) + "</b>\n<small>"
+ + _("Drop to move Slice") + "</small>");
+ break;
+ case CenterDisplay.DELETE:
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ this.center_renderer.set_text("<b>" + GLib.Markup.escape_text(slices[this.active_slice].name) + "</b>\n<small>"
+ + _("Click to delete") + "\n" + _("Drag to move") + "</small>");
+ break;
+ default:
+ this.center_renderer.set_text("");
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/gui/piePreviewSliceRenderer.vala b/src/gui/piePreviewSliceRenderer.vala
new file mode 100644
index 0000000..5b4d939
--- /dev/null
+++ b/src/gui/piePreviewSliceRenderer.vala
@@ -0,0 +1,276 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// Displays the preview of a Slice.
+/////////////////////////////////////////////////////////////////////////
+
+public class PiePreviewSliceRenderer : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_clicked(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user clicked on the delete sign.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_remove(int position);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The image used to display this oject.
+ /////////////////////////////////////////////////////////////////////
+
+ public Icon icon { get; private set; }
+ public ActionGroup action_group { get; private set; }
+ public string name { get; private set; default=""; }
+ public bool delete_hovered { get; private set; default=false; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The parent renderer.
+ /////////////////////////////////////////////////////////////////////
+
+ private unowned PiePreviewRenderer parent;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The delete sign, displayed in the upper right corner of each
+ /// Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreviewDeleteSign delete_sign = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some AnimatedValues for smooth transitions.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue angle;
+ private AnimatedValue size;
+ private AnimatedValue activity;
+ private AnimatedValue clicked;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some constants determining the look and behaviour of this Slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private static const double pie_radius = 126;
+ private static const double radius = 24;
+ private static const double delete_x = 13;
+ private static const double delete_y = -13;
+ private static const double click_cancel_treshold = 5;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Storing the position where a mouse click was executed. Useful for
+ /// canceling the click when the mouse moves some pixels.
+ /////////////////////////////////////////////////////////////////////
+
+ private double clicked_x = 0.0;
+ private double clicked_y = 0.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of this slice in a pie. Clockwise assigned, starting
+ /// from the right-most slice.
+ /////////////////////////////////////////////////////////////////////
+
+ private int position;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, sets everything up.
+ /////////////////////////////////////////////////////////////////////
+
+ public PiePreviewSliceRenderer(PiePreviewRenderer parent) {
+ this.delete_sign = new PiePreviewDeleteSign();
+ this.delete_sign.load();
+ this.delete_sign.on_clicked.connect(() => {
+ this.on_remove(this.position);
+ });
+
+ this.parent = parent;
+ this.angle = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.5);
+ this.size = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 1.0);
+ this.activity = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 0, 0, 0, 0.0);
+ this.clicked = new AnimatedValue.cubic(AnimatedValue.Direction.OUT, 1, 1, 0, 1.0);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads an Action. All members are initialized accordingly.
+ /////////////////////////////////////////////////////////////////////
+
+ public void load(ActionGroup group) {
+ this.action_group = group;
+
+ // if it's a custom ActionGroup
+ if (group.get_type().depth() == 2 && group.actions.size > 0) {
+ this.icon = new Icon(group.actions[0].icon, (int)(PiePreviewSliceRenderer.radius*2));
+ this.name = group.actions[0].name;
+ } else {
+ this.icon = new Icon(GroupRegistry.descriptions[group.get_type().name()].icon, (int)(PiePreviewSliceRenderer.radius*2));
+ this.name = GroupRegistry.descriptions[group.get_type().name()].name;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the position where this object should be displayed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_position(int position, bool smoothly = true) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+
+ if (direction != this.angle.end) {
+ this.position = position;
+ this.angle.reset_target(direction, smoothly ? 0.5 : 0.0);
+
+ if (!smoothly)
+ this.angle.update(1.0);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Updates the size of this object. All transitions will be smooth.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_size(double size) {
+ this.size.reset_target(size, 0.5);
+ this.delete_sign.set_size(size);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Notifies that all quick actions should be disabled.
+ /////////////////////////////////////////////////////////////////////
+
+ public void disable_quickactions() {
+ this.action_group.disable_quickactions();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Draws the slice to the given context.
+ /////////////////////////////////////////////////////////////////////
+
+ public void draw(double frame_time, Cairo.Context ctx) {
+ this.size.update(frame_time);
+ this.angle.update(frame_time);
+ this.activity.update(frame_time);
+ this.clicked.update(frame_time);
+
+ ctx.save();
+
+ // transform the context
+ ctx.translate(cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius, sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius);
+
+ double scale = this.size.val*this.clicked.val
+ + this.activity.val*0.1 - 0.1;
+ ctx.save();
+
+ ctx.scale(scale, scale);
+
+ // paint the image
+ icon.paint_on(ctx);
+
+ ctx.restore();
+
+ ctx.translate(PiePreviewSliceRenderer.delete_x*this.size.val, PiePreviewSliceRenderer.delete_y*this.size.val);
+ this.delete_sign.draw(frame_time, ctx);
+
+ ctx.restore();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse moves to another position.
+ /////////////////////////////////////////////////////////////////////
+
+ public bool on_mouse_move(double angle, double x, double y) {
+ double direction = 2.0 * PI * position/parent.slice_count();
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ bool active = diff < 0.5*PI/parent.slice_count();
+
+ if (active) {
+ this.activity.reset_target(1.0, 0.3);
+ this.delete_sign.show();
+ } else {
+ this.activity.reset_target(0.0, 0.3);
+ this.delete_sign.hide();
+ }
+
+ if (this.clicked.end == 0.9) {
+ double dist = GLib.Math.pow(x-this.clicked_x, 2) + GLib.Math.pow(y-this.clicked_y, 2);
+ if (dist > PiePreviewSliceRenderer.click_cancel_treshold*PiePreviewSliceRenderer.click_cancel_treshold)
+ this.clicked.reset_target(1.0, 0.1);
+ }
+
+ double own_x = cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ double own_y = sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ this.delete_hovered = this.delete_sign.on_mouse_move(x - own_x - PiePreviewSliceRenderer.delete_x*this.size.val,
+ y - own_y - PiePreviewSliceRenderer.delete_y*this.size.val);
+
+ return active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the mouse leaves the area of this widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_mouse_leave() {
+ this.activity.reset_target(0.0, 0.3);
+ this.delete_sign.hide();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is pressed.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_press(double x, double y) {
+ bool delete_pressed = false;
+ if (this.activity.end == 1.0) {
+ double own_x = cos(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ double own_y = sin(this.angle.val)*PiePreviewSliceRenderer.pie_radius;
+ delete_pressed = this.delete_sign.on_button_press(x - own_x - PiePreviewSliceRenderer.delete_x*this.size.val,
+ y - own_y - PiePreviewSliceRenderer.delete_y*this.size.val);
+ }
+
+ if (!delete_pressed && this.activity.end == 1.0) {
+ this.clicked.reset_target(0.9, 0.1);
+ this.clicked_x = x;
+ this.clicked_y = y;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a button of the mouse is released.
+ /////////////////////////////////////////////////////////////////////
+
+ public void on_button_release(double x, double y) {
+ bool deleted = false;
+ if (this.activity.end == 1.0)
+ deleted = this.delete_sign.on_button_release(x, y);
+
+ if (!deleted && this.clicked.end == 0.9) {
+ this.clicked.reset_target(1.0, 0.1);
+ this.on_clicked(this.position);
+ }
+ }
+}
+
+}
diff --git a/src/gui/preferencesWindow.vala b/src/gui/preferencesWindow.vala
new file mode 100644
index 0000000..fff8168
--- /dev/null
+++ b/src/gui/preferencesWindow.vala
@@ -0,0 +1,604 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// The settings menu of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+public class PreferencesWindow : GLib.Object {
+
+ /////////////////////////////////////////////////////////////////////
+ /// The ID of the currently selected Pie.
+ /////////////////////////////////////////////////////////////////////
+
+ private string selected_id = "";
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some Gtk widgets used by this window.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.Stack? stack = null;
+ private Gtk.Notebook? notebook = null;
+
+ private Gtk.Window? window = null;
+ private Gtk.Label? no_pie_label = null;
+ private Gtk.Label? no_slice_label = null;
+ private Gtk.Box? preview_box = null;
+ private Gtk.EventBox? preview_background = null;
+ private Gtk.Button? remove_pie_button = null;
+ private Gtk.Button? edit_pie_button = null;
+ private Gtk.Button? theme_delete_button = null;
+
+ private ThemeList? theme_list = null;
+ private Gtk.ToggleButton? indicator = null;
+ private Gtk.ToggleButton? search_by_string = null;
+ private Gtk.ToggleButton? autostart = null;
+ private Gtk.ToggleButton? captions = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some custom widgets and dialogs used by this window.
+ /////////////////////////////////////////////////////////////////////
+
+ private PiePreview? preview = null;
+ private PieList? pie_list = null;
+ private PieOptionsWindow? pie_options_window = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, creates the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public PreferencesWindow() {
+ var builder = new Gtk.Builder.from_file(Paths.ui_files + "/preferences.ui");
+
+ this.window = builder.get_object("window") as Gtk.Window;
+ this.window.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.KEY_RELEASE_MASK |
+ Gdk.EventMask.KEY_PRESS_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK);
+
+ if (!Daemon.disable_header_bar) {
+ var headerbar = new Gtk.HeaderBar();
+ headerbar.show_close_button = true;
+ headerbar.title = _("Gnome-Pie Settings");
+ headerbar.subtitle = _("bake your pies!");
+ window.set_titlebar(headerbar);
+ }
+
+ this.notebook = builder.get_object("notebook") as Gtk.Notebook;
+
+ if (!Daemon.disable_stack_switcher) {
+ var main_box = builder.get_object("main-box") as Gtk.Box;
+ var pie_settings = builder.get_object("pie-settings") as Gtk.Box;
+ var general_settings = builder.get_object("general-settings") as Gtk.Box;
+
+ pie_settings.parent.remove(pie_settings);
+ general_settings.parent.remove(general_settings);
+
+ main_box.remove(this.notebook);
+
+ Gtk.StackSwitcher switcher = new Gtk.StackSwitcher();
+ switcher.margin_top = 10;
+ switcher.set_halign(Gtk.Align.CENTER);
+ main_box.pack_start(switcher, false, true, 0);
+
+ this.stack = new Gtk.Stack();
+ this.stack.transition_duration = 500;
+ this.stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
+ this.stack.homogeneous = true;
+ this.stack.halign = Gtk.Align.FILL;
+ this.stack.expand = true;
+ main_box.add(stack);
+ switcher.set_stack(stack);
+
+ this.stack.add_with_properties(general_settings, "name", "1", "title", _("General Settings"), null);
+ this.stack.add_with_properties(pie_settings, "name", "2", "title", _("Pie Settings"), null);
+ }
+
+ this.pie_list = new PieList();
+ this.pie_list.on_select.connect(this.on_pie_select);
+ this.pie_list.on_activate.connect(() => {
+ this.on_edit_pie_button_clicked();
+ });
+
+ var scroll_area = builder.get_object("pies-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.pie_list);
+
+ this.preview = new PiePreview();
+ this.preview.on_first_slice_added.connect(() => {
+ this.no_slice_label.hide();
+ });
+
+ this.preview.on_last_slice_removed.connect(() => {
+ this.no_slice_label.show();
+ });
+
+ preview_box = builder.get_object("preview-box") as Gtk.Box;
+ this.preview_box.pack_start(preview, true, true);
+ this.no_pie_label = builder.get_object("no-pie-label") as Gtk.Label;
+ this.no_slice_label = builder.get_object("no-slice-label") as Gtk.Label;
+ this.preview_background = builder.get_object("preview-background") as Gtk.EventBox;
+
+ this.remove_pie_button = builder.get_object("remove-pie-button") as Gtk.Button;
+ this.remove_pie_button.clicked.connect(on_remove_pie_button_clicked);
+
+ this.edit_pie_button = builder.get_object("edit-pie-button") as Gtk.Button;
+ this.edit_pie_button.clicked.connect(on_edit_pie_button_clicked);
+
+ (builder.get_object("add-pie-button") as Gtk.Button).clicked.connect(on_add_pie_button_clicked);
+
+ this.theme_list = new ThemeList();
+ this.theme_list.on_select_new.connect(() => {
+ this.captions.active = Config.global.show_captions;
+ if (Config.global.theme.has_slice_captions) {
+ this.captions.sensitive = true;
+ } else {
+ this.captions.sensitive = false;
+ }
+ if (Config.global.theme.is_local()) {
+ this.theme_delete_button.sensitive = true;
+ } else {
+ this.theme_delete_button.sensitive = false;
+ }
+ });
+
+ scroll_area = builder.get_object("theme-scrolledwindow") as Gtk.ScrolledWindow;
+ scroll_area.add(this.theme_list);
+
+ (builder.get_object("theme-help-button") as Gtk.Button).clicked.connect(() => {
+ try{
+ GLib.AppInfo.launch_default_for_uri("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/", null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ });
+
+ (builder.get_object("theme-export-button") as Gtk.Button).clicked.connect(on_export_theme_button_clicked);
+ (builder.get_object("theme-import-button") as Gtk.Button).clicked.connect(on_import_theme_button_clicked);
+ (builder.get_object("theme-reload-button") as Gtk.Button).clicked.connect(on_reload_theme_button_clicked);
+ (builder.get_object("theme-open-button") as Gtk.Button).clicked.connect(on_open_theme_button_clicked);
+ this.theme_delete_button = (builder.get_object("theme-delete-button") as Gtk.Button);
+ this.theme_delete_button.clicked.connect(on_delete_theme_button_clicked);
+
+ this.autostart = (builder.get_object("autostart-checkbox") as Gtk.ToggleButton);
+ this.autostart.toggled.connect(on_autostart_toggled);
+
+ this.indicator = (builder.get_object("indicator-checkbox") as Gtk.ToggleButton);
+ this.indicator.toggled.connect(on_indicator_toggled);
+
+ this.search_by_string = (builder.get_object("select-by-string-checkbox") as Gtk.ToggleButton);
+ this.search_by_string.toggled.connect(on_search_by_string_toggled);
+
+ this.captions = (builder.get_object("captions-checkbox") as Gtk.ToggleButton);
+ this.captions.toggled.connect(on_captions_toggled);
+
+ var scale_slider = (builder.get_object("scale-hscale") as Gtk.Scale);
+ scale_slider.set_range(0.5, 2.0);
+ scale_slider.set_increments(0.05, 0.25);
+ scale_slider.set_value(Config.global.global_scale);
+
+ bool changing = false;
+ bool changed_again = false;
+
+ scale_slider.value_changed.connect(() => {
+ if (!changing) {
+ changing = true;
+ Timeout.add(300, () => {
+ if (changed_again) {
+ changed_again = false;
+ return true;
+ }
+
+ Config.global.global_scale = scale_slider.get_value();
+ Config.global.load_themes(Config.global.theme.name);
+ changing = false;
+ return false;
+ });
+ } else {
+ changed_again = true;
+ }
+ });
+
+ var range_slider = (builder.get_object("range-hscale") as Gtk.Scale);
+ range_slider.set_range(0, 2000);
+ range_slider.set_increments(10, 100);
+ range_slider.set_value(Config.global.activation_range);
+ range_slider.value_changed.connect(() => {
+ Config.global.activation_range = (int)range_slider.get_value();
+ });
+
+ var range_slices = (builder.get_object("range-slices") as Gtk.Scale);
+ range_slices.set_range(12, 96);
+ range_slices.set_increments(4, 12);
+ range_slices.set_value(Config.global.max_visible_slices);
+ range_slices.value_changed.connect(() => {
+ Config.global.max_visible_slices = (int)range_slices.get_value();
+ });
+
+ var info_box = (builder.get_object("info-box") as Gtk.Box);
+
+ // info label
+ var info_label = new TipViewer({
+ _("Pies can be opened with the terminal command \"gnome-pie --open=ID\"."),
+ _("Feel free to visit Gnome-Pie's homepage at %s!").printf("<a href='http://simmesimme.github.io/gnome-pie.html'>gnome-pie.simonschneegans.de</a>"),
+ _("If you want to give some feedback, please write an e-mail to %s!").printf("<a href='mailto:code@simonschneegans.de'>code@simonschneegans.de</a>"),
+ _("You can support the development of Gnome-Pie by donating via %s.").printf("<a href='https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=X65SUVC4ZTQSC'>Paypal</a>"),
+ _("Translating Gnome-Pie to your language is easy. Translations are managed at %s.").printf("<a href='https://translate.zanata.org/zanata/iteration/view/gnome-pie/develop'>Zanata</a>"),
+ _("It's easy to create new themes for Gnome-Pie. Read the <a href='%s'>Tutorial</a> online.").printf("http://simmesimme.github.io/lessons/2015/04/26/themes-for-gnome-pie/"),
+ _("It's usually a good practice to have at most twelve slices per pie."),
+ _("You can export themes you created and share them with the community!"),
+ _("The source code of Gnome-Pie is available on %s.").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"),
+ _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"),
+ _("Suggestions can be posted on %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie/issues'>Github</a>"),
+ _("An awesome companion of Gnome-Pie is %s. It will make using your computer feel like magic!").printf("<a href='https://github.com/thjaeger/easystroke/wiki'>Easystroke</a>"),
+ _("You can drag'n'drop applications from your main menu to the pie above."),
+ _("You may drag'n'drop URLs and bookmarks from your internet browser to the pie above."),
+ _("You can drag'n'drop files and folders from your file browser to the pie above."),
+ _("You can drag'n'drop pies from the list on the left into other pies in order to create sub-pies."),
+ _("You can drag'n'drop pies from the list on the left to your desktop or dock to create a launcher for this pie.")
+ });
+ this.window.show.connect(info_label.start_slide_show);
+ this.window.hide.connect(info_label.stop_slide_show);
+
+ info_box.pack_end(info_label);
+
+ this.window.hide.connect(() => {
+ // save settings on close
+ Config.global.save();
+ Pies.save();
+
+ Timeout.add(100, () => {
+ IconSelectWindow.clear_icons();
+ return false;
+ });
+ });
+
+ this.window.delete_event.connect(this.window.hide_on_delete);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows the window.
+ /////////////////////////////////////////////////////////////////////
+
+ public void show() {
+ this.preview.draw_loop();
+ this.window.show_all();
+ this.pie_list.select_first();
+
+ var style = this.preview_background.get_style_context();
+ this.preview_background.override_background_color(Gtk.StateFlags.NORMAL, style.get_background_color(Gtk.StateFlags.NORMAL));
+
+ this.indicator.active = Config.global.show_indicator;
+ this.autostart.active = Config.global.auto_start;
+ this.captions.active = Config.global.show_captions;
+ this.search_by_string.active = Config.global.search_by_string;
+
+ if (Config.global.theme.has_slice_captions) {
+ this.captions.sensitive = true;
+ } else {
+ this.captions.sensitive = false;
+ }
+
+ if (Config.global.theme.is_local()) {
+ this.theme_delete_button.sensitive = true;
+ } else {
+ this.theme_delete_button.sensitive = false;
+ }
+
+ if (!Daemon.disable_stack_switcher) {
+ this.stack.set_visible_child_full("2", Gtk.StackTransitionType.NONE);
+ } else {
+ this.notebook.set_current_page(1);
+ }
+ this.pie_list.has_focus = true;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Creates or deletes the autostart file. This code is inspired
+ /// by project synapse as well.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_autostart_toggled(Gtk.ToggleButton check_box) {
+
+ bool active = check_box.active;
+ if (!active && FileUtils.test(Paths.autostart, FileTest.EXISTS)) {
+ Config.global.auto_start = false;
+ // delete the autostart file
+ FileUtils.remove (Paths.autostart);
+ }
+ else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) {
+ Config.global.auto_start = true;
+
+ string autostart_entry =
+ "#!/usr/bin/env xdg-open\n" +
+ "[Desktop Entry]\n" +
+ "Name=Gnome-Pie\n" +
+ "Exec=" + Paths.executable + "\n" +
+ "Encoding=UTF-8\n" +
+ "Type=Application\n" +
+ "X-GNOME-Autostart-enabled=true\n" +
+ "Icon=gnome-pie\n";
+
+ // create the autostart file
+ string autostart_dir = GLib.Path.get_dirname(Paths.autostart);
+ if (!FileUtils.test(autostart_dir, FileTest.EXISTS | FileTest.IS_DIR)) {
+ DirUtils.create_with_parents(autostart_dir, 0755);
+ }
+
+ try {
+ FileUtils.set_contents(Paths.autostart, autostart_entry);
+ FileUtils.chmod(Paths.autostart, 0755);
+ } catch (Error e) {
+ var d = new Gtk.MessageDialog(this.window, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
+ "%s", e.message);
+ d.run();
+ d.destroy();
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Saves the current theme to an archive.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_export_theme_button_clicked(Gtk.Button button) {
+ var dialog = new Gtk.FileChooserDialog("Pick a file", this.window,
+ Gtk.FileChooserAction.SAVE,
+ "_Cancel",
+ Gtk.ResponseType.CANCEL,
+ "_Save",
+ Gtk.ResponseType.ACCEPT);
+
+ dialog.set_do_overwrite_confirmation(true);
+ dialog.set_modal(true);
+ dialog.filter = new Gtk.FileFilter();
+ dialog.filter.add_pattern ("*.tar.gz");
+ dialog.set_current_name(Config.global.theme.name + ".tar.gz");
+
+ dialog.response.connect((d, result) => {
+ if (result == Gtk.ResponseType.ACCEPT) {
+ var file = dialog.get_filename();
+ if (!file.has_suffix(".tar.gz")) {
+ file = file + ".tar.gz";
+ }
+ Config.global.theme.export(file);
+ }
+ dialog.destroy();
+ });
+ dialog.show();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Imports a new theme from an archive.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_import_theme_button_clicked(Gtk.Button button) {
+ var dialog = new Gtk.FileChooserDialog("Pick a file", this.window,
+ Gtk.FileChooserAction.OPEN,
+ "_Cancel",
+ Gtk.ResponseType.CANCEL,
+ "_Open",
+ Gtk.ResponseType.ACCEPT);
+
+ dialog.set_modal(true);
+ dialog.filter = new Gtk.FileFilter();
+ dialog.filter.add_pattern ("*.tar.gz");
+
+ var result = Gtk.MessageType.INFO;
+ var message = _("Sucessfully imported new theme!");
+
+ dialog.response.connect((d, r) => {
+ if (r == Gtk.ResponseType.ACCEPT) {
+ var file = dialog.get_filename();
+
+ var a = new ThemeImporter();
+ if (a.open(file)) {
+ if (a.is_valid_theme) {
+ if (!Config.global.has_theme(a.theme_name)) {
+ if (a.extract_to(Paths.local_themes + "/" + a.theme_name)) {
+ Config.global.load_themes(a.theme_name);
+ this.theme_list.reload();
+ } else {
+ message = _("An error occured while importing the theme: Failed to extract theme!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: A theme with this name does already exist!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: Theme archive does not contain a valid theme!");
+ result = Gtk.MessageType.ERROR;
+ }
+ } else {
+ message = _("An error occured while importing the theme: Failed to open theme archive!");
+ result = Gtk.MessageType.ERROR;
+ }
+ a.close();
+
+ var result_dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,
+ result, Gtk.ButtonsType.CLOSE, message);
+ result_dialog.run();
+ result_dialog.destroy();
+ }
+ dialog.destroy();
+
+ });
+ dialog.show();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Deleted the slected theme.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_delete_theme_button_clicked(Gtk.Button button) {
+
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.get_toplevel(), Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("Do you really want to delete the selected theme from %s?").printf(Config.global.theme.directory));
+
+ dialog.response.connect((response) => {
+ if (response == Gtk.ResponseType.YES) {
+ Paths.delete_directory(Config.global.theme.directory);
+ Config.global.load_themes("");
+ this.theme_list.reload();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Reloads all themes.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_reload_theme_button_clicked(Gtk.Button button) {
+ Config.global.load_themes(Config.global.theme.name);
+ this.theme_list.reload();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Opens the loaction of the them in the file browser.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_open_theme_button_clicked(Gtk.Button button) {
+ try{
+ GLib.AppInfo.launch_default_for_uri("file://" + Config.global.theme.directory, null);
+ } catch (Error e) {
+ warning(e.message);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows or hides the indicator.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_indicator_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.show_indicator = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Shows or hides the captions of Slices.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_captions_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.show_captions = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Enables or disables Slice selection by typing.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_search_by_string_toggled(Gtk.ToggleButton check_box) {
+ var check = check_box as Gtk.CheckButton;
+ Config.global.search_by_string = check.active;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when a new Pie is selected in the PieList.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_pie_select(string id) {
+ selected_id = id;
+
+ this.no_slice_label.hide();
+ this.no_pie_label.hide();
+ this.preview_box.hide();
+
+ this.remove_pie_button.sensitive = false;
+ this.edit_pie_button.sensitive = false;
+
+ if (id == "") {
+ this.no_pie_label.show();
+ } else {
+ var pie = PieManager.all_pies[selected_id];
+
+ this.preview.set_pie(id);
+ this.preview_box.show();
+
+ if (pie.action_groups.size == 0) {
+ this.no_slice_label.show();
+ }
+
+ this.remove_pie_button.sensitive = true;
+ this.edit_pie_button.sensitive = true;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the add Pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_add_pie_button_clicked(Gtk.Button button) {
+ var new_pie = PieManager.create_persistent_pie(_("New Pie"), "stock_unknown", null);
+ this.pie_list.reload_all();
+ this.pie_list.select(new_pie.id);
+
+ this.on_edit_pie_button_clicked();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the remove Pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_remove_pie_button_clicked(Gtk.Button button) {
+ if (this.selected_id != "") {
+ var dialog = new Gtk.MessageDialog((Gtk.Window)this.window.get_toplevel(), Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
+ _("Do you really want to delete the selected Pie with all contained Slices?"));
+
+ dialog.response.connect((response) => {
+ if (response == Gtk.ResponseType.YES) {
+ PieManager.remove_pie(selected_id);
+ this.pie_list.reload_all();
+ this.pie_list.select_first();
+ }
+ });
+
+ dialog.run();
+ dialog.destroy();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the edit pie button is clicked.
+ /////////////////////////////////////////////////////////////////////
+
+ private void on_edit_pie_button_clicked(Gtk.Button? button = null) {
+ if (this.pie_options_window == null) {
+ this.pie_options_window = new PieOptionsWindow();
+ this.pie_options_window.set_parent(window);
+ this.pie_options_window.on_ok.connect((trigger, name, icon) => {
+ var pie = PieManager.all_pies[selected_id];
+ pie.name = name;
+ pie.icon = icon;
+ PieManager.bind_trigger(trigger, selected_id);
+ PieManager.create_launcher(pie.id);
+ this.pie_list.reload_all();
+ });
+ }
+
+ this.pie_options_window.set_pie(selected_id);
+ this.pie_options_window.show();
+ }
+}
+
+}
diff --git a/src/gui/sliceTypeList.vala b/src/gui/sliceTypeList.vala
new file mode 100644
index 0000000..1a9ecc4
--- /dev/null
+++ b/src/gui/sliceTypeList.vala
@@ -0,0 +1,173 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A list displaying all available Action types and ActionGroup types.
+/////////////////////////////////////////////////////////////////////////
+
+class SliceTypeList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted when the user selects a new Type.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(string id, string icon_name);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The listore which staroes all types internally.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.ListStore data;
+ private enum DataPos {ICON, ICON_NAME, NAME, ID}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public SliceTypeList() {
+ GLib.Object();
+
+ this.data = new Gtk.ListStore(4, typeof(Gdk.Pixbuf),
+ typeof(string),
+ typeof(string),
+ typeof(string));
+
+ this.data.set_sort_column_id(2, Gtk.SortType.ASCENDING);
+
+ base.set_model(this.data);
+ base.set_headers_visible(true);
+ base.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.set_fixed_height_mode(true);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED);
+ main_column.title = _("Slice types");
+ var icon_render = new Gtk.CellRendererPixbuf();
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ main_column.pack_start(name_render, true);
+
+ base.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ string id = "";
+ string icon = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.data.get(active, DataPos.ICON_NAME, out icon);
+ this.on_select(id, icon);
+ }
+ });
+
+ reload_all();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Loads a registered actions and action groups.
+ /////////////////////////////////////////////////////////////////////
+
+ public void reload_all() {
+ Gtk.TreeIter active;
+ string current_id = "";
+ if (this.get_selection().get_selected(null, out active))
+ this.data.get(active, DataPos.ID, out current_id);
+
+ data.clear();
+
+ foreach (var action_type in ActionRegistry.types) {
+ var description = ActionRegistry.descriptions[action_type];
+
+ Gtk.TreeIter current;
+ data.append(out current);
+ var icon = new Icon(description.icon, 36);
+ data.set(current, DataPos.ICON, icon.to_pixbuf());
+ data.set(current, DataPos.ICON_NAME, description.icon);
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(description.name) + "\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(description.description) + "</span>");
+ data.set(current, DataPos.ID, description.id);
+ }
+
+ foreach (var group_type in GroupRegistry.types) {
+ var description = GroupRegistry.descriptions[group_type];
+
+ Gtk.TreeIter current;
+ data.append(out current);
+ var icon = new Icon(description.icon, 36);
+ data.set(current, DataPos.ICON, icon.to_pixbuf());
+ data.set(current, DataPos.ICON_NAME, description.icon);
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(description.name) + "\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(description.description) + "</span>");
+ data.set(current, DataPos.ID, description.id);
+ }
+
+ select_first();
+ select(current_id);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Selects the first type in the list.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select_first() {
+ Gtk.TreeIter active;
+
+ if(this.data.get_iter_first(out active) ) {
+ this.get_selection().select_iter(active);
+ string id = "";
+ string icon = "";
+ this.data.get(active, DataPos.ID, out id);
+ this.data.get(active, DataPos.ICON_NAME, out icon);
+ this.on_select(id, icon);
+ } else {
+ this.on_select("", "stock_unknown");
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Select the given slice type.
+ /////////////////////////////////////////////////////////////////////
+
+ public void select(string id) {
+ this.data.foreach((model, path, iter) => {
+ string pie_id;
+ this.data.get(iter, DataPos.ID, out pie_id);
+
+ if (id == pie_id) {
+ this.get_selection().select_iter(iter);
+ string icon = "";
+ this.data.get(iter, DataPos.ICON_NAME, out icon);
+ this.on_select(pie_id, icon);
+ this.scroll_to_cell(path, null, true, 0.5f, 0.5f);
+ this.has_focus = true;
+
+ return true;
+ }
+
+ return false;
+ });
+ }
+}
+
+}
diff --git a/src/gui/themeList.vala b/src/gui/themeList.vala
new file mode 100644
index 0000000..786c305
--- /dev/null
+++ b/src/gui/themeList.vala
@@ -0,0 +1,118 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A widget displaying all available themes of Gnome-Pie.
+/////////////////////////////////////////////////////////////////////////
+
+class ThemeList : Gtk.TreeView {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal gets emitted, when a new theme is selected by the
+ /// user. This new theme is applied automatically, with this signal
+ /// actions may be triggered which should be executed AFTER the
+ /// change to a new theme.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select_new();
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently selected row.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gtk.TreeIter active { private get; private set; }
+
+ /////////////////////////////////////////////////////////////////////
+ /// The positions in the data list store.
+ /////////////////////////////////////////////////////////////////////
+
+ private enum DataPos {ICON, NAME}
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs the Widget.
+ /////////////////////////////////////////////////////////////////////
+
+ public ThemeList() {
+ GLib.Object();
+
+ this.set_headers_visible(true);
+ this.set_grid_lines(Gtk.TreeViewGridLines.NONE);
+ this.set_fixed_height_mode(true);
+
+ var main_column = new Gtk.TreeViewColumn();
+ main_column.title = _("Themes");
+ main_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED);
+ var icon_render = new Gtk.CellRendererPixbuf();
+ icon_render.xpad = 4;
+ icon_render.ypad = 4;
+ main_column.pack_start(icon_render, false);
+
+ var name_render = new Gtk.CellRendererText();
+ name_render.xpad = 6;
+ main_column.pack_start(name_render, true);
+
+ this.append_column(main_column);
+
+ main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON);
+ main_column.add_attribute(name_render, "markup", DataPos.NAME);
+
+ this.get_selection().changed.connect(() => {
+ Gtk.TreeIter active;
+ if (this.get_selection().get_selected(null, out active)) {
+ Timeout.add(10, () => {
+ int index = int.parse(this.model.get_path(active).to_string());
+ Config.global.theme = Config.global.themes[index];
+
+ this.on_select_new();
+
+ Config.global.theme.load();
+ Config.global.theme.load_images();
+ return false;
+ });
+ }
+ });
+
+ reload();
+ }
+
+ public void reload() {
+
+ var data = new Gtk.ListStore(2, typeof(Gdk.Pixbuf),
+ typeof(string));
+ this.set_model(data);
+
+ // load all themes into the list
+ var themes = Config.global.themes;
+ foreach(var theme in themes) {
+ Gtk.TreeIter current;
+ data.append(out current);
+ data.set(current, DataPos.ICON, theme.preview_icon.to_pixbuf());
+ data.set(current, DataPos.NAME, GLib.Markup.escape_text(theme.name)+"\n"
+ + "<span font-size='x-small'>" + GLib.Markup.escape_text(theme.description)
+ + " - <i>"+GLib.Markup.escape_text(_("by")+" "+theme.author)
+ + "</i></span>");
+ if(theme == Config.global.theme) {
+ get_selection().select_iter(current);
+ this.scroll_to_cell(get_selection().get_selected_rows(null).nth_data(0), null, true, 0.5f, 0.5f);
+ }
+ }
+ }
+}
+
+}
diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala
new file mode 100644
index 0000000..e2158bd
--- /dev/null
+++ b/src/gui/tipViewer.vala
@@ -0,0 +1,163 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// A widget showing tips. The tips are beautifully faded in and out.
+/////////////////////////////////////////////////////////////////////////
+
+public class TipViewer : Gtk.Label {
+
+ /////////////////////////////////////////////////////////////////////
+ /// Some settings tweaking the behavior of the TipViewer.
+ /////////////////////////////////////////////////////////////////////
+
+ private const double fade_time = 0.5;
+ private const double frame_rate = 20.0;
+ private const double base_delay = 3.0;
+
+ /////////////////////////////////////////////////////////////////////
+ /// False, if the playback of tips is stopped.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool playing = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// An array containing all tips.
+ /////////////////////////////////////////////////////////////////////
+
+ private string[] tips;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The index of the currently displayed tip.
+ /////////////////////////////////////////////////////////////////////
+
+ private int index = -1;
+
+ /////////////////////////////////////////////////////////////////////
+ /// The fading value.
+ /////////////////////////////////////////////////////////////////////
+
+ private AnimatedValue alpha;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, initializes all members and sets the basic layout.
+ /////////////////////////////////////////////////////////////////////
+
+ public TipViewer(string[] tips) {
+ this.tips = tips;
+
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, fade_time);
+
+ this.set_alignment (0.0f, 0.5f);
+ this.opacity = 0;
+ this.wrap = true;
+ this.valign = Gtk.Align.END;
+ this.set_use_markup(true);
+
+ this.override_font(Pango.FontDescription.from_string("8"));
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts the playback of tips.
+ /////////////////////////////////////////////////////////////////////
+
+ public void start_slide_show() {
+ if (!this.playing && tips.length > 1) {
+ this.playing = true;
+ show_tip();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Stops the playback of tips.
+ /////////////////////////////////////////////////////////////////////
+
+ public void stop_slide_show() {
+ this.playing = false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts the fading in.
+ /////////////////////////////////////////////////////////////////////
+
+ private void fade_in() {
+ this.alpha = new AnimatedValue.linear(this.alpha.val, 1.0, fade_time);
+
+ GLib.Timeout.add((uint)(1000.0/frame_rate), () => {
+ this.alpha.update(1.0/frame_rate);
+ this.opacity = this.alpha.val;
+
+ return (this.alpha.val != 1.0);
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Starts the fading out.
+ /////////////////////////////////////////////////////////////////////
+
+ private void fade_out() {
+ this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, fade_time);
+
+ GLib.Timeout.add((uint)(1000.0/frame_rate), () => {
+ this.alpha.update(1.0/frame_rate);
+ this.opacity = this.alpha.val;
+
+ return (this.alpha.val != 0.0);
+ });
+ }
+
+ private void show_tip() {
+
+ this.set_random_tip();
+
+ this.fade_in();
+
+ uint delay = (uint)(base_delay*1000.0) + tips[this.index].length*30;
+
+ GLib.Timeout.add(delay, () => {
+ this.fade_out();
+
+ if (this.playing) {
+ GLib.Timeout.add((uint)(1000.0*fade_time), () => {
+ this.show_tip();
+ return false;
+ });
+ }
+
+ return false;
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Chooses the next random tip.
+ /////////////////////////////////////////////////////////////////////
+
+ private void set_random_tip() {
+ if (tips.length > 1) {
+ int next_index = -1;
+ do {
+ next_index = GLib.Random.int_range(0, tips.length);
+ } while (next_index == this.index);
+ this.index = next_index;
+ this.label = tips[this.index];
+ }
+ }
+}
+
+}
diff --git a/src/gui/triggerSelectButton.vala b/src/gui/triggerSelectButton.vala
new file mode 100644
index 0000000..eb34066
--- /dev/null
+++ b/src/gui/triggerSelectButton.vala
@@ -0,0 +1,163 @@
+/////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2011-2015 by Simon Schneegans
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/////////////////////////////////////////////////////////////////////////
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This window allows the selection of a hotkey. It is returned in form
+/// of a Trigger. Therefore it can be either a keyboard driven hotkey or
+/// a mouse based hotkey.
+/////////////////////////////////////////////////////////////////////////
+
+public class TriggerSelectButton : Gtk.ToggleButton {
+
+ /////////////////////////////////////////////////////////////////////
+ /// This signal is emitted when the user selects a new hot key.
+ /////////////////////////////////////////////////////////////////////
+
+ public signal void on_select(Trigger trigger);
+
+ /////////////////////////////////////////////////////////////////////
+ /// The currently contained Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private Trigger trigger = null;
+
+ /////////////////////////////////////////////////////////////////////
+ /// True, if mouse buttons can be bound as well.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool enable_mouse = false;
+
+ /////////////////////////////////////////////////////////////////////
+ /// These modifiers are ignored.
+ /////////////////////////////////////////////////////////////////////
+
+ private Gdk.ModifierType lock_modifiers = Gdk.ModifierType.MOD2_MASK
+ |Gdk.ModifierType.MOD4_MASK
+ |Gdk.ModifierType.MOD5_MASK
+ |Gdk.ModifierType.LOCK_MASK;
+
+ /////////////////////////////////////////////////////////////////////
+ /// C'tor, constructs a new TriggerSelectWindow.
+ /////////////////////////////////////////////////////////////////////
+
+ public TriggerSelectButton(bool enable_mouse) {
+ this.enable_mouse = enable_mouse;
+
+ this.toggled.connect(() => {
+ if (this.active) {
+ this.set_label(_("Press a hotkey ..."));
+ Gtk.grab_add(this);
+ FocusGrabber.grab(this.get_window(), true, true, true);
+ }
+ });
+
+ this.button_press_event.connect(this.on_button_press);
+ this.key_press_event.connect(this.on_key_press);
+ this.set_trigger(new Trigger());
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the button display the given Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ public void set_trigger(Trigger trigger) {
+ this.trigger = trigger;
+ this.set_label(trigger.label);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Can be called to cancel the selection process.
+ /////////////////////////////////////////////////////////////////////
+
+ private void cancel() {
+ this.set_label(trigger.label);
+ this.set_active(false);
+ Gtk.grab_remove(this);
+ FocusGrabber.ungrab(true, true);
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Makes the button display the given Trigger.
+ /////////////////////////////////////////////////////////////////////
+
+ private void update_trigger(Trigger trigger) {
+ if (this.trigger.name != trigger.name) {
+ this.set_trigger(trigger);
+ this.on_select(this.trigger);
+ }
+
+ this.cancel();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses a keyboard key.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_key_press(Gdk.EventKey event) {
+ if (this.active) {
+ if (Gdk.keyval_name(event.keyval) == "Escape") {
+ this.cancel();
+ } else if (Gdk.keyval_name(event.keyval) == "BackSpace") {
+ this.update_trigger(new Trigger());
+ } else if (event.is_modifier == 0) {
+ Gdk.ModifierType state = event.state & ~ this.lock_modifiers;
+ this.update_trigger(new Trigger.from_values(event.keyval, state, false, false, false,
+ false, false, 5));
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ /// Called when the user presses a button of the mouse.
+ /////////////////////////////////////////////////////////////////////
+
+ private bool on_button_press(Gdk.EventButton event) {
+ if (this.active) {
+ Gtk.Allocation rect;
+ this.get_allocation(out rect);
+ if (event.x < 0 || event.x > rect.width
+ || event.y < 0 || event.y > rect.height) {
+
+ this.cancel();
+ return true;
+ }
+ }
+
+ if (this.active && this.enable_mouse) {
+ Gdk.ModifierType state = event.state & ~ this.lock_modifiers;
+ var new_trigger = new Trigger.from_values((int)event.button, state, true,
+ false, false, false, false, 5);
+
+ if (new_trigger.key_code != 1) this.update_trigger(new_trigger);
+ else this.cancel();
+
+ return true;
+ } else if (this.active) {
+ this.cancel();
+ return true;
+ }
+
+ return false;
+ }
+}
+
+}