# -*- coding: utf-8 -*- # Copyright © 2005 Lateef Alabi-Oki # # This file is part of Scribes. # # Scribes 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 2 of the License, or # (at your option) any later version. # # Scribes 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 Scribes; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ This module exposes a class that manages triggers and accelerators associated with them. @author: Lateef Alabi-Oki @organization: The Scribes Project @copyright: Copyright © 2005 Lateef Alabi-Oki @license: GNU GPLv2 or Later @contact: mystilleef@gmail.com """ from gobject import GObject, SIGNAL_RUN_LAST, TYPE_NONE class TriggerManager(GObject): """ The trigger manager maps accelerators and strings to user operations or actions. This class creates an object that manages triggers and accelerators associated with them. The class links triggers to actions and provides quicker interface to add multiple triggers. It also provides an interface to activate triggers. """ __gsignals__ = { "added-trigger": (SIGNAL_RUN_LAST, TYPE_NONE, ()), } def __init__(self, editor): """ Initialize the manager object. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ GObject.__init__(self) self.__init_attributes(editor) from gobject import idle_add, PRIORITY_LOW idle_add(self.__precompile_methods, priority=PRIORITY_LOW) self.connect("added-trigger", self.__mangager_added_trigger_cb) self.__editor.window.connect("key-press-event", self.__manager_key_press_event_cb) self.__editor.connect("show-bar", self.__show_bar_cb) self.__editor.connect("hide-bar", self.__hide_bar_cb) self.__editor.emit("initialized-trigger-manager") def __init_attributes(self, editor): """ Initialize the manager's attributes @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param editor: Reference to the text editor. @type editor: An Editor object. """ self.__editor = editor self.__bar_is_visible = False # A window to associate with triggers and accelerators. self.window = editor.window # A dictionary containing accelerators for keys and triggers for values. self.trigger_dictionary = {} # List of trigger objects. self.triggers = [] # Supported Modifiers. self.accelerator_modifiers = ("Control", "control", "Ctrl", "ctrl", "Shift", "shift", "Alt", "alt") # A list of supported accelerators. self.accelerator_list = [] # FIXME: Remove as soon as possible. self.accelerator_keys = [] # List of accelerators to be associated with a window. # FIXME: Not used yet. self.accelerator_without_modifiers = [] self.accelerator_with_ctrl_modifier = [] self.accelerator_with_alt_modifier = [] self.accelerator_with_ctrl_alt_modifier = [] return ################################################################################ # # Public API methods # ################################################################################ def add_trigger(self, trigger, accelerator=None): """ Add a new trigger object to be managed by the trigger manager. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param trigger: An object that triggers an action. @type trigger: A Trigger object. """ self.__editor.response() self.triggers.append(trigger) if accelerator: accel = self.__format_accelerator_for_manager(accelerator) if accel in self.trigger_dictionary.keys(): print accelerator, accel print "Accelerator already exists, please use a unique accelerator. \ TriggerManager.list_accelerators() will display a list of accelerators already \ in use." else: self.trigger_dictionary[accel] = trigger, accelerator self.accelerator_list.append(accelerator) self.emit("added-trigger") self.__editor.response() return def remove_trigger(self, trigger): self.__editor.response() has_accelerator = False trigger_accel_list = self.trigger_dictionary.values() for trigger_accel in trigger_accel_list: if trigger in trigger_accel: has_accelerator = True break if has_accelerator: for accel, trigger_accel in self.trigger_dictionary.items(): if trigger_accel[0] == trigger: self.accelerator_list.remove(trigger_accel[1]) break del self.trigger_dictionary[accel] if trigger in self.triggers: self.triggers.remove(trigger) trigger.destroy() from utils import delete_attributes delete_attributes(trigger) del trigger trigger = None self.__editor.response() return def remove_triggers(self, triggers): if not triggers: return map(self.remove_trigger, triggers) return def add_triggers(self, entries, data=None): """ Add a group of triggers to be managed by the trigger manager. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param entries: A list representing trigger objects @type entries: A List object. """ from trigger import Trigger for trigger_name, accelerator, callback in entries: trigger = Trigger(trigger_name) self.add_trigger(trigger, accelerator) trigger.connect("activate", callback, data) return def trigger(self, name): """ Activate a trigger via its name. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param name: A name associated with a trigger. @type name: A String object. """ for trigger in self.triggers: if trigger.name == name: trigger.activate() break return ################################################################################ # # Accelerator Manipulations # ################################################################################ def __format_accelerator_for_manager(self, accelerator): """ Restructure accelerator in a form that can be used for quick analysis. This function converts the accelerator provided as an argument into a form that aids quick analysis and parsing by the TriggerManager. The accelerator provided as an argument is split into a list containing accelerator modifiers and keys. To ensure that all accelerators managed by the TriggerManager are unique, the list is sorted and an internal string value for modifiers are used. For instance, "Control", "control", and "Ctrl" are converted to "ctrl" by this function. After the accelerator modifiers have been standardized for internal use and the list sorted, the list is converted into a tuple object. This is necessary so it can serve as a key for dictionary objects and also to prevent mutability. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param accelerator: An accelerator to be associated with a Trigger. @type accelerator: A String object. @return: An object representing an accelerator for internal use. @rtype: A Tuple object. """ accel_list = [accel.strip() for accel in accelerator.split("-")] accel = [] for item in accel_list: if item in("Control", "control", "Ctrl", "ctrl"): accel.append("ctrl") elif item in ("Alt", "alt"): accel.append("alt") elif item in ("Shift", "shift"): accel.append("shift") else: accel.append(item) # Remove duplicate elements accel = set(accel) accel = list(accel) accel.sort() return tuple(accel) def __activate_accelerator(self, accelerator): """ Activate a trigger associated with an accelerator. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param accelerator: A shortcut key associated with trigger. @type accelerator: A String object. @return: True if a trigger was activated. @rtype: A Boolean object. """ value = False accel = self.__format_accelerator_for_manager(accelerator) if accel: if accel in self.trigger_dictionary.keys(): self.trigger_dictionary[accel][0].activate() value = True return value def __generate_accelerator_keys(self, accelerator): """ Generate accelerator keys. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param accelerator: A shortcut key associated with a trigger. @type accelerator: A String object. """ accel_list = [accel.strip() for accel in accelerator.split("-")] for item in accel_list: if not item in self.accelerator_modifiers: if not item in self.accelerator_keys: self.accelerator_keys.append(item) return False def __precompile_methods(self): try: from psyco import bind bind(self.__generate_accelerator_keys) bind(self.__activate_accelerator) bind(self.__format_accelerator_for_manager) bind(self.__manager_key_press_event_cb) bind(self.__mangager_added_trigger_cb) except ImportError: pass return False ################################################################################ # # Signal and Event Handlers # ################################################################################ def __manager_key_press_event_cb(self, window, event): """ Handles callback when the "key-press-event" is emitted. This function monitors accelerators associated with trigger objects. A list of accelerators for trigger objects is stored by the TriggerManager. When a key-press-event matches an accelerator in the the TriggerManager's accelerator list, the trigger object associated with the accelerator is activated. Trigger objects must have unique accelerators. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param window: A window associated with accelerators and triggers. @type window: A gtk.Window object. @param event: A key press event @type event: An gtk.gdk.Event object. @return: True to block and handle the "key-press-event" event. @rtype: An Boolean object. """ if self.__bar_is_visible: return False from gtk.gdk import CONTROL_MASK, MOD1_MASK, SHIFT_MASK, keyval_name # Control and Shift key are pressed. if event.state & CONTROL_MASK and event.state & SHIFT_MASK: if keyval_name(event.keyval) in self.accelerator_keys: accelerator = "ctrl - " + keyval_name(event.keyval) result = self.__activate_accelerator(accelerator) return result # Alt and Shift key are pressed. if event.state & SHIFT_MASK and event.state & MOD1_MASK: if keyval_name(event.keyval) in self.accelerator_keys: if keyval_name(event.keyval) in ("Delete", "Insert", "Home", "End", "PageUp", "PageDown", "Right", "Left", "Up", "Down", "F1", "F12", "F10", "Return"): accelerator = "alt - shift - " + keyval_name(event.keyval) else: accelerator = "alt - " + keyval_name(event.keyval) result = self.__activate_accelerator(accelerator) return result # Control and Alt key are pressed. if event.state & CONTROL_MASK and event.state & MOD1_MASK: if keyval_name(event.keyval) in self.accelerator_keys: accelerator = "ctrl - alt - " + keyval_name(event.keyval) result = self.__activate_accelerator(accelerator) return result # Control key is pressed. if event.state & CONTROL_MASK: if keyval_name(event.keyval) in self.accelerator_keys: accelerator = "ctrl - " + keyval_name(event.keyval) result = self.__activate_accelerator(accelerator) return result # Alt key is pressed. if event.state & MOD1_MASK: if keyval_name(event.keyval) in self.accelerator_keys: accelerator = "alt - " + keyval_name(event.keyval) result = self.__activate_accelerator(accelerator) return result # No modifiers. if keyval_name(event.keyval) in self.accelerator_keys: result = self.__activate_accelerator(keyval_name(event.keyval)) return result return False def __mangager_added_trigger_cb(self, triggermanager): """ Handles callback when "added-trigger" is emitted. @param self: Reference to the TriggerManager instance. @type self: A TriggerManager object. @param triggermanager: Reference to the TriggerManager instance. @type triggermanager: A TriggerManager object. """ from gobject import timeout_add for accelerator in self.accelerator_list: timeout_add(1, self.__generate_accelerator_keys, accelerator) return def __show_bar_cb(self, *args): self.__bar_is_visible = True return def __hide_bar_cb(self, *args): self.__bar_is_visible = False return