1
#-------------------------------------------------------------------------------
3
# Copyright (c) 2005, Enthought, Inc.
6
# This software is provided without warranty under the terms of the BSD
7
# license included in enthought/LICENSE.txt and may be redistributed only
8
# under the conditions described in the aforementioned license. The license
9
# is also available online at http://www.enthought.com/licenses/BSD.txt
11
# Thanks for using Enthought open source!
13
# Author: David C. Morrill
16
#-------------------------------------------------------------------------------
18
""" Defines KeyBinding and KeyBindings classes, which manage the mapping of
19
keystroke events into method calls on controller objects that are supplied
23
#-------------------------------------------------------------------------------
25
#-------------------------------------------------------------------------------
27
from __future__ import absolute_import
29
from traits.api import (Any, Event, HasPrivateTraits, HasStrictTraits, Instance, List,
30
Property, Str, cached_property, on_trait_change)
32
from .api import HGroup, Item, KeyBindingEditor, ListEditor, View, toolkit
34
from traits.trait_base import SequenceTypes
36
#-------------------------------------------------------------------------------
37
# Key binding trait definition:
38
#-------------------------------------------------------------------------------
40
# Trait definition for key bindings
41
Binding = Str( event = 'binding', editor = KeyBindingEditor() )
43
#-------------------------------------------------------------------------------
45
#-------------------------------------------------------------------------------
47
class KeyBinding ( HasStrictTraits ):
48
""" Binds one or two keystrokes to a method.
51
#---------------------------------------------------------------------------
53
#---------------------------------------------------------------------------
61
# Description of what application function the method performs
64
# Name of controller method the key is bound to
67
# KeyBindings object that "owns" the KeyBinding
68
owner = Instance( 'KeyBindings' )
70
#---------------------------------------------------------------------------
71
# Traits view definitions:
72
#---------------------------------------------------------------------------
78
Item( 'description', style = 'readonly' ),
83
#---------------------------------------------------------------------------
84
# Handles a binding trait being changed:
85
#---------------------------------------------------------------------------
87
def _binding_changed ( self ):
88
if self.owner is not None:
89
self.owner.binding_modified = self
91
#-------------------------------------------------------------------------------
92
# 'KeyBindings' class:
93
#-------------------------------------------------------------------------------
95
class KeyBindings ( HasPrivateTraits ):
96
""" A set of key bindings.
99
#---------------------------------------------------------------------------
101
#---------------------------------------------------------------------------
103
# Set of defined key bindings (redefined dynamically)
104
bindings = List( KeyBinding )
106
# Optional prefix to add to each method name
109
# Optional suffix to add to each method name
112
#-- Private Traits ---------------------------------------------------------
114
# The (optional) list of controllers associated with this KeyBindings
115
# object. The controllers may also be provided with the 'do' method:
116
controllers = List( transient = True )
118
# The 'parent' KeyBindings object of this one (if any):
119
parent = Instance( 'KeyBindings', transient = True )
121
# The root of the KeyBindings tree this object is part of:
122
root = Property( depends_on = 'parent' )
124
# The child KeyBindings of this object (if any):
125
children = List( transient = True )
127
# Event fired when one of the contained KeyBinding objects is changed
128
binding_modified = Event( KeyBinding )
130
# Control that currently has the focus (if any)
131
focus_owner = Any( transient = True )
133
#---------------------------------------------------------------------------
134
# Traits view definitions:
135
#---------------------------------------------------------------------------
137
traits_view = View( [ Item( 'bindings',
140
editor = ListEditor( style = 'custom' ) ),
141
'|{Click on an entry field, then press the key to '
142
'assign. Double-click a field to clear it.}<>' ],
143
title = 'Update Key Bindings',
149
#---------------------------------------------------------------------------
150
# Initializes the object:
151
#---------------------------------------------------------------------------
153
def __init__ ( self, *bindings, **traits ):
154
super( KeyBindings, self ).__init__( **traits )
156
if (len( bindings ) == 1) and isinstance( bindings[0], SequenceTypes ):
157
bindings = bindings[0]
160
self.add_trait( 'bindings', List( KeyBinding, minlen = n,
163
self.bindings = [ binding.set( owner = self ) for binding in bindings ]
165
#---------------------------------------------------------------------------
166
# Processes a keyboard event:
167
#---------------------------------------------------------------------------
169
def do ( self, event, controllers = [], *args, **kw ):
170
""" Processes a keyboard event.
172
if isinstance( controllers, dict ):
173
controllers = controllers.values()
174
elif not isinstance( controllers, SequenceTypes ):
175
controllers = [ controllers ]
177
controllers = list( controllers )
179
return self._do( toolkit().key_event_to_name( event ),
180
controllers, args, kw.get( 'recursive', False ) )
182
#---------------------------------------------------------------------------
183
# Merges another set of key bindings into this set:
184
#---------------------------------------------------------------------------
186
def merge ( self, key_bindings ):
187
""" Merges another set of key bindings into this set.
190
for binding in self.bindings:
191
binding_dic[ binding.method_name ] = binding
193
for binding in key_bindings.bindings:
194
binding2 = binding_dic.get( binding.method_name )
195
if binding2 is not None:
196
binding2.binding1 = binding.binding1
197
binding2.binding2 = binding.binding2
199
#---------------------------------------------------------------------------
200
# Returns a clone of the KeyBindings object:
201
#---------------------------------------------------------------------------
203
def clone ( self, **traits ):
204
""" Returns a clone of the KeyBindings object.
206
return self.__class__( *self.bindings, **traits ).set(
207
**self.get( 'prefix', 'suffix' ) )
209
#---------------------------------------------------------------------------
210
# Dispose of the object:
211
#---------------------------------------------------------------------------
213
def dispose ( self ):
214
""" Dispose of the object.
216
if self.parent is not None:
217
self.parent.children.remove( self )
223
self.parent = self._root = self.focus_owner = None
225
#---------------------------------------------------------------------------
226
# Edits a possibly hierarchical set of KeyBindings:
227
#---------------------------------------------------------------------------
230
""" Edits a possibly hierarchical set of KeyBindings.
232
bindings = list( set( self.root._get_bindings( [] ) ) )
233
bindings.sort( lambda l, r:
234
cmp( '%s%02d' % ( l.binding1[-1:], len( l.binding1 ) ),
235
'%s%02d' % ( r.binding1[-1:], len( r.binding1 ) ) ) )
236
KeyBindings( bindings ).edit_traits()
238
#---------------------------------------------------------------------------
239
# Returns the current binding for a specified key (if any):
240
#---------------------------------------------------------------------------
242
def key_binding_for ( self, binding, key_name ):
243
""" Returns the current binding for a specified key (if any).
246
for a_binding in self.bindings:
247
if ((a_binding is not binding) and
248
((key_name == a_binding.binding1) or
249
(key_name == a_binding.binding2))):
254
#-- Property Implementations -----------------------------------------------
257
def _get_root ( self ):
259
while root.parent is not None:
264
#-- Event Handlers ---------------------------------------------------------
266
def _binding_modified_changed ( self, binding ):
267
""" Handles a binding being changed.
269
binding1 = binding.binding1
270
binding2 = binding.binding2
271
for a_binding in self.bindings:
272
if binding is not a_binding:
273
if binding1 == a_binding.binding1:
274
a_binding.binding1 = ''
275
if binding1 == a_binding.binding2:
276
a_binding.binding2 = ''
277
if binding2 == a_binding.binding1:
278
a_binding.binding1 = ''
279
if binding2 == a_binding.binding2:
280
a_binding.binding2 = ''
282
def _focus_owner_changed ( self, old, new ):
283
""" Handles the focus owner being changed.
288
@on_trait_change( 'children[]' )
289
def _children_modified ( self, removed, added ):
290
""" Handles child KeyBindings being added to the object.
295
#-- object Method Overrides ------------------------------------------------
297
#---------------------------------------------------------------------------
298
# Restores the state of a previously pickled object:
299
#---------------------------------------------------------------------------
301
def __setstate__ ( self, state ):
302
""" Restores the state of a previously pickled object.
304
n = len( state[ 'bindings' ] )
305
self.add_trait( 'bindings', List( KeyBinding, minlen = n, maxlen = n ) )
306
self.__dict__.update( state )
307
self.bindings = self.bindings[:]
309
#-- Private Methods --------------------------------------------------------
311
def _get_bindings ( self, bindings ):
312
""" Returns all of the bindings of this object and all of its children.
314
bindings.extend( self.bindings )
315
for child in self.children:
316
child._get_bindings( bindings )
320
def _do ( self, key_name, controllers, args, recursive ):
321
""" Process the specified key for the specified set of controllers for
322
this KeyBindings object and all of its children.
324
# Search through our own bindings for a match:
325
for binding in self.bindings:
326
if (key_name == binding.binding1) or (key_name == binding.binding2):
327
method_name = '%s%s%s' % (
328
self.prefix, binding.method_name, self.suffix )
329
for controller in (controllers + self.controllers):
330
method = getattr( controller, method_name, None )
331
if method is not None:
332
result = method( *args )
333
if result is not False:
336
if binding.method_name == 'edit_bindings':
340
# If recursive, continue searching through a children's bindings:
342
for child in self.children:
343
if child._do( key_name, controllers, args, recursive ):
346
# Indicate no one processed the key: