~ubuntu-branches/ubuntu/utopic/python-traitsui/utopic

« back to all changes in this revision

Viewing changes to traitsui/wx/tree_editor.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-09 13:57:39 UTC
  • Revision ID: james.westby@ubuntu.com-20110709135739-x5u20q86huissmn1
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#------------------------------------------------------------------------------
 
2
#
 
3
#  Copyright (c) 2005, Enthought, Inc.
 
4
#  All rights reserved.
 
5
#
 
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
 
10
#
 
11
#  Thanks for using Enthought open source!
 
12
#
 
13
#  Author: David C. Morrill
 
14
#  Date:   12/03/2004
 
15
#
 
16
#------------------------------------------------------------------------------
 
17
 
 
18
""" Defines the tree editor for the wxPython user interface toolkit.
 
19
"""
 
20
 
 
21
#-------------------------------------------------------------------------------
 
22
#  Imports:
 
23
#-------------------------------------------------------------------------------
 
24
 
 
25
import os
 
26
import wx
 
27
import copy
 
28
 
 
29
try:
 
30
    from pyface.wx.drag_and_drop import PythonDropSource, \
 
31
                                                PythonDropTarget
 
32
except:
 
33
    PythonDropSource = PythonDropTarget = None
 
34
 
 
35
from pyface.resource_manager \
 
36
    import resource_manager
 
37
 
 
38
from pyface.image_list \
 
39
    import ImageList
 
40
 
 
41
from traits.api \
 
42
    import HasStrictTraits, Any, Str, Event, TraitError
 
43
 
 
44
from traits.trait_base \
 
45
    import enumerate
 
46
 
 
47
from traitsui.api \
 
48
    import View, TreeNode, ObjectTreeNode, MultiTreeNode, Image
 
49
 
 
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
 
55
 
 
56
from traitsui.undo \
 
57
    import ListUndoItem
 
58
 
 
59
from traitsui.tree_node \
 
60
    import ITreeNodeAdapterBridge
 
61
 
 
62
from traitsui.tree_node \
 
63
    import ITreeNodeAdapterBridge
 
64
 
 
65
from traitsui.menu \
 
66
    import Menu, Action, Separator
 
67
 
 
68
from pyface.api \
 
69
    import ImageResource
 
70
 
 
71
from pyface.dock.api \
 
72
    import DockWindow, DockSizer, DockSection, DockRegion, DockControl
 
73
 
 
74
from constants \
 
75
    import OKColor
 
76
 
 
77
from editor \
 
78
    import Editor
 
79
 
 
80
from helper \
 
81
    import open_fbi, TraitsUIPanel, TraitsUIScrolledPanel
 
82
 
 
83
#-------------------------------------------------------------------------------
 
84
#  Global data:
 
85
#-------------------------------------------------------------------------------
 
86
 
 
87
# Paste buffer for copy/cut/paste operations
 
88
paste_buffer = None
 
89
 
 
90
#-------------------------------------------------------------------------------
 
91
#  The core tree node menu actions:
 
92
#-------------------------------------------------------------------------------
 
93
 
 
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)' )
 
110
 
 
111
#-------------------------------------------------------------------------------
 
112
#  'SimpleEditor' class:
 
113
#-------------------------------------------------------------------------------
 
114
 
 
115
class SimpleEditor ( Editor ):
 
116
    """ Simple style of tree editor.
 
117
    """
 
118
 
 
119
    #---------------------------------------------------------------------------
 
120
    #  Trait definitions:
 
121
    #---------------------------------------------------------------------------
 
122
 
 
123
    # Is the tree editor is scrollable? This value overrides the default.
 
124
    scrollable = True
 
125
 
 
126
    # Allows an external agent to set the tree selection
 
127
    selection = Event
 
128
 
 
129
    # The currently selected object
 
130
    selected = Any
 
131
 
 
132
    # The event fired when a tree node is clicked on:
 
133
    click = Event
 
134
 
 
135
    # The event fired when a tree node is double-clicked on:
 
136
    dclick = Event
 
137
 
 
138
    # The event fired when the application wants to veto an operation:
 
139
    veto = Event
 
140
 
 
141
    #-- Private Traits ---------------------------------------------------------
 
142
 
 
143
    # An icon used by a TreeNode:
 
144
    _icon = Image
 
145
 
 
146
    #---------------------------------------------------------------------------
 
147
    #  Finishes initializing the editor by creating the underlying toolkit
 
148
    #  widget:
 
149
    #---------------------------------------------------------------------------
 
150
 
 
151
    def init ( self, parent ):
 
152
        """ Finishes initializing the editor by creating the underlying toolkit
 
153
            widget.
 
154
        """
 
155
        factory = self.factory
 
156
        style = self._get_style()
 
157
 
 
158
        if factory.editable:
 
159
 
 
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
 
163
 
 
164
                # If this is the editor that defines the trait editor panel:
 
165
                if factory_editor is None:
 
166
 
 
167
                    # Remember which editor has the trait editor in the factory:
 
168
                    factory._editor = self
 
169
 
 
170
                    # Create the trait editor panel:
 
171
                    self.control = TraitsUIPanel( parent, -1 )
 
172
                    self.control._node_ui = self.control._editor_nid = None
 
173
 
 
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:
 
179
 
 
180
                            # If the editor is part of this UI:
 
181
                            if editor.ui is self.ui:
 
182
 
 
183
                                # Then bind it to the trait editor panel:
 
184
                                editor._editor = self.control
 
185
 
 
186
                        # Indicate all pending editors have been processed:
 
187
                        factory._shared_editors = None
 
188
 
 
189
                    # We only needed to build the trait editor panel, so exit:
 
190
                    return
 
191
 
 
192
                # Check to see if the matching trait editor panel has been
 
193
                # created yet:
 
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 )
 
201
                else:
 
202
                    # Otherwise, bind our trait editor panel to the shared one:
 
203
                    self._editor = editor.control
 
204
 
 
205
                # Finally, create only the tree control:
 
206
                self.control = self._tree = tree = wx.TreeCtrl( parent, -1,
 
207
                                                                style = style )
 
208
            else:
 
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,
 
215
                                                       style = style )
 
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 ) )
 
220
 
 
221
                self._editor._node_ui = self._editor._editor_nid = None
 
222
                item = self.item
 
223
                hierarchy_name = editor_name = ''
 
224
                style = 'fixed'
 
225
                name  = item.label
 
226
                if name != '':
 
227
                    hierarchy_name = name + ' Hierarchy'
 
228
                    editor_name    = name + ' Editor'
 
229
                    style          = item.dock
 
230
 
 
231
                splitter.SetSizer( DockSizer( contents =
 
232
                    DockSection( contents = [
 
233
                        DockRegion( contents = [
 
234
                            DockControl( name    = hierarchy_name,
 
235
                                         id      = 'tree',
 
236
                                         control = tree,
 
237
                                         style   = style ) ] ),
 
238
                        DockRegion( contents = [
 
239
                            DockControl( name    = editor_name,
 
240
                                         id      = 'editor',
 
241
                                         control = self._editor,
 
242
                                         style   = style ) ] ) ],
 
243
                        is_row = (factory.orientation == 'horizontal') ) ) )
 
244
        else:
 
245
            # Otherwise, just create the tree control:
 
246
            self.control = self._tree = tree = wx.TreeCtrl( parent, -1,
 
247
                                                            style = style )
 
248
 
 
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 )
 
253
 
 
254
        # Set up the mapping between objects and tree id's:
 
255
        self._map = {}
 
256
 
 
257
        # Initialize the 'undo state' stack:
 
258
        self._undoable = []
 
259
 
 
260
        # Get the tree control id:
 
261
        tid = tree.GetId()
 
262
 
 
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 )
 
266
 
 
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 )
 
278
 
 
279
        # Set up general mouse events
 
280
        wx.EVT_MOTION(tree, self._on_hover)
 
281
 
 
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' )
 
287
 
 
288
        # Set up the drag and drop target:
 
289
        if PythonDropTarget is not None:
 
290
            tree.SetDropTarget( PythonDropTarget( self ) )
 
291
 
 
292
    #---------------------------------------------------------------------------
 
293
    #  Disposes of the contents of an editor:
 
294
    #---------------------------------------------------------------------------
 
295
 
 
296
    def dispose ( self ):
 
297
        """ Disposes of the contents of an editor.
 
298
        """
 
299
        tree = self._tree
 
300
        if tree is not None:
 
