Repository URL to install this package:
Version:
6.1.1 ▾
|
traitsui
/
key_bindings.py
|
---|
#-------------------------------------------------------------------------
#
# Copyright (c) 2005, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
#
# Author: David C. Morrill
# Date: 05/20/2005
#
#-------------------------------------------------------------------------
""" Defines KeyBinding and KeyBindings classes, which manage the mapping of
keystroke events into method calls on controller objects that are supplied
by the application.
"""
#-------------------------------------------------------------------------
# Imports:
#-------------------------------------------------------------------------
from __future__ import absolute_import
from traits.api import (
Any,
Event,
HasPrivateTraits,
HasStrictTraits,
Instance,
List,
Property,
Str,
cached_property,
on_trait_change)
from .api import HGroup, Item, KeyBindingEditor, ListEditor, View, toolkit
from traits.trait_base import SequenceTypes
#-------------------------------------------------------------------------
# Key binding trait definition:
#-------------------------------------------------------------------------
# Trait definition for key bindings
Binding = Str(event='binding', editor=KeyBindingEditor())
#-------------------------------------------------------------------------
# 'KeyBinding' class:
#-------------------------------------------------------------------------
class KeyBinding(HasStrictTraits):
""" Binds one or two keystrokes to a method.
"""
#-------------------------------------------------------------------------
# Trait definitions:
#-------------------------------------------------------------------------
# First key binding
binding1 = Binding
# Second key binding
binding2 = Binding
# Description of what application function the method performs
description = Str
# Name of controller method the key is bound to
method_name = Str
# KeyBindings object that "owns" the KeyBinding
owner = Instance('KeyBindings')
#-------------------------------------------------------------------------
# Traits view definitions:
#-------------------------------------------------------------------------
traits_view = View(
HGroup(
Item('binding1'),
Item('binding2'),
Item('description', style='readonly'),
show_labels=False
)
)
#-------------------------------------------------------------------------
# Handles a binding trait being changed:
#-------------------------------------------------------------------------
def _binding_changed(self):
if self.owner is not None:
self.owner.binding_modified = self
#-------------------------------------------------------------------------
# 'KeyBindings' class:
#-------------------------------------------------------------------------
class KeyBindings(HasPrivateTraits):
""" A set of key bindings.
"""
#-------------------------------------------------------------------------
# Trait definitions:
#-------------------------------------------------------------------------
# Set of defined key bindings (redefined dynamically)
bindings = List(KeyBinding)
# Optional prefix to add to each method name
prefix = Str
# Optional suffix to add to each method name
suffix = Str
#-- Private Traits -------------------------------------------------------
# The (optional) list of controllers associated with this KeyBindings
# object. The controllers may also be provided with the 'do' method:
controllers = List(transient=True)
# The 'parent' KeyBindings object of this one (if any):
parent = Instance('KeyBindings', transient=True)
# The root of the KeyBindings tree this object is part of:
root = Property(depends_on='parent')
# The child KeyBindings of this object (if any):
children = List(transient=True)
# Event fired when one of the contained KeyBinding objects is changed
binding_modified = Event(KeyBinding)
# Control that currently has the focus (if any)
focus_owner = Any(transient=True)
#-------------------------------------------------------------------------
# Traits view definitions:
#-------------------------------------------------------------------------
traits_view = View([Item('bindings',
style='custom',
show_label=False,
editor=ListEditor(style='custom')),
'|{Click on an entry field, then press the key to '
'assign. Double-click a field to clear it.}<>'],
title='Update Key Bindings',
kind='livemodal',
resizable=True,
width=0.4,
height=0.4)
#-------------------------------------------------------------------------
# Initializes the object:
#-------------------------------------------------------------------------
def __init__(self, *bindings, **traits):
super(KeyBindings, self).__init__(**traits)
if (len(bindings) == 1) and isinstance(bindings[0], SequenceTypes):
bindings = bindings[0]
n = len(bindings)
self.add_trait('bindings', List(KeyBinding, minlen=n,
maxlen=n,
mode='list'))
self.bindings = [binding.trait_set(owner=self) for binding in bindings]
#-------------------------------------------------------------------------
# Processes a keyboard event:
#-------------------------------------------------------------------------
def do(self, event, controllers=[], *args, **kw):
""" Processes a keyboard event.
"""
if isinstance(controllers, dict):
controllers = list(controllers.values())
elif not isinstance(controllers, SequenceTypes):
controllers = [controllers]
else:
controllers = list(controllers)
return self._do(toolkit().key_event_to_name(event),
controllers, args, kw.get('recursive', False))
#-------------------------------------------------------------------------
# Merges another set of key bindings into this set:
#-------------------------------------------------------------------------
def merge(self, key_bindings):
""" Merges another set of key bindings into this set.
"""
binding_dic = {}
for binding in self.bindings:
binding_dic[binding.method_name] = binding
for binding in key_bindings.bindings:
binding2 = binding_dic.get(binding.method_name)
if binding2 is not None:
binding2.binding1 = binding.binding1
binding2.binding2 = binding.binding2
#-------------------------------------------------------------------------
# Returns a clone of the KeyBindings object:
#-------------------------------------------------------------------------
def clone(self, **traits):
""" Returns a clone of the KeyBindings object.
"""
return self.__class__(*self.bindings, **traits).trait_set(
**self.get('prefix', 'suffix'))
#-------------------------------------------------------------------------
# Dispose of the object:
#-------------------------------------------------------------------------
def dispose(self):
""" Dispose of the object.
"""
if self.parent is not None:
self.parent.children.remove(self)
del self.controllers
del self.children
del self.bindings
self.parent = self._root = self.focus_owner = None
#-------------------------------------------------------------------------
# Edits a possibly hierarchical set of KeyBindings:
#-------------------------------------------------------------------------
def edit(self):
""" Edits a possibly hierarchical set of KeyBindings.
"""
bindings = list(set(self.root._get_bindings([])))
bindings.sort(key=lambda x: '%s%02d' % (x.binding1[-1:], x.binding1))
KeyBindings(bindings).edit_traits()
#-------------------------------------------------------------------------
# Returns the current binding for a specified key (if any):
#-------------------------------------------------------------------------
def key_binding_for(self, binding, key_name):
""" Returns the current binding for a specified key (if any).
"""
if key_name != '':
for a_binding in self.bindings:
if ((a_binding is not binding) and
((key_name == a_binding.binding1) or
(key_name == a_binding.binding2))):
return a_binding
return None
#-- Property Implementations ---------------------------------------------
@cached_property
def _get_root(self):
root = self
while root.parent is not None:
root = root.parent
return root
#-- Event Handlers -------------------------------------------------------
def _binding_modified_changed(self, binding):
""" Handles a binding being changed.
"""
binding1 = binding.binding1
binding2 = binding.binding2
for a_binding in self.bindings:
if binding is not a_binding:
if binding1 == a_binding.binding1:
a_binding.binding1 = ''
if binding1 == a_binding.binding2:
a_binding.binding2 = ''
if binding2 == a_binding.binding1:
a_binding.binding1 = ''
if binding2 == a_binding.binding2:
a_binding.binding2 = ''
def _focus_owner_changed(self, old, new):
""" Handles the focus owner being changed.
"""
if old is not None:
old.border_size = 0
@on_trait_change('children[]')
def _children_modified(self, removed, added):
""" Handles child KeyBindings being added to the object.
"""
for item in added:
item.parent = self
#-- object Method Overrides ----------------------------------------------
#-------------------------------------------------------------------------
# Restores the state of a previously pickled object:
#-------------------------------------------------------------------------
def __setstate__(self, state):
""" Restores the state of a previously pickled object.
"""
n = len(state['bindings'])
self.add_trait('bindings', List(KeyBinding, minlen=n, maxlen=n))
self.__dict__.update(state)
self.bindings = self.bindings[:]
#-- Private Methods ------------------------------------------------------
def _get_bindings(self, bindings):
""" Returns all of the bindings of this object and all of its children.
"""
bindings.extend(self.bindings)
for child in self.children:
child._get_bindings(bindings)
return bindings
def _do(self, key_name, controllers, args, recursive):
""" Process the specified key for the specified set of controllers for
this KeyBindings object and all of its children.
"""
# Search through our own bindings for a match:
for binding in self.bindings:
if (key_name == binding.binding1) or (
key_name == binding.binding2):
method_name = '%s%s%s' % (
self.prefix, binding.method_name, self.suffix)
for controller in (controllers + self.controllers):
method = getattr(controller, method_name, None)
if method is not None:
result = method(*args)
if result is not False:
return True
if binding.method_name == 'edit_bindings':
self.edit()
return True
# If recursive, continue searching through a children's bindings:
if recursive:
for child in self.children:
if child._do(key_name, controllers, args, recursive):
return True
# Indicate no one processed the key:
return False