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 the tree editor for the wxPython user interface toolkit.
21
#-------------------------------------------------------------------------------
23
#-------------------------------------------------------------------------------
30
from pyface.wx.drag_and_drop import PythonDropSource, \
33
PythonDropSource = PythonDropTarget = None
35
from pyface.resource_manager \
36
import resource_manager
38
from pyface.image_list \
42
import HasStrictTraits, Any, Str, Event, TraitError
44
from traits.trait_base \
48
import View, TreeNode, ObjectTreeNode, MultiTreeNode, Image
50
# FIXME: ToolkitEditorFactory is a proxy class defined here just for backward
51
# compatibility. The class has been moved to the
52
# traitsui.editors.tree_editor file.
53
from traitsui.editors.tree_editor \
54
import ToolkitEditorFactory
59
from traitsui.tree_node \
60
import ITreeNodeAdapterBridge
62
from traitsui.tree_node \
63
import ITreeNodeAdapterBridge
66
import Menu, Action, Separator
71
from pyface.dock.api \
72
import DockWindow, DockSizer, DockSection, DockRegion, DockControl
81
import open_fbi, TraitsUIPanel, TraitsUIScrolledPanel
83
#-------------------------------------------------------------------------------
85
#-------------------------------------------------------------------------------
87
# Paste buffer for copy/cut/paste operations
90
#-------------------------------------------------------------------------------
91
# The core tree node menu actions:
92
#-------------------------------------------------------------------------------
94
NewAction = 'NewAction'
95
CopyAction = Action( name = 'Copy',
96
action = 'editor._menu_copy_node',
97
enabled_when = 'editor._is_copyable(object)' )
98
CutAction = Action( name = 'Cut',
99
action = 'editor._menu_cut_node',
100
enabled_when = 'editor._is_cutable(object)' )
101
PasteAction = Action( name = 'Paste',
102
action = 'editor._menu_paste_node',
103
enabled_when = 'editor._is_pasteable(object)' )
104
DeleteAction = Action( name = 'Delete',
105
action = 'editor._menu_delete_node',
106
enabled_when = 'editor._is_deletable(object)' )
107
RenameAction = Action( name = 'Rename',
108
action = 'editor._menu_rename_node',
109
enabled_when = 'editor._is_renameable(object)' )
111
#-------------------------------------------------------------------------------
112
# 'SimpleEditor' class:
113
#-------------------------------------------------------------------------------
115
class SimpleEditor ( Editor ):
116
""" Simple style of tree editor.
119
#---------------------------------------------------------------------------
121
#---------------------------------------------------------------------------
123
# Is the tree editor is scrollable? This value overrides the default.
126
# Allows an external agent to set the tree selection
129
# The currently selected object
132
# The event fired when a tree node is clicked on:
135
# The event fired when a tree node is double-clicked on:
138
# The event fired when the application wants to veto an operation:
141
#-- Private Traits ---------------------------------------------------------
143
# An icon used by a TreeNode:
146
#---------------------------------------------------------------------------
147
# Finishes initializing the editor by creating the underlying toolkit
149
#---------------------------------------------------------------------------
151
def init ( self, parent ):
152
""" Finishes initializing the editor by creating the underlying toolkit
155
factory = self.factory
156
style = self._get_style()
160
# Check to see if the tree view is based on a shared trait editor:
161
if factory.shared_editor:
162
factory_editor = factory.editor
164
# If this is the editor that defines the trait editor panel:
165
if factory_editor is None:
167
# Remember which editor has the trait editor in the factory:
168
factory._editor = self
170
# Create the trait editor panel:
171
self.control = TraitsUIPanel( parent, -1 )
172
self.control._node_ui = self.control._editor_nid = None
174
# Check to see if there are any existing editors that are
175
# waiting to be bound to the trait editor panel:
176
editors = factory._shared_editors
177
if editors is not None:
178
for editor in factory._shared_editors:
180
# If the editor is part of this UI:
181
if editor.ui is self.ui:
183
# Then bind it to the trait editor panel:
184
editor._editor = self.control
186
# Indicate all pending editors have been processed:
187
factory._shared_editors = None
189
# We only needed to build the trait editor panel, so exit:
192
# Check to see if the matching trait editor panel has been
194
editor = factory_editor._editor
195
if (editor is None) or (editor.ui is not self.ui):
196
# If not, add ourselves to the list of pending editors:
197
shared_editors = factory_editor._shared_editors
198
if shared_editors is None:
199
factory_editor._shared_editors = shared_editors = []
200
shared_editors.append( self )
202
# Otherwise, bind our trait editor panel to the shared one:
203
self._editor = editor.control
205
# Finally, create only the tree control:
206
self.control = self._tree = tree = wx.TreeCtrl( parent, -1,
209
# If editable, create a tree control and an editor panel:
210
self._is_dock_window = True
211
theme = factory.dock_theme or self.item.container.dock_theme
212
self.control = splitter = DockWindow( parent,
213
theme = theme ).control
214
self._tree = tree = wx.TreeCtrl( splitter, -1,
216
self._editor = editor = TraitsUIScrolledPanel( splitter )
217
editor.SetSizer( wx.BoxSizer( wx.VERTICAL ) )
218
editor.SetScrollRate( 16, 16 )
219
editor.SetMinSize( wx.Size( 100, 100 ) )
221
self._editor._node_ui = self._editor._editor_nid = None
223
hierarchy_name = editor_name = ''
227
hierarchy_name = name + ' Hierarchy'
228
editor_name = name + ' Editor'
231
splitter.SetSizer( DockSizer( contents =
232
DockSection( contents = [
233
DockRegion( contents = [
234
DockControl( name = hierarchy_name,
238
DockRegion( contents = [
239
DockControl( name = editor_name,
241
control = self._editor,
242
style = style ) ] ) ],
243
is_row = (factory.orientation == 'horizontal') ) ) )
245
# Otherwise, just create the tree control:
246
self.control = self._tree = tree = wx.TreeCtrl( parent, -1,
249
# Set up to show tree node icon (if requested):
250
if factory.show_icons:
251
self._image_list = ImageList( *factory.icon_size )
252
tree.AssignImageList( self._image_list )
254
# Set up the mapping between objects and tree id's:
257
# Initialize the 'undo state' stack:
260
# Get the tree control id:
263
# Set up the mouse event handlers:
264
wx.EVT_LEFT_DOWN( tree, self._on_left_down )
265
wx.EVT_RIGHT_DOWN( tree, self._on_right_down )
267
# Set up the tree event handlers:
268
wx.EVT_TREE_ITEM_EXPANDING( tree, tid, self._on_tree_item_expanding )
269
wx.EVT_TREE_ITEM_EXPANDED( tree, tid, self._on_tree_item_expanded )
270
wx.EVT_TREE_ITEM_COLLAPSING( tree, tid, self._on_tree_item_collapsing )
271
wx.EVT_TREE_ITEM_COLLAPSED( tree, tid, self._on_tree_item_collapsed )
272
wx.EVT_TREE_ITEM_ACTIVATED( tree, tid, self._on_tree_item_activated )
273
wx.EVT_TREE_SEL_CHANGED( tree, tid, self._on_tree_sel_changed )
274
wx.EVT_TREE_BEGIN_DRAG( tree, tid, self._on_tree_begin_drag )
275
wx.EVT_TREE_BEGIN_LABEL_EDIT( tree, tid, self._on_tree_begin_label_edit)
276
wx.EVT_TREE_END_LABEL_EDIT( tree, tid, self._on_tree_end_label_edit )
277
wx.EVT_TREE_ITEM_GETTOOLTIP( tree, tid, self._on_tree_item_gettooltip )
279
# Set up general mouse events
280
wx.EVT_MOTION(tree, self._on_hover)
282
# Synchronize external object traits with the editor:
283
self.sync_value( factory.selected, 'selected' )
284
self.sync_value( factory.click, 'click', 'to' )
285
self.sync_value( factory.dclick, 'dclick', 'to' )
286
self.sync_value( factory.veto, 'veto', 'from' )
288
# Set up the drag and drop target:
289
if PythonDropTarget is not None:
290
tree.SetDropTarget( PythonDropTarget( self ) )
292
#---------------------------------------------------------------------------
293
# Disposes of the contents of an editor:
294
#---------------------------------------------------------------------------
296
def dispose ( self ):
297
""" Disposes of the contents of an editor.
302
wx.EVT_LEFT_DOWN( tree, None )
303
wx.EVT_RIGHT_DOWN( tree, None )
304
wx.EVT_TREE_ITEM_EXPANDING( tree, id, None )
305
wx.EVT_TREE_ITEM_EXPANDED( tree, id, None )
306
wx.EVT_TREE_ITEM_COLLAPSING( tree, id, None )
307
wx.EVT_TREE_ITEM_COLLAPSED( tree, id, None )
308
wx.EVT_TREE_ITEM_ACTIVATED( tree, id, None )
309
wx.EVT_TREE_SEL_CHANGED( tree, id, None )
310
wx.EVT_TREE_BEGIN_DRAG( tree, id, None )
311
wx.EVT_TREE_BEGIN_LABEL_EDIT( tree, id, None )
312
wx.EVT_TREE_END_LABEL_EDIT( tree, id, None )
313
wx.EVT_TREE_ITEM_GETTOOLTIP( tree, id, None )
315
nid = self._tree.GetRootItem()
317
self._delete_node( nid )
319
super( SimpleEditor, self ).dispose()
321
#---------------------------------------------------------------------------
322
# Handles the 'selection' trait being changed:
323
#---------------------------------------------------------------------------
325
def _selection_changed ( self, selection ):
326
""" Handles the **selection** event.
329
self._tree.SelectItem( self._object_info( selection )[2] )
333
#---------------------------------------------------------------------------
334
# Handles the 'selected' trait being changed:
335
#---------------------------------------------------------------------------
337
def _selected_changed ( self, selected ):
338
""" Handles the **selected** trait being changed.
340
if not self._no_update_selected:
341
self._selection_changed( selected )
343
#---------------------------------------------------------------------------
344
# Handles the 'veto' event being fired:
345
#---------------------------------------------------------------------------
347
def _veto_changed ( self ):
348
""" Handles the 'veto' event being fired.
352
#---------------------------------------------------------------------------
353
# Returns the style settings used for displaying the wx tree:
354
#---------------------------------------------------------------------------
356
def _get_style ( self ):
357
""" Returns the style settings used for displaying the wx tree.
359
factory = self.factory
360
style = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN
362
# Turn lines off if explicit or for appearance on *nix:
363
if ((factory.lines_mode == 'off') or
364
((factory.lines_mode == 'appearance') and (os.name == 'posix'))):
365
style |= wx.TR_NO_LINES
367
if factory.hide_root:
368
style |= (wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT)
370
if factory.selection_mode != 'single':
371
style |= wx.TR_MULTIPLE | wx.TR_EXTENDED
376
#---------------------------------------------------------------------------
377
# Handles the user entering input data in the edit control:
378
#---------------------------------------------------------------------------
380
def update_object ( self, event ):
381
""" Handles the user entering input data in the edit control.
384
self.value = self._get_value()
385
self.control.SetBackgroundColour( OKColor )
386
self.control.Refresh()
387
except TraitError, excp:
390
#---------------------------------------------------------------------------
391
# Saves the current 'expanded' state of all tree nodes:
392
#---------------------------------------------------------------------------
394
def _save_state ( self ):
396
nid = tree.GetRootItem()
399
nodes_to_do = [ nid ]
401
node = nodes_to_do.pop()
402
data = self._get_node_data( node )
404
is_expanded = tree.IsExpanded( node )
407
state[ hash( data[-1] ) ] = ( data[0], is_expanded )
408
for cnid in self._nodes( node ):
409
nodes_to_do.append( cnid )
412
#---------------------------------------------------------------------------
413
# Restores the 'expanded' state of all tree nodes:
414
#---------------------------------------------------------------------------
416
def _restore_state ( self, state ):
420
nid = tree.GetRootItem()
422
nodes_to_do = [ nid ]
424
node = nodes_to_do.pop()
425
for cnid in self._nodes( node ):
426
data = self._get_node_data( cnid )
427
key = hash( data[-1] )
429
was_expanded, current_state = state[ key ]
431
self._expand_node( cnid )
434
nodes_to_do.append( cnid )
436
#---------------------------------------------------------------------------
437
# Expands all nodes starting from the current selection:
438
#---------------------------------------------------------------------------
440
def expand_all ( self ):
441
""" Expands all nodes, starting from the selected node.
445
def _do_expand ( nid ):
446
expanded, node, object = self._get_node_data( nid )
447
if self._has_children( node, object ):
448
tree.SetItemHasChildren( nid, True )
449
self._expand_node( nid )
452
nid = tree.GetSelection()
454
nodes_to_do = [ nid ]
456
node = nodes_to_do.pop()
458
for n in self._nodes( node ):
460
nodes_to_do.append( n )
462
#---------------------------------------------------------------------------
463
# Expands from the specified node the specified number of sub-levels:
464
#---------------------------------------------------------------------------
466
def expand_levels ( self, nid, levels, expand = True ):
467
""" Expands from the specified node the specified number of sub-levels.
470
expanded, node, object = self._get_node_data( nid )
471
if self._has_children( node, object ):
472
self._tree.SetItemHasChildren( nid, True )
473
self._expand_node( nid )
475
self._tree.Expand( nid )
476
for cnid in self._nodes( nid ):
477
self.expand_levels( cnid, levels - 1 )
479
#---------------------------------------------------------------------------
480
# Updates the editor when the object trait changes external to the editor:
481
#---------------------------------------------------------------------------
483
def update_editor ( self ):
484
""" Updates the editor when the object trait changes externally to the
490
nid = tree.GetRootItem()
492
self._delete_node( nid )
493
object, node = self._node_for( self.value )
495
icon = self._get_icon( node, object )
496
self._root_nid = nid = tree.AddRoot( node.get_label( object ),
498
self._map[ id( object ) ] = [ ( node.get_children_id( object ),
500
self._add_listeners( node, object )
501
self._set_node_data( nid, ( False, node, object) )
502
if self.factory.hide_root or self._has_children( node, object ):
503
tree.SetItemHasChildren( nid, True )
504
self._expand_node( nid )
505
if not self.factory.hide_root:
507
tree.SelectItem( nid )
508
self._on_tree_sel_changed()
510
self.expand_levels( nid, self.factory.auto_open, False )
512
# It seems like in some cases, an explicit Refresh is needed to
513
# trigger a screen update:
516
# fixme: Clear the current editor (if any)...
518
#---------------------------------------------------------------------------
519
# Returns the editor's control for indicating error status:
520
#---------------------------------------------------------------------------
522
def get_error_control ( self ):
523
""" Returns the editor's control for indicating error status.
527
#---------------------------------------------------------------------------
528
# Appends a new node to the specified node:
529
#---------------------------------------------------------------------------
531
def _append_node ( self, nid, node, object ):
532
""" Appends a new node to the specified node.
534
return self._insert_node( nid, None, node, object )
536
#---------------------------------------------------------------------------
537
# Inserts a new node before a specified index into the children of the
539
#---------------------------------------------------------------------------
541
def _insert_node ( self, nid, index, node, object ):
542
""" Inserts a new node before a specified index into the children of the
546
icon = self._get_icon( node, object )
547
label = node.get_label( object )
549
cnid = tree.AppendItem( nid, label, icon, icon )
551
cnid = tree.InsertItemBefore( nid, index, label, icon, icon )
552
has_children = self._has_children( node, object )
553
tree.SetItemHasChildren( cnid, has_children )
554
self._set_node_data( cnid, ( False, node, object ) )
555
self._map.setdefault( id( object ), [] ).append(
556
( node.get_children_id( object ), cnid ) )
557
self._add_listeners( node, object )
559
# Automatically expand the new node (if requested):
560
if has_children and node.can_auto_open( object ):
563
# Return the newly created node:
566
#---------------------------------------------------------------------------
567
# Deletes a specified tree node and all its children:
568
#---------------------------------------------------------------------------
570
def _delete_node ( self, nid ):
571
""" Deletes a specified tree node and all of its children.
573
for cnid in self._nodes_for( nid ):
574
self._delete_node( cnid )
576
expanded, node, object = self._get_node_data( nid )
577
id_object = id( object )
578
object_info = self._map[ id_object ]
579
for i, info in enumerate( object_info ):
584
if len( object_info ) == 0:
585
self._remove_listeners( node, object )
586
del self._map[ id_object ]
588
# We set the '_locked' flag here because wx seems to generate a
589
# 'node selected' event when the node is deleted. This can lead to
590
# some bad side effects. So the 'node selected' event handler exits
591
# immediately if the '_locked' flag is set:
593
self._tree.Delete( nid )
596
# If the deleted node had an active editor panel showing, remove it:
597
if (self._editor is not None) and (nid == self._editor._editor_nid):
600
#---------------------------------------------------------------------------
601
# Expands the contents of a specified node (if required):
602
#---------------------------------------------------------------------------
604
def _expand_node ( self, nid ):
605
""" Expands the contents of a specified node (if required).
607
expanded, node, object = self._get_node_data( nid )
609
# Lazily populate the item's children:
611
for child in node.get_children( object ):
612
child, child_node = self._node_for( child )
613
if child_node is not None:
614
self._append_node( nid, child_node, child )
616
# Indicate the item is now populated:
617
self._set_node_data( nid, ( True, node, object) )
619
#---------------------------------------------------------------------------
620
# Returns each of the child nodes of a specified node id:
621
#---------------------------------------------------------------------------
623
def _nodes ( self, nid ):
624
""" Returns each of the child nodes of a specified node.
627
cnid, cookie = tree.GetFirstChild( nid )
630
cnid, cookie = tree.GetNextChild( nid, cookie )
632
def _nodes_for ( self, nid ):
633
""" Returns all child node ids of a specified node id.
635
return [ cnid for cnid in self._nodes( nid ) ]
637
#---------------------------------------------------------------------------
638
# Return the index of a specified node id within its parent:
639
#---------------------------------------------------------------------------
641
def _node_index ( self, nid ):
642
pnid = self._tree.GetItemParent( nid )
644
return ( None, None, None )
646
for i, cnid in enumerate( self._nodes( pnid ) ):
648
ignore, pnode, pobject = self._get_node_data( pnid )
650
return ( pnode, pobject, i )
652
#---------------------------------------------------------------------------
653
# Returns whether a specified object has any children:
654
#---------------------------------------------------------------------------
656
def _has_children ( self, node, object ):
657
""" Returns whether a specified object has any children.
659
return (node.allows_children( object ) and node.has_children( object ))
661
#---------------------------------------------------------------------------
662
# Returns whether a given object is droppable on the node:
663
#---------------------------------------------------------------------------
665
def _is_droppable ( self, node, object, add_object, for_insert ):
666
""" Returns whether a given object is droppable on the node.
668
if for_insert and (not node.can_insert( object )):
671
return node.can_add( object, add_object )
673
#---------------------------------------------------------------------------
674
# Returns a droppable version of a specified object:
675
#---------------------------------------------------------------------------
677
def _drop_object ( self, node, object, dropped_object, make_copy = True ):
678
""" Returns a droppable version of a specified object.
680
new_object = node.drop_object( object, dropped_object )
681
if (new_object is not dropped_object) or (not make_copy):
684
return copy.deepcopy( new_object )
686
#---------------------------------------------------------------------------
687
# Returns the icon index for the specified object:
688
#---------------------------------------------------------------------------
690
def _get_icon ( self, node, object, is_expanded = False ):
691
""" Returns the index of the specified object icon.
693
if self._image_list is None:
696
icon_name = node.get_icon( object, is_expanded )
697
if isinstance( icon_name, basestring ):
698
if icon_name[:1] == '@':
699
self._icon = icon_name
700
icon_name = self._icon
702
if icon_name[:1] == '<':
703
icon_name = icon_name[1:-1]
706
path = node.get_icon_path( object )
707
if isinstance( path, basestring ):
708
path = [ path, node ]
712
reference = resource_manager.locate_image( icon_name, path )
713
if reference is None:
716
file_name = reference.filename
718
# If it is an ImageResource, get its file name directly:
719
if isinstance( icon_name, ImageResource ):
720
file_name = icon_name
722
return self._image_list.GetIndex( file_name )
724
#---------------------------------------------------------------------------
725
# Adds the event listeners for a specified object:
726
#---------------------------------------------------------------------------
728
def _add_listeners ( self, node, object ):
729
""" Adds the event listeners for a specified object.
731
if node.allows_children( object ):
732
node.when_children_replaced( object, self._children_replaced, False)
733
node.when_children_changed( object, self._children_updated, False)
735
node.when_label_changed( object, self._label_updated, False )
737
#---------------------------------------------------------------------------
738
# Removes any event listeners from a specified object:
739
#---------------------------------------------------------------------------
741
def _remove_listeners ( self, node, object ):
742
""" Removes any event listeners from a specified object.
744
if node.allows_children( object ):
745
node.when_children_replaced( object, self._children_replaced, True )
746
node.when_children_changed( object, self._children_updated, True )
748
node.when_label_changed( object, self._label_updated, True )
750
#---------------------------------------------------------------------------
751
# Returns the tree node data for a specified object in the form
752
# ( expanded, node, nid ):
753
#---------------------------------------------------------------------------
755
def _object_info ( self, object, name = '' ):
756
""" Returns the tree node data for a specified object in the form
757
( expanded, node, nid ).
759
info = self._map[ id( object ) ]
760
for name2, nid in info:
766
expanded, node, ignore = self._get_node_data( nid )
768
return ( expanded, node, nid )
770
def _object_info_for ( self, object, name = '' ):
771
""" Returns the tree node data for a specified object as a list of the
772
form: [ ( expanded, node, nid ), ... ].
775
for name2, nid in self._map[ id( object ) ]:
777
expanded, node, ignore = self._get_node_data( nid )
778
result.append( ( expanded, node, nid ) )
782
#---------------------------------------------------------------------------
783
# Returns the TreeNode associated with a specified object:
784
#---------------------------------------------------------------------------
786
def _node_for ( self, object ):
787
""" Returns the TreeNode associated with a specified object.
789
if ((type( object ) is tuple) and (len( object ) == 2) and
790
isinstance( object[1], TreeNode )):
793
# Select all nodes which understand this object:
794
factory = self.factory
795
nodes = [ node for node in factory.nodes
796
if object is not None and node.is_node_for( object ) ]
798
# If only one found, we're done, return it:
799
if len( nodes ) == 1:
800
return ( object, nodes[0] )
802
# If none found, try to create an adapted node for the object:
803
if len( nodes ) == 0:
804
return ( object, ITreeNodeAdapterBridge( adapter = object ) )
806
# Use all selected nodes that have the same 'node_for' list as the
807
# first selected node:
808
base = nodes[0].node_for
809
nodes = [ node for node in nodes if base == node.node_for ]
811
# If only one left, then return that node:
812
if len( nodes ) == 1:
813
return ( object, nodes[0] )
815
# Otherwise, return a MultiTreeNode based on all selected nodes...
817
# Use the node with no specified children as the root node. If not
818
# found, just use the first selected node as the 'root node':
820
for i, node in enumerate( nodes ):
821
if node.get_children_id( object ) == '':
828
# If we have a matching MultiTreeNode already cached, return it:
829
key = ( root_node, ) + tuple( nodes )
830
if key in factory.multi_nodes:
831
return ( object, factory.multi_nodes[ key ] )
833
# Otherwise create one, cache it, and return it:
834
factory.multi_nodes[ key ] = multi_node = MultiTreeNode(
835
root_node = root_node,
838
return ( object, multi_node )
840
#---------------------------------------------------------------------------
841
# Returns the TreeNode associated with a specified class:
842
#---------------------------------------------------------------------------
844
def _node_for_class ( self, klass ):
845
""" Returns the TreeNode associated with a specified class.
847
for node in self.factory.nodes:
848
if issubclass( klass, tuple( node.node_for ) ):
852
#---------------------------------------------------------------------------
853
# Returns the node and class associated with a specified class name:
854
#---------------------------------------------------------------------------
856
def _node_for_class_name ( self, class_name ):
857
""" Returns the node and class associated with a specified class name.
859
for node in self.factory.nodes:
860
for klass in node.node_for:
861
if class_name == klass.__name__:
862
return ( node, klass )
863
return ( None, None )
865
#---------------------------------------------------------------------------
866
# Updates the icon for a specified node:
867
#---------------------------------------------------------------------------
869
def _update_icon ( self, event, is_expanded ):
870
""" Updates the icon for a specified node.
872
self._update_icon_for_nid( event.GetItem() )
874
#---------------------------------------------------------------------------
875
# Updates the icon for a specified node id:
876
#---------------------------------------------------------------------------
878
def _update_icon_for_nid ( self, nid ):
879
""" Updates the icon for a specified node ID.
881
if self._image_list is not None:
882
expanded, node, object = self._get_node_data( nid )
883
icon = self._get_icon( node, object, expanded )
884
self._tree.SetItemImage( nid, icon, wx.TreeItemIcon_Normal )
885
self._tree.SetItemImage( nid, icon, wx.TreeItemIcon_Selected )
887
#---------------------------------------------------------------------------
888
# Unpacks an event to see whether a tree item was involved:
889
#---------------------------------------------------------------------------
891
def _unpack_event ( self, event ):
892
""" Unpacks an event to see whether a tree item was involved.
895
point = event.GetPosition()
897
point = event.GetPoint()
900
if hasattr( event, 'GetItem' ):
901
nid = event.GetItem()
903
if (nid is None) or (not nid.IsOk()):
904
nid, flags = self._tree.HitTest( point )
907
return self._get_node_data( nid ) + ( nid, point )
909
return ( None, None, None, nid, point )
911
#---------------------------------------------------------------------------
912
# Returns information about the node at a specified point:
913
#---------------------------------------------------------------------------
915
def _hit_test ( self, point ):
916
""" Returns information about the node at a specified point.
918
nid, flags = self._tree.HitTest( point )
920
return self._get_node_data( nid ) + ( nid, point )
921
return ( None, None, None, nid, point )
923
#---------------------------------------------------------------------------
924
# Begins an 'undoable' transaction:
925
#---------------------------------------------------------------------------
927
def _begin_undo ( self ):
928
""" Begins an "undoable" transaction.
931
self._undoable.append( ui._undoable )
932
if (ui._undoable == -1) and (ui.history is not None):
933
ui._undoable = ui.history.now
935
#---------------------------------------------------------------------------
936
# Ends an 'undoable' transaction:
937
#---------------------------------------------------------------------------
939
def _end_undo ( self ):
940
if self._undoable.pop() == -1:
941
self.ui._undoable = -1
943
#---------------------------------------------------------------------------
944
# Gets an 'undo' item for a change made to a node's children:
945
#---------------------------------------------------------------------------
947
def _get_undo_item ( self, object, name, event ):
948
return ListUndoItem( object = object,
952
removed = event.removed )
954
#---------------------------------------------------------------------------
955
# Performs an undoable 'append' operation:
956
#---------------------------------------------------------------------------
958
def _undoable_append ( self, node, object, data, make_copy = True ):
959
""" Performs an undoable append operation.
964
data = copy.deepcopy( data )
965
node.append_child( object, data )
969
#---------------------------------------------------------------------------
970
# Performs an undoable 'insert' operation:
971
#---------------------------------------------------------------------------
973
def _undoable_insert ( self, node, object, index, data, make_copy = True ):
974
""" Performs an undoable insert operation.
979
data = copy.deepcopy( data )
980
node.insert_child( object, index, data )
984
#---------------------------------------------------------------------------
985
# Performs an undoable 'delete' operation:
986
#---------------------------------------------------------------------------
988
def _undoable_delete ( self, node, object, index ):
989
""" Performs an undoable delete operation.
993
node.delete_child( object, index )
997
#---------------------------------------------------------------------------
998
# Gets the id associated with a specified object (if any):
999
#---------------------------------------------------------------------------
1001
def _get_object_nid ( self, object, name = '' ):
1002
""" Gets the ID associated with a specified object (if any).
1004
info = self._map.get( id( object ) )
1008
for name2, nid in info:
1014
#---------------------------------------------------------------------------
1015
# Clears the current editor pane (if any):
1016
#---------------------------------------------------------------------------
1018
def _clear_editor ( self ):
1019
""" Clears the current editor pane (if any).
1021
editor = self._editor
1022
if editor._node_ui is not None:
1023
editor.SetSizer( None )
1024
editor._node_ui.dispose()
1025
editor._node_ui = editor._editor_nid = None
1027
#---------------------------------------------------------------------------
1028
# Gets/Sets the node specific data:
1029
#---------------------------------------------------------------------------
1031
def _get_node_data ( self, nid ):
1032
""" Gets the node specific data.
1034
if nid == self._root_nid:
1035
return self._root_nid_data
1037
return self._tree.GetPyData( nid )
1039
def _set_node_data ( self, nid, data ):
1040
""" Sets the node specific data.
1042
if nid == self._root_nid:
1043
self._root_nid_data = data
1045
self._tree.SetPyData( nid, data )
1047
#----- User callable methods: --------------------------------------------------
1049
#---------------------------------------------------------------------------
1050
# Gets the object associated with a specified node:
1051
#---------------------------------------------------------------------------
1053
def get_object ( self, nid ):
1054
""" Gets the object associated with a specified node.
1056
return self._get_node_data( nid )[2]
1058
#---------------------------------------------------------------------------
1059
# Returns the object which is the immmediate parent of a specified object
1061
#---------------------------------------------------------------------------
1063
def get_parent ( self, object, name = '' ):
1064
""" Returns the object that is the immmediate parent of a specified
1067
nid = self._get_object_nid( object, name )
1069
pnid = self._tree.GetItemParent( nid )
1071
return self.get_object( pnid )
1075
#---------------------------------------------------------------------------
1076
# Returns the node associated with a specified object:
1077
#---------------------------------------------------------------------------
1079
def get_node ( self, object, name = '' ):
1080
""" Returns the node associated with a specified object.
1082
nid = self._get_object_nid( object, name )
1084
return self._get_node_data( nid )[1]
1088
#-- Tree Event Handlers: ---------------------------------------------------
1090
#---------------------------------------------------------------------------
1091
# Handles a tree node expanding:
1092
#---------------------------------------------------------------------------
1094
def _on_tree_item_expanding ( self, event ):
1095
""" Handles a tree node expanding.
1102
nid = event.GetItem()
1104
expanded, node, object = self._get_node_data( nid )
1106
# If 'auto_close' requested for this node type, close all of the node's
1108
if node.can_auto_close( object ):
1111
snid = tree.GetPrevSibling( snid )
1114
tree.Collapse( snid )
1117
snid = tree.GetNextSibling( snid )
1120
tree.Collapse( snid )
1122
# Expand the node (i.e. populate its children if they are not there
1124
self._expand_node( nid )
1126
#---------------------------------------------------------------------------
1127
# Handles a tree node being expanded:
1128
#---------------------------------------------------------------------------
1130
def _on_tree_item_expanded ( self, event ):
1131
""" Handles a tree node being expanded.
1133
self._update_icon( event, True )
1135
#---------------------------------------------------------------------------
1136
# Handles a tree node collapsing:
1137
#---------------------------------------------------------------------------
1139
def _on_tree_item_collapsing ( self, event ):
1140
""" Handles a tree node collapsing.
1146
#---------------------------------------------------------------------------
1147
# Handles a tree node being collapsed:
1148
#---------------------------------------------------------------------------
1150
def _on_tree_item_collapsed ( self, event ):
1151
""" Handles a tree node being collapsed.
1153
self._update_icon( event, False )
1155
#---------------------------------------------------------------------------
1156
# Handles a tree node being selected:
1157
#---------------------------------------------------------------------------
1159
def _on_tree_sel_changed ( self, event = None ):
1160
""" Handles a tree node being selected.
1165
# Get the new selection:
1168
nids = self._tree.GetSelections()
1175
# If there is a real selection, get the associated object:
1176
expanded, node, sel_object = self._get_node_data( nid )
1177
selected.append(sel_object)
1179
# Try to inform the node specific handler of the selection,
1180
# if there are multiple selections, we only care about the
1181
# first (or maybe the last makes more sense?)
1184
not_handled = node.select( object )
1186
# Set the value of the new selection:
1187
if self.factory.selection_mode == 'single':
1188
self._no_update_selected = True
1189
self.selected = object
1190
self._no_update_selected = False
1192
self._no_update_selected = True
1193
self.selected = selected
1194
self._no_update_selected = False
1196
# If no one has been notified of the selection yet, inform the editor's
1197
# select handler (if any) of the new selection:
1198
if not_handled is True:
1199
self.ui.evaluate( self.factory.on_select, object )
1201
# Check to see if there is an associated node editor pane:
1202
editor = self._editor
1203
if editor is not None:
1204
# If we already had a node editor, destroy it:
1206
self._clear_editor()
1208
# If there is a selected object, create a new editor for it:
1209
if object is not None:
1210
# Try to chain the undo history to the main undo history:
1211
view = node.get_view( object )
1213
view = object.trait_view()
1215
if (self.ui.history is not None) or (view.kind == 'subpanel'):
1216
ui = object.edit_traits( parent = editor,
1220
# Otherwise, just set up our own new one:
1221
ui = object.edit_traits( parent = editor,
1225
# Make our UI the parent of the new UI:
1228
# Remember the new editor's UI and node info:
1229
editor._node_ui = ui
1230
editor._editor_nid = nid
1232
# Finish setting up the editor:
1233
sizer = wx.BoxSizer( wx.VERTICAL )
1234
sizer.Add( ui.control, 1, wx.EXPAND )
1235
editor.SetSizer( sizer )
1238
# fixme: The following is a hack needed to make the editor window
1239
# (which is a wx.ScrolledWindow) recognize that its contents have
1241
dx, dy = editor.GetSize()
1242
editor.SetSize( wx.Size( dx, dy + 1 ) )
1243
editor.SetSize( wx.Size( dx, dy ) )
1245
# Allow the editor view to show any changes that have occurred:
1248
def _on_hover(self, event):
1249
""" Handles the mouse moving over a tree node
1251
id, flags = self._tree.HitTest(event.GetPosition())
1252
if flags & wx.TREE_HITTEST_ONITEMLABEL:
1253
expanded, node, object = self._get_node_data( id )
1254
if self.factory.on_hover is not None:
1255
self.ui.evaluate( self.factory.on_hover, object )
1257
elif self.factory.on_hover is not None:
1258
self.ui.evaluate( self.factory.on_hover, None )
1260
# allow other events to be processed
1265
#---------------------------------------------------------------------------
1266
# Handles a tree item being activated (i.e. double clicked):
1267
#---------------------------------------------------------------------------
1269
def _on_tree_item_activated ( self, event ):
1270
""" Handles a tree item being activated (i.e. double-clicked).
1272
expanded, node, object = self._get_node_data( event.GetItem() )
1273
if node.dclick( object ) is True:
1274
if self.factory.on_dclick is not None:
1275
self.ui.evaluate( self.factory.on_dclick, object )
1280
# Fire the 'dclick' event with the clicked on object as value:
1281
self.dclick = object
1283
#---------------------------------------------------------------------------
1284
# Handles the user starting to edit a tree node label:
1285
#---------------------------------------------------------------------------
1287
def _on_tree_begin_label_edit ( self, event ):
1288
""" Handles the user starting to edit a tree node label.
1290
item = event.GetItem()
1291
parent = self._tree.GetItemParent( item )
1294
expanded, node, object = self._get_node_data( parent )
1295
can_rename = node.can_rename( object )
1298
expanded, node, object = self._get_node_data( item )
1299
if node.can_rename_me( object ):
1304
#---------------------------------------------------------------------------
1305
# Handles the user completing tree node label editing:
1306
#---------------------------------------------------------------------------
1308
def _on_tree_end_label_edit ( self, event ):
1309
""" Handles the user completing tree node label editing.
1311
label = event.GetLabel()
1312
if len( label ) > 0:
1313
expanded, node, object = self._get_node_data( event.GetItem() )
1314
# Tell the node to change the label. If it raises an exception,
1315
# that means it didn't like the label, so veto the tree node change:
1317
node.set_label( object, label )
1323
#---------------------------------------------------------------------------
1324
# Handles a drag operation starting on a tree node:
1325
#---------------------------------------------------------------------------
1327
def _on_tree_begin_drag ( self, event ):
1328
""" Handles a drag operation starting on a tree node.
1330
if PythonDropSource is not None:
1331
expanded, node, object, nid, point = self._unpack_event( event )
1332
if node is not None:
1334
self._dragging = nid
1335
PythonDropSource( self._tree,
1336
node.get_drag_object( object ) )
1338
self._dragging = None
1340
#---------------------------------------------------------------------------
1341
# Handles a tooltip request on a tree node:
1342
#---------------------------------------------------------------------------
1344
def _on_tree_item_gettooltip ( self, event ):
1345
""" Handles a tooltip request on a tree node.
1347
nid = event.GetItem()
1349
node_data = self._get_node_data( nid )
1350
if node_data is not None:
1351
expanded, node, object = node_data
1352
tooltip = node.get_tooltip( object )
1354
event.SetToolTip( tooltip )
1358
#---------------------------------------------------------------------------
1359
# Handles the user left clicking on a tree node:
1360
#---------------------------------------------------------------------------
1362
def _on_left_down ( self, event ):
1363
""" Handles the user right clicking on a tree node.
1365
# Determine what node (if any) was clicked on:
1366
expanded, node, object, nid, point = self._unpack_event( event )
1368
# If the mouse is over a node, then process the click:
1369
if node is not None:
1370
if ((node.click( object ) is True) and
1371
(self.factory.on_click is not None)):
1372
self.ui.evaluate( self.factory.on_click, object )
1374
# Fire the 'click' event with the object as its value:
1377
# Allow normal mouse event processing to occur:
1380
#---------------------------------------------------------------------------
1381
# Handles the user right clicking on a tree node:
1382
#---------------------------------------------------------------------------
1384
def _on_right_down ( self, event ):
1385
""" Handles the user right clicking on a tree node.
1387
expanded, node, object, nid, point = self._unpack_event( event )
1389
if node is not None:
1390
self._data = ( node, object, nid )
1391
self._context = { 'object': object,
1394
'info': self.ui.info,
1395
'handler': self.ui.handler }
1397
# Try to get the parent node of the node clicked on:
1398
pnid = self._tree.GetItemParent( nid )
1400
ignore, parent_node, parent_object = self._get_node_data( pnid )
1402
parent_node = parent_object = None
1404
self._menu_node = node
1405
self._menu_parent_node = parent_node
1406
self._menu_parent_object = parent_object
1408
menu = node.get_menu( object )
1411
# Use the standard, default menu:
1412
menu = self._standard_menu( node, object )
1414
elif isinstance( menu, Menu ):
1415
# Use the menu specified by the node:
1416
group = menu.find_group( NewAction )
1417
if group is not None:
1418
# Only set it the first time:
1420
actions = self._new_actions( node, object )
1421
if len( actions ) > 0:
1422
group.insert( 0, Menu( name = 'New', *actions ) )
1425
# All other values mean no menu should be displayed:
1428
# Only display the menu if a valid menu is defined:
1429
if menu is not None:
1430
wxmenu = menu.create_menu( self._tree, self )
1431
self._tree.PopupMenuXY( wxmenu,
1432
point[0] - 10, point[1] - 10 )
1435
# Reset all menu related cached values:
1436
self._data = self._context = self._menu_node = \
1437
self._menu_parent_node = self._menu_parent_object = None
1439
#---------------------------------------------------------------------------
1440
# Returns the standard contextual pop-up menu:
1441
#---------------------------------------------------------------------------
1443
def _standard_menu ( self, node, object ):
1444
""" Returns the standard contextual pop-up menu.
1446
actions = [ CutAction, CopyAction, PasteAction, Separator(),
1447
DeleteAction, Separator(), RenameAction ]
1449
# See if the 'New' menu section should be added:
1450
items = self._new_actions( node, object )
1451
if len( items ) > 0:
1452
actions[0:0] = [ Menu( name = 'New', *items ), Separator() ]
1454
return Menu( *actions )
1456
#---------------------------------------------------------------------------
1457
# Returns a list of Actions that will create 'new' objects:
1458
#---------------------------------------------------------------------------
1460
def _new_actions ( self, node, object ):
1461
""" Returns a list of Actions that will create new objects.
1463
object = self._data[1]
1465
add = node.get_add( object )
1469
if isinstance( klass, tuple ):
1470
klass, prompt = klass
1471
add_node = self._node_for_class( klass )
1472
if add_node is not None:
1473
class_name = klass.__name__
1474
name = add_node.get_name( object )
1478
Action( name = name,
1479
action = "editor._menu_new_node('%s',%s)" %
1480
( class_name, prompt ) ) )
1483
#---------------------------------------------------------------------------
1484
# Menu action helper methods:
1485
#---------------------------------------------------------------------------
1487
def _is_copyable ( self, object ):
1488
parent = self._menu_parent_node
1489
if isinstance( parent, ObjectTreeNode ):
1490
return parent.can_copy( self._menu_parent_object )
1491
return ((parent is not None) and parent.can_copy( object ))
1493
def _is_cutable ( self, object ):
1494
parent = self._menu_parent_node
1495
if isinstance( parent, ObjectTreeNode ):
1496
can_cut = (parent.can_copy( self._menu_parent_object ) and
1497
parent.can_delete( self._menu_parent_object ))
1499
can_cut = ((parent is not None) and
1500
parent.can_copy( object ) and
1501
parent.can_delete( object ))
1502
return (can_cut and self._menu_node.can_delete_me( object ))
1504
def _is_pasteable ( self, object ):
1505
from pyface.wx.clipboard import clipboard
1507
return self._menu_node.can_add( object, clipboard.object_type )
1509
def _is_deletable ( self, object ):
1510
parent = self._menu_parent_node
1511
if isinstance( parent, ObjectTreeNode ):
1512
can_delete = parent.can_delete( self._menu_parent_object )
1514
can_delete = ((parent is not None) and parent.can_delete( object ))
1515
return (can_delete and self._menu_node.can_delete_me( object ))
1517
def _is_renameable ( self, object ):
1518
parent = self._menu_parent_node
1519
if isinstance( parent, ObjectTreeNode ):
1520
can_rename = parent.can_rename( self._menu_parent_object )
1521
elif parent is not None:
1522
can_rename = parent.can_rename( object )
1525
return (can_rename and self._menu_node.can_rename_me( object ))
1527
#----- Drag and drop event handlers: -------------------------------------------
1529
#---------------------------------------------------------------------------
1530
# Handles a Python object being dropped on the tree:
1531
#---------------------------------------------------------------------------
1533
def wx_dropped_on ( self, x, y, data, drag_result ):
1534
""" Handles a Python object being dropped on the tree.
1536
if isinstance( data, list ):
1539
rc = self.wx_dropped_on( x, y, item, drag_result )
1542
expanded, node, object, nid, point = self._hit_test( wx.Point( x, y ) )
1543
if node is not None:
1544
if drag_result == wx.DragMove:
1545
if not self._is_droppable( node, object, data, False ):
1548
if self._dragging is not None:
1549
data = self._drop_object( node, object, data, False )
1550
if data is not None:
1553
self._undoable_delete(
1554
*self._node_index( self._dragging ) )
1555
self._undoable_append( node, object, data, False )
1559
data = self._drop_object( node, object, data )
1560
if data is not None:
1561
self._undoable_append( node, object, data, False )
1565
to_node, to_object, to_index = self._node_index( nid )
1566
if to_node is not None:
1567
if self._dragging is not None:
1568
data = self._drop_object( node, to_object, data, False )
1569
if data is not None:
1570
from_node, from_object, from_index = \
1571
self._node_index( self._dragging )
1572
if ((to_object is from_object) and
1573
(to_index > from_index)):
1577
self._undoable_delete( from_node, from_object,
1579
self._undoable_insert( to_node, to_object, to_index,
1584
data = self._drop_object( to_node, to_object, data )
1585
if data is not None:
1586
self._undoable_insert( to_node, to_object, to_index,
1593
#---------------------------------------------------------------------------
1594
# Handles a Python object being dragged over the tree:
1595
#---------------------------------------------------------------------------
1597
def wx_drag_over ( self, x, y, data, drag_result ):
1598
""" Handles a Python object being dragged over the tree.
1600
expanded, node, object, nid, point = self._hit_test( wx.Point( x, y ) )
1603
if (node is not None) and (drag_result == wx.DragCopy):
1604
node, object, index = self._node_index( nid )
1607
if ((self._dragging is not None) and
1608
(not self._is_drag_ok( self._dragging, data, object ))):
1611
if ((node is not None) and
1612
self._is_droppable( node, object, data, insert )):
1617
#---------------------------------------------------------------------------
1618
# Makes sure that the target is not the same as or a child of the source
1620
#---------------------------------------------------------------------------
1622
def _is_drag_ok ( self, snid, source, target ):
1623
if (snid is None) or (target is source):
1626
for cnid in self._nodes( snid ):
1627
if not self._is_drag_ok( cnid, self._get_node_data( cnid )[2],
1633
#----- pyface.action 'controller' interface implementation: --------------------
1635
#---------------------------------------------------------------------------
1636
# Adds a menu item to the menu being constructed:
1637
#---------------------------------------------------------------------------
1639
def add_to_menu ( self, menu_item ):
1640
""" Adds a menu item to the menu bar being constructed.
1642
action = menu_item.item.action
1643
self.eval_when( action.enabled_when, menu_item, 'enabled' )
1644
self.eval_when( action.checked_when, menu_item, 'checked' )
1646
#---------------------------------------------------------------------------
1647
# Adds a tool bar item to the tool bar being constructed:
1648
#---------------------------------------------------------------------------
1650
def add_to_toolbar ( self, toolbar_item ):
1651
""" Adds a toolbar item to the toolbar being constructed.
1653
self.add_to_menu( toolbar_item )
1655
#---------------------------------------------------------------------------
1656
# Returns whether the menu action should be defined in the user interface:
1657
#---------------------------------------------------------------------------
1659
def can_add_to_menu ( self, action ):
1660
""" Returns whether the action should be defined in the user interface.
1662
if action.defined_when != '':
1664
if not eval( action.defined_when, globals(), self._context ):
1669
if action.visible_when != '':
1671
if not eval( action.visible_when, globals(), self._context ):
1678
#---------------------------------------------------------------------------
1679
# Returns whether the toolbar action should be defined in the user
1681
#---------------------------------------------------------------------------
1683
def can_add_to_toolbar ( self, action ):
1684
""" Returns whether the toolbar action should be defined in the user
1687
return self.can_add_to_menu( action )
1689
#---------------------------------------------------------------------------
1690
# Performs the action described by a specified Action object:
1691
#---------------------------------------------------------------------------
1693
def perform ( self, action, action_event = None ):
1694
""" Performs the action described by a specified Action object.
1696
self.ui.do_undoable( self._perform, action )
1698
def _perform ( self, action ):
1699
node, object, nid = self._data
1700
method_name = action.action
1702
handler = self.ui.handler
1704
if method_name.find( '.' ) >= 0:
1705
if method_name.find( '(' ) < 0:
1708
eval( method_name, globals(),
1713
'handler': handler } )
1715
# fixme: Should the exception be logged somewhere?
1720
method = getattr( handler, method_name, None )
1721
if method is not None:
1722
method( info, object )
1725
if action.on_perform is not None:
1726
action.on_perform( object )
1728
#----- Menu support methods: ---------------------------------------------------
1730
#---------------------------------------------------------------------------
1731
# Evaluates a condition within a defined context and sets a specified
1732
# object trait based on the (assumed) boolean result:
1733
#---------------------------------------------------------------------------
1735
def eval_when ( self, condition, object, trait ):
1736
""" Evaluates a condition within a defined context, and sets a
1737
specified object trait based on the result, which is assumed to be a
1743
if not eval( condition, globals(), self._context ):
1747
setattr( object, trait, value )
1749
#----- Menu event handlers: ----------------------------------------------------
1751
#---------------------------------------------------------------------------
1752
# Copies the current tree node object to the paste buffer:
1753
#---------------------------------------------------------------------------
1755
def _menu_copy_node ( self ):
1756
""" Copies the current tree node object to the paste buffer.
1758
from pyface.wx.clipboard import clipboard
1760
clipboard.data = self._data[1]
1763
#---------------------------------------------------------------------------
1764
# Cuts the current tree node object into the paste buffer:
1765
#---------------------------------------------------------------------------
1767
def _menu_cut_node ( self ):
1768
""" Cuts the current tree node object into the paste buffer.
1770
from pyface.wx.clipboard import clipboard
1772
node, object, nid = self._data
1773
clipboard.data = object
1775
self._undoable_delete( *self._node_index( nid ) )
1777
#---------------------------------------------------------------------------
1778
# Pastes the current contents of the paste buffer into the current node:
1779
#---------------------------------------------------------------------------
1781
def _menu_paste_node ( self ):
1782
""" Pastes the current contents of the paste buffer into the current
1785
from pyface.wx.clipboard import clipboard
1787
node, object, nid = self._data
1789
self._undoable_append( node, object, clipboard.object_data, False )
1791
#---------------------------------------------------------------------------
1792
# Deletes the current node from the tree:
1793
#---------------------------------------------------------------------------
1795
def _menu_delete_node ( self ):
1796
""" Deletes the current node from the tree.
1798
node, object, nid = self._data
1800
rc = node.confirm_delete( object )
1803
if self.ui.history is None:
1804
# If no undo history, ask user to confirm the delete:
1805
dlg = wx.MessageDialog(
1807
'Are you sure you want to delete %s?' %
1808
node.get_label( object ),
1810
style = wx.OK | wx.CANCEL | wx.ICON_EXCLAMATION )
1811
if dlg.ShowModal() != wx.ID_OK:
1814
self._undoable_delete( *self._node_index( nid ) )
1816
#---------------------------------------------------------------------------
1817
# Renames the current tree node:
1818
#---------------------------------------------------------------------------
1820
def _menu_rename_node ( self ):
1821
""" Renames the current tree node.
1823
node, object, nid = self._data
1825
object_label = ObjectLabel( label = node.get_label( object ) )
1826
if object_label.edit_traits().result:
1827
label = object_label.label.strip()
1829
node.set_label( object, label )
1831
#---------------------------------------------------------------------------
1832
# Adds a new object to the current node:
1833
#---------------------------------------------------------------------------
1835
def _menu_new_node ( self, class_name, prompt = False ):
1836
""" Adds a new object to the current node.
1838
node, object, nid = self._data
1840
new_node, new_class = self._node_for_class_name( class_name )
1841
new_object = new_class()
1842
if (not prompt) or new_object.edit_traits(
1843
parent = self.control, kind = 'livemodal' ).result:
1844
self._undoable_append( node, object, new_object, False )
1846
# Automatically select the new object if editing is being performed:
1847
if self.factory.editable:
1848
self._tree.SelectItem( self._tree.GetLastChild( nid ) )
1850
#-- Model event handlers ---------------------------------------------------
1852
#---------------------------------------------------------------------------
1853
# Handles the children of a node being completely replaced:
1854
#---------------------------------------------------------------------------
1856
def _children_replaced ( self, object, name = '', new = None ):
1857
""" Handles the children of a node being completely replaced.
1860
for expanded, node, nid in self._object_info_for( object, name ):
1861
children = node.get_children( object )
1863
# Only add/remove the changes if the node has already been expanded:
1865
# Delete all current child nodes:
1866
for cnid in self._nodes_for( nid ):
1867
self._delete_node( cnid )
1869
# Add all of the children back in as new nodes:
1870
for child in children:
1871
child, child_node = self._node_for( child )
1872
if child_node is not None:
1873
self._append_node( nid, child_node, child )
1875
# Indicate whether the node has any children now:
1876
tree.SetItemHasChildren( nid, len( children ) > 0 )
1878
# Try to expand the node (if requested):
1879
if node.can_auto_open( object ):
1882
#---------------------------------------------------------------------------
1883
# Handles the children of a node being changed:
1884
#---------------------------------------------------------------------------
1886
def _children_updated ( self, object, name, event ):
1887
""" Handles the children of a node being changed.
1889
# Log the change that was made (removing '_items' from the end of the
1892
self.log_change( self._get_undo_item, object, name, event )
1895
end = start + len( event.removed )
1898
for expanded, node, nid in self._object_info_for( object, name ):
1899
n = len( node.get_children( object ) )
1901
# Only add/remove the changes if the node has already been expanded:
1903
# Remove all of the children that were deleted:
1904
for cnid in self._nodes_for( nid )[ start: end ]:
1905
self._delete_node( cnid )
1907
# Add all of the children that were added:
1908
remaining = n - len( event.removed )
1910
for child in event.added:
1911
child, child_node = self._node_for( child )
1912
if child_node is not None:
1913
insert_index = (start + child_index) if \
1914
(start < remaining) else None
1915
self._insert_node( nid, insert_index, child_node,
1920
# Indicate whether the node has any children now:
1921
tree.SetItemHasChildren( nid, n > 0 )
1923
# Try to expand the node (if requested):
1924
root = tree.GetRootItem()
1925
if node.can_auto_open( object ):
1926
if ( nid != root ) or not self.factory.hide_root:
1929
#---------------------------------------------------------------------------
1930
# Handles the label of an object being changed:
1931
#---------------------------------------------------------------------------
1933
def _label_updated ( self, object, name, label ):
1934
""" Handles the label of an object being changed.
1937
for name2, nid in self._map[ id( object ) ]:
1940
node = self._get_node_data( nid )[1]
1941
self._tree.SetItemText( nid, node.get_label( object ) )
1942
self._update_icon_for_nid ( nid )
1944
#-- UI preference save/restore interface ---------------------------------------
1946
#---------------------------------------------------------------------------
1947
# Restores any saved user preference information associated with the
1949
#---------------------------------------------------------------------------
1951
def restore_prefs ( self, prefs ):
1952
""" Restores any saved user preference information associated with the
1955
if self._is_dock_window:
1956
if isinstance( prefs, dict ):
1957
structure = prefs.get( 'structure' )
1960
self.control.GetSizer().SetStructure( self.control, structure )
1962
#---------------------------------------------------------------------------
1963
# Returns any user preference information associated with the editor:
1964
#---------------------------------------------------------------------------
1966
def save_prefs ( self ):
1967
""" Returns any user preference information associated with the editor.
1969
if self._is_dock_window:
1970
return { 'structure': self.control.GetSizer().GetStructure() }
1974
#-- End UI preference save/restore interface -----------------------------------
1976
#-------------------------------------------------------------------------------
1977
# 'ObjectLabel' class:
1978
#-------------------------------------------------------------------------------
1980
class ObjectLabel ( HasStrictTraits ):
1981
""" An editable label for an object.
1984
#---------------------------------------------------------------------------
1985
# Trait definitions:
1986
#---------------------------------------------------------------------------
1988
# Label to be edited
1991
#---------------------------------------------------------------------------
1992
# Traits view definition:
1993
#---------------------------------------------------------------------------
1995
traits_view = View( 'label',
1996
title = 'Edit Label',
1998
buttons = [ 'OK', 'Cancel' ] )
2000
### EOF #######################################################################