301
            id = tree.GetId()
 
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 )
 
314
 
 
315
            nid = self._tree.GetRootItem()
 
316
            if nid.IsOk():
 
317
                self._delete_node( nid )
 
318
 
 
319
        super( SimpleEditor, self ).dispose()
 
320
 
 
321
    #---------------------------------------------------------------------------
 
322
    #  Handles the 'selection' trait being changed:
 
323
    #---------------------------------------------------------------------------
 
324
 
 
325
    def _selection_changed ( self, selection ):
 
326
        """ Handles the **selection** event.
 
327
        """
 
328
        try:
 
329
            self._tree.SelectItem( self._object_info( selection )[2] )
 
330
        except:
 
331
            pass
 
332
 
 
333
    #---------------------------------------------------------------------------
 
334
    #  Handles the 'selected' trait being changed:
 
335
    #---------------------------------------------------------------------------
 
336
 
 
337
    def _selected_changed ( self, selected ):
 
338
        """ Handles the **selected** trait being changed.
 
339
        """
 
340
        if not self._no_update_selected:
 
341
            self._selection_changed( selected )
 
342
 
 
343
    #---------------------------------------------------------------------------
 
344
    #  Handles the 'veto' event being fired:
 
345
    #---------------------------------------------------------------------------
 
346
 
 
347
    def _veto_changed ( self ):
 
348
        """ Handles the 'veto' event being fired.
 
349
        """
 
350
        self._veto = True
 
351
 
 
352
    #---------------------------------------------------------------------------
 
353
    #  Returns the style settings used for displaying the wx tree:
 
354
    #---------------------------------------------------------------------------
 
355
 
 
356
    def _get_style ( self ):
 
357
        """ Returns the style settings used for displaying the wx tree.
 
358
        """
 
359
        factory = self.factory
 
360
        style   = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN
 
361
 
 
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
 
366
 
 
367
        if factory.hide_root:
 
368
            style |= (wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT)
 
369
 
 
370
        if factory.selection_mode != 'single':
 
371
            style |= wx.TR_MULTIPLE | wx.TR_EXTENDED
 
372
 
 
373
 
 
374
        return style
 
375
 
 
376
    #---------------------------------------------------------------------------
 
377
    #  Handles the user entering input data in the edit control:
 
378
    #---------------------------------------------------------------------------
 
379
 
 
380
    def update_object ( self, event ):
 
381
        """ Handles the user entering input data in the edit control.
 
382
        """
 
383
        try:
 
384
            self.value = self._get_value()
 
385
            self.control.SetBackgroundColour( OKColor )
 
386
            self.control.Refresh()
 
387
        except TraitError, excp:
 
388
            pass
 
389
 
 
390
    #---------------------------------------------------------------------------
 
391
    #  Saves the current 'expanded' state of all tree nodes:
 
392
    #---------------------------------------------------------------------------
 
393
 
 
394
    def _save_state ( self ):
 
395
        tree  = self._tree
 
396
        nid   = tree.GetRootItem()
 
397
        state = {}
 
398
        if nid.IsOk():
 
399
            nodes_to_do = [ nid ]
 
400
            while nodes_to_do:
 
401
                node = nodes_to_do.pop()
 
402
                data = self._get_node_data( node )
 
403
                try:
 
404
                    is_expanded = tree.IsExpanded( node )
 
405
                except:
 
406
                    is_expanded = True
 
407
                state[ hash( data[-1] ) ] = ( data[0], is_expanded )
 
408
                for cnid in self._nodes( node ):
 
409
                    nodes_to_do.append( cnid )
 
410
        return state
 
411
 
 
412
    #---------------------------------------------------------------------------
 
413
    #  Restores the 'expanded' state of all tree nodes:
 
414
    #---------------------------------------------------------------------------
 
415
 
 
416
    def _restore_state ( self, state ):
 
417
        if not state:
 
418
            return
 
419
        tree = self._tree
 
420
        nid  = tree.GetRootItem()
 
421
        if nid.IsOk():
 
422
            nodes_to_do = [ nid ]
 
423
            while nodes_to_do:
 
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] )
 
428
                    if key in state:
 
429
                        was_expanded, current_state = state[ key ]
 
430
                        if was_expanded:
 
431
                            self._expand_node( cnid )
 
432
                            if current_state:
 
433
                                tree.Expand( cnid )
 
434
                            nodes_to_do.append( cnid )
 
435
 
 
436
    #---------------------------------------------------------------------------
 
437
    #  Expands all nodes starting from the current selection:
 
438
    #---------------------------------------------------------------------------
 
439
 
 
440
    def expand_all ( self ):
 
441
        """ Expands all nodes, starting from the selected node.
 
442
        """
 
443
        tree = self._tree
 
444
 
 
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 )
 
450
                tree.Expand( nid )
 
451
 
 
452
        nid = tree.GetSelection()
 
453
        if nid.IsOk():
 
454
            nodes_to_do = [ nid ]
 
455
            while nodes_to_do:
 
456
                node = nodes_to_do.pop()
 
457
                _do_expand( node )
 
458
                for n in self._nodes( node ):
 
459
                    _do_expand( n )
 
460
                    nodes_to_do.append( n )
 
461
 
 
462
    #---------------------------------------------------------------------------
 
463
    #  Expands from the specified node the specified number of sub-levels:
 
464
    #---------------------------------------------------------------------------
 
465
 
 
466
    def expand_levels ( self, nid, levels, expand = True ):
 
467
        """ Expands from the specified node the specified number of sub-levels.
 
468
        """
 
469
        if levels > 0:
 
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 )
 
474
                if expand:
 
475
                    self._tree.Expand( nid )
 
476
                for cnid in self._nodes( nid ):
 
477
                    self.expand_levels( cnid, levels - 1 )
 
478
 
 
479
    #---------------------------------------------------------------------------
 
480
    #  Updates the editor when the object trait changes external to the editor:
 
481
    #---------------------------------------------------------------------------
 
482
 
 
483
    def update_editor ( self ):
 
484
        """ Updates the editor when the object trait changes externally to the
 
485
            editor.
 
486
        """
 
487
        tree        = self._tree
 
488
        saved_state = {}
 
489
        if tree is not None:
 
490
            nid = tree.GetRootItem()
 
491
            if nid.IsOk():
 
492
                self._delete_node( nid )
 
493
            object, node = self._node_for( self.value )
 
494
            if node is not None:
 
495
                icon = self._get_icon( node, object )
 
496
                self._root_nid = nid = tree.AddRoot( node.get_label( object ),
 
497
                                                     icon, icon )
 
498
                self._map[ id( object ) ] = [ ( node.get_children_id( object ),
 
499
                                                nid ) ]
 
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:
 
506
                        tree.Expand( nid )
 
507
                        tree.SelectItem( nid )
 
508
                        self._on_tree_sel_changed()
 
509
 
 
510
                self.expand_levels( nid, self.factory.auto_open, False )
 
511
 
 
512
                # It seems like in some cases, an explicit Refresh is needed to
 
513
                # trigger a screen update:
 
514
                tree.Refresh()
 
515
 
 
516
            # fixme: Clear the current editor (if any)...
 
517
 
 
518
    #---------------------------------------------------------------------------
 
519
    #  Returns the editor's control for indicating error status:
 
520
    #---------------------------------------------------------------------------
 
521
 
 
522
    def get_error_control ( self ):
 
523
        """ Returns the editor's control for indicating error status.
 
524
        """
 
525
        return self._tree
 
526
 
 
527
    #---------------------------------------------------------------------------
 
528
    #  Appends a new node to the specified node:
 
529
    #---------------------------------------------------------------------------
 
530
 
 
531
    def _append_node ( self, nid, node, object ):
 
532
        """ Appends a new node to the specified node.
 
533
        """
 
534
        return self._insert_node( nid, None, node, object )
 
535
 
 
536
    #---------------------------------------------------------------------------
 
537
    #  Inserts a new node before a specified index into the children of the
 
538
    #  specified node:
 
539
    #---------------------------------------------------------------------------
 
540
 
 
541
    def _insert_node ( self, nid, index, node, object ):
 
542
        """ Inserts a new node before a specified index into the children of the
 
543
            specified node.
 
544
        """
 
545
        tree  = self._tree
 
546
        icon  = self._get_icon( node, object )
 
547
        label = node.get_label( object )
 
548
        if index is None:
 
549
            cnid = tree.AppendItem( nid, label, icon, icon )
 
