diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/aboutWindow.vala (renamed from src/gui/about.vala) | 29 | ||||
-rw-r--r-- | src/gui/cellRendererIcon.vala | 132 | ||||
-rw-r--r-- | src/gui/cellRendererTrigger.vala | 84 | ||||
-rw-r--r-- | src/gui/iconSelectWindow.vala | 428 | ||||
-rw-r--r-- | src/gui/indicator.vala | 14 | ||||
-rw-r--r-- | src/gui/newSliceWindow.vala | 394 | ||||
-rw-r--r-- | src/gui/pieComboList.vala | 155 | ||||
-rw-r--r-- | src/gui/pieList.vala | 1105 | ||||
-rw-r--r-- | src/gui/piePreview.vala | 369 | ||||
-rw-r--r-- | src/gui/piePreviewAddSign.vala | 220 | ||||
-rw-r--r-- | src/gui/piePreviewCenter.vala | 108 | ||||
-rw-r--r-- | src/gui/piePreviewDeleteSign.vala | 195 | ||||
-rw-r--r-- | src/gui/piePreviewRenderer.vala | 436 | ||||
-rw-r--r-- | src/gui/piePreviewSliceRenderer.vala | 276 | ||||
-rw-r--r-- | src/gui/preferences.vala | 325 | ||||
-rw-r--r-- | src/gui/preferencesWindow.vala | 311 | ||||
-rw-r--r-- | src/gui/renameWindow.vala | 109 | ||||
-rw-r--r-- | src/gui/settingsWindow.vala | 174 | ||||
-rw-r--r-- | src/gui/sliceTypeList.vala | 172 | ||||
-rw-r--r-- | src/gui/themeList.vala | 74 | ||||
-rw-r--r-- | src/gui/tipViewer.vala | 172 | ||||
-rw-r--r-- | src/gui/triggerSelectButton.vala | 161 | ||||
-rw-r--r-- | src/gui/triggerSelectWindow.vala | 257 |
23 files changed, 3592 insertions, 2108 deletions
diff --git a/src/gui/about.vala b/src/gui/aboutWindow.vala index ce4256e..ccd956a 100644 --- a/src/gui/about.vala +++ b/src/gui/aboutWindow.vala @@ -18,12 +18,17 @@ this program. If not, see <http://www.gnu.org/licenses/>. namespace GnomePie { ///////////////////////////////////////////////////////////////////////// -/// A simple about Dialog. +/// A simple about dialog. ///////////////////////////////////////////////////////////////////////// -public class GnomePieAboutDialog: Gtk.AboutDialog { - - public GnomePieAboutDialog () { +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>", "Francesco Piccinno <stack.box@gmail.com>" @@ -32,11 +37,13 @@ public class GnomePieAboutDialog: Gtk.AboutDialog { "Simon Schneegans <code@simonschneegans.de>" }; string[] translators = { - "DE\t\t Simon Schneegans <code@simonschneegans.de>", - "IT\t\t Riccardo Traverso <gr3yfox.fw@gmail.com>", - "PT-BR\t Magnun Leno <magnun@codecommunity.org>", - "EN\t\t Simon Schneegans <code@simonschneegans.de>", - "KO\t\t Kim Boram <Boramism@gmail.com>" + "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)", + "Gregoire Bellon-Gervais <greggbg@gmail.com> (FR)", + "Eugene Roskin <pams@imail.ru> (RU)" }; // sort translators @@ -56,12 +63,12 @@ public class GnomePieAboutDialog: Gtk.AboutDialog { artists : artists, authors : devs, translator_credits : translator_string, - copyright : "Copyright (C) 2011 Simon Schneegans <code@simonschneegans.de>", + copyright : "Copyright (C) 2011-2012 Simon Schneegans <code@simonschneegans.de>", program_name: "Gnome-Pie", logo_icon_name: "gnome-pie", website: "http://www.simonschneegans.de/?page_id=12", website_label: "www.gnome-pie.simonschneegans.de", - version: "0.3.1" + version: "0.4.0" ); } } diff --git a/src/gui/cellRendererIcon.vala b/src/gui/cellRendererIcon.vala deleted file mode 100644 index 959a0b7..0000000 --- a/src/gui/cellRendererIcon.vala +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright (c) 2011 by Simon Schneegans - -This program is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) -any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -namespace GnomePie { - -///////////////////////////////////////////////////////////////////////// -/// A cellrenderer which displays an Icon. When clicked onto, a window -/// opens for selecting another icon. This needs to be a subclass of -/// Gtk.CellRendererText because Gtk.CellRendererPixbuf can't receive -/// click events. Internally it stores a Gtk.CellRendererPixbuf -/// which renders and stuff. -///////////////////////////////////////////////////////////////////////// - -public class CellRendererIcon : Gtk.CellRendererText { - - ///////////////////////////////////////////////////////////////////// - /// This signal is emitted when the user selects another icon. - ///////////////////////////////////////////////////////////////////// - - public signal void on_select(string path, string icon); - - ///////////////////////////////////////////////////////////////////// - /// The IconSelectWindow which is shown on click. - ///////////////////////////////////////////////////////////////////// - - private IconSelectWindow select_window = null; - - ///////////////////////////////////////////////////////////////////// - /// The internal Renderer used for drawing. - ///////////////////////////////////////////////////////////////////// - - private Gtk.CellRendererPixbuf renderer = null; - - ///////////////////////////////////////////////////////////////////// - /// A helper variable, needed to emit the current path. - ///////////////////////////////////////////////////////////////////// - - private string current_path = ""; - - public string icon_name { get; set; } - - ///////////////////////////////////////////////////////////////////// - /// Forward some parts of the CellRendererPixbuf's interface. - ///////////////////////////////////////////////////////////////////// - - public bool follow_state { - get { return renderer.follow_state; } - set { renderer.follow_state = value; } - } - - public bool icon_sensitive { - get { return renderer.sensitive; } - set { renderer.sensitive = value; } - } - - public Gdk.Pixbuf pixbuf { - owned get { return renderer.pixbuf; } - set { renderer.pixbuf = value; } - } - - ///////////////////////////////////////////////////////////////////// - /// C'tor, creates a new CellRendererIcon. - ///////////////////////////////////////////////////////////////////// - - public CellRendererIcon() { - this.select_window = new IconSelectWindow(); - this.renderer = new Gtk.CellRendererPixbuf(); - - this.select_window.on_select.connect((icon) => { - this.on_select(current_path, icon); - }); - } - - ///////////////////////////////////////////////////////////////////// - /// Forward some parts of the CellRendererPixbuf's interface. - ///////////////////////////////////////////////////////////////////// - - public override void get_size (Gtk.Widget widget, Gdk.Rectangle? cell_area, - out int x_offset, out int y_offset, - out int width, out int height) { - - this.renderer.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height); - } - - ///////////////////////////////////////////////////////////////////// - /// Forward some parts of the CellRendererPixbuf's interface. - ///////////////////////////////////////////////////////////////////// - - public override void render (Gdk.Window window, Gtk.Widget widget, - Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, - Gdk.Rectangle expose_area, - Gtk.CellRendererState flags) { - - this.renderer.render(window, widget, bg_area, cell_area, expose_area, flags); - } - - ///////////////////////////////////////////////////////////////////// - /// Open the IconSelectWindow on click. - ///////////////////////////////////////////////////////////////////// - - public override unowned Gtk.CellEditable start_editing( - Gdk.Event event, Gtk.Widget widget, string path, Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { - - this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); - this.select_window.set_modal(true); - - this.current_path = path; - this.select_window.show(); - this.select_window.active_icon = this.icon_name; - - return this.renderer.start_editing(event, widget, path, bg_area, cell_area, flags); - } -} - -} - diff --git a/src/gui/cellRendererTrigger.vala b/src/gui/cellRendererTrigger.vala deleted file mode 100644 index a825c32..0000000 --- a/src/gui/cellRendererTrigger.vala +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright (c) 2011 by Simon Schneegans - -This program is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) -any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -namespace GnomePie { - -///////////////////////////////////////////////////////////////////////// -/// A CellRenderer which opens a TriggerSelectWindow. -///////////////////////////////////////////////////////////////////////// - -public class CellRendererTrigger : Gtk.CellRendererText { - - ///////////////////////////////////////////////////////////////////// - /// This signal is emitted when the user selects another trigger. - ///////////////////////////////////////////////////////////////////// - - public signal void on_select(string path, Trigger trigger); - - ///////////////////////////////////////////////////////////////////// - /// The trigger which can be set with this window. - ///////////////////////////////////////////////////////////////////// - - public string trigger { get; set; } - - ///////////////////////////////////////////////////////////////////// - /// The IconSelectWindow which is shown on click. - ///////////////////////////////////////////////////////////////////// - - private TriggerSelectWindow select_window = null; - - ///////////////////////////////////////////////////////////////////// - /// A helper variable, needed to emit the current path. - ///////////////////////////////////////////////////////////////////// - - private string current_path = ""; - - ///////////////////////////////////////////////////////////////////// - /// C'tor, creates a new CellRendererIcon. - ///////////////////////////////////////////////////////////////////// - - public CellRendererTrigger() { - this.select_window = new TriggerSelectWindow(); - - this.select_window.on_select.connect((trigger) => { - this.trigger = trigger.name; - this.on_select(current_path, trigger); - }); - } - - ///////////////////////////////////////////////////////////////////// - /// Open the TriggerSelectWindow on click. - ///////////////////////////////////////////////////////////////////// - - public override unowned Gtk.CellEditable start_editing( - Gdk.Event event, Gtk.Widget widget, string path, Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { - - this.current_path = path; - - this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); - this.select_window.set_modal(true); - this.select_window.set_trigger(new Trigger.from_string(this.trigger)); - - this.select_window.show(); - - return base.start_editing(event, widget, path, bg_area, cell_area, flags); - } -} - -} - diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala index 01a4a40..d66c654 100644 --- a/src/gui/iconSelectWindow.vala +++ b/src/gui/iconSelectWindow.vala @@ -23,45 +23,19 @@ namespace GnomePie { /// happens in an extra thread and a spinner is displayed while loading. ///////////////////////////////////////////////////////////////////////// -public class IconSelectWindow : Gtk.Dialog { - +public class IconSelectWindow : GLib.Object { + ///////////////////////////////////////////////////////////////////// - /// The currently selected icon. If set, this icon gets focused. + /// This signal gets emitted when the user selects a new icon. ///////////////////////////////////////////////////////////////////// - private string _active_icon = "application-default-icon"; - - public string active_icon { - get { - return _active_icon; - } - set { - if (value.contains("/")) { - this.file_chooser.set_filename(value); - this.tabs.set_current_page(1); - } else { - this.icon_list_filtered.foreach((model, path, iter) => { - string name = ""; - model.get(iter, 0, out name); - - if (name == value) { - this.icon_view.select_path(path); - this.icon_view.scroll_to_path(path, true, 0.5f, 0.0f); - this.icon_view.set_cursor(path, null, false); - } - return (name == value); - }); - - this.tabs.set_current_page(0); - } - } - } + public signal void on_ok(string icon_name); ///////////////////////////////////////////////////////////////////// - /// This signal gets emitted when the user selects a new icon. + /// Stores the currently selected icon. ///////////////////////////////////////////////////////////////////// - public signal void on_select(string icon_name); + private string active_icon = ""; ///////////////////////////////////////////////////////////////////// /// The ListStore storing all theme-icons. @@ -121,6 +95,12 @@ public class IconSelectWindow : Gtk.Dialog { 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. ///////////////////////////////////////////////////////////////////// @@ -156,217 +136,221 @@ public class IconSelectWindow : Gtk.Dialog { /// C'tor, creates a new IconSelectWindow. ///////////////////////////////////////////////////////////////////// - public IconSelectWindow() { - this.title = _("Choose an Icon"); - this.set_size_request(520, 520); - this.delete_event.connect(hide_on_delete); - this.load_queue = new GLib.AsyncQueue<ListEntry?>(); + public IconSelectWindow(Gtk.Window parent) { + try { + this.load_queue = new GLib.AsyncQueue<ListEntry?>(); - if (this.icon_list == null) { - this.icon_list = new Gtk.ListStore(3, typeof(string), // icon name - typeof(IconContext), // icon type - typeof(Gdk.Pixbuf)); // the icon itself - - // disable sorting until all icons are loaded - // else loading becomes horribly slow - this.icon_list.set_default_sort_func(() => {return 0;}); + if (this.icon_list == null) { + this.icon_list = new Gtk.ListStore(3, typeof(string), // icon name + typeof(IconContext), // icon type + typeof(Gdk.Pixbuf)); // the icon itself + + // disable sorting until all icons are loaded + // else loading becomes horribly slow + this.icon_list.set_default_sort_func(() => {return 0;}); - // reload if icon theme changes - Gtk.IconTheme.get_default().changed.connect(() => { - if (this.visible) load_icons(); - else need_reload = true; - }); - } - - // make the icon_view filterable - this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null); - - var container = new Gtk.VBox(false, 12); - container.set_border_width(12); + // reload if icon theme changes + Gtk.IconTheme.get_default().changed.connect(() => { + if (this.window.visible) load_icons(); + else need_reload = true; + }); + } + + // make the icon_view filterable + this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null); + + Gtk.Builder builder = new Gtk.Builder(); - // tab container - this.tabs = new Gtk.Notebook(); + builder.add_from_file (Paths.ui_files + "/icon_select.ui"); - // icon theme tab - var theme_tab = new Gtk.VBox(false, 12); - theme_tab.set_border_width(12); + 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(); - // type chooser combo-box - var context_combo = new Gtk.ComboBox.text(); - context_combo.append_text(_("All icons")); - context_combo.append_text(_("Applications")); - context_combo.append_text(_("Actions")); - context_combo.append_text(_("Places")); - context_combo.append_text(_("File types")); - context_combo.append_text(_("Emotes")); - context_combo.append_text(_("Miscellaneous")); + (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.VBox; + + // context combo + #if HAVE_GTK_3 + var context_combo = new Gtk.ComboBoxText(); + #else + var context_combo = new Gtk.ComboBox.text(); + #endif + context_combo.append_text(_("All icons")); + context_combo.append_text(_("Applications")); + context_combo.append_text(_("Actions")); + context_combo.append_text(_("Places")); + context_combo.append_text(_("File types")); + context_combo.append_text(_("Emotes")); + context_combo.append_text(_("Miscellaneous")); - context_combo.set_active(0); - - context_combo.changed.connect(() => { - this.icon_list_filtered.refilter(); - }); - - theme_tab.pack_start(context_combo, false, false); + 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); - // string filter entry - var filter = new Gtk.Entry(); - filter.primary_icon_stock = Gtk.Stock.FIND; - filter.primary_icon_activatable = false; - filter.secondary_icon_stock = Gtk.Stock.CLEAR; - theme_tab.pack_start(filter, false, false); - - // only display items which have the selected type - // and whose name contains the text entered in the entry - this.icon_list_filtered.set_visible_func((model, iter) => { - string name = ""; - IconContext context = IconContext.ALL; - 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(); - }); + if (name == null) return false; - // container for the icon_view - var scroll = new Gtk.ScrolledWindow (null, null); - scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - scroll.set_shadow_type (Gtk.ShadowType.IN); + 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 = 3; - this.icon_view.pixbuf_column = 2; - this.icon_view.tooltip_column = 0; - - // set _active_icon if selection changes - this.icon_view.selection_changed.connect(() => { - foreach (var path in this.icon_view.get_selected_items()) { - Gtk.TreeIter iter; - this.icon_list_filtered.get_iter(out iter, path); - 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_select(this._active_icon); - this.hide(); - }); + // displays the filtered icons + this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered); + this.icon_view.item_width = 32; + this.icon_view.item_padding = 3; + this.icon_view.pixbuf_column = 2; + this.icon_view.tooltip_column = 0; - scroll.add(this.icon_view); + // 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); + } + }); - theme_tab.pack_start(scroll, true, true); - - tabs.append_page(theme_tab, new Gtk.Label(_("Icon Theme"))); + // 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); - // tab containing the possibility to choose a custom icon - var custom_tab = new Gtk.VBox(false, 6); - custom_tab.border_width = 12; + // file chooser widget + this.file_chooser = builder.get_object("filechooser") as Gtk.FileChooserWidget; + var file_filter = new Gtk.FileFilter(); + file_filter.add_pixbuf_formats(); - // file chooser widget - this.file_chooser = new Gtk.FileChooserWidget(Gtk.FileChooserAction.OPEN); - var file_filter = new Gtk.FileFilter(); - file_filter.add_pixbuf_formats(); + #if HAVE_GTK_3 + file_filter.set_filter_name(_("All supported image formats")); + #else file_filter.set_name(_("All supported image formats")); - file_chooser.add_filter(file_filter); - - // set _active_icon if the user selected a file - file_chooser.selection_changed.connect(() => { - if (file_chooser.get_filename() != null && - GLib.FileUtils.test(file_chooser.get_filename(), - GLib.FileTest.IS_REGULAR)) - - this._active_icon = file_chooser.get_filename(); - }); - - // hide this window when the user activates a file - file_chooser.file_activated.connect(() => { - this._active_icon = file_chooser.get_filename(); - this.on_select(this._active_icon); - this.hide(); - }); - - - custom_tab.pack_start(file_chooser, true, true); - - tabs.append_page(custom_tab, new Gtk.Label(_("Custom Icon"))); - - container.pack_start(tabs, true, true); - - // button box --- this dialog has a custom button box at the bottom because it - // should have a spinner there. Sadly that's impossible with the "normal" - // action_area of Gtk.Dialog's - var bottom_box = new Gtk.HBox(false, 0); - - var bbox = new Gtk.HButtonBox(); - bbox.set_spacing(6); - bbox.set_layout(Gtk.ButtonBoxStyle.END); - - var cancel_button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL); - cancel_button.clicked.connect(() => { - this.hide(); - }); - bbox.pack_start(cancel_button); - - var ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK); - ok_button.clicked.connect(() => { - this.on_select(this._active_icon); - this.hide(); - }); - bbox.pack_start(ok_button); - - bottom_box.pack_end(bbox, false); + #endif - this.spinner = new Gtk.Spinner(); - this.spinner.set_size_request(16, 16); - this.spinner.start(); + 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)) - bottom_box.pack_start(this.spinner, false, false); + 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(); + }); - container.pack_start(bottom_box, false, false); - - this.vbox.pack_start(container, true, true); - - this.vbox.show_all(); - - this.set_focus(this.icon_view); + 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); + } } ///////////////////////////////////////////////////////////////////// - /// Hide the "normal" action_area when this window is shown. Reload - /// all icons if necessary. + /// Displays the window. The icons are reloaded if neccessary. ///////////////////////////////////////////////////////////////////// - - public override void show() { - base.show(); - - // hide the "normal" action_area --- this Dialog has a custom set of - // buttons containg the spinner - this.action_area.hide(); + + public void show() { + this.window.show_all(); + this.spinner.hide(); if (this.need_reload) { this.need_reload = false; this.load_icons(); } + } + + ///////////////////////////////////////////////////////////////////// + /// Makes the window select the icon of the given Pie. + ///////////////////////////////////////////////////////////////////// + + public void set_pie(string id) { + string icon = PieManager.all_pies[id].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(); } ///////////////////////////////////////////////////////////////////// @@ -400,13 +384,13 @@ public class IconSelectWindow : Gtk.Dialog { var new_entry = this.load_queue.pop(); Gtk.TreeIter current; this.icon_list.append(out current); - this.icon_list.set(current, 0, new_entry.name, + this.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 (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING); + if (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING); return loading; }); @@ -439,12 +423,12 @@ public class IconSelectWindow : Gtk.Dialog { default: break; } - try { + 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); + 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) @@ -462,7 +446,7 @@ public class IconSelectWindow : Gtk.Dialog { // hide the spinner if (spinner != null) - spinner.visible = this.loading; + spinner.visible = false; return null; } diff --git a/src/gui/indicator.vala b/src/gui/indicator.vala index 8033cb7..dea4d3c 100644 --- a/src/gui/indicator.vala +++ b/src/gui/indicator.vala @@ -38,7 +38,7 @@ public class Indicator : GLib.Object { /// The Preferences Menu of Gnome-Pie. ///////////////////////////////////////////////////////////////////// - private Preferences prefs { private get; private set; } + private PreferencesWindow prefs { private get; private set; } ///////////////////////////////////////////////////////////////////// /// Returns true, when the indicator is currently visible. @@ -73,7 +73,7 @@ public class Indicator : GLib.Object { string icon = "indicator-applet"; try { path = GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources"; - icon = "gnome-pie-indicator"; + icon = "gnome-pie"; } catch (GLib.FileError e) { warning("Failed to get path of executable!"); } @@ -86,23 +86,23 @@ public class Indicator : GLib.Object { try { var file = GLib.File.new_for_path(GLib.Path.build_filename( GLib.Path.get_dirname(GLib.FileUtils.read_link("/proc/self/exe"))+"/resources", - "gnome-pie-indicator.svg" + "gnome-pie.svg" )); if (!file.query_exists()) - this.indicator.set_from_icon_name("gnome-pie-indicator"); + this.indicator.set_from_icon_name("gnome-pie"); else this.indicator.set_from_file(file.get_path()); } catch (GLib.FileError e) { warning("Failed to get path of executable!"); - this.indicator.set_from_icon_name("gnome-pie-indicator"); + this.indicator.set_from_icon_name("gnome-pie"); } this.menu = new Gtk.Menu(); var menu = this.menu; #endif - this.prefs = new Preferences(); + this.prefs = new PreferencesWindow(); // preferences item var item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.PREFERENCES, null); @@ -117,7 +117,7 @@ public class Indicator : GLib.Object { item = new Gtk.ImageMenuItem.from_stock (Gtk.Stock.ABOUT, null); item.show(); item.activate.connect(() => { - var about = new GnomePieAboutDialog(); + var about = new AboutWindow(); about.run(); about.destroy(); }); diff --git a/src/gui/newSliceWindow.vala b/src/gui/newSliceWindow.vala new file mode 100644 index 0000000..4e38376 --- /dev/null +++ b/src/gui/newSliceWindow.vala @@ -0,0 +1,394 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A window which allows selection of 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.HBox name_box = null; + private Gtk.HBox command_box = null; + private Gtk.Button icon_button = null; + private Gtk.VBox no_options_box = null; + private Gtk.HBox pie_box = null; + private Gtk.HBox hotkey_box = null; + private Gtk.HBox uri_box = null; + private Gtk.HBox quickaction_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.CheckButton quickaction_checkbutton = 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.current_type = type; + + switch (type) { + case "bookmarks": case "clipboard": case "devices": + case "menu": case "session": case "window_list": + this.no_options_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.HBox; + this.command_box = builder.get_object("command-box") as Gtk.HBox; + this.icon_button = builder.get_object("icon-button") as Gtk.Button; + this.no_options_box = builder.get_object("no-options-box") as Gtk.VBox; + this.pie_box = builder.get_object("pie-box") as Gtk.HBox; + 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.HBox; + 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.HBox; + + 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.CheckButton; + + this.quickaction_box = builder.get_object("quickaction-box") as Gtk.HBox; + this.icon = builder.get_object("icon") as Gtk.Image; + + 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.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; + 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 "clipboard": group = new ClipboardGroup(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 "window_list": group = new WindowListGroup(this.current_id); 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 (icon_window == null) { + icon_window = new IconSelectWindow(this.window); + icon_window.on_ok.connect((icon) => { + this.current_custom_icon = icon; + this.set_icon(icon); + }); + } + + icon_window.show(); + } + + ///////////////////////////////////////////////////////////////////// + /// 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/pieComboList.vala b/src/gui/pieComboList.vala new file mode 100644 index 0000000..3b54944 --- /dev/null +++ b/src/gui/pieComboList.vala @@ -0,0 +1,155 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A 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, "markup", 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 index 46970d5..bfcb832 100644 --- a/src/gui/pieList.vala +++ b/src/gui/pieList.vala @@ -17,1029 +17,226 @@ this program. If not, see <http://www.gnu.org/licenses/>. namespace GnomePie { -// A very complex Widget. This is by far the most ugly file of this project -// but well, this list *is* complex... sorry ;) +///////////////////////////////////////////////////////////////////////// +/// A list, containing one entry for each existing Pie. +///////////////////////////////////////////////////////////////////////// class PieList : Gtk.TreeView { - private Gtk.ListStore groups; - private Gtk.ListStore pies; - private Gtk.ListStore actions; - private Gtk.TreeStore data; + ///////////////////////////////////////////////////////////////////// + /// This signal gets emitted when the user selects a new Pie. + ///////////////////////////////////////////////////////////////////// - private const int small_icon = 24; - private const int large_icon = 36; + public signal void on_select(string id); - // data positions in the data ListStore - private enum DataPos {IS_QUICKACTION, ICON, NAME, TYPE_ID, ACTION_TYPE, - ICON_PIXBUF, FONT_WEIGHT, ICON_NAME_EDITABLE, QUICKACTION_VISIBLE, QUICKACTION_ACTIVATABLE, - TYPE_VISIBLE, GROUP_VISIBLE, APP_VISIBLE, KEY_VISIBLE, PIE_VISIBLE, - URI_VISIBLE, TRIGGER_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP, - DISPLAY_COMMAND_KEY, DISPLAY_COMMAND_PIE, DISPLAY_COMMAND_URI, - REAL_COMMAND_GROUP, REAL_COMMAND_PIE, REAL_COMMAND_KEY} + ///////////////////////////////////////////////////////////////////// + /// Stores the data internally. + ///////////////////////////////////////////////////////////////////// - // data positions in the actions ListStore - private enum ActionPos {NAME, TYPE, CAN_QUICKACTION, ICON_NAME_EDITABLE} + private Gtk.ListStore data; + private enum DataPos {ICON, ICON_NAME, NAME, ID} - // data positions in the pies ListStore - private enum PiePos {NAME, ID} + ///////////////////////////////////////////////////////////////////// + /// Stores where a drag startet. + ///////////////////////////////////////////////////////////////////// - // data positions in the groups ListStore - private enum GroupPos {NAME, TYPE, ICON} + 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(); - Gtk.TreeIter last; - - // group choices - this.groups = new Gtk.ListStore(3, typeof(string), // group name - typeof(string), // group type - typeof(string)); // group icon - - // add all registered group types - foreach (var type in GroupRegistry.types) { - this.groups.append(out last); - this.groups.set(last, GroupPos.NAME, GroupRegistry.names[type], - GroupPos.TYPE, type.name(), - GroupPos.ICON, GroupRegistry.icons[type]); - } - - // pie choices - this.pies = new Gtk.ListStore(2, typeof(string), // pie name - typeof(string)); // pie id - - // action type choices - this.actions = new Gtk.ListStore(4, typeof(string), // type name - typeof(string), // action type - typeof(bool), // can be quickaction - typeof(bool)); // icon/name editable - - // add all registered action types - foreach (var type in ActionRegistry.types) { - this.actions.append(out last); - this.actions.set(last, ActionPos.NAME, ActionRegistry.names[type], - ActionPos.TYPE, type.name(), - ActionPos.CAN_QUICKACTION, true, - ActionPos.ICON_NAME_EDITABLE, ActionRegistry.icon_name_editables[type]); - } - // and one type for groups - this.actions.append(out last); - this.actions.set(last, ActionPos.NAME, _("Slice group"), - ActionPos.TYPE, typeof(ActionGroup).name(), - ActionPos.CAN_QUICKACTION, false, - ActionPos.ICON_NAME_EDITABLE, false); + 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); - // main data model - this.data = new Gtk.TreeStore(25, typeof(bool), // is quickaction - typeof(string), // icon - typeof(string), // name - typeof(string), // slice: type label, pie: "ID: %id" - typeof(string), // typeof(action), typeof(ActionGroup).name() if group action, pie_id if Pie - - typeof(Gdk.Pixbuf), // icon pixbuf - typeof(int), // font weight - - typeof(bool), // icon/name editable - - typeof(bool), // quickaction visible - typeof(bool), // quickaction activatable - typeof(bool), // type visible - typeof(bool), // group renderer visible - typeof(bool), // app renderer visible - typeof(bool), // key renderer visible - typeof(bool), // pie renderer visible - typeof(bool), // uri renderer visible - typeof(bool), // trigger renderer visible - - typeof(string), // display command group - typeof(string), // display command app - typeof(string), // display command key - typeof(string), // display command pie - typeof(string), // display command uri - - typeof(string), // real command group - typeof(string), // real command pie - typeof(string)); // real command key - - this.set_model(this.data); + this.set_headers_visible(false); this.set_grid_lines(Gtk.TreeViewGridLines.NONE); - this.set_enable_tree_lines(false); - this.set_reorderable(false); - this.set_level_indentation(-10); + this.width_request = 170; + this.set_enable_search(false); - // create the gui - // icon column - var icon_column = new Gtk.TreeViewColumn(); - icon_column.title = _("Icon"); - icon_column.expand = false; - - // quickaction checkbox - var check_render = new Gtk.CellRendererToggle(); - check_render.activatable = true; - check_render.radio = true; - check_render.width = 15; - - check_render.toggled.connect((path) => { - Gtk.TreeIter toggled; - this.data.get_iter_from_string(out toggled, path); - - bool current = false; - this.data.get(toggled, DataPos.IS_QUICKACTION, out current); - - // set all others off - Gtk.TreeIter parent; - this.data.iter_parent(out parent, toggled); - string parent_pos = this.data.get_string_from_iter(parent); - int child_count = this.data.iter_n_children(parent); - - for (int i=0; i<child_count; ++i) { - Gtk.TreeIter child; - this.data.get_iter_from_string(out child, "%s:%d".printf(parent_pos, i)); - this.data.set(child, DataPos.IS_QUICKACTION, false); - } - - // toggle selected - this.data.set(toggled, DataPos.IS_QUICKACTION, !current); - - this.update_pie(toggled); - }); - - icon_column.pack_start(check_render, false); - icon_column.add_attribute(check_render, "activatable", DataPos.QUICKACTION_ACTIVATABLE); - icon_column.add_attribute(check_render, "sensitive", DataPos.QUICKACTION_ACTIVATABLE); - icon_column.add_attribute(check_render, "visible", DataPos.QUICKACTION_VISIBLE); - icon_column.add_attribute(check_render, "active", DataPos.IS_QUICKACTION); - + this.set_events(Gdk.EventMask.POINTER_MOTION_MASK); - // icon - var icon_render = new GnomePie.CellRendererIcon(); - icon_render.editable = true; - - icon_render.on_select.connect((path, icon_name) => { - Gtk.TreeIter iter; - this.data.get_iter_from_string(out iter, path); - int icon_size = this.data.iter_depth(iter) == 0 ? this.large_icon : this.small_icon; - - this.data.set(iter, DataPos.ICON, icon_name); - this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(icon_name, icon_size)); - - this.update_pie(iter); - this.update_linked(); - }); - - icon_column.pack_start(icon_render, false); - icon_column.add_attribute(icon_render, "icon_name", DataPos.ICON); - icon_column.add_attribute(icon_render, "pixbuf", DataPos.ICON_PIXBUF); - icon_column.add_attribute(icon_render, "editable", DataPos.ICON_NAME_EDITABLE); - icon_column.add_attribute(icon_render, "icon_sensitive", DataPos.ICON_NAME_EDITABLE); - - // command column - var command_column = new Gtk.TreeViewColumn(); - command_column.title = _("Command"); - command_column.resizable = true; - command_column.expand = true; - - // trigger - var command_renderer_trigger = new CellRendererTrigger(); - command_renderer_trigger.editable = true; - command_renderer_trigger.ellipsize = Pango.EllipsizeMode.END; - - command_renderer_trigger.on_select.connect((path, trigger) => { - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, trigger.label_with_specials); - this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, trigger.name); - - this.update_pie(data_iter); - }); - - command_column.pack_end(command_renderer_trigger, true); - command_column.add_attribute(command_renderer_trigger, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_trigger, "markup", DataPos.DISPLAY_COMMAND_KEY); - command_column.add_attribute(command_renderer_trigger, "visible", DataPos.TRIGGER_VISIBLE); - command_column.add_attribute(command_renderer_trigger, "trigger", DataPos.REAL_COMMAND_KEY); - - // slice group - var command_renderer_group = new Gtk.CellRendererCombo(); - command_renderer_group.editable = true; - command_renderer_group.has_entry = false; - command_renderer_group.text_column = 0; - command_renderer_group.ellipsize = Pango.EllipsizeMode.END; - command_renderer_group.model = this.groups; - - command_renderer_group.changed.connect((path, iter) => { - string display_name; - string type; - string icon; - - this.groups.get(iter, GroupPos.NAME, out display_name); - this.groups.get(iter, GroupPos.TYPE, out type); - this.groups.get(iter, GroupPos.ICON, out icon); - - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_GROUP, display_name); - this.data.set(data_iter, DataPos.REAL_COMMAND_GROUP, type); - this.data.set(data_iter, DataPos.NAME, display_name); - this.data.set(data_iter, DataPos.ICON, icon); - - this.update_pie(data_iter); - }); - - command_column.pack_end(command_renderer_group, true); - command_column.add_attribute(command_renderer_group, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_group, "text", DataPos.DISPLAY_COMMAND_GROUP); - command_column.add_attribute(command_renderer_group, "visible", DataPos.GROUP_VISIBLE); - - - // app action - var command_renderer_app = new Gtk.CellRendererText(); - command_renderer_app.editable = true; - command_renderer_app.ellipsize = Pango.EllipsizeMode.END; - - command_renderer_app.edited.connect((path, command) => { - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_APP, command); - - this.update_pie(data_iter); - }); - - command_column.pack_end(command_renderer_app, true); - command_column.add_attribute(command_renderer_app, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_app, "text", DataPos.DISPLAY_COMMAND_APP); - command_column.add_attribute(command_renderer_app, "visible", DataPos.APP_VISIBLE); - - - // key action - var command_renderer_key = new Gtk.CellRendererAccel(); - command_renderer_key.editable = true; - command_renderer_key.ellipsize = Pango.EllipsizeMode.END; - - command_renderer_key.accel_edited.connect((path, key, mods) => { - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - string label = Gtk.accelerator_get_label(key, mods); - string accelerator = Gtk.accelerator_name(key, mods); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, label); - this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, accelerator); - - this.update_pie(data_iter); - }); - - command_renderer_key.accel_cleared.connect((path) => { - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, _("Not bound")); - this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, ""); - - this.update_pie(data_iter); - }); - - command_column.pack_end(command_renderer_key, true); - command_column.add_attribute(command_renderer_key, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_key, "text", DataPos.DISPLAY_COMMAND_KEY); - command_column.add_attribute(command_renderer_key, "visible", DataPos.KEY_VISIBLE); - - - // pie action - var command_renderer_pie = new Gtk.CellRendererCombo(); - command_renderer_pie.editable = true; - command_renderer_pie.has_entry = false; - command_renderer_pie.text_column = 0; - command_renderer_pie.ellipsize = Pango.EllipsizeMode.END; - command_renderer_pie.model = this.pies; - - command_renderer_pie.changed.connect((path, iter) => { - string name; - string id; - - this.pies.get(iter, PiePos.NAME, out name); - this.pies.get(iter, PiePos.ID, out id); - - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_PIE, name); - this.data.set(data_iter, DataPos.REAL_COMMAND_PIE, id); - - this.update_pie(data_iter); - this.update_linked(); - }); - - command_column.pack_end(command_renderer_pie, true); - command_column.add_attribute(command_renderer_pie, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_pie, "text", DataPos.DISPLAY_COMMAND_PIE); - command_column.add_attribute(command_renderer_pie, "visible", DataPos.PIE_VISIBLE); - - - // uri action - var command_renderer_uri = new Gtk.CellRendererText(); - command_renderer_uri.editable = true; - command_renderer_uri.ellipsize = Pango.EllipsizeMode.END; - - command_renderer_uri.edited.connect((path, uri) => { - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.DISPLAY_COMMAND_URI, uri); - - this.update_pie(data_iter); - }); - - command_column.pack_end(command_renderer_uri, true); - command_column.add_attribute(command_renderer_uri, "weight", DataPos.FONT_WEIGHT); - command_column.add_attribute(command_renderer_uri, "text", DataPos.DISPLAY_COMMAND_URI); - command_column.add_attribute(command_renderer_uri, "visible", DataPos.URI_VISIBLE); - - - // type column - var type_column = new Gtk.TreeViewColumn(); - type_column.title = _("Pie-ID / Action type"); - type_column.resizable = true; - type_column.expand = false; - - var type_render = new Gtk.CellRendererCombo(); - type_render.editable = true; - type_render.has_entry = false; - type_render.model = actions; - type_render.text_column = 0; - type_render.ellipsize = Pango.EllipsizeMode.END; - - // change command_render's visibility accordingly - type_render.changed.connect((path, iter) => { - string text = ""; - string type; - bool can_quickaction; - bool icon_name_editable; - - this.actions.get(iter, ActionPos.NAME, out text); - this.actions.get(iter, ActionPos.TYPE, out type); - this.actions.get(iter, ActionPos.CAN_QUICKACTION, out can_quickaction); - this.actions.get(iter, ActionPos.ICON_NAME_EDITABLE, out icon_name_editable); - - Gtk.TreeIter data_iter; - this.data.get_iter_from_string(out data_iter, path); - - this.data.set(data_iter, DataPos.TYPE_ID, text); - this.data.set(data_iter, DataPos.ACTION_TYPE, type); - this.data.set(data_iter, DataPos.QUICKACTION_ACTIVATABLE, can_quickaction); - this.data.set(data_iter, DataPos.ICON_NAME_EDITABLE, icon_name_editable); - - // set all command renderes invisible - this.data.set(data_iter, DataPos.GROUP_VISIBLE, false); - this.data.set(data_iter, DataPos.APP_VISIBLE, false); - this.data.set(data_iter, DataPos.KEY_VISIBLE, false); - this.data.set(data_iter, DataPos.PIE_VISIBLE, false); - this.data.set(data_iter, DataPos.URI_VISIBLE, false); - - // set one visible - int type_id = 0; - if(type == typeof(AppAction).name()) type_id = 1; - else if(type == typeof(KeyAction).name()) type_id = 2; - else if(type == typeof(PieAction).name()) type_id = 3; - else if(type == typeof(UriAction).name()) type_id = 4; - else type_id = 0; - - this.data.set(data_iter, DataPos.GROUP_VISIBLE + type_id, true); - - this.update_linked(); - this.update_pie(data_iter); - - //this.set_cursor(new Gtk.TreePath.from_string(path), command_column, true); - }); - - type_column.pack_start(type_render, true); - type_column.add_attribute(type_render, "sensitive", DataPos.TYPE_VISIBLE); - type_column.add_attribute(type_render, "editable", DataPos.TYPE_VISIBLE); - type_column.add_attribute(type_render, "text", DataPos.TYPE_ID); - - // name column - var name_column = new Gtk.TreeViewColumn(); - name_column.title = _("Name"); - name_column.expand = true; - name_column.resizable = true; + var main_column = new Gtk.TreeViewColumn(); + 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.editable = true; name_render.ellipsize = Pango.EllipsizeMode.END; - - name_render.edited.connect((path, text) => { - Gtk.TreeIter iter; - this.data.get_iter_from_string(out iter, path); - - this.data.set(iter, DataPos.NAME, text); - - // try to change icon to a fitting one - string icon; - this.data.get(iter, DataPos.ICON, out icon); - if (icon == "application-default-icon" && Gtk.IconTheme.get_default().has_icon(text.down())) { - this.data.set(iter, DataPos.ICON, text.down()); - } - - this.update_pie(iter); - this.update_linked(); - - //this.set_cursor(new Gtk.TreePath.from_string(path), type_column, true); - }); - - name_column.pack_start(name_render, true); - name_column.add_attribute(name_render, "weight", DataPos.FONT_WEIGHT); - name_column.add_attribute(name_render, "text", DataPos.NAME); - name_column.add_attribute(name_render, "sensitive", DataPos.ICON_NAME_EDITABLE); - name_column.add_attribute(name_render, "editable", DataPos.ICON_NAME_EDITABLE); - - this.append_column(icon_column); - this.append_column(name_column); - this.append_column(type_column); - this.append_column(command_column); + name_render.ellipsize_set = true; + main_column.pack_start(name_render, true); - this.realize.connect(this.load); - - // context menu - var menu = new Gtk.Menu(); - - var item = new Gtk.ImageMenuItem.with_label(_("Add new Pie")); - item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); - item.activate.connect(this.add_empty_pie); - menu.append(item); - - item = new Gtk.ImageMenuItem.with_label(_("Add new Slice")); - item.set_image(new Gtk.Image.from_stock(Gtk.Stock.ADD, Gtk.IconSize.MENU)); - item.activate.connect(this.add_empty_slice); - menu.append(item); - - var sepa = new Gtk.SeparatorMenuItem(); - menu.append(sepa); - - item = new Gtk.ImageMenuItem.with_label(_("Delete")); - item.set_image(new Gtk.Image.from_stock(Gtk.Stock.DELETE, Gtk.IconSize.MENU)); - item.activate.connect(this.delete_selection); - menu.append(item); + base.append_column(main_column); - menu.show_all(); - - this.button_press_event.connect((event) => { - if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) { - menu.popup(null, null, null, event.button, event.time); - } - return false; - }); + 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.drag_data_received.connect(this.on_dnd_received); - this.drag_data_get.connect(this.on_dnd_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_begin.connect_after(this.on_start_drag); + this.drag_motion.connect(this.on_drag_move); + this.drag_leave.connect(() => { + this.last_hover = 0; + }); this.get_selection().changed.connect(() => { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - if (this.data.iter_depth(selected) == 0) { - this.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.LINK); - } else { - this.unset_rows_drag_source(); - } + 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); } }); - this.drag_begin.connect(() => { - this.unset_rows_drag_dest(); - }); - - this.drag_end.connect(() => { - this.enable_model_drag_dest(entries, Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.LINK); - }); + reload_all(); } - // moves the selected slice up - public void selection_up() { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - Gtk.TreePath path = this.data.get_path(selected); - Gtk.TreeIter? before = null;; - if (path.prev() && this.data.get_iter(out before, path)) { - this.data.swap(selected, before); - this.get_selection().changed(); - this.update_pie(selected); - } - } - } + ///////////////////////////////////////////////////////////////////// + /// Loads all existing Pies to the list. + ///////////////////////////////////////////////////////////////////// - // moves the selected slice down - public void selection_down() { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - Gtk.TreePath path = this.data.get_path(selected); - Gtk.TreeIter? after = null; - path.next(); - if (this.data.get_iter(out after, path)) { - this.data.swap(selected, after); - this.get_selection().changed(); - this.update_pie(selected); - } - } - } + 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); - // updates the entire list, checking for changed cross-references via PieActions - // updates their names and icons if needed - private void update_linked() { - this.data.foreach((model, path, iter) => { - string action_type; - this.data.get(iter, DataPos.ACTION_TYPE, out action_type); - - if (action_type == typeof(PieAction).name()) { - string command; - this.data.get(iter, DataPos.REAL_COMMAND_PIE, out command); - - var referee = PieManager.all_pies[command]; - - if (referee != null) { - this.data.set(iter, DataPos.ICON, referee.icon); - this.data.set(iter, DataPos.NAME, referee.name); - this.data.set(iter, DataPos.ICON_PIXBUF, this.load_icon(referee.icon, this.small_icon)); - this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, referee.name); - } else { - // referenced Pie does not exist anymore or no is selected; - // select the first one... - Gtk.TreeIter first_pie; - this.pies.get_iter_first(out first_pie); - - string name; - string id; - - this.pies.get(first_pie, PiePos.NAME, out name); - this.pies.get(first_pie, PiePos.ID, out id); - - this.data.set(iter, DataPos.DISPLAY_COMMAND_PIE, name); - this.data.set(iter, DataPos.REAL_COMMAND_PIE, id); - - update_linked(); - } - } else if (action_type == typeof(ActionGroup).name()) { - string command; - this.data.get(iter, DataPos.REAL_COMMAND_GROUP, out command); - - if (command == "") { - // no group is selected, select the first one... - Gtk.TreeIter first_group; - this.groups.get_iter_first(out first_group); - - string name; - string type; - string icon; - - this.groups.get(first_group, GroupPos.NAME, out name); - this.groups.get(first_group, GroupPos.TYPE, out type); - this.groups.get(first_group, GroupPos.ICON, out icon); - - this.data.set(iter, DataPos.DISPLAY_COMMAND_GROUP, name); - this.data.set(iter, DataPos.NAME, name); - this.data.set(iter, DataPos.REAL_COMMAND_GROUP, type); - this.data.set(iter, DataPos.ICON, icon); - } - } - - return false; - }); + data.clear(); + foreach (var pie in PieManager.all_pies.entries) { + this.load_pie(pie.value); + } + + select(id); } - // adds a new, empty pie to the list - private void add_empty_pie() { - var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", null); - - Gtk.TreeIter last; - this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id); + ///////////////////////////////////////////////////////////////////// + /// Selects the first Pie. + ///////////////////////////////////////////////////////////////////// - Gtk.TreeIter parent; - this.data.append(out parent, null); - this.data.set(parent, DataPos.IS_QUICKACTION, false, - DataPos.ICON, new_one.icon, - DataPos.NAME, new_one.name, - DataPos.TYPE_ID, "ID: " + new_one.id, - DataPos.ACTION_TYPE, new_one.id, - DataPos.ICON_PIXBUF, this.load_icon(new_one.icon, this.large_icon), - DataPos.FONT_WEIGHT, 800, - DataPos.ICON_NAME_EDITABLE, true, - DataPos.QUICKACTION_VISIBLE, false, - DataPos.QUICKACTION_ACTIVATABLE, false, - DataPos.TYPE_VISIBLE, false, - DataPos.GROUP_VISIBLE, false, - DataPos.APP_VISIBLE, false, - DataPos.KEY_VISIBLE, false, - DataPos.PIE_VISIBLE, false, - DataPos.URI_VISIBLE, false, - DataPos.TRIGGER_VISIBLE, true, - DataPos.DISPLAY_COMMAND_GROUP, "", - DataPos.DISPLAY_COMMAND_APP, "", - DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), - DataPos.DISPLAY_COMMAND_PIE, "", - DataPos.DISPLAY_COMMAND_URI, "", - DataPos.REAL_COMMAND_GROUP, "", - DataPos.REAL_COMMAND_PIE, "", - DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(new_one.id)); - + public void select_first() { + Gtk.TreeIter active; - this.get_selection().select_iter(parent); - this.scroll_to_cell(this.data.get_path(parent), null, true, 0.5f, 0.0f); - } - - // adds a new empty slice to the list - private void add_empty_slice() { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - var path = this.data.get_path(selected); - if (path != null) { - if (path.get_depth() == 2) - this.data.iter_parent(out selected, selected); - - this.load_action(selected, new AppAction(_("New Action"), "application-default-icon", "")); - - Gtk.TreeIter new_one; - this.data.iter_nth_child(out new_one, selected, this.data.iter_n_children(selected)-1); - this.expand_to_path(this.data.get_path(new_one)); - this.get_selection().select_iter(new_one); - this.scroll_to_cell(this.data.get_path(new_one), null, true, 0.5f, 0.0f); - - this.update_pie(selected); - } + 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 { - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, - Gtk.MessageType.INFO, - Gtk.ButtonsType.CLOSE, - _("You have to select a Pie to add a Slice to!")); - dialog.run(); - dialog.destroy(); + this.on_select(""); } } - // writes the contents of action to the position pointed by slice - private void write_action(Action action, Gtk.TreeIter slice) { - this.data.set(slice, DataPos.IS_QUICKACTION, action.is_quick_action, - DataPos.ICON, action.icon, - DataPos.NAME, action.name, - DataPos.TYPE_ID, ActionRegistry.names[action.get_type()], - DataPos.ACTION_TYPE, action.get_type().name(), - DataPos.ICON_PIXBUF, this.load_icon(action.icon, this.small_icon), - DataPos.FONT_WEIGHT, 400, - DataPos.ICON_NAME_EDITABLE, !(action is PieAction), - DataPos.QUICKACTION_VISIBLE, true, - DataPos.QUICKACTION_ACTIVATABLE, true, - DataPos.TYPE_VISIBLE, true, - DataPos.GROUP_VISIBLE, false, - DataPos.TRIGGER_VISIBLE, false, - DataPos.APP_VISIBLE, action is AppAction, - DataPos.KEY_VISIBLE, action is KeyAction, - DataPos.PIE_VISIBLE, action is PieAction, - DataPos.URI_VISIBLE, action is UriAction, - DataPos.DISPLAY_COMMAND_GROUP, "", - DataPos.DISPLAY_COMMAND_APP, (action is AppAction) ? action.display_command : "", - DataPos.DISPLAY_COMMAND_KEY, (action is KeyAction) ? action.display_command : _("Not bound"), - DataPos.DISPLAY_COMMAND_PIE, (action is PieAction) ? action.display_command : "", - DataPos.DISPLAY_COMMAND_URI, (action is UriAction) ? action.display_command : "", - DataPos.REAL_COMMAND_GROUP, "", - DataPos.REAL_COMMAND_PIE, (action is PieAction) ? action.real_command : "", - DataPos.REAL_COMMAND_KEY, (action is KeyAction) ? action.real_command : ""); - } + ///////////////////////////////////////////////////////////////////// + /// Selects the Pie with the given ID. + ///////////////////////////////////////////////////////////////////// - // deletes the currently selected pie or slice - private void delete_selection() { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - var path = this.data.get_path(selected); - if (path != null) { - if (path.get_depth() == 1) - this.delete_pie(selected); - else - this.delete_slice(selected); - } - } else { - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, - Gtk.MessageType.INFO, - Gtk.ButtonsType.CLOSE, - _("You have to select a Pie or a Slice to delete!")); - dialog.run(); - dialog.destroy(); - } - } - - // deletes the given pie - private void delete_pie(Gtk.TreeIter pie) { - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, - Gtk.MessageType.QUESTION, - Gtk.ButtonsType.YES_NO, - _("Do you really want to delete the selected Pie with all contained Slices?")); - - dialog.response.connect((response) => { - if (response == Gtk.ResponseType.YES) { - string id; - this.data.get(pie, DataPos.ACTION_TYPE, out id); - this.data.remove(pie); - PieManager.remove_pie(id); - - this.pies.foreach((model, path, iter) => { - string pies_id; - this.pies.get(iter, PiePos.ID, out pies_id); - - if (id == pies_id) { - this.pies.remove(iter); - return true; - } - - return false; - }); - - this.update_linked(); - } - }); - - dialog.run(); - dialog.destroy(); - } - - // deletes the given slice - private void delete_slice(Gtk.TreeIter slice) { - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, - Gtk.MessageType.QUESTION, - Gtk.ButtonsType.YES_NO, - _("Do you really want to delete the selected Slice?")); - - dialog.response.connect((response) => { - if (response == Gtk.ResponseType.YES) { - Gtk.TreeIter parent; - this.data.iter_parent(out parent, slice); - this.data.remove(slice); - this.update_pie(parent); + 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; }); - - dialog.run(); - dialog.destroy(); } - // loads all pies to the list - private void load() { - foreach (var pie in PieManager.all_pies.entries) { - this.load_pie(pie.value); - } - } + ///////////////////////////////////////////////////////////////////// + /// Loads one given pie to the list. + ///////////////////////////////////////////////////////////////////// - // loads one given pie to the list private void load_pie(Pie pie) { if (pie.id.length == 3) { - Gtk.TreeIter last; - this.pies.append(out last); this.pies.set(last, PiePos.NAME, pie.name, - PiePos.ID, pie.id); - - Gtk.TreeIter parent; - this.data.append(out parent, null); - this.data.set(parent, DataPos.IS_QUICKACTION, false, - DataPos.ICON, pie.icon, - DataPos.NAME, pie.name, - DataPos.TYPE_ID, "ID: " + pie.id, - DataPos.ACTION_TYPE, pie.id, - DataPos.ICON_PIXBUF, this.load_icon(pie.icon, this.large_icon), - DataPos.FONT_WEIGHT, 800, - DataPos.ICON_NAME_EDITABLE, true, - DataPos.QUICKACTION_VISIBLE, false, - DataPos.QUICKACTION_ACTIVATABLE, false, - DataPos.TYPE_VISIBLE, false, - DataPos.GROUP_VISIBLE, false, - DataPos.APP_VISIBLE, false, - DataPos.KEY_VISIBLE, false, - DataPos.PIE_VISIBLE, false, - DataPos.URI_VISIBLE, false, - DataPos.TRIGGER_VISIBLE, true, - DataPos.DISPLAY_COMMAND_GROUP, "", - DataPos.DISPLAY_COMMAND_APP, "", - DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), - DataPos.DISPLAY_COMMAND_PIE, "", - DataPos.DISPLAY_COMMAND_URI, "", - DataPos.REAL_COMMAND_GROUP, "", - DataPos.REAL_COMMAND_PIE, "", - DataPos.REAL_COMMAND_KEY, PieManager.get_accelerator_of(pie.id)); - - foreach (var group in pie.action_groups) { - this.load_group(parent, group); - } + 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, pie.name, + DataPos.ID, pie.id); } } - // loads a given group - private void load_group(Gtk.TreeIter parent, ActionGroup group) { - if (group.get_type() == typeof(ActionGroup)) { - foreach (var action in group.actions) { - this.load_action(parent, action); - } - } else { - Gtk.TreeIter child; - this.data.append(out child, parent); - this.data.set(child, DataPos.IS_QUICKACTION, false, - DataPos.ICON, GroupRegistry.icons[group.get_type()], - DataPos.NAME, GroupRegistry.names[group.get_type()], - DataPos.TYPE_ID, _("Slice group"), - DataPos.ACTION_TYPE, typeof(ActionGroup).name(), - DataPos.ICON_PIXBUF, this.load_icon(GroupRegistry.icons[group.get_type()], this.small_icon), - DataPos.FONT_WEIGHT, 400, - DataPos.ICON_NAME_EDITABLE, false, - DataPos.QUICKACTION_VISIBLE, true, - DataPos.QUICKACTION_ACTIVATABLE, false, - DataPos.TYPE_VISIBLE, true, - DataPos.GROUP_VISIBLE, true, - DataPos.APP_VISIBLE, false, - DataPos.KEY_VISIBLE, false, - DataPos.PIE_VISIBLE, false, - DataPos.URI_VISIBLE, false, - DataPos.TRIGGER_VISIBLE, false, - DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()], - DataPos.DISPLAY_COMMAND_APP, "", - DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), - DataPos.DISPLAY_COMMAND_PIE, "", - DataPos.DISPLAY_COMMAND_URI, "", - DataPos.REAL_COMMAND_GROUP, group.get_type().name(), - DataPos.REAL_COMMAND_PIE, "", - DataPos.REAL_COMMAND_KEY, ""); + ///////////////////////////////////////////////////////////////////// + /// 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"}); } } - // loads a given slice - private void load_action(Gtk.TreeIter parent, Action action) { - Gtk.TreeIter child; - this.data.append(out child, parent); - this.write_action(action, child); - } + ///////////////////////////////////////////////////////////////////// + /// Called when a drag operation is started on this Widget. + ///////////////////////////////////////////////////////////////////// - // applies all changes done to the given pie - private void update_pie(Gtk.TreeIter slice_or_pie) { - // get pie iter - var path = this.data.get_path(slice_or_pie); - if (path != null) { - var pie = slice_or_pie; - if (path.get_depth() == 2) - this.data.iter_parent(out pie, slice_or_pie); + 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); - // get information on pie - string id; - string icon; - string name; - string hotkey; - - this.data.get(pie, DataPos.ICON, out icon); - this.data.get(pie, DataPos.NAME, out name); - this.data.get(pie, DataPos.ACTION_TYPE, out id); - this.data.get(pie, DataPos.REAL_COMMAND_KEY, out hotkey); - - // remove pie - PieManager.remove_pie(id); - - this.pies.foreach((model, path, iter) => { - string pies_id; - this.pies.get(iter, PiePos.ID, out pies_id); - - if (id == pies_id) { - this.pies.set(iter, PiePos.NAME, name); - return true; - } - - return false; - }); - - // create new pie - var new_pie = PieManager.create_persistent_pie(name, icon, new Trigger.from_string(hotkey), id); - - // add actions accordingly - if (this.data.iter_has_child(pie)) { - Gtk.TreeIter child; - this.data.iter_children(out child, pie); - - do { - // get slice information - string slice_type; - string slice_icon; - string slice_name; - bool is_quick_action; - - this.data.get(child, DataPos.ICON, out slice_icon); - this.data.get(child, DataPos.NAME, out slice_name); - this.data.get(child, DataPos.ACTION_TYPE, out slice_type); - this.data.get(child, DataPos.IS_QUICKACTION, out is_quick_action); - - if (slice_type == typeof(AppAction).name()) { - string slice_command; - this.data.get(child, DataPos.DISPLAY_COMMAND_APP, out slice_command); - var group = new ActionGroup(new_pie.id); - group.add_action(new AppAction(slice_name, slice_icon, slice_command, is_quick_action)); - new_pie.add_group(group); - } else if (slice_type == typeof(KeyAction).name()) { - string slice_command; - this.data.get(child, DataPos.REAL_COMMAND_KEY, out slice_command); - var group = new ActionGroup(new_pie.id); - group.add_action(new KeyAction(slice_name, slice_icon, slice_command, is_quick_action)); - new_pie.add_group(group); - } else if (slice_type == typeof(PieAction).name()) { - string slice_command; - this.data.get(child, DataPos.REAL_COMMAND_PIE, out slice_command); - var group = new ActionGroup(new_pie.id); - group.add_action(new PieAction(slice_command, is_quick_action)); - new_pie.add_group(group); - } else if (slice_type == typeof(UriAction).name()) { - string slice_command; - this.data.get(child, DataPos.DISPLAY_COMMAND_URI, out slice_command); - var group = new ActionGroup(new_pie.id); - group.add_action(new UriAction(slice_name, slice_icon, slice_command, is_quick_action)); - new_pie.add_group(group); - } else if (slice_type == typeof(ActionGroup).name()) { - string slice_command; - this.data.get(child, DataPos.REAL_COMMAND_GROUP, out slice_command); - - var group = GLib.Object.new(GLib.Type.from_name(slice_command), "parent_id", new_pie.id); - new_pie.add_group(group as ActionGroup); - } - - } while (this.data.iter_next(ref child)); - } - } + 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); + } } - // creates new action when the list receives a drag'n'drop event - private void on_dnd_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time_) { - string[] uris = selection_data.get_uris(); - + ///////////////////////////////////////////////////////////////////// + /// 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; - Gtk.TreeViewDropPosition pos; - // check for valid position - if (!this.get_dest_row_at_pos(x, y, out path, out pos) - || (path.to_string() == "0" && pos == Gtk.TreeViewDropPosition.BEFORE)) { - - warning("Failed to insert Slice: Invalid location!"); - return; - } - - // get position to insert (when child: after, when parent: as first child) - Gtk.TreeIter parent; - int insert_pos = 0; - if (path.get_depth() == 1) { - if (pos == Gtk.TreeViewDropPosition.BEFORE) { - path.prev(); - this.data.get_iter(out parent, path); - insert_pos = this.data.iter_n_children(parent); - } else { - this.data.get_iter(out parent, path); - } - } else { - if (pos == Gtk.TreeViewDropPosition.BEFORE) { - insert_pos = path.get_indices()[1]; - } else { - insert_pos = path.get_indices()[1]+1; - } - - path.up(); - this.data.get_iter(out parent, path); - } + if (!this.get_dest_row_at_pos(x, y, out path, out position)) + return false; - foreach (var uri in uris) { - Gtk.TreeIter new_child; - this.data.insert(out new_child, parent, insert_pos); - this.write_action(ActionRegistry.new_for_uri(uri), new_child); - } + 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); - this.update_pie(parent); - } - - private void on_dnd_source(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) { - Gtk.TreeIter selected; - if (this.get_selection().get_selected(null, out selected)) { - string id = ""; - this.data.get(selected, DataPos.ACTION_TYPE, out id); - selection_data.set_uris({"file://" + Paths.launchers + "/" + id + ".desktop"}); - } - } - - private Gdk.Pixbuf load_icon(string name, int size) { - Gdk.Pixbuf pixbuf = null; + // avoid too frequent selection... + this.last_hover = time; - try { - if (name.contains("/")) - pixbuf = new Gdk.Pixbuf.from_file_at_size(name, size, size); - else - pixbuf = new Gdk.Pixbuf.from_file_at_size(Icon.get_icon_file(name, size), size, size); - } catch (GLib.Error e) { - warning(e.message); - } + GLib.Timeout.add(150, () => { + if (this.last_hover == time) + this.get_selection().select_path(path); + return false; + }); - return pixbuf; + return true; } } diff --git a/src/gui/piePreview.vala b/src/gui/piePreview.vala new file mode 100644 index 0000000..5745fcb --- /dev/null +++ b/src/gui/piePreview.vala @@ -0,0 +1,369 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A 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; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates the widget. + ///////////////////////////////////////////////////////////////////// + + public PiePreview() { + this.renderer = new PiePreviewRenderer(); + + #if HAVE_GTK_3 + this.draw.connect(this.on_draw); + #else + this.expose_event.connect(this.on_draw); + #endif + + 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) { + this.current_id = id; + this.modify_bg(Gtk.StateType.NORMAL, Gtk.rc_get_style(this).light[0]); + this.renderer.load_pie(PieManager.all_pies[id]); + } + + ///////////////////////////////////////////////////////////////////// + /// 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. + ///////////////////////////////////////////////////////////////////// + + #if HAVE_GTK_3 + private bool on_draw(Cairo.Context ctx) { + #else + private bool on_draw(Gtk.Widget da, Gdk.EventExpose event) { + var ctx = Gdk.cairo_create(this.get_window()); + #endif + // 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(); + 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") { + 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); + } + }); + } + } + + ///////////////////////////////////////////////////////////////////// + /// 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); + } + } + + ///////////////////////////////////////////////////////////////////// + /// 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..ee8c14b --- /dev/null +++ b/src/gui/piePreviewAddSign.vala @@ -0,0 +1,220 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A 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("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) { + 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); + } + + ///////////////////////////////////////////////////////////////////// + /// 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.clicked.reset_target(1.0, 0.1); + this.on_clicked((int)this.position); + } + } +} + +} diff --git a/src/gui/piePreviewCenter.vala b/src/gui/piePreviewCenter.vala new file mode 100644 index 0000000..21bbd78 --- /dev/null +++ b/src/gui/piePreviewCenter.vala @@ -0,0 +1,108 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// +///////////////////////////////////////////////////////////////////////// + +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 = new Gtk.Style(); + + this.old_text = this.text; + this.text = new RenderedText.with_markup(text, 180, 180, style.font_desc.get_family()+" 10", + new Color.from_gdk(style.fg[0]), 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..2ff3321 --- /dev/null +++ b/src/gui/piePreviewDeleteSign.vala @@ -0,0 +1,195 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// 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 const int radius = 18; + private const double globale_scale = 0.8; + private 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("stock_delete", 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)*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 > this.click_cancel_treshold*this.click_cancel_treshold) + this.clicked.reset_target(1.0, 0.1); + } + + if (GLib.Math.fabs(x) <= radius*globale_scale && GLib.Math.fabs(y) <= radius*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..1cf83ff --- /dev/null +++ b/src/gui/piePreviewRenderer.vala @@ -0,0 +1,436 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A 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; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, initializes members. + ///////////////////////////////////////////////////////////////////// + + public PiePreviewRenderer() { + 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>" + 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>" + 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>" + 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..af39c1f --- /dev/null +++ b/src/gui/piePreviewSliceRenderer.vala @@ -0,0 +1,276 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +using GLib.Math; + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// 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 const double pie_radius = 126; + private const double radius = 24; + private const double delete_x = 13; + private const double delete_y = -13; + private 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)(radius*2)); + this.name = group.actions[0].name; + } else { + this.icon = new Icon(GroupRegistry.descriptions[group.get_type().name()].icon, (int)(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)*pie_radius, sin(this.angle.val)*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(delete_x*this.size.val, 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 > this.click_cancel_treshold*this.click_cancel_treshold) + this.clicked.reset_target(1.0, 0.1); + } + + double own_x = cos(this.angle.val)*pie_radius; + double own_y = sin(this.angle.val)*pie_radius; + this.delete_hovered = this.delete_sign.on_mouse_move(x - own_x - delete_x*this.size.val, + y - own_y - 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)*pie_radius; + double own_y = sin(this.angle.val)*pie_radius; + delete_pressed = this.delete_sign.on_button_press(x - own_x - delete_x*this.size.val, + y - own_y - 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/preferences.vala b/src/gui/preferences.vala deleted file mode 100644 index 9444fac..0000000 --- a/src/gui/preferences.vala +++ /dev/null @@ -1,325 +0,0 @@ -/* -Copyright (c) 2011 by Simon Schneegans - -This program is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) -any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -namespace GnomePie { - -///////////////////////////////////////////////////////////////////////// -/// The Gtk settings menu of Gnome-Pie. -///////////////////////////////////////////////////////////////////////// - -public class Preferences : Gtk.Window { - - ///////////////////////////////////////////////////////////////////// - /// C'tor, constructs the whole dialog. Many thanks to the - /// synapse-project, since some of this code is taken from there! - ///////////////////////////////////////////////////////////////////// - - public Preferences() { - this.title = _("Gnome-Pie - Settings"); - this.set_position(Gtk.WindowPosition.CENTER); - this.set_size_request(550, 550); - this.resizable = false; - this.icon_name = "gnome-pie"; - this.delete_event.connect(hide_on_delete); - - // main container - var main_vbox = new Gtk.VBox(false, 12); - main_vbox.border_width = 12; - add(main_vbox); - - // tab container - var tabs = new Gtk.Notebook(); - - // general tab - var general_tab = new Gtk.VBox(false, 6); - general_tab.border_width = 12; - - // behavior frame - var behavior_frame = new Gtk.Frame(null); - behavior_frame.set_shadow_type(Gtk.ShadowType.NONE); - var behavior_frame_label = new Gtk.Label(null); - behavior_frame_label.set_markup(Markup.printf_escaped ("<b>%s</b>", _("Behavior"))); - behavior_frame.set_label_widget(behavior_frame_label); - - var behavior_vbox = new Gtk.VBox (false, 6); - var align = new Gtk.Alignment (0.5f, 0.5f, 1.0f, 1.0f); - align.set_padding (6, 12, 12, 12); - align.add (behavior_vbox); - behavior_frame.add (align); - - // Autostart checkbox - var autostart = new Gtk.CheckButton.with_label (_("Startup on Login")); - autostart.tooltip_text = _("If checked, Gnome-Pie will start when you log in."); - autostart.active = Config.global.auto_start; - autostart.toggled.connect(autostart_toggled); - behavior_vbox.pack_start(autostart, false); - - // Indicator icon - var indicator = new Gtk.CheckButton.with_label (_("Show Indicator")); - indicator.tooltip_text = _("If checked, an indicator for easy access of the settings menu is shown in your panel."); - indicator.active = Config.global.show_indicator; - indicator.toggled.connect(indicator_toggled); - behavior_vbox.pack_start(indicator, false); - - // Open Pies at Mouse - var open_at_mouse = new Gtk.CheckButton.with_label (_("Open Pies at Mouse")); - open_at_mouse.tooltip_text = _("If checked, pies will open at your pointer. Otherwise they'll pop up in the middle of the screen."); - open_at_mouse.active = Config.global.open_at_mouse; - open_at_mouse.toggled.connect(open_at_mouse_toggled); - behavior_vbox.pack_start(open_at_mouse, false); - - // Slider - var slider_hbox = new Gtk.HBox (false, 6); - behavior_vbox.pack_start(slider_hbox); - - var scale_label = new Gtk.Label(_("Global Scale")); - slider_hbox.pack_start(scale_label, false, false); - - var scale_slider = new Gtk.HScale.with_range(0.5, 2.0, 0.05); - scale_slider.set_value(Config.global.global_scale); - scale_slider.value_pos = Gtk.PositionType.RIGHT; - - bool changing = false; - bool changed_again = false; - - scale_slider.value_changed.connect(() => { - if (!changing) { - changing = true; - Timeout.add(300, () => { - if (changed_again) { - changed_again = false; - return true; - } - - Config.global.global_scale = scale_slider.get_value(); - Config.global.load_themes(Config.global.theme.name); - changing = false; - return false; - }); - } else { - changed_again = true; - } - }); - - slider_hbox.pack_end(scale_slider, true, true); - - general_tab.pack_start (behavior_frame, false); - - // theme frame - var theme_frame = new Gtk.Frame(null); - theme_frame.set_shadow_type(Gtk.ShadowType.NONE); - var theme_frame_label = new Gtk.Label(null); - theme_frame_label.set_markup(Markup.printf_escaped("<b>%s</b>", _("Themes"))); - theme_frame.set_label_widget(theme_frame_label); - - // scrollable frame - var scroll = new Gtk.ScrolledWindow (null, null); - align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); - align.set_padding(6, 12, 12, 12); - align.add(scroll); - theme_frame.add(align); - - scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - scroll.set_shadow_type (Gtk.ShadowType.IN); - - // themes list - var theme_list = new ThemeList(); - scroll.add(theme_list); - - general_tab.pack_start (theme_frame, true, true); - tabs.append_page(general_tab, new Gtk.Label(_("General"))); - - // pies tab - var pies_tab = new Gtk.VBox(false, 6); - pies_tab.border_width = 12; - tabs.append_page(pies_tab, new Gtk.Label(_("Pies"))); - - // scrollable frame - scroll = new Gtk.ScrolledWindow (null, null); - align = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f); - align.add(scroll); - pies_tab.add(align); - - scroll.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); - scroll.set_shadow_type (Gtk.ShadowType.IN); - - // pies list - var pie_list = new PieList(); - scroll.add(pie_list); - - // bottom box - var info_box = new Gtk.HBox (false, 6); - - // info image - var info_image = new Gtk.Image.from_stock (Gtk.Stock.INFO, Gtk.IconSize.MENU); - info_box.pack_start (info_image, false); - - // info label - var info_label = new TipViewer({ - _("You can right-click in the list for adding or removing entries."), - _("You can reset Gnome-Pie to its default options with the terminal command \"gnome-pie --reset\"."), - _("The radiobutton at the beginning of each slice-line indicates the QuickAction of the pie."), - _("Pies can be opened with the terminal command \"gnome-pie --open=ID\"."), - _("Feel free to visit Gnome-Pie's homepage at %s!").printf("<a href='http://gnome-pie.simonschneegans.de'>gnome-pie.simonschneegans.de</a>"), - _("You can drag'n'drop applications from your main menu to the list above."), - _("If you want to give some feedback, please write an e-mail to %s!").printf("<a href='mailto:code@simonschneegans.de'>code@simonschneegans.de</a>"), - _("You may drag'n'drop URLs and bookmarks from your internet browser to the list above."), - _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"), - _("It's possible to drag'n'drop files and folders from your file browser to the list above."), - _("It's recommended to keep your Pies small (at most 6-8 Slices). Else they will become hard to navigate."), - _("In order to create a launcher for a Pie, drag the Pie from the list to your desktop!") - }); - this.show.connect(info_label.start_slide_show); - this.hide.connect(info_label.stop_slide_show); - - info_box.pack_start (info_label); - - // down Button - var down_button = new Gtk.Button(); - down_button.tooltip_text = _("Moves the selected Slice down"); - down_button.sensitive = false; - var down_image = new Gtk.Image.from_stock (Gtk.Stock.GO_DOWN, Gtk.IconSize.LARGE_TOOLBAR); - down_button.add(down_image); - down_button.clicked.connect (() => { - pie_list.selection_down(); - }); - - info_box.pack_end(down_button, false, false); - - // up Button - var up_button = new Gtk.Button(); - up_button.tooltip_text = _("Moves the selected Slice up"); - up_button.sensitive = false; - var up_image = new Gtk.Image.from_stock (Gtk.Stock.GO_UP, Gtk.IconSize.LARGE_TOOLBAR); - up_button.add(up_image); - up_button.clicked.connect (() => { - pie_list.selection_up(); - }); - - info_box.pack_end(up_button, false, false); - - pie_list.get_selection().changed.connect(() => { - Gtk.TreeIter selected; - if (pie_list.get_selection().get_selected(null, out selected)) { - Gtk.TreePath path = pie_list.model.get_path(selected); - if (path.get_depth() == 1) { - up_button.sensitive = false; - down_button.sensitive = false; - } else { - up_button.sensitive = true; - down_button.sensitive = true; - - int child_pos = path.get_indices()[1]; - - if (child_pos == 0) - up_button.sensitive = false; - - path.up(); - Gtk.TreeIter parent_iter; - pie_list.model.get_iter(out parent_iter, path); - if (child_pos == pie_list.model.iter_n_children(parent_iter)-1) - down_button.sensitive = false; - - } - } - }); - - pies_tab.pack_start (info_box, false); - - main_vbox.pack_start(tabs); - - // close button - var bbox = new Gtk.HButtonBox (); - bbox.set_layout (Gtk.ButtonBoxStyle.END); - var close_button = new Gtk.Button.from_stock(Gtk.Stock.CLOSE); - close_button.clicked.connect (() => { - hide(); - }); - bbox.pack_start (close_button); - - main_vbox.pack_start(bbox, false); - - main_vbox.show_all(); - - this.hide.connect(() => { - // save settings on close - Config.global.save(); - Pies.save(); - }); - } - - ///////////////////////////////////////////////////////////////////// - /// Creates or deletes the autostart file. This code is inspired - /// by project synapse as well. - ///////////////////////////////////////////////////////////////////// - - private void autostart_toggled(Gtk.ToggleButton check_box) { - bool active = check_box.active; - if (!active && FileUtils.test(Paths.autostart, FileTest.EXISTS)) { - // delete the autostart file - FileUtils.remove (Paths.autostart); - } - else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) { - string autostart_entry = - "#!/usr/bin/env xdg-open\n" + - "[Desktop Entry]\n" + - "Name=Gnome-Pie\n" + - "Exec=gnome-pie\n" + - "Encoding=UTF-8\n" + - "Type=Application\n" + - "X-GNOME-Autostart-enabled=true\n" + - "Icon=gnome-pie\n"; - - // create the autostart file - string autostart_dir = GLib.Path.get_dirname(Paths.autostart); - if (!FileUtils.test(autostart_dir, FileTest.EXISTS | FileTest.IS_DIR)) { - DirUtils.create_with_parents(autostart_dir, 0755); - } - - try { - FileUtils.set_contents(Paths.autostart, autostart_entry); - FileUtils.chmod(Paths.autostart, 0755); - } catch (Error e) { - var d = new Gtk.MessageDialog (this, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, - "%s", e.message); - d.run (); - d.destroy (); - } - } - } - - ///////////////////////////////////////////////////////////////////// - /// Shows or hides the indicator. - ///////////////////////////////////////////////////////////////////// - - private void indicator_toggled(Gtk.ToggleButton check_box) { - var check = check_box as Gtk.CheckButton; - Config.global.show_indicator = check.active; - } - - ///////////////////////////////////////////////////////////////////// - /// Toggles whether the Pies are shown at the mouse or in the middle - /// of the screen. - ///////////////////////////////////////////////////////////////////// - - private void open_at_mouse_toggled(Gtk.ToggleButton check_box) { - var check = check_box as Gtk.CheckButton; - Config.global.open_at_mouse = check.active; - } -} - -} diff --git a/src/gui/preferencesWindow.vala b/src/gui/preferencesWindow.vala new file mode 100644 index 0000000..022e44b --- /dev/null +++ b/src/gui/preferencesWindow.vala @@ -0,0 +1,311 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// The 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.Window? window = null; + private Gtk.Label? id_label = null; + private Gtk.Label? name_label = null; + private Gtk.Label? hotkey_label = null; + private Gtk.Label? no_pie_label = null; + private Gtk.Label? no_slice_label = null; + private Gtk.VBox? preview_box = null; + private Gtk.Image? icon = null; + private Gtk.EventBox? preview_background = null; + private Gtk.Button? icon_button = null; + private Gtk.Button? name_button = null; + private Gtk.Button? hotkey_button = null; + private Gtk.ToolButton? remove_pie_button = null; + + ///////////////////////////////////////////////////////////////////// + /// Some custom widgets and dialogs used by this window. + ///////////////////////////////////////////////////////////////////// + + private PiePreview? preview = null; + private PieList? pie_list = null; + private SettingsWindow? settings_window = null; + private TriggerSelectWindow? trigger_window = null; + private IconSelectWindow? icon_window = null; + private RenameWindow? rename_window = null; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates the window. + ///////////////////////////////////////////////////////////////////// + + public PreferencesWindow() { + try { + var builder = new Gtk.Builder(); + + builder.add_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 HAVE_GTK_3 + var toolbar = builder.get_object ("toolbar") as Gtk.Widget; + toolbar.get_style_context().add_class("primary-toolbar"); + + var inline_toolbar = builder.get_object ("pies-toolbar") as Gtk.Widget; + inline_toolbar.get_style_context().add_class("inline-toolbar"); + #endif + + this.pie_list = new PieList(); + this.pie_list.on_select.connect(this.on_pie_select); + + 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.VBox; + this.preview_box.pack_start(preview, true, true); + + this.id_label = builder.get_object("id-label") as Gtk.Label; + this.name_label = builder.get_object("pie-name-label") as Gtk.Label; + this.hotkey_label = builder.get_object("hotkey-label") as Gtk.Label; + 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.icon = builder.get_object("icon") as Gtk.Image; + this.preview_background = builder.get_object("preview-background") as Gtk.EventBox; + + (builder.get_object("settings-button") as Gtk.ToolButton).clicked.connect(on_settings_button_clicked); + + this.hotkey_button = builder.get_object("key-button") as Gtk.Button; + this.hotkey_button.clicked.connect(on_key_button_clicked); + + this.icon_button = builder.get_object("icon-button") as Gtk.Button; + this.icon_button.clicked.connect(on_icon_button_clicked); + + this.name_button = builder.get_object("rename-button") as Gtk.Button; + this.name_button.clicked.connect(on_rename_button_clicked); + + this.remove_pie_button = builder.get_object("remove-pie-button") as Gtk.ToolButton; + this.remove_pie_button.clicked.connect(on_remove_pie_button_clicked); + + (builder.get_object("add-pie-button") as Gtk.ToolButton).clicked.connect(on_add_pie_button_clicked); + + this.window.hide.connect(() => { + // save settings on close + Config.global.save(); + Pies.save(); + }); + + this.window.delete_event.connect(this.window.hide_on_delete); + + } catch (GLib.Error e) { + error("Could not load UI: %s\n", e.message); + } + } + + ///////////////////////////////////////////////////////////////////// + /// Shows the window. + ///////////////////////////////////////////////////////////////////// + + public void show() { + this.preview.draw_loop(); + this.window.show_all(); + this.pie_list.select_first(); + this.preview_background.modify_bg(Gtk.StateType.NORMAL, Gtk.rc_get_style(this.window).light[0]); + } + + ///////////////////////////////////////////////////////////////////// + /// 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.name_button.sensitive = false; + this.hotkey_button.sensitive = false; + this.icon_button.sensitive = false; + this.remove_pie_button.sensitive = false; + + if (id == "") { + this.id_label.label = ""; + this.name_label.label = _("No Pie selected."); + this.hotkey_label.set_markup(""); + this.icon.icon_name = "application-default-icon"; + + this.no_pie_label.show(); + } else { + var pie = PieManager.all_pies[selected_id]; + this.id_label.label = ("ID: %s").printf(pie.id); + this.name_label.label = PieManager.get_name_of(pie.id); + this.hotkey_label.set_markup(PieManager.get_accelerator_label_of(pie.id)); + + if (pie.icon.contains("/")) + try { + this.icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale(pie.icon, + this.icon.get_pixel_size(), + this.icon.get_pixel_size(), + true); + } catch (GLib.Error error) { + warning(error.message); + } + else + this.icon.icon_name = pie.icon; + + this.preview.set_pie(id); + this.preview_box.show(); + + if (pie.action_groups.size == 0) { + this.no_slice_label.show(); + } + + this.name_button.sensitive = true; + this.hotkey_button.sensitive = true; + this.icon_button.sensitive = true; + this.remove_pie_button.sensitive = true; + } + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the add Pie button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_add_pie_button_clicked(Gtk.ToolButton button) { + var new_pie = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", null); + this.pie_list.reload_all(); + this.pie_list.select(new_pie.id); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the remove Pie button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_remove_pie_button_clicked(Gtk.ToolButton 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 rename Pie button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_rename_button_clicked(Gtk.Button button) { + if (this.rename_window == null) { + this.rename_window = new RenameWindow(); + this.rename_window.set_parent(window); + this.rename_window.on_ok.connect((name) => { + var pie = PieManager.all_pies[selected_id]; + pie.name = name; + PieManager.create_launcher(pie.id); + this.name_label.label = name; + this.pie_list.reload_all(); + }); + } + + this.rename_window.set_pie(selected_id); + this.rename_window.show(); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the hotkey button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_key_button_clicked(Gtk.Button button) { + if (this.trigger_window == null) { + this.trigger_window = new TriggerSelectWindow(); + this.trigger_window.set_parent(window); + this.trigger_window.on_ok.connect((trigger) => { + PieManager.bind_trigger(trigger, selected_id); + this.hotkey_label.set_markup(trigger.label_with_specials); + }); + } + + this.trigger_window.set_pie(selected_id); + this.trigger_window.show(); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the general settings button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_settings_button_clicked(Gtk.ToolButton button) { + if (this.settings_window == null) { + this.settings_window = new SettingsWindow(); + this.settings_window.set_parent(this.window.get_toplevel() as Gtk.Window); + } + + this.settings_window.show(); + } + + ///////////////////////////////////////////////////////////////////// + /// 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) => { + var pie = PieManager.all_pies[selected_id]; + pie.icon = icon; + PieManager.create_launcher(pie.id); + this.pie_list.reload_all(); + }); + } + + this.icon_window.show(); + } +} + +} diff --git a/src/gui/renameWindow.vala b/src/gui/renameWindow.vala new file mode 100644 index 0000000..389b460 --- /dev/null +++ b/src/gui/renameWindow.vala @@ -0,0 +1,109 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A window which allows selection of a new name for a Pie. +///////////////////////////////////////////////////////////////////////// + +public class RenameWindow : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Gets emitted when the user selects a new name. + ///////////////////////////////////////////////////////////////////// + + public signal void on_ok(string new_name); + + ///////////////////////////////////////////////////////////////////// + /// Some Widgets used by this dialog. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Dialog window = null; + private Gtk.Entry entry = null; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, constructs the Widget. + ///////////////////////////////////////////////////////////////////// + + public RenameWindow() { + try { + + Gtk.Builder builder = new Gtk.Builder(); + + builder.add_from_file (Paths.ui_files + "/rename_pie.ui"); + + window = builder.get_object("window") as Gtk.Dialog; + entry = builder.get_object("name-entry") as Gtk.Entry; + + entry.activate.connect(this.on_ok_button_clicked); + + (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); + } + + ///////////////////////////////////////////////////////////////////// + /// Displays the window on the screen. + ///////////////////////////////////////////////////////////////////// + + public void show() { + this.window.show_all(); + this.entry.is_focus = true; + } + + ///////////////////////////////////////////////////////////////////// + /// Make the text entry display the name of the Pie with given ID. + ///////////////////////////////////////////////////////////////////// + + public void set_pie(string id) { + entry.text = PieManager.get_name_of(id); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the ok button is pressed. + ///////////////////////////////////////////////////////////////////// + + private void on_ok_button_clicked() { + this.on_ok(entry.text); + this.window.hide(); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the cancel button is pressed. + ///////////////////////////////////////////////////////////////////// + + private void on_cancel_button_clicked() { + this.window.hide(); + } +} + +} diff --git a/src/gui/settingsWindow.vala b/src/gui/settingsWindow.vala new file mode 100644 index 0000000..1eaa0b4 --- /dev/null +++ b/src/gui/settingsWindow.vala @@ -0,0 +1,174 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// The settings menu of Gnome-Pie, with options for theme switching and +/// some general options. +///////////////////////////////////////////////////////////////////////// + +public class SettingsWindow : GLib.Object { + + ///////////////////////////////////////////////////////////////////// + /// Some widgets. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Dialog? window = null; + private ThemeList? theme_list = null; + private Gtk.ToggleButton? indicator = null; + private Gtk.ToggleButton? autostart = null; + + ///////////////////////////////////////////////////////////////////// + /// C'tor creates, the dialog. + ///////////////////////////////////////////////////////////////////// + + public SettingsWindow() { + try { + + Gtk.Builder builder = new Gtk.Builder(); + + builder.add_from_file (Paths.ui_files + "/settings.ui"); + + this.window = builder.get_object("window") as Gtk.Dialog; + + this.theme_list = new ThemeList(); + + var scroll_area = builder.get_object("theme-scrolledwindow") as Gtk.ScrolledWindow; + scroll_area.add(this.theme_list); + + (builder.get_object("close-button") as Gtk.Button).clicked.connect(on_close_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); + + var scale_slider = (builder.get_object("scale-hscale") as Gtk.HScale); + 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; + } + }); + + 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.indicator.active = Config.global.show_indicator; + this.autostart.active = Config.global.auto_start; + + this.window.show_all(); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the close button is clicked. + ///////////////////////////////////////////////////////////////////// + + private void on_close_button_clicked() { + this.window.hide(); + } + + ///////////////////////////////////////////////////////////////////// + /// 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)) { + // delete the autostart file + FileUtils.remove (Paths.autostart); + } + else if (active && !FileUtils.test(Paths.autostart, FileTest.EXISTS)) { + string autostart_entry = + "#!/usr/bin/env xdg-open\n" + + "[Desktop Entry]\n" + + "Name=Gnome-Pie\n" + + "Exec=gnome-pie\n" + + "Encoding=UTF-8\n" + + "Type=Application\n" + + "X-GNOME-Autostart-enabled=true\n" + + "Icon=gnome-pie\n"; + + // create the autostart file + string autostart_dir = GLib.Path.get_dirname(Paths.autostart); + if (!FileUtils.test(autostart_dir, FileTest.EXISTS | FileTest.IS_DIR)) { + DirUtils.create_with_parents(autostart_dir, 0755); + } + + try { + FileUtils.set_contents(Paths.autostart, autostart_entry); + FileUtils.chmod(Paths.autostart, 0755); + } catch (Error e) { + var d = new Gtk.MessageDialog (this.window, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, + "%s", e.message); + d.run (); + d.destroy (); + } + } + } + + ///////////////////////////////////////////////////////////////////// + /// 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; + } +} + +} diff --git a/src/gui/sliceTypeList.vala b/src/gui/sliceTypeList.vala new file mode 100644 index 0000000..541658e --- /dev/null +++ b/src/gui/sliceTypeList.vala @@ -0,0 +1,172 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A 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(); + 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, "<b>" + description.name + "</b>\n" + + "<small>" + description.description + "</small>"); + 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, "<b>" + description.name + "</b>\n" + + "<small>" + description.description + "</small>"); + 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("", "application-default-icon"); + } + } + + ///////////////////////////////////////////////////////////////////// + /// 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 index 7eadcdb..62e0721 100644 --- a/src/gui/themeList.vala +++ b/src/gui/themeList.vala @@ -28,6 +28,12 @@ class ThemeList : Gtk.TreeView { ///////////////////////////////////////////////////////////////////// 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. @@ -36,58 +42,50 @@ class ThemeList : Gtk.TreeView { public ThemeList() { GLib.Object(); - var data = new Gtk.ListStore(2, typeof(bool), // selected - typeof(string)); // description - base.set_model(data); - base.set_headers_visible(false); - base.set_rules_hint(true); - base.set_grid_lines(Gtk.TreeViewGridLines.NONE); + var data = new Gtk.ListStore(2, typeof(Gdk.Pixbuf), + typeof(string)); + this.set_model(data); + this.set_headers_visible(true); + this.set_grid_lines(Gtk.TreeViewGridLines.NONE); + this.set_fixed_height_mode(true); var main_column = new Gtk.TreeViewColumn(); - var check_render = new Gtk.CellRendererToggle(); - check_render.set_radio(true); - check_render.set_activatable(true); - main_column.pack_start(check_render, false); - - // switch the theme if the entry has been toggled - check_render.toggled.connect((r, path) => { - Gtk.TreeIter toggled; - data.get_iter(out toggled, new Gtk.TreePath.from_string(path)); - - if (toggled != this.active) { - Timeout.add(10, () => { - int index = int.parse(path); - Config.global.theme = Config.global.themes[index]; - Config.global.theme.load(); - Config.global.theme.load_images(); - return false; - }); - - data.set(this.active, 0, false); - data.set(toggled, 0, true); - - this.active = toggled; - } - }); + main_column.title = _("Themes"); + main_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED); + var icon_render = new Gtk.CellRendererPixbuf(); + main_column.pack_start(icon_render, false); var theme_render = new Gtk.CellRendererText(); main_column.pack_start(theme_render, true); - base.append_column(main_column); + this.append_column(main_column); + + main_column.add_attribute(icon_render, "pixbuf", DataPos.ICON); + main_column.add_attribute(theme_render, "markup", DataPos.NAME); - main_column.add_attribute(check_render, "active", 0); - main_column.add_attribute(theme_render, "markup", 1); + this.get_selection().changed.connect(() => { + Gtk.TreeIter active; + if (this.get_selection().get_selected(null, out active)) { + Timeout.add(10, () => { + int index = int.parse(data.get_path(active).to_string()); + Config.global.theme = Config.global.themes[index]; + Config.global.theme.load(); + Config.global.theme.load_images(); + return false; + }); + } + }); // load all themes into the list var themes = Config.global.themes; foreach(var theme in themes) { Gtk.TreeIter current; data.append(out current); - data.set(current, 0, theme == Config.global.theme); - data.set(current, 1, "<b>" + theme.name + "</b>\n" + theme.description - + " <small> - " + _("by") + " " + theme.author + "</small>"); + data.set(current, DataPos.ICON, theme.preview_icon.to_pixbuf()); + data.set(current, DataPos.NAME, "<b>"+theme.name+"</b><small> - "+theme.description+"\n" + +"<i>"+_("By")+" "+theme.author+"</i></small>"); if(theme == Config.global.theme) - this.active = current; + get_selection().select_iter(current); } } } diff --git a/src/gui/tipViewer.vala b/src/gui/tipViewer.vala deleted file mode 100644 index c653dd9..0000000 --- a/src/gui/tipViewer.vala +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright (c) 2011 by Simon Schneegans - -This program is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the Free -Software Foundation, either version 3 of the License, or (at your option) -any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -namespace GnomePie { - -///////////////////////////////////////////////////////////////////////// -/// A widget showing tips. The tips are beautifully faded in and out. -///////////////////////////////////////////////////////////////////////// - -public class TipViewer : Gtk.Label { - - ///////////////////////////////////////////////////////////////////// - /// Some settings tweaking the behavior of the TipViewer. - ///////////////////////////////////////////////////////////////////// - - private const double fade_time = 0.5; - private const double frame_rate = 20.0; - private const double delay = 7.0; - - ///////////////////////////////////////////////////////////////////// - /// False, if the playback of tips is stopped. - ///////////////////////////////////////////////////////////////////// - - private bool playing = false; - - ///////////////////////////////////////////////////////////////////// - /// An array containing all tips. - ///////////////////////////////////////////////////////////////////// - - private string[] tips; - - ///////////////////////////////////////////////////////////////////// - /// The index of the currently displayed tip. - ///////////////////////////////////////////////////////////////////// - - private int index = -1; - - ///////////////////////////////////////////////////////////////////// - /// Colors of the font and the background. Used for fading effects. - ///////////////////////////////////////////////////////////////////// - - private Gdk.Color fg; - private Gdk.Color bg; - - ///////////////////////////////////////////////////////////////////// - /// The fading value. - ///////////////////////////////////////////////////////////////////// - - private AnimatedValue alpha; - - ///////////////////////////////////////////////////////////////////// - /// C'tor, initializes all members and sets the basic layout. - ///////////////////////////////////////////////////////////////////// - - public TipViewer(string[] tips) { - this.tips = tips; - this.fg = this.get_style().fg[0]; - this.bg = this.get_style().bg[0]; - - this.alpha = new AnimatedValue.linear(1.0, 0.0, this.fade_time); - - this.set_alignment (0.0f, 0.5f); - this.wrap = true; - this.set_use_markup(true); - this.modify_font(Pango.FontDescription.from_string("9")); - - this.set_random_tip(); - } - - ///////////////////////////////////////////////////////////////////// - /// Starts the playback of tips. - ///////////////////////////////////////////////////////////////////// - - public void start_slide_show() { - if (!this.playing && tips.length > 1) { - this.playing = true; - GLib.Timeout.add((uint)(this.delay*1000.0), () => { - this.fade_out(); - - GLib.Timeout.add((uint)(1000.0*this.fade_time), () => { - this.set_random_tip(); - this.fade_in(); - return false; - }); - - return this.playing; - }); - } - } - - ///////////////////////////////////////////////////////////////////// - /// Stops the playback of tips. - ///////////////////////////////////////////////////////////////////// - - public void stop_slide_show() { - this.playing = false; - } - - ///////////////////////////////////////////////////////////////////// - /// Starts the fading in. - ///////////////////////////////////////////////////////////////////// - - private void fade_in() { - this.alpha = new AnimatedValue.linear(this.alpha.val, 1.0, this.fade_time); - - GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { - this.alpha.update(1.0/this.frame_rate); - this.update_label(); - - return (this.alpha.val != 1.0); - }); - } - - ///////////////////////////////////////////////////////////////////// - /// Starts the fading out. - ///////////////////////////////////////////////////////////////////// - - private void fade_out() { - this.alpha = new AnimatedValue.linear(this.alpha.val, 0.0, this.fade_time); - - GLib.Timeout.add((uint)(1000.0/this.frame_rate), () => { - this.alpha.update(1.0/this.frame_rate); - this.update_label(); - - return (this.alpha.val != 0.0); - }); - } - - ///////////////////////////////////////////////////////////////////// - /// Updates the color of the label. Called every frame while fading. - ///////////////////////////////////////////////////////////////////// - - private void update_label() { - Gdk.Color color = {(uint32)(fg.pixel*this.alpha.val + bg.pixel*(1.0 - this.alpha.val)), - (uint16)(fg.red*this.alpha.val + bg.red*(1.0 - this.alpha.val)), - (uint16)(fg.green*this.alpha.val + bg.green*(1.0 - this.alpha.val)), - (uint16)(fg.blue*this.alpha.val + bg.blue*(1.0 - this.alpha.val))}; - - this.modify_fg(Gtk.StateType.NORMAL, color); - } - - ///////////////////////////////////////////////////////////////////// - /// Chooses the next random tip. - ///////////////////////////////////////////////////////////////////// - - private void set_random_tip() { - if (tips.length > 1) { - int next_index = -1; - do { - next_index = GLib.Random.int_range(0, tips.length); - } while (next_index == this.index); - this.index = next_index; - this.label = tips[this.index]; - } - } -} - -} diff --git a/src/gui/triggerSelectButton.vala b/src/gui/triggerSelectButton.vala new file mode 100644 index 0000000..eeb37e2 --- /dev/null +++ b/src/gui/triggerSelectButton.vala @@ -0,0 +1,161 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// 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.LOCK_MASK + |Gdk.ModifierType.MOD5_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)); + } + + 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 < rect.x || event.x > rect.x + rect.width + || event.y < rect.y || event.y > rect.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); + + 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; + } +} + +} diff --git a/src/gui/triggerSelectWindow.vala b/src/gui/triggerSelectWindow.vala index e003a84..23eea3c 100644 --- a/src/gui/triggerSelectWindow.vala +++ b/src/gui/triggerSelectWindow.vala @@ -23,21 +23,23 @@ namespace GnomePie { /// a mouse based hotkey. ///////////////////////////////////////////////////////////////////////// -public class TriggerSelectWindow : Gtk.Dialog { +public class TriggerSelectWindow : GLib.Object { ///////////////////////////////////////////////////////////////////// /// This signal is emitted when the user selects a new hot key. ///////////////////////////////////////////////////////////////////// - public signal void on_select(Trigger trigger); + public signal void on_ok(Trigger trigger); ///////////////////////////////////////////////////////////////////// /// Some private members which are needed by other methods. ///////////////////////////////////////////////////////////////////// + private Gtk.Dialog window; private Gtk.CheckButton turbo; private Gtk.CheckButton delayed; - private Gtk.Label preview; + private Gtk.CheckButton centered; + private TriggerSelectButton button; ///////////////////////////////////////////////////////////////////// /// The currently configured trigger. @@ -54,204 +56,133 @@ public class TriggerSelectWindow : Gtk.Dialog { private Trigger original_trigger = null; ///////////////////////////////////////////////////////////////////// - /// These modifiers are ignored. - ///////////////////////////////////////////////////////////////////// - - private Gdk.ModifierType lock_modifiers = Gdk.ModifierType.MOD2_MASK - |Gdk.ModifierType.LOCK_MASK - |Gdk.ModifierType.MOD5_MASK; - - ///////////////////////////////////////////////////////////////////// /// C'tor, constructs a new TriggerSelectWindow. ///////////////////////////////////////////////////////////////////// public TriggerSelectWindow() { - this.title = _("Define an open-command"); - this.resizable = false; - this.delete_event.connect(hide_on_delete); - this.key_press_event.connect(on_key_press); - this.button_press_event.connect(on_button_press); - - this.show.connect_after(() => { - FocusGrabber.grab(this); - }); + try { - this.hide.connect(() => { - FocusGrabber.ungrab(this); - }); + Gtk.Builder builder = new Gtk.Builder(); + + builder.add_from_file (Paths.ui_files + "/trigger_select.ui"); - var container = new Gtk.VBox(false, 6); - container.set_border_width(6); - - // click area - var click_frame = new Gtk.Frame(_("Click here if you want to bind a mouse button!")); + this.window = builder.get_object("window") as Gtk.Dialog; + this.button = new TriggerSelectButton(true); + this.button.show(); - var click_box = new Gtk.EventBox(); - click_box.height_request = 100; - click_box.button_press_event.connect(on_area_clicked); - - this.preview = new Gtk.Label(null); - - click_box.add(this.preview); - - click_frame.add(click_box); - - container.pack_start(click_frame, false); + this.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); + }); - // turbo checkbox - this.turbo = new Gtk.CheckButton.with_label (_("Turbo mode")); - this.turbo.tooltip_text = _("If checked, the Pie will close when you " + - "release the chosen hot key."); - this.turbo.active = false; - this.turbo.toggled.connect(() => { - if (this.trigger != null) - this.update_trigger(new Trigger.from_values( - this.trigger.key_sym, this.trigger.modifiers, - this.trigger.with_mouse, this.turbo.active, - this.delayed.active)); - }); - - container.pack_start(turbo, false); + (builder.get_object("trigger-box") as Gtk.VBox).pack_start(this.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); - // delayed checkbox - this.delayed = new Gtk.CheckButton.with_label (_("Long press for activation")); - this.delayed.tooltip_text = _("If checked, the Pie will only open if you " + - "press this hot key a bit longer."); - this.delayed.active = false; - this.delayed.toggled.connect(() => { - if (this.trigger != null) - this.update_trigger(new Trigger.from_values( - this.trigger.key_sym, this.trigger.modifiers, - this.trigger.with_mouse, this.turbo.active, - this.delayed.active)); - }); + 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.window.delete_event.connect(this.window.hide_on_delete); - container.pack_start(delayed, false); - - container.show_all(); - - this.vbox.pack_start(container, true, true); - - this.add_button(Gtk.Stock.CANCEL, 1); - this.add_button(Gtk.Stock.OK, 0); - - // select a new trigger on OK, hide on CANCEL - this.response.connect((id) => { - if (id == 1) - this.hide(); - else if (id == 0) { - var assigned_id = PieManager.get_assigned_id(this.trigger); + } catch (GLib.Error e) { + error("Could not load UI: %s\n", e.message); + } + } - if (this.trigger == this.original_trigger) { - // nothing did change - this.hide(); - } else if (this.trigger.key_code == this.original_trigger.key_code - && this.trigger.modifiers == this.original_trigger.modifiers - && this.trigger.with_mouse == this.original_trigger.with_mouse) { - // only turbo and/or delayed mode changed, no need to check for double assignment - this.on_select(this.trigger); - this.hide(); - } else if (assigned_id != "") { - // it's already assigned - var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " + - "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id)); - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), - Gtk.DialogFlags.MODAL, - Gtk.MessageType.ERROR, - Gtk.ButtonsType.CANCEL, - error); - dialog.run(); - dialog.destroy(); - } else { - // a unused hot key has been chosen, great! - this.on_select(this.trigger); - this.hide(); - } - } - }); + ///////////////////////////////////////////////////////////////////// + /// 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); } ///////////////////////////////////////////////////////////////////// - /// Used to set the currently selected trigger on opening. + /// Displays the window on the screen. ///////////////////////////////////////////////////////////////////// - public void set_trigger(Trigger trigger) { - this.turbo.active = trigger.turbo; - this.delayed.active = trigger.delayed; - this.original_trigger = trigger; - this.update_trigger(trigger); + public void show() { + this.window.show_all(); } ///////////////////////////////////////////////////////////////////// - /// Called when the user clicks in the click area. + /// Initilizes all members to match the Trigger of the Pie with the + /// given ID. ///////////////////////////////////////////////////////////////////// - private bool on_area_clicked(Gdk.EventButton event) { - Gdk.ModifierType state = event.state & ~ this.lock_modifiers; + public void set_pie(string id) { + var trigger = new Trigger.from_string(PieManager.get_accelerator_of(id)); - var new_trigger = new Trigger.from_values((int)event.button, state, true, - this.turbo.active, this.delayed.active); - if (new_trigger.key_code == 1) { - var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, - Gtk.MessageType.WARNING, - Gtk.ButtonsType.YES_NO, - _("It possible to make your system unusable if " + - "you bind a Pie to your left mouse button. Do " + - "you really want to do this?")); - - dialog.response.connect((response) => { - if (response == Gtk.ResponseType.YES) { - this.update_trigger(new_trigger); - } - }); - - dialog.run(); - dialog.destroy(); - } else { - this.update_trigger(new_trigger); - } + this.turbo.active = trigger.turbo; + this.delayed.active = trigger.delayed; + this.centered.active = trigger.centered; + this.original_trigger = trigger; + this.trigger = trigger; - return true; + this.button.set_trigger(trigger); } ///////////////////////////////////////////////////////////////////// - /// Called when the user presses a keyboard key. + /// Called when one of the three checkoxes is toggled. ///////////////////////////////////////////////////////////////////// - private bool on_key_press(Gdk.EventKey event) { - if (Gdk.keyval_name(event.keyval) == "Escape") { - this.hide(); - } else if (Gdk.keyval_name(event.keyval) == "BackSpace") { - this.update_trigger(new Trigger()); - } else if (event.is_modifier == 0) { - Gdk.ModifierType state = event.state & ~ this.lock_modifiers; - this.update_trigger(new Trigger.from_values((int)event.keyval, state, false, - this.turbo.active, this.delayed.active)); - } - - return true; + 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); } ///////////////////////////////////////////////////////////////////// - /// Called when the user presses a mouse button. + /// Called when the OK-button is pressed. ///////////////////////////////////////////////////////////////////// - private bool on_button_press(Gdk.EventButton event) { - int width = 0, height = 0; - this.window.get_geometry(null, null, out width, out height, null); - if (event.x < 0 || event.x > width || event.y < 0 || event.y > height) - this.hide(); - return true; + private void on_ok_button_clicked() { + var assigned_id = PieManager.get_assigned_id(this.trigger); + + if (this.trigger == this.original_trigger) { + // nothing did change + this.window.hide(); + } else if (this.trigger.key_code == this.original_trigger.key_code + && this.trigger.modifiers == this.original_trigger.modifiers + && this.trigger.with_mouse == this.original_trigger.with_mouse) { + // only turbo and/or delayed mode changed, no need to check for double assignment + this.on_ok(this.trigger); + this.window.hide(); + } else if (assigned_id != "") { + // it's already assigned + var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " + + "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id)); + var dialog = new Gtk.MessageDialog((Gtk.Window)this.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.window.hide(); + } } ///////////////////////////////////////////////////////////////////// - /// Helper method to update the content of the trigger preview label. + /// Called when the cancel button is pressed. ///////////////////////////////////////////////////////////////////// - private void update_trigger(Trigger new_trigger) { - this.trigger = new_trigger; - this.preview.set_markup("<big><b>" + this.trigger.label + "</b></big>"); - } + private void on_cancel_button_clicked() { + this.window.hide(); + } } } |