summaryrefslogtreecommitdiff
path: root/cad/src/exprs/testmode.py
blob: f9d5c1052f6f77ebc9a37ceaabd82277d0ed012a (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
# Copyright 2006-2009 Nanorex, Inc.  See LICENSE file for details.
"""
testmode.py -- Command/GraphicsMode for testing graphical exprs
(implemented in the exprs module).

@author: Bruce
@version: $Id$
@copyright: 2006-2009 Nanorex, Inc.  See LICENSE file for details.

How to use:

  [see also exprs/README.txt, which is more up to date]

  [this symlink is no longer needed as of 061207:]
  make a symlink from ~/Nanorex/Modes/testmode.py to this file, i.e.
  % cd ~/Nanorex/Modes
  % ln -s /.../cad/src/exprs/testmode.py .

then find testmode in the debug menu's custom modes submenu. It reloads testdraw.py
on every click on empty space, so new drawing code can be tried by editing that file and clicking.

(It also hides most of your toolbars, but you can get them back using the context menu
on the Qt dock, or by changing to another mode, or customize this feature by editing
the "annoyers" list below to leave out the toolbars you want to have remain.)

WARNING: some of the code in here might not be needed;
conversely, some of it might depend on code or data files bruce has at home.
Much of it depends on the "cad/src/exprs" module, and some of that used to depend
on the "cad/src/experimental/textures" directory. As of 070604 that's being changed
to depend on a new subdirectory of cad/src/ui named exprs (so it will be supported
in built releases).
"""

import time

from geometry.VQT import V, Q
## from commands.BuildAtoms.depositMode import depositMode

from utilities.debug import print_compact_traceback
from utilities.debug_prefs import Choice_boolean_True
from utilities.debug_prefs import Choice_boolean_False
from utilities.debug_prefs import debug_pref


annoyers = ['editToolbar', 'fileToolbar', 'helpToolbar', 'modifyToolbar',
            'molecularDispToolbar', 'selectToolbar', 'simToolbar',
            ## 'toolsToolbar', # not sure why this is removed, maybe it no longer exists
            ## 'viewToolbar', # I often want this one
            ## one for modes too -- not sure of its name, but I guess I'll let it keep showing too
            ]

## superclass = depositMode
from commands.SelectAtoms.SelectAtoms_Command import SelectAtoms_Command # [bruce 080825 split testmode into C & GM]
_superclass_C = SelectAtoms_Command
_superclass_for_GM = _superclass_C.GraphicsMode_class

class testmode_GM(_superclass_for_GM):
    """
    GraphicsMode for testing graphical exprs
    """
    # class constants for GraphicsMode

    ## check_target_depth_fudge_factor = 0.0001 # same as GLPane, tho it caused a demo_drag bug 070115 -- try lower val sometime ###e
    if 0:
        check_target_depth_fudge_factor = 0.00001 # (in 'if 0'?)
            # this gives us another 10x safety factor in demo_drag [070116]
        # Unfortunately, this change helps *cause* a bug in which highlighting
        # "flickers" (works only on alternate frames) whenever renderText is
        # used (e.g. by View -> Rulers) in testmode. So I'm disabling it now.
        # For more info see the related change in exprs/test.py.
        # [bruce 081211]

    # unclassified (Command vs GraphicsMode) [guessing for GraphicsMode -- bruce 080825]

    backgroundColor = 103/256.0, 124/256.0, 53/256.0

    compass_moved_in_from_corner = True # only works when compassPosition == UPPER_RIGHT; should set False in basicMode [revised 070110]

    _defeat_update_selobj_MMB_specialcase = True # 070224; overridden at runtime in some methods below

    ## UNKNOWN_SELOBJ = something... this is actually not set here (necessary for now) but a bit later in exprs/test.py [061218 kluge]

##    # work around some of our superclass depositMode's dependence on having its particular PM
##    # [now taken care of in depositMode itself]
##
##    def pastable_element(self): # bruce 070921
##        "[overrides superclass method]"
##        ## return self.propMgr.elementChooser.getElement()
##        from elements import Carbon
##        return Carbon

    # ==

    def render_scene(self, glpane):
        # This is always called, and modifying it would let us revise the entire rendering algorithm however needed.
        # This encompasses almost everything done within a single paintGL call -- even the framebuffer clear!
        # For what it doesn't include, see GLPane.paintGL, which gets to its call of this quickly.
        if 0:
            glpane.render_scene()
        else:
            import exprs.testdraw as testdraw
            try:
                testdraw.render_scene(self, glpane)
            except:
                print_compact_traceback("exception in testdraw.render_scene ignored: ")
        return

    def Draw_preparation(self):
        self.command._background_object = None
            # Note: by resetting this at the start of Draw (by "Draw" I mean
            # the sequence of Draw_* methods into which the old Draw method
            # was split on 090310), we make sure that a new _background_object
            # set during Draw is only active for event handlers
            # when it was set during the most recent call of Draw
            # before they run. [bruce 090310 clarified old comment]

        import exprs.testdraw as testdraw
        try:
            testdraw.Draw_preparation(self, self.o, _superclass_for_GM)
        except:
            #e history message?
            print_compact_traceback("exception in testdraw.Draw_preparation ignored: ")
        return

    def Draw_model(self):
        import exprs.testdraw as testdraw
        try:
            testdraw.Draw_model(self, self.o, _superclass_for_GM)
        except:
            #e history message?
            print_compact_traceback("exception in testdraw.Draw_model ignored: ")
        return

    def Draw_other(self):
        import exprs.testdraw as testdraw
        try:
            testdraw.Draw_other(self, self.o, _superclass_for_GM)
        except:
            #e history message?
            print_compact_traceback("exception in testdraw.Draw_other ignored: ")
        return

    def Draw_after_highlighting(self, pickCheckOnly = False): #bruce 050610
        # I'm very suspicious of this pickCheckOnly arg in the API... it's not even acceptable to some modes,
        # it's not clear whether they'll be called with it, it's not documented what it should do,
        # and there seems to be a return value when it's used, but not all methods provide one.
        # [update: I subsequently rationalized it, so it's now ok and explained in a comment somewhere]
        """
        Do more drawing, after the main drawing code has completed its
        highlighting/stenciling for selobj. Caller will leave glstate in
        standard form for Draw. Implems are free to turn off depth buffer
        read or write.

        @warning: anything implems do to depth or stencil buffers
            will affect the standard selobj-check in bareMotion.
        """
        ## _superclass_for_GM.Draw_after_highlighting(self, pickCheckOnly) # let testdraw do this if if wants to
        import exprs.testdraw as testdraw
        try:
            return testdraw.Draw_after_highlighting(self, pickCheckOnly, self.o, _superclass_for_GM)
        except:
            #e history message?
            print_compact_traceback("exception in testdraw.Draw_after_highlighting ignored: ")
        return

    def leftDown(self, event):
        import exprs.testdraw as testdraw
        try:
            testdraw.leftDown(self, event, self.o, _superclass_for_GM)
                # note: if _superclass_for_GM.leftDown and/or gl_update should be called now, this must do it.
                # note: this might reload testdraw (inside self.emptySpaceLeftDown).
        except:
            #e history message?
            print_compact_traceback("exception in testdraw.leftDown ignored: ")

    def leftDouble(self, event):
        # Note: added to testmode 070324. (See also selectMode.dragHandlerLeftDouble, which is never called --
        # either it's obsolete, or this should be merged into selectMode.leftDouble and it should call that
        # and let that do the rest of what this does now.)
        #
        # Some issues:
        # - depositMode version deposits when in empty space; ours must not do that if there's a background object
        #   (unless that object says to, somehow), but should otherwise.
        # - depositMode version sets "self.ignore_next_leftUp_event = True # Fixes bug 1467",
        #   which I guess we should always set (even if not depositing) -- not sure. In theory, bg object should decide.
        #   Also in theory, good code would never need this, since leftUp would only do things its leftDown asked it to do.
        #   Guess: depositMode.leftUp doesn't follow that principle, and this flag works around that flaw by
        #   "turning off an assumption about what leftDown did".
        #   (For example (speculation), that it reset certain variables left over from prior drag??)
        self.ignore_next_leftUp_event = True
            # I'm guessing this is always necessary, though I'm not sure.
            # Note that it will prevent the call of drag_handler.ReleasedOn -- this seems desirable,
            # since the press method (leftClick) was not called either, for the 2nd click of a double click --
            # and it's also the same behavior as before we added this implem of leftDouble.
            # However, minor ###BUG: the superclasses only reset it in leftUp -- if they were more cautious
            # they'd also do so in leftDown, in case of a missing (or buggily overidden) leftUp call.
            # (Or GLPane would, so not every mode class has to. Or it'd just call leftDoubleUp or so.)
        if self.drag_handler is not None:
            print "fyi: testmode passing leftDouble to drag_handler = %r" % (self.drag_handler,) ####
            # This works: it's a highlightable or a _background_object if we double-clicked on one,
            # and otherwise false (presumably None).
            # So when it's there, let's pass this event to it for handling, and not to the superclass.
            # (Note: most of the DragHandler interface is implemented in selectMode (see also DragHandler.py),
            #  but this new method in it ('leftDouble') is implemented only in testmode.)
            method = getattr( self.drag_handler, 'leftDouble', None)
            if method is not None:
                method(event, self) #e protect from exceptions?
        else:
            print "fyi: testmode passing leftDouble to superclass (since no drag_handler)" ####
            _superclass_for_GM.leftDouble(self, event)
        return

    def get_obj_under_cursor(self, event): #070322 [assume part of GraphicsMode even though not in all subclasses of that]
        """
        this is called in *some cases* by various event handler methods
        to find out what object the cursor is over
        """
        # The kinds of calls of this method (whose interface assumptions we need to be sensitive to):
        # - depositMode.singletLeftUp indirectly calls it and checks whether the return value is an instance of Atom,
        #   handling it specially if so.
        # - similarly, selectMode.atomLeftUp can indirectly call it and check whether it returns a non-singlet instance of Atom.
        # - its main call is in selectMode.leftDown to decide (based on the kind of object returned -- None, or anything which
        #   hasattr 'leftClick', or being an instance of Atom or Bond or Jig)
        #   which kind of more specialized leftDown method to call.
        # Note that this method is only called for leftDown or leftUp, never during a drag or baremotion,
        # so its speed is not all-important. That means it might be fast enough, even if it creates new Instances
        # (of exprs module classes) on each call.
        #    WARNING: overriding this method doesn't override the lower-level determination of glpane.selobj,
        # which is also used directly by mode event-handling methods in a similar way as they use this method,
        # and is used in GLPane to determine hover-highlighting behavior (both between and within drags -- in fact,
        #  there is not yet a good-enough way for hover-highlighting behavior to be different during a drag).
        # For some purposes, overriding the determination of selobj in GLPane would be more useful and more flexible
        # than overriding this method; to affect highlighting behavior, it would be essential. [070323 comment]
        res = _superclass_for_GM.get_obj_under_cursor(self, event)
        #e here is where we might let some other mode attr replace or wrap objects in specified classes. [070323 comment]
        if res is None:
            res = self.command._background_object # usually None, sometimes set during draw to something else [070322]
            if res is not None:
                if not hasattr(res, 'leftClick'):
                    print "bug: testmode._background_object %r has no leftClick, will be ineffective" % (res,)
        return res

    def emptySpaceLeftDown(self, event):
        #e note: if we override self.get_obj_under_cursor(event) to return a bg object rather than None,
        # i.e. if something sets self.command._background_object to something other than None,
        # then this won't be called by selectMode.leftDown.
        emptySpace_reload = debug_pref("testmode: reload on empty space leftDown?",
                                       Choice_boolean_True,
                                       prefs_key = "A9 devel/testmode/reload on empty space leftDown" ) #070312, also in exprs/test.py
        if emptySpace_reload:
            self.command.reload()
        _superclass_for_GM.emptySpaceLeftDown(self, event) #e does testdraw need to intercept this? no... instead we override get_obj_under_cursor

    # let superclass do these -- no need to define them here and let testdraw intercept them:
##    def leftDrag(self, event):
##        pass
##
##    def leftUp(self, event):
##        pass

    # middle* event methods, defined here to permit "New motion UI" to be prototyped in testmode [bruce 070224].
    #
    # Notes: there are 12 "middle" methods in all (4 key combos, and Down Drag Up),
    # tho for left & right buttons there are only 9 (since there is no ShiftCntl combo).
    #   That doesn't bother selectMode since it ignores the ShiftCntl part of the methodname,
    # channelling them all together and later using glpane.modkeys to sort them out
    # (which does define a separate state for 'Shift+Control').
    #   But for capturing these middle
    # methods (i.e. Alt/Option button) for the "New motion UI", it's tougher -- we want to use
    # selectMode's ability to count a tiny drag as not a drag, to dispatch on selobj type
    # (Atom, Bond, Jig, DragHandler_API), and maybe more, so we need to turn these into left methods here,
    # but we need to add something like glpane.altkey to not forget they were really middle,
    # and then we need to modify selectMode to not think they are really left if this would mess it up
    # (which may only matter for Atom Bond Jig being visible in testmode) -- only for DragHandler would
    # it pass the event inside (and then we'd detect glpane.altkey inside Highlightable).
    #   Either that, or we could actually implement the "New motion UI" directly inside selectMode,
    # for all objects.
    #   Either way, we need extensive mods to selectMode, but that's just been split into new files
    # in Qt4 branch but not Qt3 branch, so it might be better to not do that now, either splitting it
    # in Qt3 the same way, or holding off until Qt4 is standard.
    #   I guess there is one simpler way: ignore the issue of Atom Bond Jig being visible in testmode
    # (the bug will be that middle clicks on them act like left clicks) -- just turn middle into left,
    # don't tell selectMode, and worry only about the empty space or DragHandler cases. We can also
    # forget the ShiftCntl part at the same time. Ok, I'll try it! (It required a minor change in
    # selectMode, to notice a new attr _defeat_update_selobj_MMB_specialcase defined above.)
    #   It was simpler before I added the debug_pref to turn it off! 3 distinct methods before, all 12 needed now.

    def _update_MMB_policy(self):
        "[private helper for middle* methods]"
        capture_MMB = debug_pref("testmode capture MMB", Choice_boolean_False, prefs_key = "A9 devel/testmode/testmode capture MMB")
        self._capture_MMB = self._defeat_update_selobj_MMB_specialcase = capture_MMB
        return

    def _middle_anything(self, event, methodname):
        "[private helper for middle* methods]"
        # glpane = self.o
        # print "%s: glpane.button = %r" % (methodname, glpane.button,)
            # 'MMB' for Down and Drag, None for Up, in singlet middleClick; when it's None,
            # Highlightable will just have to remember it from the middleDown or middleDrag [and now it does]
        self._update_MMB_policy()
        if self._capture_MMB:
            if methodname.endswith('Down'):
                _superclass_for_GM.leftDown(self, event) # don't go through self.leftDown for now -- it's only used for reload, gl_update, etc
            elif methodname.endswith('Drag'):
                _superclass_for_GM.leftDrag(self, event)
            elif methodname.endswith('Up'):
                _superclass_for_GM.leftUp(self, event)
            else:
                assert 0, "bad methodname %r" % (methodname,)
        else:
            method = getattr(_superclass_for_GM, methodname)
            method(self, event)
        return

    def middleDown(self, event):
        self._middle_anything( event, 'middleDown')
    def middleShiftDown(self, event):
        self._middle_anything( event, 'middleShiftDown')
    def middleCntlDown(self, event):
        self._middle_anything( event, 'middleCntlDown')
    def middleShiftCntlDown(self, event):
        self._middle_anything( event, 'middleShiftCntlDown')

    def middleDrag(self, event):
        self._middle_anything( event, 'middleDrag')
    def middleShiftDrag(self, event):
        self._middle_anything( event, 'middleShiftDrag')
    def middleCntlDrag(self, event):
        self._middle_anything( event, 'middleCntlDrag')
    def middleShiftCntlDrag(self, event):
        self._middle_anything( event, 'middleShiftCntlDrag')

    def middleUp(self, event):
        self._middle_anything( event, 'middleUp')
    def middleShiftUp(self, event):
        self._middle_anything( event, 'middleShiftUp')
    def middleCntlUp(self, event):
        self._middle_anything( event, 'middleCntlUp')
    def middleShiftCntlUp(self, event):
        self._middle_anything( event, 'middleShiftCntlUp')


    def keyPressEvent(self, event):
        try:
            key = event.key()
        except:
            # no one reported a problem with this, but we might as well be paranoid about it [bruce 070122]
            key = -1
        if key == 32:
            pass ## self._please_exit_loop = True
        else:
            _superclass_for_GM.keyPressEvent(self, event) #060429 try to get ',' and '.' binding #bruce 070122 basicMode->superclass
        return

    def keyReleaseEvent(self, event):
        _superclass_for_GM.keyReleaseEvent(self, event) #bruce 070122 new feature (probably fixes some bugs), and basicMode->superclass

##    # probably unused, but mentioned in real code, and might be added back (related to self.myloop):
##    _please_exit_loop = False
##    _in_loop = False
##    _loop_start_time = 0

    pass # end of class testmode_GM

# ==

class testmode(_superclass_C):
    """
    Command for testing graphical exprs
    """
    # class constants for Command

    GraphicsMode_class = testmode_GM

    commandName = 'testmode' # must be same as module basename, for 'custom mode'
    featurename = "Test Command: Exprs Package"
    from utilities.constants import CL_ENVIRONMENT_PROVIDING
    command_level = CL_ENVIRONMENT_PROVIDING

    def reload(self):
        import exprs.testdraw as testdraw
        #bruce 070611 bugfix for release builds: add try/except
        try:
            reload(testdraw)
        except ImportError:
            pass # print "testdraw reload ImportError, ignoring"
        return

    _background_object = None #070322 new feature: can be set during Draw
        # to something to handle clicks on background
        # Note: this is stored in the Command instance, but usually accessed
        # from the GraphicsMode instance using .command._background_object.
        # [bruce 071010]

    if 0:
        ### TODO: port these methods to new command API!
        # the code works without it, but presumably some of these side effects
        # were desirable. [bruce 080929 comment, and 'if 0']

        def Enter(self):
            print
            print "entering testmode again", time.asctime()
            self.reload()
    ##        self.assy = self.w.assy # [now done by basicCommand]
            self.o.pov = V(0,0,0)
            self.o.quat = Q(1,0,0,0)

            res = _superclass_C.Enter(self)
            if 1:
                # new 070103; needs reload??
                import exprs.testdraw as testdraw
                testdraw.end_of_Enter(self.o)
            return res

        def init_gui(self): #050528; revised 070123
            ## self.w.modifyToolbar.hide()
            self.hidethese = hidethese = []
            win = self.w
            for tbname in annoyers:
                try:
                    try:
                        tb = getattr(win, tbname)
                    except AttributeError: # someone might rename one of them
                        ## import __main__
                        if 0: ## __main__.USING_Qt3: # most of them are not defined in Qt4 so don't bother printing this then [bruce 070123]
                            print "testmode: fyi: toolbar missing: %s" % tbname # someone might rename one of them
                    else:
                        if tb.isVisible(): # someone might make one not visible by default
                            tb.hide()
                            hidethese.append(tb) # only if hiding it was needed and claims it worked
                except:
                    # bug
                    print_compact_traceback("bug in hiding toolbar %r in testmode init_gui: " % tbname)
                continue
            return

        def restore_gui(self): #050528
            ## self.w.modifyToolbar.show()
            for tb in self.hidethese:
                tb.show()

    def makeMenus(self):
        ### WARNING: this copies and slightly modifies selectMode.makeMenus (not those of our superclass, depositMode!);
        # with slightly more work, we could instead just decide when to call the superclass one here
        # vs. when not to, rather than duplicating the menu items it produces.
        # But we can't do that for now, since we want to ditch its general items
        # whenever there is a selobj which defines make_selobj_cmenu_items,
        # even when we add atom-specific ones it also hardcodes,
        # and later we may also decide to not ditch them if the selobj's make_selobj_cmenu_items returns nothing.
        # DANGER: if this copied code got changed for Qt4, we're introducing a Qt4 porting problem into testmode.
        # [bruce 070228]

        selatom, selobj = self.graphicsMode.update_selatom_and_selobj( None)

        # not doing:
        ## superclass.makeMenus(self) # this makes standard items for selobj if it's atom or bond or Highlightable, and a few more

        self.Menu_spec = []

        # Local minimize [now called Adjust Atoms in history/Undo, Adjust <what> here and in selectMode -- mark & bruce 060705]
        # WARNING: This code is duplicated in depositMode.makeMenus(). mark 060314.
        if selatom is not None and not selatom.is_singlet() and self.w.simSetupAction.isEnabled():
            # see comments in depositMode version
            self.Menu_spec.append(( 'Adjust atom %s' % selatom, lambda e1 = None, a = selatom: self.localmin(a,0) ))
            self.Menu_spec.append(( 'Adjust 1 layer', lambda e1 = None, a = selatom: self.localmin(a,1) ))
            self.Menu_spec.append(( 'Adjust 2 layers', lambda e1 = None, a = selatom: self.localmin(a,2) ))

        # selobj-specific menu items. [revised by bruce 060405; for more info see the same code in depositMode]
        if selobj is not None and hasattr(selobj, 'make_selobj_cmenu_items'):
            try:
                selobj.make_selobj_cmenu_items(self.Menu_spec)
            except:
                print_compact_traceback("bug: exception (ignored) in make_selobj_cmenu_items for %r: " % selobj)

        return # from makeMenus

    pass # end of class testmode

# end