550
        else:
 
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 )
 
558
 
 
559
        # Automatically expand the new node (if requested):
 
560
        if has_children and node.can_auto_open( object ):
 
561
            tree.Expand( cnid )
 
562
 
 
563
        # Return the newly created node:
 
564
        return cnid
 
565
 
 
566
    #---------------------------------------------------------------------------
 
567
    #  Deletes a specified tree node and all its children:
 
568
    #---------------------------------------------------------------------------
 
569
 
 
570
    def _delete_node ( self, nid ):
 
571
        """ Deletes a specified tree node and all of its children.
 
572
        """
 
573
        for cnid in self._nodes_for( nid ):
 
574
            self._delete_node( cnid )
 
575
 
 
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 ):
 
580
            if nid == info[1]:
 
581
                del object_info[i]
 
582
                break
 
583
 
 
584
        if len( object_info ) == 0:
 
585
            self._remove_listeners( node, object )
 
586
            del self._map[ id_object ]
 
587
 
 
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:
 
592
        self._locked = True
 
593
        self._tree.Delete( nid )
 
594
        self._locked = False
 
595
 
 
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):
 
598
            self._clear_editor()
 
599
 
 
600
    #---------------------------------------------------------------------------
 
601
    #  Expands the contents of a specified node (if required):
 
602
    #---------------------------------------------------------------------------
 
603
 
 
604
    def _expand_node ( self, nid ):
 
605
        """ Expands the contents of a specified node (if required).
 
606
        """
 
607
        expanded, node, object = self._get_node_data( nid )
 
608
 
 
609
        # Lazily populate the item's children:
 
610
        if not expanded:
 
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 )
 
615
 
 
616
            # Indicate the item is now populated:
 
617
            self._set_node_data( nid, ( True, node, object) )
 
618
 
 
619
    #---------------------------------------------------------------------------
 
620
    #  Returns each of the child nodes of a specified node id:
 
621
    #---------------------------------------------------------------------------
 
622
 
 
623
    def _nodes ( self, nid ):
 
624
        """ Returns each of the child nodes of a specified node.
 
625
        """
 
626
        tree         = self._tree
 
627
        cnid, cookie = tree.GetFirstChild( nid )
 
628
        while cnid.IsOk():
 
629
            yield cnid
 
630
            cnid, cookie = tree.GetNextChild( nid, cookie )
 
631
 
 
632
    def _nodes_for ( self, nid ):
 
633
        """ Returns all child node ids of a specified node id.
 
634
        """
 
635
        return [ cnid for cnid in self._nodes( nid ) ]
 
636
 
 
637
    #---------------------------------------------------------------------------
 
638
    #  Return the index of a specified node id within its parent:
 
639
    #---------------------------------------------------------------------------
 
640
 
 
641
    def _node_index ( self, nid ):
 
642
        pnid = self._tree.GetItemParent( nid )
 
643
        if not pnid.IsOk():
 
644
            return ( None, None, None )
 
645
 
 
646
        for i, cnid in enumerate( self._nodes( pnid ) ):
 
647
            if cnid == nid:
 
648
                ignore, pnode, pobject = self._get_node_data( pnid )
 
649
 
 
650
                return ( pnode, pobject, i )
 
651
 
 
652
    #---------------------------------------------------------------------------
 
653
    #  Returns whether a specified object has any children:
 
654
    #---------------------------------------------------------------------------
 
655
 
 
656
    def _has_children ( self, node, object ):
 
657
        """ Returns whether a specified object has any children.
 
658
        """
 
659
        return (node.allows_children( object ) and node.has_children( object ))
 
660
 
 
661
    #---------------------------------------------------------------------------
 
662
    #  Returns whether a given object is droppable on the node:
 
663
    #---------------------------------------------------------------------------
 
664
 
 
665
    def _is_droppable ( self, node, object, add_object, for_insert ):
 
666
        """ Returns whether a given object is droppable on the node.
 
667
        """
 
668
        if for_insert and (not node.can_insert( object )):
 
669
            return False
 
670
 
 
671
        return node.can_add( object, add_object )
 
672
 
 
673
    #---------------------------------------------------------------------------
 
674
    #  Returns a droppable version of a specified object:
 
675
    #---------------------------------------------------------------------------
 
676
 
 
677
    def _drop_object ( self, node, object, dropped_object, make_copy = True ):
 
678
        """ Returns a droppable version of a specified object.
 
679
        """
 
680
        new_object = node.drop_object( object, dropped_object )
 
681
        if (new_object is not dropped_object) or (not make_copy):
 
682
            return new_object
 
683
 
 
684
        return copy.deepcopy( new_object )
 
685
 
 
686
    #---------------------------------------------------------------------------
 
687
    #  Returns the icon index for the specified object:
 
688
    #---------------------------------------------------------------------------
 
689
 
 
690
    def _get_icon ( self, node, object, is_expanded = False ):
 
691
        """ Returns the index of the specified object icon.
 
692
        """
 
693
        if self._image_list is None:
 
694
            return -1
 
695
 
 
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
 
701
            else:
 
702
                if icon_name[:1] == '<':
 
703
                    icon_name = icon_name[1:-1]
 
704
                    path      = self
 
705
                else:
 
706
                    path = node.get_icon_path( object )
 
707
                    if isinstance( path, basestring ):
 
708
                        path = [ path, node ]
 
709
                    else:
 
710
                        path.append( node )
 
711
 
 
712
                reference = resource_manager.locate_image( icon_name, path )
 
713
                if reference is None:
 
714
                    return -1
 
715
 
 
716
                file_name = reference.filename
 
717
 
 
718
        # If it is an ImageResource, get its file name directly:
 
719
        if isinstance( icon_name, ImageResource ):
 
720
            file_name = icon_name
 
721
 
 
722
        return self._image_list.GetIndex( file_name )
 
723
 
 
724
    #---------------------------------------------------------------------------
 
725
    #  Adds the event listeners for a specified object:
 
726
    #---------------------------------------------------------------------------
 
727
 
 
728
    def _add_listeners ( self, node, object ):
 
729
        """ Adds the event listeners for a specified object.
 
730
        """
 
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)
 
734
 
 
735
        node.when_label_changed( object, self._label_updated, False )
 
736
 
 
737
    #---------------------------------------------------------------------------
 
738
    #  Removes any event listeners from a specified object:
 
739
    #---------------------------------------------------------------------------
 
740
 
 
741
    def _remove_listeners ( self, node, object ):
 
742
        """ Removes any event listeners from a specified object.
 
743
        """
 
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 )
 
747
 
 
748
        node.when_label_changed( object, self._label_updated, True )
 
749
 
 
750
    #---------------------------------------------------------------------------
 
751
    #  Returns the tree node data for a specified object in the form
 
752
    #  ( expanded, node, nid ):
 
753
    #---------------------------------------------------------------------------
 
754
 
 
755
    def _object_info ( self, object, name = '' ):
 
756
        """ Returns the tree node data for a specified object in the form
 
757
            ( expanded, node, nid ).
 
758
        """
 
759
        info = self._map[ id( object ) ]
 
760
        for name2, nid in info:
 
761
            if name == name2:
 
762
                break
 
763
        else:
 
764
            nid = info[0][1]
 
765
 
 
766
        expanded, node, ignore = self._get_node_data( nid )
 
767
 
 
768
        return ( expanded, node, nid )
 
769
 
 
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 ), ... ].
 
773
        """
 
774
        result = []
 
775
        for name2, nid in self._map[ id( object ) ]:
 
776
            if name == name2:
 
777
                expanded, node, ignore = self._get_node_data( nid )
 
778
                result.append( ( expanded, node, nid ) )
 
779
 
 
780
        return result
 
781
 
 
782
    #---------------------------------------------------------------------------
 
783
    #  Returns the TreeNode associated with a specified object:
 
784
    #---------------------------------------------------------------------------
 
785
 
 
786
    def _node_for ( self, object ):
 
787
        """ Returns the TreeNode associated with a specified object.
 
