diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/about.vala | 34 | ||||
-rw-r--r-- | src/gui/cellRendererTrigger.vala | 84 | ||||
-rw-r--r-- | src/gui/iconSelectWindow.vala | 200 | ||||
-rw-r--r-- | src/gui/pieList.vala | 40 | ||||
-rw-r--r-- | src/gui/preferences.vala | 29 | ||||
-rw-r--r-- | src/gui/triggerSelectWindow.vala | 257 |
6 files changed, 574 insertions, 70 deletions
diff --git a/src/gui/about.vala b/src/gui/about.vala index 1ace9cb..ce4256e 100644 --- a/src/gui/about.vala +++ b/src/gui/about.vala @@ -24,18 +24,44 @@ namespace GnomePie { public class GnomePieAboutDialog: Gtk.AboutDialog { public GnomePieAboutDialog () { - string[] devs = {"Simon Schneegans <code@simonschneegans.de>", - "Francesco Piccinno"}; - string[] artists = {"Simon Schneegans <code@simonschneegans.de>"}; + string[] devs = { + "Simon Schneegans <code@simonschneegans.de>", + "Francesco Piccinno <stack.box@gmail.com>" + }; + string[] artists = { + "Simon Schneegans <code@simonschneegans.de>" + }; + string[] translators = { + "DE\t\t Simon Schneegans <code@simonschneegans.de>", + "IT\t\t Riccardo Traverso <gr3yfox.fw@gmail.com>", + "PT-BR\t Magnun Leno <magnun@codecommunity.org>", + "EN\t\t Simon Schneegans <code@simonschneegans.de>", + "KO\t\t Kim Boram <Boramism@gmail.com>" + }; + + // sort translators + GLib.List<string> translator_list = new GLib.List<string>(); + foreach (var translator in translators) + translator_list.append(translator); + + translator_list.sort((a, b) => { + return a.ascii_casecmp(b); + }); + + string translator_string = ""; + foreach (var translator in translator_list) + translator_string += translator + "\n"; + GLib.Object ( artists : artists, authors : devs, + translator_credits : translator_string, copyright : "Copyright (C) 2011 Simon Schneegans <code@simonschneegans.de>", program_name: "Gnome-Pie", logo_icon_name: "gnome-pie", website: "http://www.simonschneegans.de/?page_id=12", website_label: "www.gnome-pie.simonschneegans.de", - version: "0.2" + version: "0.3.1" ); } } diff --git a/src/gui/cellRendererTrigger.vala b/src/gui/cellRendererTrigger.vala new file mode 100644 index 0000000..a825c32 --- /dev/null +++ b/src/gui/cellRendererTrigger.vala @@ -0,0 +1,84 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// A CellRenderer which opens a TriggerSelectWindow. +///////////////////////////////////////////////////////////////////////// + +public class CellRendererTrigger : Gtk.CellRendererText { + + ///////////////////////////////////////////////////////////////////// + /// This signal is emitted when the user selects another trigger. + ///////////////////////////////////////////////////////////////////// + + public signal void on_select(string path, Trigger trigger); + + ///////////////////////////////////////////////////////////////////// + /// The trigger which can be set with this window. + ///////////////////////////////////////////////////////////////////// + + public string trigger { get; set; } + + ///////////////////////////////////////////////////////////////////// + /// The IconSelectWindow which is shown on click. + ///////////////////////////////////////////////////////////////////// + + private TriggerSelectWindow select_window = null; + + ///////////////////////////////////////////////////////////////////// + /// A helper variable, needed to emit the current path. + ///////////////////////////////////////////////////////////////////// + + private string current_path = ""; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new CellRendererIcon. + ///////////////////////////////////////////////////////////////////// + + public CellRendererTrigger() { + this.select_window = new TriggerSelectWindow(); + + this.select_window.on_select.connect((trigger) => { + this.trigger = trigger.name; + this.on_select(current_path, trigger); + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Open the TriggerSelectWindow on click. + ///////////////////////////////////////////////////////////////////// + + public override unowned Gtk.CellEditable start_editing( + Gdk.Event event, Gtk.Widget widget, string path, Gdk.Rectangle bg_area, + Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { + + this.current_path = path; + + this.select_window.set_transient_for((Gtk.Window)widget.get_toplevel()); + this.select_window.set_modal(true); + this.select_window.set_trigger(new Trigger.from_string(this.trigger)); + + this.select_window.show(); + + return base.start_editing(event, widget, path, bg_area, cell_area, flags); + } +} + +} + diff --git a/src/gui/iconSelectWindow.vala b/src/gui/iconSelectWindow.vala index 2274ec5..01a4a40 100644 --- a/src/gui/iconSelectWindow.vala +++ b/src/gui/iconSelectWindow.vala @@ -19,45 +19,17 @@ namespace GnomePie { ///////////////////////////////////////////////////////////////////////// /// A window which allows selection of an Icon of the user's current icon -/// theme. Loading of Icons happens in an extra thread and a spinner is -/// displayed while loading. +/// theme. Custom icons/images can be selested as well. Loading of icons +/// happens in an extra thread and a spinner is displayed while loading. ///////////////////////////////////////////////////////////////////////// public class IconSelectWindow : Gtk.Dialog { - private static Gtk.ListStore icon_list = null; - - private static bool loading {get; set; default = false;} - private static bool need_reload {get; set; default = true;} + ///////////////////////////////////////////////////////////////////// + /// The currently selected icon. If set, this icon gets focused. + ///////////////////////////////////////////////////////////////////// - private const string disabled_contexts = "Animations, FileSystems, MimeTypes"; - private Gtk.TreeModelFilter icon_list_filtered = null; - private Gtk.IconView icon_view = null; - private Gtk.Spinner spinner = null; - - private Gtk.FileChooserWidget file_chooser = null; - - private Gtk.Notebook tabs = null; - - private class ListEntry { - public string name; - public IconContext context; - public Gdk.Pixbuf pixbuf; - } - - private GLib.AsyncQueue<ListEntry?> load_queue; - - private enum IconContext { - ALL, - APPS, - ACTIONS, - PLACES, - FILES, - EMOTES, - OTHER - } - - public string _active_icon = "application-default-icon"; + private string _active_icon = "application-default-icon"; public string active_icon { get { @@ -85,7 +57,104 @@ public class IconSelectWindow : Gtk.Dialog { } } + ///////////////////////////////////////////////////////////////////// + /// This signal gets emitted when the user selects a new icon. + ///////////////////////////////////////////////////////////////////// + public signal void on_select(string icon_name); + + ///////////////////////////////////////////////////////////////////// + /// The ListStore storing all theme-icons. + ///////////////////////////////////////////////////////////////////// + + private static Gtk.ListStore icon_list = null; + + ///////////////////////////////////////////////////////////////////// + /// True, if the icon theme is currently reloaded. + ///////////////////////////////////////////////////////////////////// + + private static bool loading = false; + + ///////////////////////////////////////////////////////////////////// + /// If set to true, the icon list will be reloaded next time the + /// window opens. + ///////////////////////////////////////////////////////////////////// + + private static bool need_reload = true; + + ///////////////////////////////////////////////////////////////////// + /// Icons of these contexts won't appear in the list. + ///////////////////////////////////////////////////////////////////// + + private const string disabled_contexts = "Animations, FileSystems"; + + ///////////////////////////////////////////////////////////////////// + /// The list of icons, filtered according to the chosen type and + /// filter string. + ///////////////////////////////////////////////////////////////////// + + private Gtk.TreeModelFilter icon_list_filtered = null; + + ///////////////////////////////////////////////////////////////////// + /// The Gtk widget displaying the icons. + ///////////////////////////////////////////////////////////////////// + + private Gtk.IconView icon_view = null; + + ///////////////////////////////////////////////////////////////////// + /// This spinner is displayed when the icons are loaded. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Spinner spinner = null; + + ///////////////////////////////////////////////////////////////////// + /// A Gtk widget used for custom icon/image selection. + ///////////////////////////////////////////////////////////////////// + + private Gtk.FileChooserWidget file_chooser = null; + + ///////////////////////////////////////////////////////////////////// + /// The notebook containing the different icon choice possibilities: + /// from the theme or custom. + ///////////////////////////////////////////////////////////////////// + + private Gtk.Notebook tabs = null; + + ///////////////////////////////////////////////////////////////////// + /// A little structure containing data for one icon in the icon_view. + ///////////////////////////////////////////////////////////////////// + + private class ListEntry { + public string name; + public IconContext context; + public Gdk.Pixbuf pixbuf; + } + + ///////////////////////////////////////////////////////////////////// + /// This queue is used for icon loading. A loading thread pushes + /// icons into it --- the main thread updates the icon_view + /// accordingly. + ///////////////////////////////////////////////////////////////////// + + private GLib.AsyncQueue<ListEntry?> load_queue; + + ///////////////////////////////////////////////////////////////////// + /// Possible icon types. + ///////////////////////////////////////////////////////////////////// + + private enum IconContext { + ALL, + APPS, + ACTIONS, + PLACES, + FILES, + EMOTES, + OTHER + } + + ///////////////////////////////////////////////////////////////////// + /// C'tor, creates a new IconSelectWindow. + ///////////////////////////////////////////////////////////////////// public IconSelectWindow() { this.title = _("Choose an Icon"); @@ -94,15 +163,22 @@ public class IconSelectWindow : Gtk.Dialog { this.load_queue = new GLib.AsyncQueue<ListEntry?>(); if (this.icon_list == null) { - this.icon_list = new Gtk.ListStore(3, typeof(string), typeof(IconContext), typeof(Gdk.Pixbuf)); + this.icon_list = new Gtk.ListStore(3, typeof(string), // icon name + typeof(IconContext), // icon type + typeof(Gdk.Pixbuf)); // the icon itself + + // disable sorting until all icons are loaded + // else loading becomes horribly slow this.icon_list.set_default_sort_func(() => {return 0;}); + // reload if icon theme changes Gtk.IconTheme.get_default().changed.connect(() => { if (this.visible) load_icons(); else need_reload = true; }); } + // make the icon_view filterable this.icon_list_filtered = new Gtk.TreeModelFilter(this.icon_list, null); var container = new Gtk.VBox(false, 12); @@ -111,9 +187,11 @@ public class IconSelectWindow : Gtk.Dialog { // tab container this.tabs = new Gtk.Notebook(); + // icon theme tab var theme_tab = new Gtk.VBox(false, 12); theme_tab.set_border_width(12); + // type chooser combo-box var context_combo = new Gtk.ComboBox.text(); context_combo.append_text(_("All icons")); context_combo.append_text(_("Applications")); @@ -130,13 +208,16 @@ public class IconSelectWindow : Gtk.Dialog { }); theme_tab.pack_start(context_combo, false, false); - + + // string filter entry var filter = new Gtk.Entry(); filter.primary_icon_stock = Gtk.Stock.FIND; filter.primary_icon_activatable = false; filter.secondary_icon_stock = Gtk.Stock.CLEAR; theme_tab.pack_start(filter, false, false); + // only display items which have the selected type + // and whose name contains the text entered in the entry this.icon_list_filtered.set_visible_func((model, iter) => { string name = ""; IconContext context = IconContext.ALL; @@ -150,33 +231,39 @@ public class IconSelectWindow : Gtk.Dialog { name.down().contains(filter.text.down()); }); + // clear when the users clicks on the "clear" icon filter.icon_release.connect((pos, event) => { if (pos == Gtk.EntryIconPosition.SECONDARY) filter.text = ""; }); + // refilter on input filter.notify["text"].connect(() => { this.icon_list_filtered.refilter(); }); + // container for the icon_view var scroll = new Gtk.ScrolledWindow (null, null); scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); scroll.set_shadow_type (Gtk.ShadowType.IN); + // displays the filtered icons this.icon_view = new Gtk.IconView.with_model(this.icon_list_filtered); this.icon_view.item_width = 32; this.icon_view.item_padding = 3; this.icon_view.pixbuf_column = 2; this.icon_view.tooltip_column = 0; + // set _active_icon if selection changes this.icon_view.selection_changed.connect(() => { foreach (var path in this.icon_view.get_selected_items()) { Gtk.TreeIter iter; this.icon_list_filtered.get_iter(out iter, path); - icon_list_filtered.get(iter, 0, out this._active_icon); + this.icon_list_filtered.get(iter, 0, out this._active_icon); } }); + // hide this window when the user activates an icon this.icon_view.item_activated.connect((path) => { Gtk.TreeIter iter; this.icon_list_filtered.get_iter(out iter, path); @@ -191,20 +278,27 @@ public class IconSelectWindow : Gtk.Dialog { tabs.append_page(theme_tab, new Gtk.Label(_("Icon Theme"))); + // tab containing the possibility to choose a custom icon var custom_tab = new Gtk.VBox(false, 6); custom_tab.border_width = 12; + // file chooser widget this.file_chooser = new Gtk.FileChooserWidget(Gtk.FileChooserAction.OPEN); var file_filter = new Gtk.FileFilter(); file_filter.add_pixbuf_formats(); file_filter.set_name(_("All supported image formats")); file_chooser.add_filter(file_filter); + // set _active_icon if the user selected a file file_chooser.selection_changed.connect(() => { - if (file_chooser.get_filename() != null && GLib.FileUtils.test(file_chooser.get_filename(), GLib.FileTest.IS_REGULAR)) + if (file_chooser.get_filename() != null && + GLib.FileUtils.test(file_chooser.get_filename(), + GLib.FileTest.IS_REGULAR)) + this._active_icon = file_chooser.get_filename(); }); + // hide this window when the user activates a file file_chooser.file_activated.connect(() => { this._active_icon = file_chooser.get_filename(); this.on_select(this._active_icon); @@ -218,7 +312,9 @@ public class IconSelectWindow : Gtk.Dialog { container.pack_start(tabs, true, true); - // button box + // button box --- this dialog has a custom button box at the bottom because it + // should have a spinner there. Sadly that's impossible with the "normal" + // action_area of Gtk.Dialog's var bottom_box = new Gtk.HBox(false, 0); var bbox = new Gtk.HButtonBox(); @@ -255,8 +351,16 @@ public class IconSelectWindow : Gtk.Dialog { this.set_focus(this.icon_view); } + ///////////////////////////////////////////////////////////////////// + /// Hide the "normal" action_area when this window is shown. Reload + /// all icons if necessary. + ///////////////////////////////////////////////////////////////////// + public override void show() { base.show(); + + // hide the "normal" action_area --- this Dialog has a custom set of + // buttons containg the spinner this.action_area.hide(); if (this.need_reload) { @@ -265,23 +369,32 @@ public class IconSelectWindow : Gtk.Dialog { } } + ///////////////////////////////////////////////////////////////////// + /// (Re)load all icons. + ///////////////////////////////////////////////////////////////////// + private void load_icons() { + // only if it's not loading currently if (!this.loading) { this.loading = true; this.icon_list.clear(); + // display the spinner if (spinner != null) this.spinner.visible = true; + // disable sorting of the icon_view - else it's horribly slow this.icon_list.set_sort_column_id(-1, Gtk.SortType.ASCENDING); try { + // start loading in another thread unowned Thread loader = Thread.create<void*>(load_thread, false); loader.set_priority(ThreadPriority.LOW); } catch (GLib.ThreadError e) { error("Failed to create icon loader thread!"); } + // insert loaded icons every 200 ms Timeout.add(200, () => { while (this.load_queue.length() > 0) { var new_entry = this.load_queue.pop(); @@ -292,6 +405,7 @@ public class IconSelectWindow : Gtk.Dialog { 2, new_entry.pixbuf); } + // enable sorting of the icon_view if loading finished if (!this.loading) this.icon_list.set_sort_column_id(0, Gtk.SortType.ASCENDING); return loading; @@ -299,6 +413,11 @@ public class IconSelectWindow : Gtk.Dialog { } } + ///////////////////////////////////////////////////////////////////// + /// Loads all icons of an icon theme and pushes them into the + /// load_queue. + ///////////////////////////////////////////////////////////////////// + private void* load_thread() { var icon_theme = Gtk.IconTheme.get_default(); @@ -321,6 +440,7 @@ public class IconSelectWindow : Gtk.Dialog { } try { + // create a new entry for the queue var new_entry = new ListEntry(); new_entry.name = icon; new_entry.context = icon_context; @@ -337,8 +457,10 @@ public class IconSelectWindow : Gtk.Dialog { } } + // finished loading this.loading = false; + // hide the spinner if (spinner != null) spinner.visible = this.loading; diff --git a/src/gui/pieList.vala b/src/gui/pieList.vala index df6135a..46970d5 100644 --- a/src/gui/pieList.vala +++ b/src/gui/pieList.vala @@ -34,7 +34,7 @@ class PieList : Gtk.TreeView { private enum DataPos {IS_QUICKACTION, ICON, NAME, TYPE_ID, ACTION_TYPE, ICON_PIXBUF, FONT_WEIGHT, ICON_NAME_EDITABLE, QUICKACTION_VISIBLE, QUICKACTION_ACTIVATABLE, TYPE_VISIBLE, GROUP_VISIBLE, APP_VISIBLE, KEY_VISIBLE, PIE_VISIBLE, - URI_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP, + URI_VISIBLE, TRIGGER_VISIBLE, DISPLAY_COMMAND_GROUP, DISPLAY_COMMAND_APP, DISPLAY_COMMAND_KEY, DISPLAY_COMMAND_PIE, DISPLAY_COMMAND_URI, REAL_COMMAND_GROUP, REAL_COMMAND_PIE, REAL_COMMAND_KEY} @@ -91,7 +91,7 @@ class PieList : Gtk.TreeView { ActionPos.ICON_NAME_EDITABLE, false); // main data model - this.data = new Gtk.TreeStore(24, typeof(bool), // is quickaction + this.data = new Gtk.TreeStore(25, typeof(bool), // is quickaction typeof(string), // icon typeof(string), // name typeof(string), // slice: type label, pie: "ID: %id" @@ -110,6 +110,7 @@ class PieList : Gtk.TreeView { typeof(bool), // key renderer visible typeof(bool), // pie renderer visible typeof(bool), // uri renderer visible + typeof(bool), // trigger renderer visible typeof(string), // display command group typeof(string), // display command app @@ -198,6 +199,28 @@ class PieList : Gtk.TreeView { var command_column = new Gtk.TreeViewColumn(); command_column.title = _("Command"); command_column.resizable = true; + command_column.expand = true; + + // trigger + var command_renderer_trigger = new CellRendererTrigger(); + command_renderer_trigger.editable = true; + command_renderer_trigger.ellipsize = Pango.EllipsizeMode.END; + + command_renderer_trigger.on_select.connect((path, trigger) => { + Gtk.TreeIter data_iter; + this.data.get_iter_from_string(out data_iter, path); + + this.data.set(data_iter, DataPos.DISPLAY_COMMAND_KEY, trigger.label_with_specials); + this.data.set(data_iter, DataPos.REAL_COMMAND_KEY, trigger.name); + + this.update_pie(data_iter); + }); + + command_column.pack_end(command_renderer_trigger, true); + command_column.add_attribute(command_renderer_trigger, "weight", DataPos.FONT_WEIGHT); + command_column.add_attribute(command_renderer_trigger, "markup", DataPos.DISPLAY_COMMAND_KEY); + command_column.add_attribute(command_renderer_trigger, "visible", DataPos.TRIGGER_VISIBLE); + command_column.add_attribute(command_renderer_trigger, "trigger", DataPos.REAL_COMMAND_KEY); // slice group var command_renderer_group = new Gtk.CellRendererCombo(); @@ -342,6 +365,7 @@ class PieList : Gtk.TreeView { var type_column = new Gtk.TreeViewColumn(); type_column.title = _("Pie-ID / Action type"); type_column.resizable = true; + type_column.expand = false; var type_render = new Gtk.CellRendererCombo(); type_render.editable = true; @@ -592,7 +616,7 @@ class PieList : Gtk.TreeView { // adds a new, empty pie to the list private void add_empty_pie() { - var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", ""); + var new_one = PieManager.create_persistent_pie(_("New Pie"), "application-default-icon", null); Gtk.TreeIter last; this.pies.append(out last); this.pies.set(last, 0, new_one.name, 1, new_one.id); @@ -612,9 +636,10 @@ class PieList : Gtk.TreeView { DataPos.TYPE_VISIBLE, false, DataPos.GROUP_VISIBLE, false, DataPos.APP_VISIBLE, false, - DataPos.KEY_VISIBLE, true, + DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, + DataPos.TRIGGER_VISIBLE, true, DataPos.DISPLAY_COMMAND_GROUP, "", DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(new_one.id), @@ -672,6 +697,7 @@ class PieList : Gtk.TreeView { DataPos.QUICKACTION_ACTIVATABLE, true, DataPos.TYPE_VISIBLE, true, DataPos.GROUP_VISIBLE, false, + DataPos.TRIGGER_VISIBLE, false, DataPos.APP_VISIBLE, action is AppAction, DataPos.KEY_VISIBLE, action is KeyAction, DataPos.PIE_VISIBLE, action is PieAction, @@ -791,9 +817,10 @@ class PieList : Gtk.TreeView { DataPos.TYPE_VISIBLE, false, DataPos.GROUP_VISIBLE, false, DataPos.APP_VISIBLE, false, - DataPos.KEY_VISIBLE, true, + DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, + DataPos.TRIGGER_VISIBLE, true, DataPos.DISPLAY_COMMAND_GROUP, "", DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, PieManager.get_accelerator_label_of(pie.id), @@ -834,6 +861,7 @@ class PieList : Gtk.TreeView { DataPos.KEY_VISIBLE, false, DataPos.PIE_VISIBLE, false, DataPos.URI_VISIBLE, false, + DataPos.TRIGGER_VISIBLE, false, DataPos.DISPLAY_COMMAND_GROUP, GroupRegistry.names[group.get_type()], DataPos.DISPLAY_COMMAND_APP, "", DataPos.DISPLAY_COMMAND_KEY, _("Not bound"), @@ -888,7 +916,7 @@ class PieList : Gtk.TreeView { }); // create new pie - var new_pie = PieManager.create_persistent_pie(name, icon, hotkey, id); + var new_pie = PieManager.create_persistent_pie(name, icon, new Trigger.from_string(hotkey), id); // add actions accordingly if (this.data.iter_has_child(pie)) { diff --git a/src/gui/preferences.vala b/src/gui/preferences.vala index f43fd4a..9444fac 100644 --- a/src/gui/preferences.vala +++ b/src/gui/preferences.vala @@ -82,13 +82,6 @@ public class Preferences : Gtk.Window { open_at_mouse.toggled.connect(open_at_mouse_toggled); behavior_vbox.pack_start(open_at_mouse, false); - // Click to activate - var click_to_activate = new Gtk.CheckButton.with_label (_("Turbo mode")); - click_to_activate.tooltip_text = _("If checked, the pie closes when its keystroke is released. The currently hovered slice gets executed. This allows very fast selection but disables keyboard navigation."); - click_to_activate.active = Config.global.turbo_mode; - click_to_activate.toggled.connect(turbo_mode_toggled); - behavior_vbox.pack_start(click_to_activate, false); - // Slider var slider_hbox = new Gtk.HBox (false, 6); behavior_vbox.pack_start(slider_hbox); @@ -187,6 +180,7 @@ public class Preferences : Gtk.Window { _("You may drag'n'drop URLs and bookmarks from your internet browser to the list above."), _("Bugs can be reported at %s!").printf("<a href='https://github.com/Simmesimme/Gnome-Pie'>Github</a>"), _("It's possible to drag'n'drop files and folders from your file browser to the list above."), + _("It's recommended to keep your Pies small (at most 6-8 Slices). Else they will become hard to navigate."), _("In order to create a launcher for a Pie, drag the Pie from the list to your desktop!") }); this.show.connect(info_label.start_slide_show); @@ -251,18 +245,21 @@ public class Preferences : Gtk.Window { // close button var bbox = new Gtk.HButtonBox (); bbox.set_layout (Gtk.ButtonBoxStyle.END); - var close_button = new Gtk.Button.from_stock (Gtk.Stock.CLOSE); + var close_button = new Gtk.Button.from_stock(Gtk.Stock.CLOSE); close_button.clicked.connect (() => { hide(); - // save settings on close - Config.global.save(); - Pies.save(); }); bbox.pack_start (close_button); main_vbox.pack_start(bbox, false); main_vbox.show_all(); + + this.hide.connect(() => { + // save settings on close + Config.global.save(); + Pies.save(); + }); } ///////////////////////////////////////////////////////////////////// @@ -323,16 +320,6 @@ public class Preferences : Gtk.Window { var check = check_box as Gtk.CheckButton; Config.global.open_at_mouse = check.active; } - - ///////////////////////////////////////////////////////////////////// - /// Toggles whether the user has to click with the mouse in order to - /// activate a slice. - ///////////////////////////////////////////////////////////////////// - - private void turbo_mode_toggled(Gtk.ToggleButton check_box) { - var check = check_box as Gtk.CheckButton; - Config.global.turbo_mode = check.active; - } } } diff --git a/src/gui/triggerSelectWindow.vala b/src/gui/triggerSelectWindow.vala new file mode 100644 index 0000000..e003a84 --- /dev/null +++ b/src/gui/triggerSelectWindow.vala @@ -0,0 +1,257 @@ +/* +Copyright (c) 2011 by Simon Schneegans + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace GnomePie { + +///////////////////////////////////////////////////////////////////////// +/// This window allows the selection of a hotkey. It is returned in form +/// of a Trigger. Therefore it can be either a keyboard driven hotkey or +/// a mouse based hotkey. +///////////////////////////////////////////////////////////////////////// + +public class TriggerSelectWindow : Gtk.Dialog { + + ///////////////////////////////////////////////////////////////////// + /// This signal is emitted when the user selects a new hot key. + ///////////////////////////////////////////////////////////////////// + + public signal void on_select(Trigger trigger); + + ///////////////////////////////////////////////////////////////////// + /// Some private members which are needed by other methods. + ///////////////////////////////////////////////////////////////////// + + private Gtk.CheckButton turbo; + private Gtk.CheckButton delayed; + private Gtk.Label preview; + + ///////////////////////////////////////////////////////////////////// + /// The currently configured trigger. + ///////////////////////////////////////////////////////////////////// + + private Trigger trigger = null; + + ///////////////////////////////////////////////////////////////////// + /// The trigger which was active when this window was opened. It is + /// stored in order to check whether anything has changed when the + /// user clicks on OK. + ///////////////////////////////////////////////////////////////////// + + private Trigger original_trigger = null; + + ///////////////////////////////////////////////////////////////////// + /// These modifiers are ignored. + ///////////////////////////////////////////////////////////////////// + + private Gdk.ModifierType lock_modifiers = Gdk.ModifierType.MOD2_MASK + |Gdk.ModifierType.LOCK_MASK + |Gdk.ModifierType.MOD5_MASK; + + ///////////////////////////////////////////////////////////////////// + /// C'tor, constructs a new TriggerSelectWindow. + ///////////////////////////////////////////////////////////////////// + + public TriggerSelectWindow() { + this.title = _("Define an open-command"); + this.resizable = false; + this.delete_event.connect(hide_on_delete); + this.key_press_event.connect(on_key_press); + this.button_press_event.connect(on_button_press); + + this.show.connect_after(() => { + FocusGrabber.grab(this); + }); + + this.hide.connect(() => { + FocusGrabber.ungrab(this); + }); + + var container = new Gtk.VBox(false, 6); + container.set_border_width(6); + + // click area + var click_frame = new Gtk.Frame(_("Click here if you want to bind a mouse button!")); + + var click_box = new Gtk.EventBox(); + click_box.height_request = 100; + click_box.button_press_event.connect(on_area_clicked); + + this.preview = new Gtk.Label(null); + + click_box.add(this.preview); + + click_frame.add(click_box); + + container.pack_start(click_frame, false); + + // turbo checkbox + this.turbo = new Gtk.CheckButton.with_label (_("Turbo mode")); + this.turbo.tooltip_text = _("If checked, the Pie will close when you " + + "release the chosen hot key."); + this.turbo.active = false; + this.turbo.toggled.connect(() => { + if (this.trigger != null) + this.update_trigger(new Trigger.from_values( + this.trigger.key_sym, this.trigger.modifiers, + this.trigger.with_mouse, this.turbo.active, + this.delayed.active)); + }); + + container.pack_start(turbo, false); + + // delayed checkbox + this.delayed = new Gtk.CheckButton.with_label (_("Long press for activation")); + this.delayed.tooltip_text = _("If checked, the Pie will only open if you " + + "press this hot key a bit longer."); + this.delayed.active = false; + this.delayed.toggled.connect(() => { + if (this.trigger != null) + this.update_trigger(new Trigger.from_values( + this.trigger.key_sym, this.trigger.modifiers, + this.trigger.with_mouse, this.turbo.active, + this.delayed.active)); + }); + + container.pack_start(delayed, false); + + container.show_all(); + + this.vbox.pack_start(container, true, true); + + this.add_button(Gtk.Stock.CANCEL, 1); + this.add_button(Gtk.Stock.OK, 0); + + // select a new trigger on OK, hide on CANCEL + this.response.connect((id) => { + if (id == 1) + this.hide(); + else if (id == 0) { + var assigned_id = PieManager.get_assigned_id(this.trigger); + + if (this.trigger == this.original_trigger) { + // nothing did change + this.hide(); + } else if (this.trigger.key_code == this.original_trigger.key_code + && this.trigger.modifiers == this.original_trigger.modifiers + && this.trigger.with_mouse == this.original_trigger.with_mouse) { + // only turbo and/or delayed mode changed, no need to check for double assignment + this.on_select(this.trigger); + this.hide(); + } else if (assigned_id != "") { + // it's already assigned + var error = _("This hotkey is already assigned to the pie \"%s\"! \n\nPlease select " + + "another one or cancel your selection.").printf(PieManager.get_name_of(assigned_id)); + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), + Gtk.DialogFlags.MODAL, + Gtk.MessageType.ERROR, + Gtk.ButtonsType.CANCEL, + error); + dialog.run(); + dialog.destroy(); + } else { + // a unused hot key has been chosen, great! + this.on_select(this.trigger); + this.hide(); + } + } + }); + } + + ///////////////////////////////////////////////////////////////////// + /// Used to set the currently selected trigger on opening. + ///////////////////////////////////////////////////////////////////// + + public void set_trigger(Trigger trigger) { + this.turbo.active = trigger.turbo; + this.delayed.active = trigger.delayed; + this.original_trigger = trigger; + this.update_trigger(trigger); + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the user clicks in the click area. + ///////////////////////////////////////////////////////////////////// + + private bool on_area_clicked(Gdk.EventButton event) { + Gdk.ModifierType state = event.state & ~ this.lock_modifiers; + + var new_trigger = new Trigger.from_values((int)event.button, state, true, + this.turbo.active, this.delayed.active); + if (new_trigger.key_code == 1) { + var dialog = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel(), Gtk.DialogFlags.MODAL, + Gtk.MessageType.WARNING, + Gtk.ButtonsType.YES_NO, + _("It possible to make your system unusable if " + + "you bind a Pie to your left mouse button. Do " + + "you really want to do this?")); + + dialog.response.connect((response) => { + if (response == Gtk.ResponseType.YES) { + this.update_trigger(new_trigger); + } + }); + + dialog.run(); + dialog.destroy(); + } else { + this.update_trigger(new_trigger); + } + + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the user presses a keyboard key. + ///////////////////////////////////////////////////////////////////// + + private bool on_key_press(Gdk.EventKey event) { + if (Gdk.keyval_name(event.keyval) == "Escape") { + this.hide(); + } else if (Gdk.keyval_name(event.keyval) == "BackSpace") { + this.update_trigger(new Trigger()); + } else if (event.is_modifier == 0) { + Gdk.ModifierType state = event.state & ~ this.lock_modifiers; + this.update_trigger(new Trigger.from_values((int)event.keyval, state, false, + this.turbo.active, this.delayed.active)); + } + + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// Called when the user presses a mouse button. + ///////////////////////////////////////////////////////////////////// + + private bool on_button_press(Gdk.EventButton event) { + int width = 0, height = 0; + this.window.get_geometry(null, null, out width, out height, null); + if (event.x < 0 || event.x > width || event.y < 0 || event.y > height) + this.hide(); + return true; + } + + ///////////////////////////////////////////////////////////////////// + /// Helper method to update the content of the trigger preview label. + ///////////////////////////////////////////////////////////////////// + + private void update_trigger(Trigger new_trigger) { + this.trigger = new_trigger; + this.preview.set_markup("<big><b>" + this.trigger.label + "</b></big>"); + } +} + +} |