summaryrefslogtreecommitdiff
path: root/cad/src/exprs/demo_MT.py
blob: ee0f8265ea6a64e41f0dddbfc2c421393920ec34 (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
# Copyright 2007 Nanorex, Inc.  See LICENSE file for details. 
"""
demo_MT.py - demo of "model tree in GLPane" (primitive, but works)

@author: bruce
@version: $Id$
@copyright: 2007 Nanorex, Inc.  See LICENSE file for details.


### WARNING: the rest of this docstring has not been reviewed since before MT_try2 was implemented
and MT_try1 was moved into an outtakes file, demo_MT_try1_obs.py. Some of the comments in this file
may belong in that outtakes file rather than here. [070210]


works in testexpr_18

warning: *not* being used in test.py's top_left_corner [as of 070105 anyway], that's something else that looks the same --
but on 070206 it is now being used there...

bugs to fix:
- overlay highlight bug (gray hides plus sign -- for best way to fix, see notesfile 070105)
- updating
- see "nim issues" comment below

nfrs:
- use DisplayListChunk
- see "needed polish" comment below, etc

[renamed from MT_demo.py, 070106]
"""

#todo 070208:
# cleanup:
# - move the new one to top left (try2 on new model) (depends on particular testexpr to find model)
# - move deprecated MT_try1 into an outtakes file to avoid confusion (maybe a loadable one so testexpr_18 & _30h still works)
# - start out being open at the top
# +? turn off debug prints
# - reorder args to _try2 helper classes
# - make helper classes attrs of main MT_try2 (rather than globals)
# demo:
# - cross-highlighting
# polish:
# - alignment bug (openclose vs other parts of row in item)
# - type icons
# - openclose needs a triangle too, so mac ppl can understand it
# someday:
# - implem type coercion and use it to let anything work as a node
#   - move Interface to its own file
#   - make ModelTreeNodeInterface real and use it for that
# - autoupdate for legacy nodes
# - permit_expr_to_vary inval during recompute logic bug: (in instance_helpers, but made use of here)
#   - verify it by test (maybe no bug in MT_try2, so the test is a new class designed to hit the bug)
#   - fix it -- see comments where it's defined


#e stub, but works in test.py:
## # demo_MT
## ###e need better error message when I accidently pass _self rather than _my]
## testexpr_18 = MT_try1( _my.env.glpane.assy.part.topnode ) # works! except for ugliness, slowness, and need for manual update by reloading.


# biggest nim issues [some marked with ####]: [solved just barely enough for testexpr_18]
# - where to put a caching map from kidnode, args to MT(kidnode, args)
#   - note, that's a general issue for any "external data editor"
#     where the node is the external data and MT is our preferred-edit-method
#   - does the scheme used for _texture_holder make sense? ###k
#     - should the usual place for maps like this be explicit MemoDicts located in self.env?? ###k
#       by "like this" I mean, maps from objects to fancier things used to interface to them, potentially shared.
#     - how similar is this to the map by a ColumnList from a kid fixed_type_instance to CLE(that instance)?
#       - guess: it can be identical if we specify what ought to be included in the map-key, i.e. when to share.
#         If that includes "my ipath" or "my serno" (my = the instance), it can act like a local dict (except for weakref issues).
# - where & how to register class MT as the "default ModelTree viewer for Node",
        # [note 070206: that class MT is not MT_try1 -- that's a helper expr for a whole-MT exprhead, which is nim]
#   where 'ModelTree' is basically a general style or place of viewing, and Node is a type of data the user might need to view that way?
#   And in viewing a kid (inside this class), do we go through that central system to create the viewer for it, passing it enough env
#   that it's likely to choose the same MT class to view a kid of a node and that node, but not making this unavoidable? [guess: yes. ##k]
#   Note: if so, this has a lot to say about the viewer-caching question mentioned above.
#   Note: one benefit of that central system is to help with handling other requests for an "obj -> open view of it" map,
#    like for cross-highlighting, or to implem "get me either an existing or new ModelTree viewer for this obj, existing preferred".
#    These need a more explicit ref from any obj to its open viewers than just their presumed observer-dependency provides.
#    Note that the key might be more general for some purposes than others, e.g. I'll take an existing viewer even if it was opened
#    using prefs different than I'd use to make a new one. I'm not sure if any one cache needs more than one key-scheme applicable to
#    one value-slot. Let's try to avoid that for now, except for not excluding it in the general architecture of the APIs.
# - arg semantics for Node
# - or for time-varying node.MT_kids() (can be ignored for now)

# complicated details:
# - usage/mod tracking of node.open, node.MT_kids()
#   [maybe best to redo node, or use a proxy... in future all model objs need this natively]
#   - for an initial demo, do it read-only, i.e. don't bother tracking changes by others to external state

# needed polish:
# - better fonts - from screenshots of NE1 or Safari?
#   - appearing selected
#   - italic, for disabled
# - current-part indicator
# - transparency

# opportunities for new features:
# - cross-highlighting -- mouseover an atom, chunk, jig, or MT chunk or jig, highlights the others too, differently
#   - does require realtime changetracking, *but* could be demoed on entirely new data rather than Node,
#    *or* could justify adding new specialcase tracking code into Node, Atom, Bond.

# small nims:
# - map_Expr
# - mt_instance = MT_try1(obj) # with arg1 being already instantiated, this should make an instance ###IMPLEM that #k first be sure it's right

# #e more??


# ===

### WARNING: the comments above this line have not been reviewed since before MT_try2 was implemented
# and MT_try1 was moved into an outtakes file, demo_MT_try1_obs.py. Some of them
# may belong in that outtakes file rather than here, or maybe be entirely obs even there. [070210] ###


# == imports

from utilities.constants import blue, green, ave_colors, white

from utilities.debug import print_compact_traceback
from utilities.debug import safe_repr

from exprs.Highlightable import Highlightable
from exprs.TextRect import TextRect
from exprs.Column import SimpleRow, SimpleColumn
from exprs.Overlay import Overlay
from exprs.Set import Set
from exprs.Rect import Rect, Spacer
from exprs.images import Image
from exprs.Center import CenterY, Center
from exprs.transforms import Translate
from exprs.projection import DrawInCenter #e but what we need is not those, but DrawInAbsCoords or DrawInThingsCoords
    # or really, just get the place (or places) a thing will draw in, in local coords (see projection.py for more discussion)

from exprs.DisplayListChunk import DisplayListChunk
from exprs.Exprs import call_Expr, list_Expr, getattr_Expr, not_Expr, format_Expr
from exprs.Exprs import is_expr_Instance
from exprs.Exprs import is_Expr
from exprs.If_expr import If
from exprs.widget2d import Stub
from exprs.iterator_exprs import MapListToExpr
from exprs.iterator_exprs import KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr
from exprs.attr_decl_macros import Option, State, Arg
from exprs.instance_helpers import DelegatingInstanceOrExpr, ModelObject, InstanceMacro
from exprs.ExprsConstants import StubType
from exprs.py_utils import identity
from exprs.__Symbols__ import Anything, _self

# ==

##e prototype, experimental definition of an Interface
# (all use of which nim, and it's a stub anyway -- but intended to become real). [070207]

class Interface:
    """
    ###doc
       Note: most InstanceOrExpr interfaces (e.g. ModelTreeNodeInterface) consists of attrs
    (recomputable, tracked), not methods, to make it easier and more concise to give formulae for them when defining
    how some IorE subclass should satisfy the interface. If you want to define them with methods in a particular case,
    use the standard compute method prefix _C_, e.g.
    
        def _C_mt_kids(self):
            return a sequence of the MT_kids (which the caller promises it will not try to modify).
            
        or
        
        mt_kids = formulae for sequence of MT_kids
        
    This means that to tell if a node follows this interface, until we introduce a new formalism for that [#e as we should],
    or a way to ask whether a given attr is available (perhaps for recomputation) without getting its value [#e as we should],
    you may not be able to avoid getting the current value of at least one attr unique to the interface. ### DESIGN FLAW
    It also means that a text search for all implems of the interface attr should search for _C_attr as well as attr
    (if it's a whole-word search).

    Attr names: the interface attrnames in ModelTreeNodeInterface all start with mt_ as a convention, for several reasons:
    - less chance of interference with attrs in other interfaces supported by the same objects,
      or with attrs already present locally in some class;
    - less chance of accidental reuse in a subclass of a class that inherits the interface;
    - more specific name for text search;
    - to make it more obvious that their defs might be part of a definite interface (and give a hint about finding it).
    """
    pass ###e STUB

Attr = Option ###e STUB -- an attr the interface client can ask for (with its type, default value or formula, docstring)
StateAttr = State ###e STUB -- implies that the interface client can set the attr as well as getting it,
    # and type coercion to the interface might create state as needed to support that (if it's clear how to do that correctly)
Id = StubType

class ModelTreeNodeInterface(Interface):
    """
    Interface for a model tree node (something which can show up in a model tree view).
    Includes default compute method implems for use by type-coercion [which is nim]
    [see also ModelTreeNode_trivial_glue, and the node_xxx helper functions far below, for related code].
       WARNING: this class, itself, is not yet used except as a place to put this docstring.
    But the interface it describes is already in use, as a protocol involving the attrs described here.
    """
    _object = Arg(Anything, doc = "an object we want to coerce into supporting this interface") ###k?? #e does it have to sound so nasty?
    # the recompute attrs in the interface, declared using Attr [nim] so they can include types and docstrings
    mt_node_id =   Attr( Id,        call_Expr( id, _object), doc = "a unique nonrecyclable id for the node that object represents")
        ###BUG: id is wrong -- ipath would be closer (but is not really correct, see comments in def mt_node_id)
    mt_name = StateAttr( str,       "",    doc = "the name of a node in the MT; settable by the MT view (since editable in that UI)")
    mt_kids =      Attr( list_Expr, (),    doc = "the list of visible kids, of all types, in order (client MT view will filter them)")
    mt_openable =  Attr( bool,      False, doc = "whether this node should be shown as openable; if False, mt_kids is not asked for")
                ##e (consider varying mt_openable default if node defines mt_kids, even if the sequence is empty)
    # (##e nothing here yet for type icons)
    pass

# some existing interfaces we might like to clean up and formalize:
# - "selobj interface" (from GLPane to a hover-highlighted object; provided by Highlightable)
# - "DragHandler interface (from selectMode to an object that handles its own mouse clicks/drags; see class DragHandler_API)
#
# some new interfaces we'd like:
# - many of the types used in Arg(type, x) are really interfaces, e.g. ModelObject
#   (which may have variants. eg movable or not, geometric or not, 3d or not -- some of these are like orthogonal interfaces
#    since graphical decorating wrappers (eg Overlay, Column) might provide them too)
# - Draggable (something that provides _cmd_drag_from_to)
# - ModelTreeNodeInterface
# - whatever is needed to be dragged by DraggableObject
# - Drawable (whatever is needed to be drawn -- may have variants, may apply to POV-Ray too)

# ==

# the following ModelTreeNode_trivial_glue example is not yet used, but it points out that default formula for interface attrs
# would often like to refer to the object they're trying to type-coerce (in this case, to use its classname to init new state).

class ModelTreeNode_trivial_glue(DelegatingInstanceOrExpr): #070206, 070207
    # args
    delegate = Arg(ModelObject)
    # default formulae for ModelTreeNodeInterface -- including one which supplies new state
    mt_name = State(str, getattr_Expr(getattr_Expr(delegate, '__class__'), '__name__')) ###k
    #e type - grab it from object, or related to role in parent...
    mt_kids = ()
    mt_openable = False
    ###e something for mt_node_id
    pass

# ==

# define some helper functions which can apply either the new ModelTreeNodeInterface or the legacy Utility.Node interface
# to node, or supply defaults permitting any object to show up in the MT.
#
###BUG: they decide which interface to use for each attr independently, but the correct way is to decide for all attrs at once.
# shouldn't matter for now, though it means defining mt_kids is not enough, you need mt_openable too
# (note that no automatic type coercion is yet implemented, so nothing ever yet uses
#  the default formulae in class ModelTreeNodeInterface).

def node_kids(node): # revised 070207 # REVIEW: rename to something like node_MT_kids? [bruce 080108 comment]
    """
    return the kid list of the node, regardless of which model tree node interface it's trying to use [slight kluge]
    """
    try:
        node.mt_kids # look for (value computed by) ModelTreeNodeInterface method
    except AttributeError:
        pass
    else:
        return node.mt_kids

    try:
        node.MT_kids # look for legacy Node method
    except AttributeError:
            pass
    else:
        return node.MT_kids()

    return () # give up and assume it has no MT_kids

def node_openable(node): # revised 070207
    """
    return the openable property of the node, regardless of which
    model tree node interface it's trying to use [slight kluge]
    """
    try:
        node.mt_openable # look for (value computed by) ModelTreeNodeInterface method
    except AttributeError:
        pass
    else:
        return node.mt_openable

    try:
        node.openable # look for legacy Node method
    except AttributeError:
            pass
    else:
        return node.openable() ###k

    return False # give up and assume it's not openable (as the default formula for mt_openable would have us do)
        ##e (consider varying this if node defines mt_kids or kids, even if they are empty)

def node_name(node): # revised 070207
    """
    return the name property of the node, regardless of which
    model tree node interface it's trying to use [slight kluge]
    """
    try:
        node.mt_name # look for (value computed by) ModelTreeNodeInterface method
    except AttributeError:
        pass
    else:
        return node.mt_name

    try:
        node.name # look for legacy Node variable
    except AttributeError:
            pass
    else:
        return node.name

    try:
        return "%s" % node
    except:
        last_resort = safe_repr(node, maxlen = 20) ### FIX: Undefined variable safe_repr, print_compact_traceback
        print_compact_traceback("node_name fails when trying %%s on node %s: " % last_resort )
        return last_resort
    pass

print_mt_node_id = False # set True for debugging (or remove in a few days [070218])

def mt_node_id(node): # 070207; the name 'node_id' itself conflicts with a function in Utility.py (which we import and use here, btw)
    """
    return the mt_node_id property of the node, regardless of which
    model tree node interface it's trying to use [slight kluge]
    """
    # look for value of ModelTreeNodeInterface attr
    try:
        node.mt_node_id
    except AttributeError:
        pass
    else:
        if print_mt_node_id:        
            print "this node %r has mt_node_id %r" % (node,node.mt_node_id)
            # old Q: where do our Rects get it?
            # A: they don't -- the bug fixed by bugfix070218 meant this was never called except for World!!
            # [note: on 070302 the comments about bugfix070218, and the code affected by it, was rewritten and removed
            #  (becoming MapListToExpr).
            #  it might still be in an outtakes file not in cvs on bruce's g5 -- or in cvs rev 1.21 or earlier of this file.]
            # btw, we want it to be for the
            # underlying model object... sort of like how we ask for name or type... [bug noticed 070218 late; see below comment]
        return node.mt_node_id

##    assert not is_expr_Instance(node), "what node is this? %r" % (node,) # most to the point
    if is_expr_Instance(node):
        # All Instances ought to have that, or delegate to something which does -- which ought to be their contained ModelObject.
        ####FIX SOMETIME (implem that properly)
        # Until that's implemented properly (hard now -- see _e_model_type_you_make for the hoops you have to jump thru now),
        # use the ipath (non-ideal, since captures wrappers like Draggable etc, but tolerable,
        # esp since this effectively means use the world-index, which will work ok for now [070218 late])
        return node.ipath ###e should intern it as optim
        
    assert not is_Expr(node), "pure exprs like %r don't belong as nodes in the model tree" % (node,)
    
    # look for legacy Node property
    from foundation.Utility import node_id
    res = node_id(node) # not sure what to do if this fails -- let it be an error for now -- consider using id(node) if we need to
    if print_mt_node_id:
        print "legacy node %r has effective mt_node_id %r" % (node,res)
    return res

def mt_node_selected(node): #070216 experiment
    """
    #doc
    """
    # look for value of ModelTreeNodeInterface attr (note: lots of objs don't have this; it's also #WRONG(?), should ask the env)
    try:
        node.selected
    except AttributeError:
        pass
    else:
        return node.selected

    # look for legacy Node property
    try:
        node.picked
    except AttributeError:
        pass
    else:
        return node.picked

    return False
    
# ===

ModelNode = ModelObject ###stub -- should mean "something that satisfies (aka supports) ModelNodeInterface"

class MT_try2(DelegatingInstanceOrExpr): # works on assy.part.topnode in testexpr_18i, and on World in testexpr_30i
    """
    Model Tree view, using the argument as the top node.
    Has its own openclose state independent of other instances of MT_try2, MT_try1, or the nodes themselves.
    Works on IorE subclasses which support ModelTreeNodeInterface, or on legacy nodes (assy.part.topnode),
    but as of 070208 has no way to be notified of changes to legacy nodes (e.g. openclose state or MT_kids or name).
       Has minor issues listed in "todo" comment [070208] at top of source file.
    [This is the official version of a "model tree view" in the exprs package as of 070208; replaces deprecated MT_try1.]
    """
    arg = Arg(ModelNode, doc = "the toplevel node of this MT view (fixed; no provision yet for a toplevel *list* of nodes #e)")
    #e could let creator supply a nonstandard way to get mt-items (mt node views) for nodes shown in this MT
    def _C__delegate(self):
        # apply our node viewer to our arg
        return self.MT_item_for_object(self.arg, initial_open = True)
            # note: this option to self.MT_item_for_object also works here, if desired: name_suffix = " (MT_try2)"
            ## no longer needed after bugfix070218: name_suffix = " (slow when open!)"
    def MT_item_for_object(self, object, name_suffix = "", initial_open = False):
        "find or make a viewer for object in the form of an MT item for use in self"
        ###e optim: avoid redoing some of the following when we already have a viewer for this object --
        # but to find out if we have one, we probably can't avoid getting far enough in the following to get mt_node_id(object)
        # to use as index (since even id(object) being the same as one we know, does not guarantee mt_node_id(object) is the same --
        # unless we keep a reference to object, which I suppose we could do -- hmm... #k #e),
        # which means coercing object enough into ModelNodeInterface to tell us its mt_node_id.
        # Maybe try to make that fast by making most of it lazily done?
        #
        #e coerce object into supporting ModelNodeInterface 
        object = identity(object) ###e STUB: just assume it already does support it
        index = ('MT_item_for_object', mt_node_id(object))
            # note: the constant string in the index is to avoid confusion with Arg & Instance indices;
            # it would be supplied automatically if we made this using InstanceDict [nim] #e
            ###e if nodes could have >1 parent, we'd need to include parent node in index -- only sufficient if some
            # nodes have to be closed under some conds not spelled out here (I think: at most one MT item for a given node
            # is open at a time ###k) -- otherwise the entire root-parents-node path might be needed in the index,
            # at least to permit nonshared openness bit of each separately drawn mt item, which is strongly desired
            # (since we might like them to interact, but not by being shared -- rather, by opening one view of a node closing
            #  its other views)
        expr = _MT_try2_node_helper(object, self, name_suffix = name_suffix, initial_open = initial_open)
            ###BUG: if object differs but its mt_node_id is the same, the Instance expr sameness check might complain!!!
            # Can this happen?? (or does it only happen when self is also not the same, so it's not a problem?) #k
            ##e optim: have variant of Instance in which we pass a constant expr-maker, only used if index is new
            # WARNING: if we do that, it would remove the errorcheck of whether this expr is the same as any prior one at same index
        return self.Instance( expr, index) ###k arg order -- def Instance
    pass # end of class MT_try2

class _MT_try2_kids_helper(DelegatingInstanceOrExpr): # rewrote this 070302 (splitting out older one's alg as MapListToExpr) -- works!
    """
    [private helper expr class for MT_try2]
    One MT item kidlist view -- specific to one instance of _MT_try2_node_helper.
    [#e if we generalize MT_try2 to support a time-varying list of toplevel nodes,
     then we might use one of these at the top, instead of using a single node at the top --
     but more likely a variant of this (at least passing it an option), to turn off
     anything it might display in the left which only makes sense when it's elts are under a common parent node.]
    """
    # args
    kids = Arg( list_Expr, doc = "the sequence of 0 or more kid nodes to show (after filtering, reordering, etc, by _self.mt)")
    mt = Arg( MT_try2,     doc = "the whole MT view (for central storage and prefs)") ###e let this be first arg, like self is for methods??
    parent_item = Arg( mt._MT_try2_node_helper, None, doc = "the parent node item for these kid nodes") #k needed??
    # formulae
    delegate = MapListToExpr( mt.MT_item_for_object,
                              kids,
                              KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(SimpleColumn) )
    pass

class _MT_try2_node_helper(DelegatingInstanceOrExpr):
    """
    [private helper expr class for MT_try2]
    One MT item view -- specific to one node, one whole MT, and one (possibly time-varying) position with it.
    """
    # args ####e REORDER THEM
    node = Arg(ModelNode, doc = "any node that needs to be displayed in this MT")
        ###e NOTE: type coercion to this is nim; while that's true, we use helper functions like node_name(node) below;
        # once type coercion is implemented
        # (or simulated by hand by wrapping this arg with a helper expr like ModelTreeNode_trivial_glue),
        #  we could instead use node.mt_name, etc.)
    mt = Arg(MT_try2, doc = "the whole MT view, in which we store MT items for nodes, and keep other central state or prefs if needed")
    name_suffix = Option(str, "")
    initial_open = Option(bool, False, doc = "initial value of boolean state 'open'; only used when this item is first created")
        ##e should ask the node itself for the initial value of open (e.g. so new groups, trying to start open, can do so),
        # and also advise it when we open/close it, in case it wants to make that state persistent in some manner
        
    # WARNING: compare to MT_try1 -- lots of copied code after this point
    # WARNING: the comments are also copied, and not yet reviewed much for their new context! (so they could be wrong or obs) ###k
    
    # state refs
    open = State(bool, initial_open)
    
    # other formulae
    ###e optim: some of these could have shared instances over this class, since they don't depend on _self; should autodetect this
    # Note, + means openable (ie closed), - means closable (ie open) -- this is the Windows convention (I guess; not sure about Linux)
    # and until now I had them reversed. This is defined in two files and in more than one place in one of them. [bruce 070123]
    open_icon   = Overlay(Rect(0.4), TextRect('-',1,1))
    closed_icon = Overlay(Rect(0.4), TextRect('+',1,1))
    openclose_spacer = Spacer(0.4)
        #e or Invisible(open_icon); otoh that's no simpler, since open_icon & closed_icon have to be same size anyway

    # the openclose icon, when open or close is visible (i.e. for openable nodes)
    openclose_visible = Highlightable(
        If( open, open_icon, closed_icon ),
        on_press = Set(open, not_Expr(open)),
        sbar_text = getattr_Expr( _self, '_e_serno') #070301 this permits finding out how often MT gets remade/shared
            # (results as of 070301: remade when main instance is, even if going back to a prior testexpr, out of _19i & _30i)
     )
    
    openclose_slot = If( call_Expr(node_openable, node), openclose_visible, openclose_spacer )


    if 0:
        # cross-highlighting experiment, 070210, but disabled since approach seems wrong (as explained in comment)
        yellow = DZ = 'need to import these'
        indicator_over_obj_center = Center(Rect(0.4, 0.4, yellow))
        position_over_obj_center = node.center + DZ * 3 ###BUG: DZ does not point towards screen if trackballing was done
            ###STUB:
            # - should be drawn in a fixed close-to-screen plane, or cov plane (if obscuring is not an issue),
            #   - so indicator size is constant in pixels, even in perspective view (I guess),
            #   - also so it's not obscured (especially by node itself) -- or, draw it in a way visible behind obscuring things (might be a better feature)
            # - what we draw here should depend on what node is
            # - we also want to draw a line from type icon to node indicator (requires transforming coords differently)
            # - needs to work if node.center is not defined (use getattr_Expr - but what dflt? or use some Ifs about it)
        pointer_to_obj = DrawInCenter( Translate( indicator_over_obj_center, position_over_obj_center))
            #bug: Translate gets neutralized by DrawInCorner [fixed now]
            ###BUG: fundamentally wrong -- wrong coord system. We wanted DrawInAbsCoords or really DrawInThingsCoords,
            # but this is not well-defined (if thing drawn multiply) or easy (see comments about the idea in projection.py).
    else:
        # What we want instead is to set a variable which affects how the obj is drawn.
        # If this was something all objs compared themselves to, then all objs would track its use (when they compared)
        # and therefore want to redraw when we changed it! Instead we need only the involved objs (old & new value) to redraw,
        # so we need a dict from obj to this flag (drawing prefs set by this MT). Maybe the app would pass this dict to MT_try2
        # as an argument. It would be a dict of individually trackable state elements. (Key could be node_id, I guess.)
        # ### TRY IT SOMETIME -- for now, cross-highlighting experiment is disabled.
        pointer_to_obj = None

    # selection indications can use this
    node_is_selected = call_Expr( mt_node_selected, node)
    kluge_icon_color = If( node_is_selected, blue, green)
    sbar_format_for_name = If( node_is_selected, "%s (selected)", "%s")
    
    ###STUB for the type_icon ##e the Highlightable would be useful on the label too
    icon = Highlightable(
        Rect(0.4, 0.4, kluge_icon_color), ##stub; btw, would be easy to make color show hiddenness or type, bfr real icons work
        Overlay( Rect(0.4, 0.4, ave_colors(0.1, white, kluge_icon_color)),
                 #070216 mix white into the color like DraggableObject does
                 pointer_to_obj ),
        sbar_text = format_Expr( sbar_format_for_name, call_Expr(node_name, node) )
     )
    
    ##e selection behavior too

    label = DisplayListChunk(
        # added DisplayListChunk 070213 late -- does it speed it up? not much; big new-item slowness bug remains. retain, since doesn't hurt.
        TextRect( call_Expr(node_name, node) + name_suffix )
     )
        ###e will need revision to Node or proxy for it, so node.name is usage/mod-tracked
        ##e selection behavior too --
        #e probably not in these items but in the surrounding Row (incl invis bg? maybe not, in case model appears behind it!)
        ##e italic for disabled nodes
        ##e support cmenu
    
    delegate = SimpleRow(
        CenterY(openclose_slot),
        SimpleColumn(
            SimpleRow(CenterY(icon), CenterY(label)),
                #070124 added CenterY, hoping to improve text pixel alignment (after drawfont2 improvements) -- doesn't work
            If( open,
                _MT_try2_kids_helper( call_Expr(node_kids, node) , _self.mt ), # 070218 added _self.mt -- always intended, first used now
                None
                    # Note: this None used to be Spacer(0), due to a bug mentioned in a comment in ToggleShow.py
                    # (but unfortunately not explained there -- it just says "I wanted None here, but it exposes a logic bug,
                    # not trivial to fix, discuss in If or Column" -- my recollected bug-theory is described just below).
                    # On 070302 I confirmed that None seems to work (even in testexpr_18i with a group of 2 chunks, plus two more below).
                    # I don't fully know why it works, since I thought the bug was that SimpleColumn's None specialcase
                    # didn't run, since the element was not None but the If, and then delegating lbox attrs to None didn't work.
                    # (Fixable by using the newer If that evals, but for some reason that's not yet standard, I guess just because
                    # I didn't have time to test it enough or think it through fully re ipath or instance caching or something.)
                    # But as long as it works, use it -- ask Qs later. A recent perhaps-related change: None is allowed in drawkid.
                    # (A memory scrap -- does instantiating None conceivably produce a spacer?? ###k)
             )
         )
     )
    pass # end of class _MT_try2_node_helper

# ==

# note: this predates MT_try2, but it's not moved into demo_MT_try1_obs.py since it might still be used someday. [070210]

Node = Stub # [later note 061215: this is probably the same as Utility.Node;
  # it's NOT the same as that's new subclass, ModelNode in ModelNode.py.]

class test_drag_pixmap(InstanceMacro):
    mt = Arg(Anything) # pass _my.env.glpane.assy.w.mt
    node = Arg(Node) # pass the topnode
    def _C__value(self):
        mt = self.mt # fails, recursion in self.delegate computation -- why? related to parent AttributeError: win? must be.
            ###e NEED BETTER ERROR MESSAGE from exception when computing self.mt by formula from caller.
            # It did have the attrerror, but buried under (and then followed by) too much other stuff (not stopping) to notice.
        node = self.node
        pixmap = mt.get_pixmap_for_dragging_nodes('move', [node]) # (drag_type, nodes); method defined in TreeWidget.py
            #e includes "moving n items", need to make it leave that out if I pass None for drag_type
        print pixmap # <constants.qt.QPixmap object at 0x10fbc2a0>
        #e make Image (texture) from pixmap -- how?
        # - pass the pixmap into ImageUtils somehow (won't be hard, could go in in place of the filename)
        # - worry about how it works in our texture-caching key (won't be hard)
        # - teach ImageUtils how to get a PIL image or equivalent data from it -- requires learning about QPixmap, image formats
        # MAYBE NOT WORTH IT FOR NOW, since I can get the icons anyway, and for text I'd rather have a Qt-independent path anyway,
        # if I can find one -- tho I predict I'll eventually want this one too, so we can make GL text look the same as Qt text.
        # Note: PixelGrabber shows how to go in the reverse direction, from GL to Qt image.
        # Guess: QPixmap or QImage docs will tell me a solution to this. So when I want nice text it might be the quickest way.
        # (Also worth trying PIL's builtin text-image makers, but I'm not sure if we have them in NE1 even tho we have PIL.)
        # The other way is to grab actual screenshots (of whatever) and make my own font-like images. Not ideal, re user-reconfig
        # of font or its size!
        #
        # Here are hints towards using Qt: turn it into QImage, which boasts
        # "The QImage class provides a hardware-independent pixmap representation with direct access to the pixel data."
        # and then extract the data -- not yet known how much of this PyQt can do.
        #
        # - QImage QPixmap::convertToImage () const
        #
        ##uchar * QImage::scanLine ( int i ) const
        ##
        ##Returns a pointer to the pixel data at the scanline with index i. The first
        ##scanline is at index 0.
        ##
        ##The scanline data is aligned on a 32-bit boundary.
        ##
        ##Warning: If you are accessing 32-bpp image data, cast the returned pointer
        ##to QRgb* (QRgb has a 32-bit size) and use it to read/write the pixel value.
        ##You cannot use the uchar* pointer directly, because the pixel format depends
        ##on the byte order on the underlying platform. Hint: use qRed(), qGreen() and
        ##qBlue(), etc. (qcolor.h) to access the pixels.

        image = pixmap.convertToImage()
        print image # <constants.qt.QImage object at 0x10fe1900>

        print "scanlines 0 and 1 are", image.scanLine(0), image.scanLine(1)
            # <sip.voidptr object at 0x5f130> <sip.voidptr object at 0x5f130> -- hmm, same address
        print "image.bits() is",image.bits() # image.bits() is <sip.voidptr object at 0x5f130> -- also same address

        print "\n*** still same address when all collected at once?:" # no. good.
        objs = [image.scanLine(0), image.scanLine(1), image.bits()]
        for obj in objs:
            print obj
        print
        # but what can i do with a <sip.voidptr object>? Can Python buffers help?? Can PIL use it somehow??
        # Or do I have to write a C routine? Or can I pass it directly to OpenGL as texture data, if I get the format right?
        # Hmm, maybe look for Qt/OpenGL example code doing this, even if in C. ##e
        
        _value = Image( "notfound")
        env = self.env
        ipath = self.ipath
        return _value._e_eval( env, ('v', ipath)) # 'v' is wrong, self.env is guess
            ## AssertionError: recursion in self.delegate computation in <test_drag_pixmap#15786(i)> -- in pixmap set above
    pass

# end