788
        """
 
789
        if ((type( object ) is tuple) and (len( object ) == 2) and
 
790
            isinstance( object[1], TreeNode )):
 
791
            return object
 
792
 
 
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 ) ]
 
797
 
 
798
        # If only one found, we're done, return it:
 
799
        if len( nodes ) == 1:
 
800
            return ( object, nodes[0] )
 
801
 
 
802
        # If none found, try to create an adapted node for the object:
 
803
        if len( nodes ) == 0:
 
804
           return ( object, ITreeNodeAdapterBridge( adapter = object ) )
 
805
 
 
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 ]
 
810
 
 
811
        # If only one left, then return that node:
 
812
        if len( nodes ) == 1:
 
813
            return ( object, nodes[0] )
 
814
 
 
815
        # Otherwise, return a MultiTreeNode based on all selected nodes...
 
816
 
 
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':
 
819
        root_node = None
 
820
        for i, node in enumerate( nodes ):
 
821
            if node.get_children_id( object ) == '':
 
822
                root_node = node
 
823
                del nodes[i]
 
824
                break
 
825
        else:
 
826
            root_node = nodes[0]
 
827
 
 
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 ] )
 
832
 
 
833
        # Otherwise create one, cache it, and return it:
 
834
        factory.multi_nodes[ key ] = multi_node = MultiTreeNode(
 
835
                                                       root_node = root_node,
 
836
                                                       nodes     = nodes )
 
837
 
 
838
        return ( object, multi_node )
 
839
 
 
840
    #---------------------------------------------------------------------------
 
841
    #  Returns the TreeNode associated with a specified class:
 
842
    #---------------------------------------------------------------------------
 
843
 
 
844
    def _node_for_class ( self, klass ):
 
845
        """ Returns the TreeNode associated with a specified class.
 
846
        """
 
847
        for node in self.factory.nodes:
 
848
            if issubclass( klass, tuple( node.node_for ) ):
 
849
                return node
 
850
        return None
 
851
 
 
852
    #---------------------------------------------------------------------------
 
853
    #  Returns the node and class associated with a specified class name:
 
854
    #---------------------------------------------------------------------------
 
855
 
 
856
    def _node_for_class_name ( self, class_name ):
 
857
        """ Returns the node and class associated with a specified class name.
 
858
        """
 
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 )
 
864
 
 
865
    #---------------------------------------------------------------------------
 
866
    #  Updates the icon for a specified node:
 
867
    #---------------------------------------------------------------------------
 
868
 
 
869
    def _update_icon ( self, event, is_expanded ):
 
870
        """ Updates the icon for a specified node.
 
871
        """
 
872
        self._update_icon_for_nid( event.GetItem() )
 
873
 
 
874
    #---------------------------------------------------------------------------
 
875
    #  Updates the icon for a specified node id:
 
876
    #---------------------------------------------------------------------------
 
877
 
 
878
    def _update_icon_for_nid ( self, nid ):
 
879
        """ Updates the icon for a specified node ID.
 
880
        """
 
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 )
 
886
 
 
887
    #---------------------------------------------------------------------------
 
888
    #  Unpacks an event to see whether a tree item was involved:
 
889
    #---------------------------------------------------------------------------
 
890
 
 
891
    def _unpack_event ( self, event ):
 
892
        """ Unpacks an event to see whether a tree item was involved.
 
893
        """
 
894
        try:
 
895
            point = event.GetPosition()
 
896
        except:
 
897
            point = event.GetPoint()
 
898
 
 
899
        nid = None
 
900
        if hasattr( event, 'GetItem' ):
 
901
            nid = event.GetItem()
 
902
 
 
903
        if (nid is None) or (not nid.IsOk()):
 
904
            nid, flags = self._tree.HitTest( point )
 
905
 
 
906
        if nid.IsOk():
 
907
            return self._get_node_data( nid ) + ( nid, point )
 
908
 
 
909
        return ( None, None, None, nid, point )
 
910
 
 
911
    #---------------------------------------------------------------------------
 
912
    #  Returns information about the node at a specified point:
 
913
    #---------------------------------------------------------------------------
 
914
 
 
915
    def _hit_test ( self, point ):
 
916
        """ Returns information about the node at a specified point.
 
917
        """
 
918
        nid, flags = self._tree.HitTest( point )
 
919
        if nid.IsOk():
 
920
            return self._get_node_data( nid ) + ( nid, point )
 
921
        return ( None, None, None, nid, point )
 
922
 
 
923
    #---------------------------------------------------------------------------
 
924
    #  Begins an 'undoable' transaction:
 
925
    #---------------------------------------------------------------------------
 
926
 
 
927
    def _begin_undo ( self ):
 
928
        """ Begins an "undoable" transaction.
 
929
        """
 
930
        ui = self.ui
 
931
        self._undoable.append( ui._undoable )
 
932
        if (ui._undoable == -1) and (ui.history is not None):
 
933
            ui._undoable = ui.history.now
 
934
 
 
935
    #---------------------------------------------------------------------------
 
936
    #  Ends an 'undoable' transaction:
 
937
    #---------------------------------------------------------------------------
 
938
 
 
939
    def _end_undo ( self ):
 
940
        if self._undoable.pop() == -1:
 
941
            self.ui._undoable = -1
 
942
 
 
943
    #---------------------------------------------------------------------------
 
944
    #  Gets an 'undo' item for a change made to a node's children:
 
945
    #---------------------------------------------------------------------------
 
946
 
 
947
    def _get_undo_item ( self, object, name, event ):
 
948
        return ListUndoItem( object  = object,
 
949
                             name    = name,
 
950
                             index   = event.index,
 
951
                             added   = event.added,
 
952
                             removed = event.removed )
 
953
 
 
954
    #---------------------------------------------------------------------------
 
955
    #  Performs an undoable 'append' operation:
 
956
    #---------------------------------------------------------------------------
 
957
 
 
958
    def _undoable_append ( self, node, object, data, make_copy = True ):
 
959
        """ Performs an undoable append operation.
 
960
        """
 
961
        try:
 
962
            self._begin_undo()
 
963
            if make_copy:
 
964
                data = copy.deepcopy( data )
 
965
            node.append_child( object, data )
 
966
        finally:
 
967
            self._end_undo()
 
968
 
 
969
    #---------------------------------------------------------------------------
 
970
    #  Performs an undoable 'insert' operation:
 
971
    #---------------------------------------------------------------------------
 
972
 
 
973
    def _undoable_insert ( self, node, object, index, data, make_copy = True ):
 
974
        """ Performs an undoable insert operation.
 
975
        """
 
976
        try:
 
977
            self._begin_undo()
 
978
            if make_copy:
 
979
                data = copy.deepcopy( data )
 
980
            node.insert_child( object, index, data )
 
981
        finally:
 
982
            self._end_undo()
 
983
 
 
984
    #---------------------------------------------------------------------------
 
985
    #  Performs an undoable 'delete' operation:
 
986
    #---------------------------------------------------------------------------
 
987
 
 
988
    def _undoable_delete ( self, node, object, index ):
 
989
        """ Performs an undoable delete operation.
 
990
        """
 
991
        try:
 
992
            self._begin_undo()
 
993
            node.delete_child( object, index )
 
994
        finally:
 
995
            self._end_undo()
 
996
 
 
997
    #---------------------------------------------------------------------------
 
998
    #  Gets the id associated with a specified object (if any):
 
999
    #---------------------------------------------------------------------------
 
1000
 
 
1001
    def _get_object_nid ( self, object, name = '' ):
 
1002
        """ Gets the ID associated with a specified object (if any).
 
1003
        """
 
1004
        info = self._map.get( id( object ) )
 
1005
        if info is None:
 
1006
            return None
 
1007
 
 
1008
        for name2, nid in info:
 
1009
            if name == name2:
 
1010
                return nid
 
1011
        else:
 
1012
            return info[0][1]
 
1013
 
 
1014
    #---------------------------------------------------------------------------
 
1015
    #  Clears the current editor pane (if any):
 
1016
    #---------------------------------------------------------------------------
 
1017
 
 
1018
    def _clear_editor ( self ):
 
1019
        """ Clears the current editor pane (if any).
 
1020
        """
 
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
 
1026
 
 
1027
    #---------------------------------------------------------------------------
 
1028
    #  Gets/Sets the node specific data:
 
1029
    #---------------------------------------------------------------------------
 
1030
 
 
1031
    def _get_node_data ( self, nid ):
 
1032
        """ Gets the node specific data.
 
1033
        """
 
1034
        if nid == self._root_nid:
 
1035
            return self._root_nid_data
 
1036
 
 
1037
        return self._tree.GetPyData( nid )
 
1038
 
 
1039
    def _set_node_data ( self, nid, data ):
 
1040
        """ Sets the node specific data.
 
