summaryrefslogtreecommitdiff
path: root/cad/src/command_support/baseCommand.py
blob: f421401441149b7890dbc6edf35b9cc31fce6f30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
# Copyright 2008 Nanorex, Inc.  See LICENSE file for details.
"""
baseCommand.py - base class for command objects on a command sequencer stack

@author: Bruce
@version: $Id$
@copyright: 2008 Nanorex, Inc.  See LICENSE file for details.
"""

from utilities.debug import print_compact_traceback
_pct = print_compact_traceback # local abbreviation for readability

from utilities.constants import CL_ABSTRACT 

from commandSequencer.command_levels import FIXED_PARENT_LEVELS
from commandSequencer.command_levels import AFFECTS_FLYOUT_LEVELS


DEBUG_USE_COMMAND_STACK = False

# ==

class baseCommand(object):
    """
    Abstract base class for command objects compatible with Command Sequencer.

    @note: all actual command objects are instances of subclasses of our
           subclass anyCommand. The intended division of methods is that
           those needed for CommandSequencer are defined here, whereas
           those needed only by other code in NE1 are defined in anyCommand
           or basicCommand. This is roughly followed but not completely.

    @note: In some cases, methods defined in baseCommand are overridden
           in anyCommand or basicCommand.
    """
    __abstract_command_class = True
    
    # default values of command subclass constants

    # WARNING: presently some of these are overridden in anyCommand and/or
    # basicCommand; that will be cleaned up after the refactoring is complete.
    # [bruce 080815 comment]

    #Temporary attr 'command_porting_status'. Used ONLY to keep a track of
    #commands that are ported to the new command API. the default value is
    #'UNKNOWN'. This is overridden in subclasses. command_porting_status is set 
    #to 'None' when the command is fully ported to USE_COMMAND_STACK
    command_porting_status = 'UNKNOWN'
    
    command_level = CL_ABSTRACT
        #doc command_level; see command_levels.py
    
    command_parent = None # TODO: rename this to command_parentName or ... ###
        # Subclasses should set this to the commandName of the parent command
        # they require, if any (and if that is not the default command).
        # (Whether they require a parent command at all is determined by
        #  their command_level attribute.)
        #
        # For example, BreakStrands_Command requires parent command "Build Dna",
        # so it sets this to 'BUILD_DNA' == BuildDna_EditCommand.commandName.

    # internal name of command, e.g. 'DEPOSIT',
    # only seen by users in "debug" error messages;
    # might be used to create some prefs_keys and/or in some prefs values
    # [but I don't know if any such uses remain -- bruce 080727 comment]
    commandName = "(bug: missing commandName 1)" 

    # default values of instance variables; properties; access methods

    # - related to parentCommand

    is_null = False # overridden only in nullCommand
    
    _parentCommand = None # parent command object, when self is on command stack
    
    def _get_parentCommand( self):
        return self._parentCommand

    parentCommand = property( _get_parentCommand ) # a read-only property

    def command_is_active(self):
        """
        @return: whether self is presently on the command stack (whether or not
                 it's on top (aka current)).
        @rtype: boolean
        """
        # note: this is not as robust a definition as might be hoped.
        # iterating over the command stack and comparing the result
        # might catch more bugs.
        return self.parentCommand is not None

    # - related to flyout toolbar
    
    FlyoutToolbar_class = None  #Ninad 2008-09-12
       #Command subclasses can override this class attr with the appropriate
       #Flyout Toolbar class. This is used to create a FlyoutToolbar object 
       #for the commands. this attr is also used to do flyout toolbar updates. 
       #Example: if the value of this attr is None then it either implies the 
       #command should use the flyout toolbar of the parent command and do 
       #some more UI changes to it (example check an action) OR the command 
       #shouldn't do anything with the flyout at all (this is decided using the 
       #'command_level)
       #@see: self.command_update_flyout()
       #@see: self._command_entered_prepare_flyout()
       #@see: self._createFlyouttoolbarObject()
       
    flyoutToolbar = None

    # - related to a command's property manager (PM)
    
    command_has_its_own_PM = True
        # TODO: merge related code from our toplevel subclasses
        # REVIEW: can this be replaced by the condition (self.PM_class is not None)?
        # [bruce 081125 comments]
    
    propMgr = None # will be set to the PM to use with self (whether or not created by self);
        # some methods assume that object is a subclass of PM_Dialog,
        # or perhaps (more likely) of its subclass Command_PropertyManager.
        # [bruce 081125 comment]

    # == access methods

    def is_default_command(self): #bruce 080709 refactoring; moved to this file 080929
        return self.commandName == self.commandSequencer.default_commandName()

    def get_featurename(self):
        """
        [overridden in basicCommand, see there for doc]
        """
        # revised, bruce 080727; moved to this class, bruce 080929
        return "null command" # should never be seen 

    def is_fixed_parent_command(cls):
        """
        Is this command instance or class a "fixed-parent command",
        i.e. one which requires self.parentCommand to be an instance
        of a specific command?
        
        @note: it works to call this classmethod directly on the class,
               or on an instance
        """
        return cls.command_level in FIXED_PARENT_LEVELS
    
    is_fixed_parent_command = classmethod(is_fixed_parent_command)

    def command_affects_flyout(cls):
        """
        Does this command instance or class ever affect the flyout
        toolbar, either by replacing it, or by modifying its set of actions
        or their checked or enabled state?

        Used to decide when to call command_update_flyout, and when to
        restore the default flyout state for the current command if the
        user has temporarily modified it.
        
        @note: it works to call this classmethod directly on the class,
               or on an instance
        """
        return cls.command_level in AFFECTS_FLYOUT_LEVELS
    
    command_affects_flyout = classmethod(command_affects_flyout)
    
    def command_that_supplies_PM(self):
        """
        Return the innermost command (of self or its parent/grandparent/etc)
        which supplies the PM when self is current. Note that this command
        will supply a value of None for the PM, if self does.

        Self must be an active command (on the command stack).

        Print a warning if any PM encountered appears to
        have .command set incorrectly.
        """
        # find innermost command whose .propMgr differs from its
        # parent's .propMgr.
        cseq = self.commandSequencer
        commands = cseq.all_active_commands( starting_from = self )
        res = None
        for command in commands:
            if not command.parentCommand or \
               command.parentCommand.propMgr is not command.propMgr:
                res = command
                break
        assert res # since outermost command has no parent
        assert res.propMgr is self.propMgr # sanity check on algorithm
        # check PM's command field
        if res.propMgr:
            ## assert res.propMgr.command is res, \
            if not (res.propMgr.command is res):
                print "\n*** BUG: " \
                   "%r.PM %r has wrong .command %r, found from %r" % \
                   (res, res.propMgr, res.propMgr.command, self)
        return res
            
    # == exit-related methods (see also CommandSequencer.exit_all_commands)

    def command_Done(self, implicit = False):
        """
        Exit this command, after also exiting any subcommands it may have,
        as if its Done button was pressed.

        @param implicit: this is occurring as a result of the user asking
                         to enter some other command (not nestable under
                         this command), rather than by an explicit exit
                         request.
        @type implicit: boolean

        @note: subclasses should not override this method. Instead, if a
               subclass needs to add Done-specific code, it should override
               command_will_exit and condition its effects on one of the
               flags described in that method's docstring.
        """
        self.commandSequencer._f_exit_active_command(self, implicit = implicit)
        return
    
    def command_Cancel(self):
        """
        Exit this command, after also exiting any subcommands it may have,
        as if its Cancel button was pressed.

        @note: subclasses should not override this method. Instead, if a
               subclass needs to add Cancel-specific code, it should override
               command_will_exit and condition its effects on one of the
               flags described in that method's docstring.
        """
        self.commandSequencer._f_exit_active_command(self, cancel = True)
        return
    
    def _f_command_do_exit_if_ok(self):
        # todo: args: anything needed to decide if ok or when asking user
        """
        Exit this command (but not any other commands), when it's the current
        command, if possible or if self.commandSequencer.exit_is_forced is true,
        and return True.
        
        If not possible for some reason, emit messages as needed,
        and return False.

        @return: whether exit succeeded.
        @rtype: boolean
        
        @note: caller must only call this if this command *should* exit if
               possible, and if it's the current command (not merely an active
               command, even if it's the command which is supplying the PM).
               Deciding whether exit is needed, based on a desired next command,
               is up to the caller.

        @note: possible future change to return value: if we ask user about
               exiting, the answer might affect how or how much to exit, needed
               by caller for later stages of exiting multiple commands. In that
               case we'd need to return info about that, whenever exit succeeds.

        @note: certain attrs in self.commandSequencer (e.g. .exit_is_cancel)
               will tell self.command_will_exit which side effects to do.
               For documentation of those attrs, see CommandSequencer methods
               _f_exit_active_command and _exit_currentCommand_with_flags,
               and class CommandSequencer docstring.
        """
        self.commandSequencer._f_lock_command_stack("preparing to exit a command")
        try:
            try:
                ok = self._command_ok_to_exit() # must explain to user if not
            except:
                _pct()
                ok = True
            
            if not ok and not self.commandSequencer.exit_is_forced:
                self._command_log("not exiting")
                return False

            try:
                self.command_will_exit() # args?
            except:
                _pct()
        finally:
            self.commandSequencer._f_unlock_command_stack()

        self._command_do_exit()

        return True

    def _command_ok_to_exit(self): # only in this file so far, 080826
        ask = self.command_exit_should_ask_user()
        if ask:
            print "asking is nim" # put up dialog with 3 choices (or more?)
                # call method on self to put it up? or determine choices anyway? (yes)
                # also, if ok to exit but only after some side effects, do those side effects.
                # (especially likely if self.commandSequencer.exit_is_forced is true; ### TODO: put this in some docstring)
        return True

    def command_exit_should_ask_user(self): # only in this file so far, 080826
        """
        @return: whether user should be asked whether it's ok to exit self
        @rtype: boolean
        
        @warning: not yet properly implemented in caller

        @warning: overriding this method should be rare.

        @warning: if self.commandSequencer.exit_is_forced,
                  the caller will still call this method
                  but will always exit regardless of its
                  return value.
        """
        # note: someday this may call a new command API method
        # related to the old self.haveNontrivialState() method,
        # and if that returns true, check a user pref to decide what to do.
        # Initially it's ok if it always returns False.
        return False
    
    def command_will_exit(self):
        """
        Perform side effects on self and the UI, needed when self
        is about to be exited for any reason.

        If the side effects need to depend on the manner of exit
        (e.g. command_Done vs command_Cancel vs exit_all_commands),
        or on the command being exited from or intended to be entered,
        this should be determined by testing an appropriate attribute of
        self.commandSequencer, e.g. exit_is_cancel, exit_is_forced,
        exit_is_implicit, exit_target, enter_target. For their meanings,
        see the docstring of class CommandSequencer.

        @note: when this is called, self is on top of the command stack,
               but it may or may not have been on top when the current user
               event handler started (e.g. some other command may have already
               exited during the same user event).
               
        @note: base class implementation calls self methods
               command_exit_misc_actions and command_exit_PM.
        
        [subclasses should extend this as needed, typically calling
         superclass implementation at the end]
        """
        # note: we call these in the reverse order of how
        # command_entered calls the corresponding _enter_ methods.
        self.command_exit_misc_actions()            
        self.command_exit_PM()
        return
    
    def command_exit_PM(self):
        """
        Do whatever needs to be done to a command's PM
        when the command is about to exit. (Usually,
        nothing needs to be done.)

        [subclasses should override this as needed]
        """
        return
    
    def command_exit_misc_actions(self):
        """
        Undo whatever was done
        by self.command_enter_misc_actions() when this command was entered.

        [subclasses should override this as needed]
        """
        return

    def _command_do_exit(self):
        """
        [private]
        pop self from the top of the command stack
        """
        if DEBUG_USE_COMMAND_STACK:
            print "_command_do_exit:", self
        assert self is self.commandSequencer._f_currentCommand, \
               "can't pop %r since it's not currentCommand %r" % \
               (self, self.commandSequencer._f_currentCommand)
        assert self._parentCommand is not None # would fail for default command
        self.commandSequencer._f_set_currentCommand( self._parentCommand )
        self._parentCommand = None
        return

    # == enter-related methods 
    
    def _command_do_enter_if_ok(self, args = None):
        """
        #doc

        @return: whether enter succeeded.
        @rtype: boolean
        
        @note: caller must only call this if this command *should* enter if possible.

        @note: always called on an instance, even if a command class alone
               could (in principle) decide to refuse entry.
        """
        self.commandSequencer._f_lock_command_stack("preparing to enter a command")
        try:
            try:
                ok = self.command_ok_to_enter() # must explain to user if not
            except:
                _pct()
                ok = True
            
            if not ok:
                self._command_log("not entering")
                return False

            try:
                self.command_prepare_to_enter() # get ready to receive events (usually a noop) # args?
            except:
                _pct()
                self._command_log("not entering due to exception")
                return False
        finally:
            self.commandSequencer._f_unlock_command_stack()

        self._command_do_enter() # push self on command stack

        self.commandSequencer._f_lock_command_stack("calling command_entered")
        try:
            self.command_entered() # update ui as needed # args?
        except:
            _pct()
            # but since we already entered it by then,
            # return True anyway
            # REVIEW: should caller continue entering subcommands
            # if it planned to? (for now, let it try)
        self.commandSequencer._f_unlock_command_stack()

        return True

    def command_ok_to_enter(self):
        """
        Determine whether it's ok to enter self (assuming the user has
        explicitly asked to enter self), given the current state
        of the model and command stack. If not ok, explain to user
        why not (using redmsg or dialog) and return False.
        If ok, have no visible effect and return True.
        Should never have a side effect on the model.

        @return: True if ok to enter (usual case).
        @rtype: boolean

        @note: overriding this should be rare, and is always a UI design flaw.
               Instead, commands should enter, then help the user make it ok
               to use them (e.g. help them select an appropriate argument).
                
        [a few subclasses should override or extend, most don't need to]
        """
        return True

    def command_prepare_to_enter(self):
        """
        #doc
        
        [some subclasses should extend, most don't need to]
        """
        return

    def _command_do_enter(self):
        """
        [private]
        push self on command stack
        """
        if DEBUG_USE_COMMAND_STACK:
            print "_command_do_enter:", self
        assert self._parentCommand is None
        self._parentCommand = self.commandSequencer._f_currentCommand
        self.commandSequencer._f_set_currentCommand( self)
        return

    def command_entered(self):
        """
        Update self's command state and ui as needed, when self has just been
        pushed onto command stack. (But never modify the command stack.
        If that's necessary, do it in command_update_state.)

        @note: self may or may not still be the current command by the time
               the current user event is fully handled. It might be immediately
               "suspended upon entry" by a subcommand being pushed on top of it.

        @note: base class implementation calls other methods of self,
               including command_enter_misc_actions.

        @note: similar to old methods Enter and parts of init_gui.

        [subclasses should extend as needed, by calling superclass
         implementation at the start]
        
        @see: self._command_entered_prepare_flyout()
        @see: self.command_update_flyout()
        """
        self.graphicsMode.Enter_GraphicsMode()
        
        if not self.command_has_its_own_PM:
            # note: that flag must be True (so this doesn't run) in the default
            # command, since it has no self.parentCommand
            self.propMgr = self.parentCommand.propMgr
        
        self.command_enter_PM()
        
        if self.flyoutToolbar is None:
            self._command_entered_prepare_flyout()
                
        self.command_enter_misc_actions()
        return
    
    def _command_entered_prepare_flyout(self): #Ninad 2008-09-12
        """
        Create flyout toolbar object for the command if needed. Whether to 
        create it is decided using the class attr self.FlyoutToolbar_class.  
        
        Example: if FlyoutToolbar_class is None, 
        it means the command should use the flyout of the parent command
        if one exists. When FlyoutToolbar_class is defined by the command, 
        it uses that to create one for the command. 

        This method is called in self.command_entered(). Subclasses should
        NEVER override this method. 
        
        @see: self.command_update_flyout()  which actually does the updates to 
              the flyout toolbar. 
        @see: self.command_entered() which calls this method.
        """        
        if self.FlyoutToolbar_class is None:
            #The FlyoutToolbar_class is not define. This means either of the 
            #following: A) the command is , for example, a subcommand,
            #which reuses the flyoutToolbar of the parent command and an action
            #representing this command is checked in the flyout toolbar. 
            #B) The command is an internal request command and doesn't have 
            #a specific action to be checked in the flyout. So all it does is 
            #to not do any changes to the flyout and just continue using it.
            
            #The following line of code makes sure that the self.parentCommand 
            #exists and if it does, it assigns self's flyout the value of 
            #parentCommand's flyoutToolbar object.
            self.flyoutToolbar = self.parentCommand and self.parentCommand.flyoutToolbar             
                
        else:
            #FlyoutToolbar_class is specified. So create a flyoutToolBarObject
            #if necessary
            if self.flyoutToolbar is None:
                self.flyoutToolbar = self._createFlyoutToolbarObject() 
                
    def _createFlyoutToolbarObject(self): #Ninad 2008-09-12
        """
        Creates and returns a FlyoutToolbar object of the specified 
        FlyoutToolbar_class. 
        @see: self._command_entered_prepare_flyout()
        @see: self.command_update_flyout()
        @see: self._createPropMgrObject()
        """
        if self.FlyoutToolbar_class is None:
            return None
        
        flyout = self.FlyoutToolbar_class(self)
        return flyout

    def command_enter_PM(self):
        """
        Do whatever needs to be done to a command's PM object (self.propMgr)
        when a command has just been entered (but don't show that PM).

        For commands which use their parent command's PM, it has already
        been assigned to self.propMgr, assuming this method is called by
        the base class implementation of command_entered (as usual)
        and that self.command_has_its_own_PM is false.
        
        In that case, this method is allowed to perform side effects on
        that "parent PM" (and this is the best place to do them),
        but this is only safe if the parent command and PM are of the expected
        class and have been coded with this in mind.

        For commands which create their own PM, typically they either do it
        in __init__, or in this method (when their PM doesn't already exist).
        A created PM is conventionally stored in self.propMgr, and publicly
        accessed from there. It will persist there even when self is not on
        the command stack.

        For many commands, nothing needs to be done by this method.

        PM signal/slot connections should typically be created just once
        when the PM is first created.

        @note: base class implementation of command_entered calls this,
               after setting self.propMgr to PM from parent command if
               self.command_has_its_own_PM is false.
               
        @see: CommandSequencer._f_update_current_command() which calls PM.show()
              for the desired PM.              

        [subclasses should override as needed]
        """
        return
    
    def command_enter_misc_actions(self):
        """
        incrementally modify the state of miscellaneous UI actions
        upon entry to this command.

        @note: Called by base class implementation of command_entered.

        [subclasses should override as needed]
        """
        return

    # == update methods

    def command_post_event_ui_updater(self):
        """
        Commands can extend this if they need to optimize by completely ignoring
        some updates under certain conditions.

        Note: this is called from MWsemantics.post_event_ui_updater.

        Note that this prevents *all* active commands, or the command sequencer,
        from doing any updates in response to this method (whose base class implem
        is indirectly the only caller of any command_update_* method),
        so it's safer to optimize one of the command_update_* methods instead.
        """
        self.commandSequencer._f_update_current_command()
        return

    def command_update_state(self):
        """
        At the end of any user event that may have changed system state
        which may need to cause changes to the command stack or to any
        active command's state or UI, the command sequencer will call
        this method on the current command, repeating that until the
        command stack doesn't change (but never calling it twice
        on the same command, during one user event, to prevent infinite
        recursion due to bugs in specific implems of this method).

        This method is responsible for:

        - optimizing for frequent calls (e.g. for every mouse drag event),
          by checking whether anything it cares about has actually changed
          (see below for how it can do this ### change counter; once per event
           means can set self._something_changed for other methods)

        - updating any "state machines" inside this command which can
          cause it to alter the command stack

        - exiting this command and/or entering other commands, if necessary
          due to its own state or system state

        If this method doesn't change the command stack (i.e. if self remains
        the current command), then after it returns, the command sequencer
        will call self.command_update_internal_state(), which should update
        internal state for all commands on the command stack, in bottom to top
        order (see its docstring for details ### superclass calls, base implem).
        
        The command sequencer will then call self.command_update_UI()
        (and, possibly, other update_UI methods on other UI objects ###doc).
        
        @note: For any command that has a "state machine", its logic should be
               implemented within this method.

        [many subclasses must override or extend this method; when extending,
         see the docstring for the specific superclass method being extended
         to decide when to call the superclass method within the new method]
        """
        return

    def command_update_internal_state(self):
        """
        Update the internal state of this command and all parent commands.
        Self is on the command stack, but might not be the current command.

        This must update all state that might be seen by other commands or
        UI elements, even if that state is stored in self.propMgr.
        It should not update UI elements themselves, unless they are used
        to store internal state.

        This must not change the command stack (error if it does).
        Any updates which might require exiting or entering commands
        must be done earlier, in a command_update method.

        @note: the base class implementation delegates to the parent command,
               so a typical subclass implementation of this method need only
               call its superclass method in order to accomplish the "all parent
               commands" part of its responsibility. Typically it should call
               its superclass method before doing its own updates.

        @see: command_update_state, for prior updates that might change the
              command stack; see its docstring for the context of this call
              
        @see: command_update_UI, for subsequent updates of UI elements only
        """
        if self.parentCommand:
            assert self.parentCommand is not self
            self.parentCommand.command_update_internal_state()
        return

    def command_update_UI(self):
        """
        Update UI elements owned by or displayed by this command
        (e.g. self.propMgr and/or self's flyout toolbar) (preferably by
        calling update_UI methods in those UI elements, rather than by
        hardcoding the update algorithms, since the UI elements themselves
        may be owned by parent commands rather than self).

        Self is always the current command.

        @see: command_update_state, for prior updates that might change the
              command stack; see its docstring for the context of this call

        @see: command_update_internal_state, for prior updates that change
              internal state variables in all active commands (even if those
              are stored in their property manager objects)
              
        @see: self.command_update_flyout()
        """
        if self.propMgr:
            self.propMgr.update_UI()
                # must work when PM is shown or not shown, and should not show it
                # note: what shows self.propMgr (if it ought to be shown but isn't)
                # is the base implem of our indirect caller,
                # commandSequencer._f_update_current_command,
                # after we return.

        # Note: 2008-09-16: updating flyout toolbar is now done by 
        #self.command_update_flyout() See that method for details. 
        
        return

    def command_update_flyout(self): #bruce 080910; Ninad 2008-09-16
        """
        Subclasses should NEVER override this method. 
        
        This method is only called for command stack changes involving commands
        that affect the flyout toolbar (according to their command_level).

        When this is called, self is the current command and
        all other update methods defined above have been called.
        
        @note: called only when command stack changed (counting
               only commands that affect flyout) since
               the last time this was called for any command
        
        @see: self.command_update_UI() where updates to PM are done
        @see: self._command_entered_prepare_flyout() which actually creates the
              flyout toolbar.
        @see: command_levels._IGNORE_FLYOUT_LEVELS 
        @see: AbstractFlyout.resetStateOfActions()
        @see: AbstractFlyout.activateFlyoutToolbar()
        @see: CommandToolbar.resetToDefaultState()
        """  
        if self.flyoutToolbar:
            #activate the flyout toolbar. 
            self.flyoutToolbar.activateFlyoutToolbar()   
            
            if self.FlyoutToolbar_class is None:
                #@see:self._getFlyoutToolBarActionAndParentCommand() docstring 
                #for a detailed documentation.
                flyoutActionToCheck, parentCommandString = self._getFlyoutToolBarActionAndParentCommand()
                #The following method needs to handle the empty action 
                #string (flyoutActionToCheck)
                self._init_gui_flyout_action(flyoutActionToCheck, 
                                             parentCommandName = parentCommandString)
            else:
                #Its not a subcommand that is using the flyout toolbar. So
                #make sure that no action is checked when this flyout is shown 
                #(e.g. an action might be checked because the earlier command 
                #was a subcommand using this flyout) 
                self.flyoutToolbar.resetStateOfActions()
        else:
            #The flyout toolbar is None. NOTE: self.command_update_flyout is 
            #only called for command stack changes involving commands that 
            #affect flyout. Thus, this method is never called for commands whose
            #command_level is in 'ignore list' (such as CL_VIEW_CHANGE)
            #What this all means, is when this 'else' condition (i.e. flyoutToolbar 
            #is None, is reached program needs to reset the flyout toolbar to its
            #default state. i.e. Check the Build Control button and show the 
            #Build control button menu in the flyout toolbar.                 
            self.win.commandToolbar.resetToDefaultState()  
                
    def _getFlyoutToolBarActionAndParentCommand(self): #Ninad 2008-09-15
        """
        Subclasses can override this method.
        Returns text of the action to check in the flyout toolbar and also in 
        some cases, the name of the parentCommand (value is None or the
        command name string) 
        
        Consider the following cases:
        A) The command is , for example, a subcommand, which reuses the 
        flyoutToolbar of the parent command and an action representing this 
        command is checked in the flyout toolbar. 
        
        B) The command is an internal request command and doesn't have 
        a specific action to be checked in the flyout. So all it does is 
        to not do any changes to the flyout and just continue using it. 
                 
        For case (A) the 'flyoutActionToCheck' returns a string with action name 
        for case (B), it returns 'None' (indicating that no 
        modifications are necessary)
        
        The 'parentCommandName' string is usually empty. But some commands 
        like OrderDna (global commands) need to specify this. 
                
        @see: self.command_update_flyout()
        @see: self._init_gui_glyout_action()
        @see: DnaDuplex_Editcommand._getFlyoutToolBarActionAndParentCommand()
        """
        flyoutActionToCheck = ''
        parentCommandName = None        
        return flyoutActionToCheck, parentCommandName

    def _init_gui_flyout_action( self, action_attr, parentCommandName = None ):
        """
        [helper method for command entry]
        
        If our direct parent command has the expected commandName,
        copy self.flyoutToolbar from it and call setChecked on the specified
        action in it (if it has that action) (setting self.flyoutToolbar = None
        if any of this fails by raising AttributeError, with no error message)
        and return the parent command. Otherwise return None.

        @param action_attr: attribute name of this command's action in
                            this or parent command's flyout toolbar.
                            Example: 'breakStrandAction'
        @type: string

        @param parentCommandName: commandName of expected parent command;
                                  if not provided or None, we use
                                  self.command_parent for this.
                                  Example: 'BUILD_DNA'
        @type: string

        @return: parent command, if it has expected commandName, otherwise None.
        @rtype: Command or None
        
        [helper method for use in init_gui implementations;
         might need refactoring]
        """
        if not action_attr:
            return 
        
        #bruce 080726 split this out of init_gui methods (by Ninad)
        # of several Commands; 080929 moved it into baseCommmand
        if parentCommandName is None:
            parentCommandName = self.command_parent
            assert self.command_parent, \
                   "_init_gui_flyout_action in %r requires " \
                   "self.command_parent assignment" % self
            # note: it's ok that we don't interpret command_parent = None
            # as the default commandName here, since the default command has no
            # flyout toolbar. This only works by accident; it might be more
            # principled to check self.is_fixed_parent_command() instead, once
            # that's always defined, and if it's true, interpret
            # command_parent = None as being the name of the default command.
            # [bruce 080814 comment]
        
        #bruce 080804
        parentCommand = self.parentCommand
        assert parentCommand # should be already set by now (during command_entered)
        if parentCommand.commandName == parentCommandName:
            try:
                self.flyoutToolbar = parentCommand.flyoutToolbar
                #Need a better way to deal with changing state of the 
                #corresponding action in the flyout toolbar. To be revised 
                #during command toolbar cleanup
                action = getattr(self.flyoutToolbar, action_attr)
                action.setChecked(True)
            except AttributeError:
                # REVIEW: this could have several causes; would any of them
                # be bugs and deserve an error message? [bruce 080726 questions]
                self.flyoutToolbar = None
            return parentCommand
        else:
            print "fyi: _init_gui_flyout_action in %r found wrong kind " \
                  "of parent command" % self # not sure if this ever happens; might be a bug if so
            return None
        pass
    
    # == other methods

    def _command_log(self, msg):
        print msg
    
    pass

# end