1041
        """
 
1042
        if nid == self._root_nid:
 
1043
            self._root_nid_data = data
 
1044
        else:
 
1045
            self._tree.SetPyData( nid, data )
 
1046
 
 
1047
#----- User callable methods: --------------------------------------------------
 
1048
 
 
1049
    #---------------------------------------------------------------------------
 
1050
    #  Gets the object associated with a specified node:
 
1051
    #---------------------------------------------------------------------------
 
1052
 
 
1053
    def get_object ( self, nid ):
 
1054
        """ Gets the object associated with a specified node.
 
1055
        """
 
1056
        return self._get_node_data( nid )[2]
 
1057
 
 
1058
    #---------------------------------------------------------------------------
 
1059
    #  Returns the object which is the immmediate parent of a specified object
 
1060
    #  in the tree:
 
1061
    #---------------------------------------------------------------------------
 
1062
 
 
1063
    def get_parent ( self, object, name = '' ):
 
1064
        """ Returns the object that is the immmediate parent of a specified
 
1065
            object in the tree.
 
1066
        """
 
1067
        nid = self._get_object_nid( object, name )
 
1068
        if nid is not None:
 
1069
            pnid = self._tree.GetItemParent( nid )
 
1070
            if pnid.IsOk():
 
1071
                return self.get_object( pnid )
 
1072
 
 
1073
        return None
 
1074
 
 
1075
    #---------------------------------------------------------------------------
 
1076
    #  Returns the node associated with a specified object:
 
1077
    #---------------------------------------------------------------------------
 
1078
 
 
1079
    def get_node ( self, object, name = '' ):
 
1080
        """ Returns the node associated with a specified object.
 
1081
        """
 
1082
        nid = self._get_object_nid( object, name )
 
1083
        if nid is not None:
 
1084
            return self._get_node_data( nid )[1]
 
1085
 
 
1086
        return None
 
1087
 
 
1088
    #-- Tree Event Handlers: ---------------------------------------------------
 
1089
 
 
1090
    #---------------------------------------------------------------------------
 
1091
    #  Handles a tree node expanding:
 
1092
    #---------------------------------------------------------------------------
 
1093
 
 
1094
    def _on_tree_item_expanding ( self, event ):
 
1095
        """ Handles a tree node expanding.
 
1096
        """
 
1097
        if self._veto:
 
1098
            self._veto = False
 
1099
            event.Veto()
 
1100
            return
 
1101
 
 
1102
        nid  = event.GetItem()
 
1103
        tree = self._tree
 
1104
        expanded, node, object = self._get_node_data( nid )
 
1105
 
 
1106
        # If 'auto_close' requested for this node type, close all of the node's
 
1107
        # siblings:
 
1108
        if node.can_auto_close( object ):
 
1109
            snid = nid
 
1110
            while True:
 
1111
                snid = tree.GetPrevSibling( snid )
 
1112
                if not snid.IsOk():
 
1113
                    break
 
1114
                tree.Collapse( snid )
 
1115
            snid = nid
 
1116
            while True:
 
1117
                snid = tree.GetNextSibling( snid )
 
1118
                if not snid.IsOk():
 
1119
                    break
 
1120
                tree.Collapse( snid )
 
1121
 
 
1122
        # Expand the node (i.e. populate its children if they are not there
 
1123
        # yet):
 
1124
        self._expand_node( nid )
 
1125
 
 
1126
    #---------------------------------------------------------------------------
 
1127
    #  Handles a tree node being expanded:
 
1128
    #---------------------------------------------------------------------------
 
1129
 
 
1130
    def _on_tree_item_expanded ( self, event ):
 
1131
        """ Handles a tree node being expanded.
 
1132
        """
 
1133
        self._update_icon( event, True )
 
1134
 
 
1135
    #---------------------------------------------------------------------------
 
1136
    #  Handles a tree node collapsing:
 
1137
    #---------------------------------------------------------------------------
 
1138
 
 
1139
    def _on_tree_item_collapsing ( self, event ):
 
1140
        """ Handles a tree node collapsing.
 
1141
        """
 
1142
        if self._veto:
 
1143
            self._veto = False
 
1144
            event.Veto()
 
1145
 
 
1146
    #---------------------------------------------------------------------------
 
1147
    #  Handles a tree node being collapsed:
 
1148
    #---------------------------------------------------------------------------
 
1149
 
 
1150
    def _on_tree_item_collapsed ( self, event ):
 
1151
        """ Handles a tree node being collapsed.
 
1152
        """
 
1153
        self._update_icon( event, False )
 
1154
 
 
1155
    #---------------------------------------------------------------------------
 
1156
    #  Handles a tree node being selected:
 
1157
    #---------------------------------------------------------------------------
 
1158
 
 
1159
    def _on_tree_sel_changed ( self, event = None ):
 
1160
        """ Handles a tree node being selected.
 
1161
        """
 
1162
        if self._locked:
 
1163
            return
 
1164
 
 
1165
        # Get the new selection:
 
1166
        object      = None
 
1167
        not_handled = True
 
1168
        nids        = self._tree.GetSelections()
 
1169
 
 
1170
        selected = []
 
1171
        for nid in nids:
 
1172
            if not nid.IsOk():
 
1173
                continue
 
1174
 
 
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)
 
1178
 
 
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?)
 
1182
            if nid == nids[0]:
 
1183
                object = sel_object
 
1184
                not_handled = node.select( object )
 
1185
 
 
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
 
1191
        else:
 
1192
            self._no_update_selected = True
 
1193
            self.selected = selected
 
1194
            self._no_update_selected = False
 
1195
 
 
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 )
 
1200
 
 
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:
 
1205
            editor.Freeze()
 
1206
            self._clear_editor()
 
1207
 
 
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 )
 
1212
                if view is None:
 
1213
                    view = object.trait_view()
 
1214
 
 
1215
                if (self.ui.history is not None) or (view.kind == 'subpanel'):
 
1216
                    ui = object.edit_traits( parent = editor,
 
1217
                                             view   = view,
 
1218
                                             kind   = 'subpanel' )
 
1219
                else:
 
1220
                    # Otherwise, just set up our own new one:
 
1221
                    ui = object.edit_traits( parent = editor,
 
1222
                                             view   = view,
 
1223
                                             kind   = 'panel' )
 
1224
 
 
1225
                # Make our UI the parent of the new UI:
 
1226
                ui.parent = self.ui
 
1227
 
 
1228
                # Remember the new editor's UI and node info:
 
1229
                editor._node_ui    = ui
 
1230
                editor._editor_nid = nid
 
1231
 
 
1232
                # Finish setting up the editor:
 
1233
                sizer = wx.BoxSizer( wx.VERTICAL )
 
1234
                sizer.Add( ui.control, 1, wx.EXPAND )
 
1235
                editor.SetSizer( sizer )
 
1236
                editor.Layout()
 
1237
 
 
1238
            # fixme: The following is a hack needed to make the editor window
 
1239
            # (which is a wx.ScrolledWindow) recognize that its contents have
 
1240
            # been changed:
 
1241
            dx, dy = editor.GetSize()
 
1242
            editor.SetSize( wx.Size( dx, dy + 1 ) )
 
1243
            editor.SetSize( wx.Size( dx, dy ) )
 
1244
 
 
1245
            # Allow the editor view to show any changes that have occurred:
 
1246
            editor.Thaw()
 
1247
 
 
1248
    def _on_hover(self, event):
 
1249
        """ Handles the mouse moving over a tree node
 
1250
        """
 
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 )
 
1256
                self._veto = True
 
1257
        elif self.factory.on_hover is not None:
 
1258
            self.ui.evaluate( self.factory.on_hover, None )
 
1259
 
 
1260
        # allow other events to be processed
 
1261
        event.Skip(True)
 
1262
 
 
1263
 
 
1264
 
 
1265
    #---------------------------------------------------------------------------
 
1266
    #  Handles a tree item being activated (i.e. double clicked):
 
1267
    #---------------------------------------------------------------------------
 
1268
 
 
1269
    def _on_tree_item_activated ( self, event ):
 
1270
        """ Handles a tree item being activated (i.e. double-clicked).
 
1271
        """
 
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 )
 
1276
                self._veto = True
 
1277
        else:
 
1278
            self._veto = True
 
1279
 
 
1280
        # Fire the 'dclick' event with the clicked on object as value:
 
1281
        self.dclick = object
 
1282
 
 
1283
    #---------------------------------------------------------------------------
 
1284
    #  Handles the user starting to edit a tree node label:
 
1285
    #---------------------------------------------------------------------------
 
1286
 
 
1287
    def _on_tree_begin_label_edit ( self, event ):
 
1288
        """ Handles the user starting to edit a tree node label.
 
1289
        """
 
1290
        item       = event.GetItem()
 
1291
        parent     = self._tree.GetItemParent( item )
 
1292
        can_rename = True
 
1293
        if parent.IsOk():
 
1294
            expanded, node, object = self._get_node_data( parent )
 
1295
            can_rename = node.can_rename( object )
 
1296
 
 
1297
        if can_rename:
 
1298
            expanded, node, object = self._get_node_data( item )
 
1299
            if node.can_rename_me( object ):
 
1300
                return
 
1301
 
 
1302
        event.Veto()
 
1303
 
 
1304
    #---------------------------------------------------------------------------
 
1305
    #  Handles the user completing tree node label editing:
 
1306
    #---------------------------------------------------------------------------
 
1307
 
 
1308
    def _on_tree_end_label_edit ( self, event ):
 
1309
        """ Handles the user completing tree node label editing.
 
1310
        """
 
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:
 
1316
            try:
 
1317
                node.set_label( object, label )
 
1318
                return
 
1319
            except:
 
1320
                pass
 
1321
        event.Veto()
 
1322
 
 
1323
    #---------------------------------------------------------------------------
 
1324
    #  Handles a drag operation starting on a tree node:
 
1325
    #---------------------------------------------------------------------------
 
1326
 
 
1327
    def _on_tree_begin_drag ( self, event ):
 
1328
        """ Handles a drag operation starting on a tree node.
 
1329
        """
 
1330
        if PythonDropSource is not None:
 
1331
            expanded, node, object, nid, point = self._unpack_event( event )
 
1332
            if node is not None:
 
1333
                try:
 
1334
                    self._dragging = nid
 
1335
                    PythonDropSource( self._tree,
 
1336
                                      node.get_drag_object( object ) )
 
1337
                finally:
 
1338
                    self._dragging = None
 
1339
 
 
1340
    #---------------------------------------------------------------------------
 
1341
    #  Handles a tooltip request on a tree node:
 
1342
    #---------------------------------------------------------------------------
 
1343
 
 
1344
    def _on_tree_item_gettooltip ( self, event ):
 
1345
        """ Handles a tooltip request on a tree node.
 
1346
        """
 
1347
        nid = event.GetItem()
 
1348
        if nid.IsOk():
 
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 )
 
1353
                if tooltip != '':
 
1354
                    event.SetToolTip( tooltip )
 
1355
 
 
1356
        event.Skip()
 
1357
 
 
1358
    #---------------------------------------------------------------------------
 
1359
    #  Handles the user left clicking on a tree node:
 
1360
    #---------------------------------------------------------------------------
 
1361
 
 
1362
    def _on_left_down ( self, event ):
 
1363
        """ Handles the user right clicking on a tree node.
 
1364
        """
 
1365
        # Determine what node (if any) was clicked on:
 
1366
        expanded, node, object, nid, point = self._unpack_event( event )
 
1367
 
 
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 )
 
1373
 
 
1374
            # Fire the 'click' event with the object as its value:
 
1375
            self.click = object
 
1376
 
 
1377
        # Allow normal mouse event processing to occur:
 
1378
        event.Skip()
 
1379
 
 
1380
    #---------------------------------------------------------------------------
 
1381
    #  Handles the user right clicking on a tree node:
 
1382
    #---------------------------------------------------------------------------
 
1383
 
 
1384
    def _on_right_down ( self, event ):
 
1385
        """ Handles the user right clicking on a tree node.
 
1386
        """
 
1387
        expanded, node, object, nid, point = self._unpack_event( event )
 
1388
 
 
1389
        if node is not None:
 
1390
            self._data    = ( node, object, nid )
 
1391
            self._context = { 'object':  object,
 
1392
                              'editor':  self,
 
1393
                              'node':    node,
 
1394
                              'info':    self.ui.info,
 
1395
                              'handler': self.ui.handler }
 
1396
 
 
1397
            # Try to get the parent node of the node clicked on:
 
1398
            pnid = self._tree.GetItemParent( nid )
 
1399
            if pnid.IsOk():
 
1400
                ignore, parent_node, parent_object = self._get_node_data( pnid )
 
1401
            else:
 
1402
                parent_node = parent_object = None
 
1403
 
 
1404
            self._menu_node          = node
 
1405
            self._menu_parent_node   = parent_node
 
1406
            self._menu_parent_object = parent_object
 
1407
 
 
1408
            menu = node.get_menu( object )
 
1409
 
 
1410
            if menu is None:
 
1411
                # Use the standard, default menu:
 
1412
                menu = self._standard_menu( node, object )
 
1413
 
 
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:
 
1419
                    group.id = ''
 
1420
                    actions  = self._new_actions( node, object )
 
1421
                    if len( actions ) > 0:
 
1422
                        group.insert( 0, Menu( name = 'New', *actions ) )
 
1423
 
 
1424
            else:
 
1425
                # All other values mean no menu should be displayed:
 
1426
                menu = None
 
1427
 
 
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 )
 
1433
                wxmenu.Destroy()
 
1434
 
 
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
 
1438
 
 
1439
    #---------------------------------------------------------------------------
 
1440
    #  Returns the standard contextual pop-up menu:
 
1441
    #---------------------------------------------------------------------------
 
1442
 
 
1443
    def _standard_menu ( self, node, object ):
 
1444
        """ Returns the standard contextual pop-up menu.
 
1445
        """
 
1446
        actions = [ CutAction, CopyAction, PasteAction, Separator(),
 
1447
                    DeleteAction, Separator(), RenameAction ]
 
1448
 
 
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() ]
 
1453
 
 
1454
        return Menu( *actions )
 
1455
 
 
1456
    #---------------------------------------------------------------------------
 
1457
    #  Returns a list of Actions that will create 'new' objects:
 
1458
    #---------------------------------------------------------------------------
 
1459
 
 
1460
    def _new_actions ( self, node, object ):
 
1461
        """ Returns a list of Actions that will create new objects.
 
1462
        """
 
1463
        object = self._data[1]
 
1464
        items  = []
 
1465
        add    = node.get_add( object )
 
1466
        if len( add ) > 0:
 
1467
            for klass in add:
 
1468
                prompt = False
 
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 )
 
1475
                    if name == '':
 
1476
                        name = class_name
 
1477
                    items.append(
 
1478
                        Action( name   = name,
 
1479
                                action = "editor._menu_new_node('%s',%s)" %
 
1480
                                         ( class_name, prompt ) ) )
 
1481
        return items
 
1482
 
 
1483
    #---------------------------------------------------------------------------
 
1484
    #  Menu action helper methods:
 
1485
    #---------------------------------------------------------------------------
 
1486
 
 
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 ))
 
1492
 
 
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 ))
 
1498
        else:
 
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 ))
 
1503
 
 
1504
    def _is_pasteable ( self, object ):
 
1505
        from pyface.wx.clipboard import clipboard
 
1506
 
 
1507
        return self._menu_node.can_add( object, clipboard.object_type )
 
1508
 
 
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 )
 
1513
        else:
 
1514
            can_delete = ((parent is not None) and parent.can_delete( object ))
 
1515
        return (can_delete and self._menu_node.can_delete_me( object ))
 
1516
 
 
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 )
 
1523
        else:
 
1524
            can_rename = True
 
1525
        return (can_rename and self._menu_node.can_rename_me( object ))
 
1526
 
 
1527
#----- Drag and drop event handlers: -------------------------------------------
 
1528
 
 
1529
    #---------------------------------------------------------------------------
 
1530
    #  Handles a Python object being dropped on the tree:
 
1531
    #---------------------------------------------------------------------------
 
1532
 
 
1533
    def wx_dropped_on ( self, x, y, data, drag_result ):
 
1534
        """ Handles a Python object being dropped on the tree.
 
1535
        """
 
1536
        if isinstance( data, list ):
 
1537
            rc = wx.DragNone
 
1538
            for item in data:
 
1539
                rc = self.wx_dropped_on( x, y, item, drag_result )
 
1540
            return rc
 
1541
 
 
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 ):
 
1546
                    return wx.DragNone
 
1547
 
 
1548
                if self._dragging is not None:
 
1549
                    data = self._drop_object( node, object, data, False )
 
1550
                    if data is not None:
 
1551
                        try:
 
1552
                            self._begin_undo()
 
1553
                            self._undoable_delete(
 
1554
                                     *self._node_index( self._dragging ) )
 
1555
                            self._undoable_append( node, object, data, False )
 
1556
                        finally:
 
1557
                            self._end_undo()
 
1558
                else:
 
1559
                    data = self._drop_object( node, object, data )
 
1560
                    if data is not None:
 
1561
                        self._undoable_append( node, object, data, False )
 
1562
 
 
1563
                return drag_result
 
1564
 
 
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)):
 
1574
                            to_index -= 1
 
1575
                        try:
 
1576
                            self._begin_undo()
 
1577
                            self._undoable_delete( from_node, from_object,
 
1578
                                                   from_index )
 
1579
                            self._undoable_insert( to_node, to_object, to_index,
 
1580
                                                   data, False )
 
1581
                        finally:
 
1582
                            self._end_undo()
 
1583
                else:
 
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,
 
1587
                                               data, False )
 
1588
 
 
1589
                return drag_result
 
1590
 
 
1591
        return wx.DragNone
 
1592
 
 
1593
    #---------------------------------------------------------------------------
 
1594
    #  Handles a Python object being dragged over the tree:
 
1595
    #---------------------------------------------------------------------------
 
1596
 
 
1597
    def wx_drag_over ( self, x, y, data, drag_result ):
 
1598
        """ Handles a Python object being dragged over the tree.
 
1599
        """
 
1600
        expanded, node, object, nid, point = self._hit_test( wx.Point( x, y ) )
 
1601
        insert = False
 
1602
 
 
1603
        if (node is not None) and (drag_result == wx.DragCopy):
 
1604
            node, object, index = self._node_index( nid )
 
1605
            insert = True
 
1606
 
 
1607
        if ((self._dragging is not None) and
 
1608
            (not self._is_drag_ok( self._dragging, data, object ))):
 
1609
            return wx.DragNone
 
1610
 
 
1611
        if ((node is not None) and
 
1612
            self._is_droppable( node, object, data, insert )):
 
1613
            return drag_result
 
1614
 
 
1615
        return wx.DragNone
 
1616
 
 
1617
    #---------------------------------------------------------------------------
 
1618
    #  Makes sure that the target is not the same as or a child of the source
 
1619
    #  object:
 
1620
    #---------------------------------------------------------------------------
 
1621
 
 
1622
    def _is_drag_ok ( self, snid, source, target ):
 
1623
        if (snid is None) or (target is source):
 
1624
            return False
 
1625
 
 
1626
        for cnid in self._nodes( snid ):
 
1627
            if not self._is_drag_ok( cnid, self._get_node_data( cnid )[2],
 
1628
                                     target ):
 
1629
                return False
 
1630
 
 
1631
        return True
 
1632
 
 
1633
#----- pyface.action 'controller' interface implementation: --------------------
 
1634
 
 
1635
    #---------------------------------------------------------------------------
 
1636
    #  Adds a menu item to the menu being constructed:
 
1637
    #---------------------------------------------------------------------------
 
1638
 
 
1639
    def add_to_menu ( self, menu_item ):
 
1640
        """ Adds a menu item to the menu bar being constructed.
 
1641
        """
 
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' )
 
1645
 
 
1646
    #---------------------------------------------------------------------------
 
1647
    #  Adds a tool bar item to the tool bar being constructed:
 
1648
    #---------------------------------------------------------------------------
 
1649
 
 
1650
    def add_to_toolbar ( self, toolbar_item ):
 
1651
        """ Adds a toolbar item to the toolbar being constructed.
 
1652
        """
 
1653
        self.add_to_menu( toolbar_item )
 
1654
 
 
1655
    #---------------------------------------------------------------------------
 
1656
    #  Returns whether the menu action should be defined in the user interface:
 
1657
    #---------------------------------------------------------------------------
 
1658
 
 
1659
    def can_add_to_menu ( self, action ):
 
1660
        """ Returns whether the action should be defined in the user interface.
 
1661
        """
 
1662
        if action.defined_when != '':
 
1663
            try:
 
1664
                if not eval( action.defined_when, globals(), self._context ):
 
1665
                    return False
 
1666
            except:
 
1667
                open_fbi()
 
1668
 
 
1669
        if action.visible_when != '':
 
1670
            try:
 
1671
                if not eval( action.visible_when, globals(), self._context ):
 
1672
                    return False
 
1673
            except:
 
1674
                open_fbi()
 
1675
 
 
1676
        return True
 
1677
 
 
1678
    #---------------------------------------------------------------------------
 
1679
    #  Returns whether the toolbar action should be defined in the user
 
1680
    #  interface:
 
1681
    #---------------------------------------------------------------------------
 
1682
 
 
1683
    def can_add_to_toolbar ( self, action ):
 
1684
        """ Returns whether the toolbar action should be defined in the user
 
1685
            interface.
 
1686
        """
 
1687
        return self.can_add_to_menu( action )
 
1688
 
 
1689
    #---------------------------------------------------------------------------
 
1690
    #  Performs the action described by a specified Action object:
 
1691
    #---------------------------------------------------------------------------
 
1692
 
 
1693
    def perform ( self, action, action_event = None ):
 
1694
        """ Performs the action described by a specified Action object.
 
1695
        """
 
1696
        self.ui.do_undoable( self._perform, action )
 
1697
 
 
1698
    def _perform ( self, action ):
 
1699
        node, object, nid = self._data
 
1700
        method_name       = action.action
 
1701
        info              = self.ui.info
 
1702
        handler           = self.ui.handler
 
1703
 
 
1704
        if method_name.find( '.' ) >= 0:
 
1705
            if method_name.find( '(' ) < 0:
 
1706
                method_name += '()'
 
1707
            try:
 
1708
                eval( method_name, globals(),
 
1709
                      { 'object':  object,
 
1710
                        'editor':  self,
 
1711
                        'node':    node,
 
1712
                        'info':    info,
 
1713
                        'handler': handler } )
 
1714
            except:
 
1715
                # fixme: Should the exception be logged somewhere?
 
1716
                pass
 
1717
 
 
1718
            return
 
1719
 
 
1720
        method = getattr( handler, method_name, None )
 
1721
        if method is not None:
 
1722
            method( info, object )
 
1723
            return
 
1724
 
 
1725
        if action.on_perform is not None:
 
1726
            action.on_perform( object )
 
1727
 
 
1728
#----- Menu support methods: ---------------------------------------------------
 
1729
 
 
1730
    #---------------------------------------------------------------------------
 
1731
    #  Evaluates a condition within a defined context and sets a specified
 
1732
    #  object trait based on the (assumed) boolean result:
 
1733
    #---------------------------------------------------------------------------
 
1734
 
 
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
 
1738
        Boolean.
 
1739
        """
 
1740
        if condition != '':
 
1741
            value = True
 
1742
            try:
 
1743
                if not eval( condition, globals(), self._context ):
 
1744
                    value = False
 
1745
            except:
 
1746
                open_fbi()
 
1747
            setattr( object, trait, value )
 
1748
 
 
1749
#----- Menu event handlers: ----------------------------------------------------
 
1750
 
 
1751
    #---------------------------------------------------------------------------
 
1752
    #  Copies the current tree node object to the paste buffer:
 
1753
    #---------------------------------------------------------------------------
 
1754
 
 
1755
    def _menu_copy_node ( self ):
 
1756
        """ Copies the current tree node object to the paste buffer.
 
1757
        """
 
1758
        from pyface.wx.clipboard import clipboard
 
1759
 
 
1760
        clipboard.data = self._data[1]
 
1761
        self._data     = None
 
1762
 
 
1763
    #---------------------------------------------------------------------------
 
1764
    #   Cuts the current tree node object into the paste buffer:
 
1765
    #---------------------------------------------------------------------------
 
1766
 
 
1767
    def _menu_cut_node ( self ):
 
1768
        """  Cuts the current tree node object into the paste buffer.
 
1769
        """
 
1770
        from pyface.wx.clipboard import clipboard
 
1771
 
 
1772
        node, object, nid = self._data
 
1773
        clipboard.data    = object
 
1774
        self._data        = None
 
1775
        self._undoable_delete( *self._node_index( nid ) )
 
1776
 
 
1777
    #---------------------------------------------------------------------------
 
1778
    #  Pastes the current contents of the paste buffer into the current node:
 
1779
    #---------------------------------------------------------------------------
 
1780
 
 
1781
    def _menu_paste_node ( self ):
 
1782
        """ Pastes the current contents of the paste buffer into the current
 
1783
            node.
 
1784
        """
 
1785
        from pyface.wx.clipboard import clipboard
 
1786
 
 
1787
        node, object, nid = self._data
 
1788
        self._data        = None
 
1789
        self._undoable_append( node, object, clipboard.object_data, False )
 
1790
 
 
1791
    #---------------------------------------------------------------------------
 
1792
    #  Deletes the current node from the tree:
 
1793
    #---------------------------------------------------------------------------
 
1794
 
 
1795
    def _menu_delete_node ( self ):
 
1796
        """ Deletes the current node from the tree.
 
1797
        """
 
1798
        node, object, nid = self._data
 
1799
        self._data        = None
 
1800
        rc = node.confirm_delete( object )
 
1801
        if rc is not False:
 
1802
            if rc is not True:
 
1803
                if self.ui.history is None:
 
1804
                    # If no undo history, ask user to confirm the delete:
 
1805
                    dlg = wx.MessageDialog(
 
1806
                              self._tree,
 
1807
                              'Are you sure you want to delete %s?' %
 
1808
                                  node.get_label( object ),
 
1809
                              'Confirm Deletion',
 
1810
                              style = wx.OK | wx.CANCEL | wx.ICON_EXCLAMATION )
 
1811
                    if dlg.ShowModal() != wx.ID_OK:
 
1812
                        return
 
1813
 
 
1814
            self._undoable_delete( *self._node_index( nid ) )
 
1815
 
 
1816
    #---------------------------------------------------------------------------
 
1817
    #  Renames the current tree node:
 
1818
    #---------------------------------------------------------------------------
 
1819
 
 
1820
    def _menu_rename_node ( self ):
 
1821
        """ Renames the current tree node.
 
1822
        """
 
1823
        node, object, nid = self._data
 
1824
        self._data        = None
 
1825
        object_label      = ObjectLabel( label = node.get_label( object ) )
 
1826
        if object_label.edit_traits().result:
 
1827
            label = object_label.label.strip()
 
1828
            if label != '':
 
1829
                node.set_label( object, label )
 
1830
 
 
1831
    #---------------------------------------------------------------------------
 
1832
    #  Adds a new object to the current node:
 
1833
    #---------------------------------------------------------------------------
 
1834
 
 
1835
    def _menu_new_node ( self, class_name, prompt = False ):
 
1836
        """ Adds a new object to the current node.
 
1837
        """
 
1838
        node, object, nid   = self._data
 
1839
        self._data          = None
 
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 )
 
1845
 
 
1846
            # Automatically select the new object if editing is being performed:
 
1847
            if self.factory.editable:
 
1848
                self._tree.SelectItem( self._tree.GetLastChild( nid ) )
 
1849
 
 
1850
    #-- Model event handlers ---------------------------------------------------
 
1851
 
 
1852
    #---------------------------------------------------------------------------
 
1853
    #  Handles the children of a node being completely replaced:
 
1854
    #---------------------------------------------------------------------------
 
1855
 
 
1856
    def _children_replaced ( self, object, name = '', new = None ):
 
1857
        """ Handles the children of a node being completely replaced.
 
1858
        """
 
1859
        tree = self._tree
 
1860
        for expanded, node, nid in self._object_info_for( object, name ):
 
1861
            children = node.get_children( object )
 
1862
 
 
1863
            # Only add/remove the changes if the node has already been expanded:
 
1864
            if expanded:
 
1865
                # Delete all current child nodes:
 
1866
                for cnid in self._nodes_for( nid ):
 
1867
                    self._delete_node( cnid )
 
1868
 
 
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 )
 
1874
 
 
1875
            # Indicate whether the node has any children now:
 
1876
            tree.SetItemHasChildren( nid, len( children ) > 0 )
 
1877
 
 
1878
            # Try to expand the node (if requested):
 
1879
            if node.can_auto_open( object ):
 
1880
                tree.Expand( nid )
 
1881
 
 
1882
    #---------------------------------------------------------------------------
 
1883
    #  Handles the children of a node being changed:
 
1884
    #---------------------------------------------------------------------------
 
1885
 
 
1886
    def _children_updated ( self, object, name, event ):
 
1887
        """ Handles the children of a node being changed.
 
1888
        """
 
1889
        # Log the change that was made (removing '_items' from the end of the
 
1890
        # name):
 
1891
        name = name[:-6]
 
1892
        self.log_change( self._get_undo_item, object, name, event )
 
1893
 
 
1894
        start = event.index
 
1895
        end   = start + len( event.removed )
 
1896
        tree  = self._tree
 
1897
 
 
1898
        for expanded, node, nid in self._object_info_for( object, name ):
 
1899
            n = len( node.get_children( object ) )
 
1900
 
 
1901
            # Only add/remove the changes if the node has already been expanded:
 
1902
            if 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 )
 
1906
 
 
1907
                # Add all of the children that were added:
 
1908
                remaining = n - len( event.removed )
 
1909
                child_index = 0
 
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,
 
1916
                                           child )
 
1917
                        child_index += 1
 
1918
 
 
1919
 
 
1920
            # Indicate whether the node has any children now:
 
1921
            tree.SetItemHasChildren( nid, n > 0 )
 
1922
 
 
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:
 
1927
                    tree.Expand( nid )
 
1928
 
 
1929
    #---------------------------------------------------------------------------
 
1930
    #   Handles the label of an object being changed:
 
1931
    #---------------------------------------------------------------------------
 
1932
 
 
1933
    def _label_updated ( self, object, name, label ):
 
1934
        """  Handles the label of an object being changed.
 
1935
        """
 
1936
        nids = {}
 
1937
        for name2, nid in self._map[ id( object ) ]:
 
1938
            if nid not in nids:
 
1939
                nids[ nid ] = None
 
1940
                node = self._get_node_data( nid )[1]
 
1941
                self._tree.SetItemText( nid, node.get_label( object ) )
 
1942
                self._update_icon_for_nid ( nid )
 
1943
 
 
1944
#-- UI preference save/restore interface ---------------------------------------
 
1945
 
 
1946
    #---------------------------------------------------------------------------
 
1947
    #  Restores any saved user preference information associated with the
 
1948
    #  editor:
 
1949
    #---------------------------------------------------------------------------
 
1950
 
 
1951
    def restore_prefs ( self, prefs ):
 
1952
        """ Restores any saved user preference information associated with the
 
1953
            editor.
 
1954
        """
 
1955
        if self._is_dock_window:
 
1956
            if isinstance( prefs, dict ):
 
1957
                structure = prefs.get( 'structure' )
 
1958
            else:
 
1959
                structure = prefs
 
1960
            self.control.GetSizer().SetStructure( self.control, structure )
 
1961
 
 
1962
    #---------------------------------------------------------------------------
 
1963
    #  Returns any user preference information associated with the editor:
 
1964
    #---------------------------------------------------------------------------
 
1965
 
 
1966
    def save_prefs ( self ):
 
1967
        """ Returns any user preference information associated with the editor.
 
1968
        """
 
1969
        if self._is_dock_window:
 
1970
            return { 'structure': self.control.GetSizer().GetStructure() }
 
1971
 
 
1972
        return None
 
1973
 
 
1974
#-- End UI preference save/restore interface -----------------------------------
 
1975
 
 
1976
#-------------------------------------------------------------------------------
 
1977
#  'ObjectLabel' class:
 
1978
#-------------------------------------------------------------------------------
 
1979
 
 
1980
class ObjectLabel ( HasStrictTraits ):
 
1981
    """ An editable label for an object.
 
1982
    """
 
1983
 
 
1984
    #---------------------------------------------------------------------------
 
1985
    #  Trait definitions:
 
1986
    #---------------------------------------------------------------------------
 
1987
 
 
1988
    # Label to be edited
 
1989
    label = Str
 
1990
 
 
1991
    #---------------------------------------------------------------------------
 
1992
    #  Traits view definition:
 
1993
    #---------------------------------------------------------------------------
 
1994
 
 
1995
    traits_view = View( 'label',
 
1996
                        title   = 'Edit Label',
 
1997
                        kind    = 'modal',
 
1998
                        buttons = [ 'OK', 'Cancel' ] )
 
1999
 
 
2000
### EOF #######################################################################