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
def Instance(expr, _index_expr=_E_ATTR, _lvalue_flag=False, _noinstance=False, doc=None): """ This macro is assigned to a class attr to declare that its value should be a lazily-instantiated Instance of expr (by default). Assuming the arg is an expr (not yet checked?), turn into the expr _self._i_instance(hold_Expr(expr), _E_ATTR), which is free in the symbols _self and _E_ATTR. [#e _E_ATTR might be changed to _E_INDEX, or otherwise revised.] This function is also used internally to help implement the Arg and Option macros; for their use only, it has a private _index_expr option, giving an index expr other than _E_ATTR for the new Instance (which is used to suggest an ipath for the new instance, relative to that of self). Similarly, it helps implement ArgExpr etc, for whose sake it has a private option _noinstance. Devel scratch comment: Note that the Arg and Option macros may have handy, not expr itself, but a "grabarg" expr which needs to be evaluated (with _self bound) to produce the expr to be instantiated. What should they pass?? eval_Expr of the expr they have. [#doc - reword this] Other private options: _lvalue_flag, _noinstance (_noinstance is only supported when EVAL_REFORM is true) """ if doc: printnim( "Instance doc is not saved anywhere; should turn into a note to formulascanner to save it as metainfo, maybe" ) #e printnim( "review: same index is used for a public Option and a private Instance on an attr; maybe ok if no overlap possible???" ) ##e global _self # not needed, just fyi if EVAL_REFORM: if _lvalue_flag: assert not _noinstance # since it makes no sense to ask for this then, AFAIK (if ok for one, ok for all -- see below) #070119 bugfix to make Set(var,val) work again (eg when clicking a checkbox_pref) # rather than evalling var to its current value. This means _lvalue_flag never gets passed to _i_instance. res = call_Expr(getattr_Expr(_self, '_i_instance'), eval_to_lval_Expr(expr), _index_expr) ####e if this works, then try simplifying it to remove the _i_instance call! (assuming the lval is never needing make) # (or given this macro's name, maybe it makes more sense for LvalueArg to manage to not call it...) elif _noinstance: #070122 # we ignore _index_expr, but callers can't help but pass one, so don't complain when they do return expr # i.e. Instance(expr) == expr -- not useful directly, but useful as a passthru option from Arg etc. else: res = call_Expr(getattr_Expr(_self, '_i_instance'), expr, _index_expr) else: assert not _noinstance, "bug: _noinstance is only supported when EVAL_REFORM is true" res = call_Expr(getattr_Expr(_self, '_i_instance'), hold_Expr(expr), _index_expr, _lvalue_flag=_lvalue_flag) return res
def demo_drag_toolcorner_expr_maker(world): #070106 improving the above # given an instance of World, return an expr for the "toolcorner" for use along with GraphDrawDemo_FixedToolOnArg1 (on the same World) expr = SimpleColumn( checkbox_pref(kluge_dragtool_state_prefs_key, "drag new nodes?", dflt=kluge_dragtool_state_prefs_default), checkbox_pref(kluge_dragtool_state_prefs_key + "bla", "make polyline?", dflt=False), checkbox_pref(kluge_dragtool_state_prefs_key + "bla2", "(make it closed?)", dflt=False), ## ActionButton( world._cmd_Clear, "button: clear") # works # 070108: try this variant which uses _cmd_Clear_nontrivial: will the If work as an Expr?? If_kluge should tell us #####k ####k also will the type tests inside ActionButton work with an If? Probably not -- that's a ###BUG but I'll put it off. ## 1. make this work later: ActionButton( world._cmd_Clear, If_kluge( world._cmd_Clear_nontrivial, "button: clear", "button (disabled): clear")) ## 2. this too: If_kluge( world._cmd_Clear_nontrivial, ## ActionButton( world._cmd_Clear, "button: clear"), ## ActionButton( world._cmd_Clear, "button (disabled): clear") ## ) If_kluge( getattr_Expr(world, '_cmd_Clear_nontrivial'), ActionButton(world._cmd_Clear, "button: clear"), ActionButton(world._cmd_Clear, "button (disabled): clear", enabled=False) ) # works, though text change is so slow I suspect there's actually a highlighting or update bug making it appear even slower... # update 070109: the bug seems to be that as long as the mouse stays on the button, it remains highlighted with the old highlight form. ) return expr
def Instance(expr, _index_expr = _E_ATTR, _lvalue_flag = False, _noinstance = False, doc = None): """ This macro is assigned to a class attr to declare that its value should be a lazily-instantiated Instance of expr (by default). Assuming the arg is an expr (not yet checked?), turn into the expr _self._i_instance(hold_Expr(expr), _E_ATTR), which is free in the symbols _self and _E_ATTR. [#e _E_ATTR might be changed to _E_INDEX, or otherwise revised.] This function is also used internally to help implement the Arg and Option macros; for their use only, it has a private _index_expr option, giving an index expr other than _E_ATTR for the new Instance (which is used to suggest an ipath for the new instance, relative to that of self). Similarly, it helps implement ArgExpr etc, for whose sake it has a private option _noinstance. Devel scratch comment: Note that the Arg and Option macros may have handy, not expr itself, but a "grabarg" expr which needs to be evaluated (with _self bound) to produce the expr to be instantiated. What should they pass?? eval_Expr of the expr they have. [#doc - reword this] Other private options: _lvalue_flag, _noinstance (_noinstance is only supported when EVAL_REFORM is true) """ if doc: printnim("Instance doc is not saved anywhere; should turn into a note to formulascanner to save it as metainfo, maybe")#e printnim("review: same index is used for a public Option and a private Instance on an attr; maybe ok if no overlap possible???")##e global _self # not needed, just fyi if EVAL_REFORM: if _lvalue_flag: assert not _noinstance # since it makes no sense to ask for this then, AFAIK (if ok for one, ok for all -- see below) #070119 bugfix to make Set(var,val) work again (eg when clicking a checkbox_pref) # rather than evalling var to its current value. This means _lvalue_flag never gets passed to _i_instance. res = call_Expr( getattr_Expr(_self, '_i_instance'), eval_to_lval_Expr(expr), _index_expr ) ####e if this works, then try simplifying it to remove the _i_instance call! (assuming the lval is never needing make) # (or given this macro's name, maybe it makes more sense for LvalueArg to manage to not call it...) elif _noinstance:#070122 # we ignore _index_expr, but callers can't help but pass one, so don't complain when they do return expr # i.e. Instance(expr) == expr -- not useful directly, but useful as a passthru option from Arg etc. else: res = call_Expr( getattr_Expr(_self, '_i_instance'), expr, _index_expr ) else: assert not _noinstance, "bug: _noinstance is only supported when EVAL_REFORM is true" res = call_Expr( getattr_Expr(_self, '_i_instance'), hold_Expr(expr), _index_expr, _lvalue_flag = _lvalue_flag ) return res
def demo_drag_toolcorner_expr_maker(world): #070106 improving the above # given an instance of World, return an expr for the "toolcorner" for use along with GraphDrawDemo_FixedToolOnArg1 (on the same World) expr = SimpleColumn( checkbox_pref(kluge_dragtool_state_prefs_key, "drag new nodes?", dflt = kluge_dragtool_state_prefs_default), checkbox_pref(kluge_dragtool_state_prefs_key + "bla", "make polyline?", dflt = False), checkbox_pref(kluge_dragtool_state_prefs_key + "bla2", "(make it closed?)", dflt = False), ## ActionButton( world._cmd_Clear, "button: clear") # works # 070108: try this variant which uses _cmd_Clear_nontrivial: will the If work as an Expr?? If_kluge should tell us #####k ####k also will the type tests inside ActionButton work with an If? Probably not -- that's a ###BUG but I'll put it off. ## 1. make this work later: ActionButton( world._cmd_Clear, If_kluge( world._cmd_Clear_nontrivial, "button: clear", "button (disabled): clear")) ## 2. this too: If_kluge( world._cmd_Clear_nontrivial, ## ActionButton( world._cmd_Clear, "button: clear"), ## ActionButton( world._cmd_Clear, "button (disabled): clear") ## ) If_kluge( getattr_Expr( world, '_cmd_Clear_nontrivial'), ActionButton( world._cmd_Clear, "button: clear"), ActionButton( world._cmd_Clear, "button (disabled): clear", enabled = False) ) # works, though text change is so slow I suspect there's actually a highlighting or update bug making it appear even slower... # update 070109: the bug seems to be that as long as the mouse stays on the button, it remains highlighted with the old highlight form. ) return expr
class World(InstanceOrExpr ): #070205 revised, public nodelist -> private _nodeset """maintains the set of objects in the model; provides general operations on them """ #k is this 070401 change ok: ModelObject -> InstanceOrExpr? At least it doesn't seem to have caused trouble. ###e Q: Is it ok for this to inherit _CoordsysHolder and have .draw save the coords? Or to own one of those to hold them? # The goal is for some external thing to be able to copy the coords we will draw a newly made thing under. # that's partly misguided given that we might in theory draw things in more than one place # and/or in a different place than in the prior frame. The external thing that wants to do this # is self._newobj.copy_saved_coordsys_from( self.world) in class PalletteWell.on_press as of 070401. # It wants to use the right coordsys to turn a mouseray into that obj's initial position in the World. Hmm. # A: In principle, if we're drawing the world twice (eg in stereo), we'd better be able to tell from a mouseray # used to put something into it, which world-instance we're putting it into! and each of those needs to know # where mouse events would drop things (based on where it was last drawn -- no ambiguity there). # So it's correct for something to save that and reveal it -- it just needs to be a world-drawn-instance # which would somehow show up (as selobj?) when interpreting that mouseray. For now, it's ok to kluge this # by assuming this class is taking the role of world-drawn-instance and being drawn only once. Alternatively, # we could say that the recipient of the mouseray should also be told what background it's over (whether or not it # hit the background or some object drawn over it) (this might be "one of several background objects" for stereo) # so it would know how to find "absolute model coords" in a canonical way. Or a third way is for the World client code # to include an invisible object drawn just to capture the coords. In the multi-world-drawn-instance case, we'd have to combine # this object with our knowledge of which worldinstance was drawn, by storing the drawn coords under the crossproduct # of that and the object... # ... Trying one of these solutions with self._coordsys_holder below. Seems to work & fix the pallette bug. Comment needs cleanup. # [070401] # ###FLAW: logically, this should store exprs needed to remake its instances, merely caching the instances, # but in fact, stores the instances themselves. This will make save/load harder, and means instances survive code-reloading. # Probably it should be fixed before trying to do save/load. [070206 comment] _nodeset = State( Anything, {} ) # self._nodeset is private; it's changetracked but only when entirely replaced (not just modified!) # (###e optim: can that be fixed? we need a way to access the LvalForState and inval it) ###e we'll need to put that State on a different state-layer (i.e. kind of StatePlace) when we start saving/loading this _coordsys_holder = Instance(SavedCoordsys( )) #070401 -- see long comment above, about _CoordsysHolder def _init_instance(self): super(World, self)._init_instance() set_default_attrs(self.untracked_model_state, _index_counter=4000 ) #070213; could State() do this for us instead? #e def _C_number_of_objects(self): """Compute self.number_of_objects, defined as the total number of objects explicitly stored in self, of all types, in all Parts and configurations (but not including objects removed by Undo but present in Undo history). WARNING: Parts, configurations, and Undo are nim for this class as of 070206. Note: probably not very useful, due to that lack of qualification by anything. Perhaps useful to know whether self is entirely empty, or as a ballpark estimate of how much data it might contain. """ return len(self._nodeset) def list_all_objects_of_type(self, model_type): """Return a nominally read-only list of all objects in self of the given model_type (or IorE class, as a special case), in a deterministic order not yet decided on (might be creation order, world-index order, MT order). [Current implem uses creation order as an Instance, as told by _e_serno; this order will become unideal as new kinds of object ops are added, like potential objs or moving them from other worlds (if possible). ##e 070206] ##e needs options for "not just this config", "not just this Part" (and revised implem, once we support configs or Parts) #e optim: extend API to be able to specify ordering -- otherwise it has to be sorted twice (here, and often in caller). """ type_predicate = model_type_predicate( model_type ) # this permits model_type to be an IorE subclass (or its name) ###BAD: not enough kinds of type exprs can be passed, and error checking on model_type (not being garbage) is nim return filter( type_predicate, self._sorted_objects ) ##e optim: filter first (or keep them already type-separated), then sort def _append_node(self, index, node): #070201 new feature ###e SHOULD RENAME [070205] "not sure if this should be private... API revised 070205, ought to rename it more (and #doc it)" ## self._nodeset = dict(self._nodeset) ## ###KLUGE: copy it first, to make sure it gets change-tracked. Inefficient when long! ###BUG in the above: # doesn't work, since the change to it is not tracked, since the old and new values compare equal! (guess) #k note that LvalForState may also not keep a deep enough copy of the old value in doing the compare, # to not be fooled by the direct modification of the dict (in the sense of that mod escaping its notice, even later)! # (i'm not sure -- needs review, e.g. about whether bugs of this kind will go undetected and be too hard to catch) ## self._nodeset[index] = node newset = dict(self._nodeset) newset[index] = node self._nodeset = newset # this ought to compare different and cause changetracking (assuming the index was in fact new) ###e OPTIM IDEA: can we modify it, then set it to itself? No, because LvalForState will compare saved & new value -- # the same mutable object! We need to fix that, but a workaround is to set it to None and then set it to itself again. # That ought to work fine. ####TRYIT but then fix LvalForState so we can tell it we modified its mutable contents. [070228] return def _C__sorted_objects(self): """compute private self._sorted_objects (a list, ordered by something not yet decided, probably creation time; probably should be the same order used by list_all_objects_of_type) """ res = self._nodeset.values() res = sorted_by(res, lambda obj: obj._e_serno) ###KLUGE: use _e_serno in place of .creation_order, not yet defined -- # works now, but wrong in general due to potential objects or moved-from-elsewhere objects. # (Q: Would index-in-world be ordered the same way? A: Yes for now, but not good to require this in the future.) return res def draw(self): # draw all the nodes [#e 070228 ###e in future we're more likely to draw X(node) for X supplied from caller & subset of nodes] # [optim idea 070103 late: have caller put this in a DisplayListChunk; will it actually work? # the hope is, yes for animating rotation, with proper inval when nodelist changes. It ought to work! Try it. It works!] self._coordsys_holder.save_coords_if_safe() #070401 for node in self._sorted_objects: # print "%r is drawing %r at %r" % (self, node, node.pos) # suspicious: all have same pos ... didn't stay true, nevermind self.drawkid(node) ## node.draw() # this assumes the items in the list track their own posns, which might not make perfect sense; # otoh if they didn't we'd probably replace them with container objs for our view of them, which did track their pos; # so it doesn't make much difference in our code. we can always have a type "Vertex for us" to coerce them to # which if necessary adds the pos which only we see -- we'd want this if one Vertex could be in two Worlds at diff posns. # (Which is likely, due to Configuration Management.) if 0 and node is self._sorted_objects[-1]: print "drew last node in list, %r, ipath[0] %r, pos %r" % ( node, node.ipath[0], node.pos) ###e see comment above: "maybe World needs to wrap all it draws with glue to add looks and behavior to it" return def make_and_add( self, expr, type=None ): #070206 added type option -- required for now (temporary); semantics not fully defined """Make a new model object instance from expr, add it to the world at a local index we choose, and return it. This is the main public method for making new model objects. [As a temporary ###KLUGE (070206), the type needs to be passed, for purposes of methods like self.list_all_objects_of_type. This will no longer be needed once we can reliably infer the type (including supertypes) from the made instance of expr. But that probably requires altering delegation to specify which attrs to delegate (based on which interface they're part of), including a new attr for model object type. ###e Even once that's done, the caller may want to pass type-like info, perhaps "tags" -- e.g. one current caller passes type = "dot" which could not be inferred from expr it passes. ###e Callers may also want to pass desired relations, like a parent object, for convenience, or to influence details of how we make and index the new instance. ##e callers will also want to wrap objects with type-nicknames (see code comment for details). [WARNING: the API may be revised to also return the index. Or, we might store that in object, or store a back-dict here.] """ index, node = self._make(expr) # that picks a unique index (using a counter in transient_state); I think that's ok # (but its change/usage tracking needs review ####k) self._append_node(index, node) # revised 070205 if 'model_type_on_object_kluge_070206': ###KLUGE -- store type on object (simulates later inferring it from object) # update 070213: the right thing to do is probably not this, but to wrap the object itself with a type-nickname # (before passing it to us), so not only this world (self), but everything that sees it (MT default label, commands/ops, # selobj checks, etc), can react to its type-nickname. if type is not None: # node._i_model_type is defined on all Instances, and node should be one ## if not hasattr(node, '_i_model_type'): ## node._i_model_type = None # this is how much of a kluge it is -- we're not even sure which classes need this attr if node._i_model_type is not None: assert node._i_model_type == type, "make_and_add type %r inconsistent with existing type %r on %r" % \ (type, node._i_model_type, node) node._i_model_type = type pass return node def _make(self, expr): # moved here from demo_drag 070202 """[private] Allocate a new index, use it as the localmost component of ipath while making [or finding?? I THINK NOT ####k] expr, and return (index, new-expr-instance). Note that it's up to expr whether to actually make use of the suggested ipath in the new instance. The case of one instance stored with different indices in World is not reviewed, and not recommended until it is, but it's up to the caller to worry about. I don't know it's wrong, just never thought about it and assumed it would not happen when analyzing the code. """ index = None #e rename? #e move to some superclass #e worry about lexenv, eg _self in the expr, _this or _my in it... is expr hardcoded or from an arg? #e revise implem in other ways eg eval vs instantiate #e default unique index?? (unique in the saved stateplace, not just here) # (in fact, is reuse of index going to occur from a Command and be a problem? note *we* are acting as command... #e use in other recent commits that inlined it if index is None: # usual case I hope (due to issues mentioned above [maybe here or maybe in demo_drag.py]): allocate one index = self.untracked_model_state._index_counter if 'index should be modified [070203]': # see comments above dated 070203 index = index + 1 self.untracked_model_state._index_counter = index # 070213 changed implem of _index_counter to make sure it's not change/usage-tracked. # (Fyi, I don't know whether or not it was before (using State()), in a way that mattered. # The tracking danger would be that whenever you make any new object, # the old objects see that the index they used is different # and think they too need remaking, or something like that! This needs thinking through # since it probably covers all make-data, not just the index. All make-data is being snapshotted. # For that matter, things used by "commands" are in general different than things used to recompute. # Maybe entire commands need to have tracked usage discarded or kept in a new kind of place. #####FIGURE OUT) # # Note (language design flaw): to set this attr (using current code), # you have to refer to it directly in the stateplace, # rather than using the kind of abbrev that (in Highlightable glname) seems to work for get: # _index_counter = _self.untracked_model_state._index_counter # This could possibly be fixed in C_rule_for_formula (not sure), or by making the abbrev in a fancier manner. # (If you try to set the abbrev, you get # "AssertionError: subclass [C_rule_for_formula] ... should implement set_for_our_cls".) # ###BUG: even for get, the abbreviated form (self._index_counter) is not updated when the spelled out form is!! # See this debug print from when we tried that here: ## print "after set to %r, self._index_counter = %r, self.untracked_model_state._index_counter = %r" % \ ## (index, self._index_counter, self.untracked_model_state._index_counter ) ## # it prints: after set to 4002, self._index_counter = 4001, self.untracked_model_state._index_counter = 4002 # This is weird, since Highlightable seems to succeed using a similar glname abbrev for get. # (Unless it doesn't, and I never noticed?? To check, I just added debug code for that -- so far untriggered.) pass ###e LOGIC ISSUE: should assert the resulting ipath has never been used, # or have a more fundamental mechanism to guarantee that env = self.env # maybe wrong, esp re _self ipath = (index, self.ipath) return index, env.make(expr, ipath) # note: does eval before actual make # == def _C_mt_kids( self): # 070206 experiment related to ModelTreeNodeInterface (sp?) ###e how do we depend on the mt display prefs? note we need to be drawn twice, once in graphics area using .draw # and once in MT using the mt_* methods, but with independent envs provided! Self.env might have room in attr namespace, # but it has a single origin. Besides there might be two indep MT views or graphics views -- each of those also needs # separate envs (of the same kind). But that makes us two instances! I guess we need separate World data obj vs World viewer, # to handle that! I'm not even sure it makes sense to put the viewing methods in the same object... but maybe it does # with this concept of partial instantiation [nim], in which we could instantiate the viewing layer(?) (interface??) twice, # and the data layer just once, letting it (an instance) serve as the expr for instantiating the viewing layer in two places. # (But this makes it clear that the env would need to be split into separate parts, one for each partially instantiable # layer -- hopefully these parts would correspond to interfaces (or sets of them), i.e. an interface's attrs would # have to all be instantiated at the same time, and decls would control which ones were instantiated together in which # partial-instantiation layers.) # # So for now let's pretend self.env can tell us... tho as initial kluge, the global env.prefs (get_pref?) could tell us. # But even sooner, just pretend we don't care and always show all the kids. return self._sorted_objects mt_name = "testmode" #e or maybe something like State(str, "Untitled"), or a stateref # or "Untitled" as it was before 070208 mt_openable = True ## mt_node_id = getattr_Expr( _self, '_e_serno') mt_node_id = getattr_Expr( _self, 'ipath' ) #e optim: should intern the ipath ###e move this to IorE? btw redundant w/ def mt_node_id # 070218 -- by test, using ipath (a bugfix) makes world.open persistent (as hoped/predicted); # probably doesn't affect open-MT newnode slowness (but now that's fixed in different way in demo_MT) # == def _cmd_Clear( self ): #070106 experimental naming convention for a "cmd method" -- a command on this object (requiring no args/opts, by default) if self._nodeset: # avoid gratuitous change-track by only doing it if it does something (see also _cmd_Clear_nontrivial) # NOTE: this cond is probably not needed, since (IIRC) LvalForState only invalidates if a same_vals comparison fails. ###k self._nodeset = {} return # related formulae for that command # (names are related by convention, only used in this file, so far; prototype for wider convention, but not yet well thought through) _cmd_Clear_nontrivial = not_Expr( not_Expr(_nodeset) ) #KLUGE: need a more direct boolean coercion (not that it's really needed at all) # can be used to enable (vs disable) a button or menu item for this command on this object _cmd_Clear_legal = True # whether giving the command to this object from a script would be an error _cmd_Clear_tooltip = "clear all objects" # a command button or menu item could use this as its tooltip ###e is this client-specific?? def _cmd_Make(self): print "world make is nim" ### _cmd_Make_tooltip = "make something [nim]" ###e when we know the source of what to make, it can also tell us this tooltip pass # end of class World
def _ArgOption_helper(attr_expr, argpos_expr, type_expr, dflt_expr, _lvalue_flag=False, _arglist=False, **moreopts): """ [private helper for Arg, Option, and maybe ArgOrOption] attr_expr should be None, or some sort of expr (in practice always _E_ATTR so far) that will get replaced by a constant_Expr for the current attr (in ExprsMeta's FormulaScanner), according to whether the current attr should be part of the index and a public option-name for supplying the arg (we make sure those conditions are the same). [#e Note that if someday we wanted to include f(attr) in the index, but still use attr alone as an option name, we'd have to modify this to permit both f(attr) (or f) and attr to be passed.] argpos_expr should similarly be None, or some sort of expr (in practice a private subclass of internal_Expr) that will get replaced by a constant_Expr for the argument position (an int) that should be allocated to the current attr's arg (determined in ExprsMeta's FormulaScanner by allocating posns 0,1,2,etc to newly seen arg-attrs, whether or not the attr itself is public for that arg). type_expr ###doc, passed herein to canon_type dflt_expr ###doc, can also be _E_DFLT_FROM_TYPE_ or [handled in caller i think, but survives here unmatteringly] _E_REQUIRED_ARG_; will be passed through canon_expr _lvalue_flag is a private option used by LvalueArg. _arglist is a private option used by ArgList. """ if _lvalue_flag: printnim("_lvalue_flag's proper interaction with dflt_expr is nim" ) # in all cases below ### guess: we want it to be an expr for a default stateref global _self # fyi type_expr = canon_type(type_expr) printnim( "can type_expr legally be self-dependent and/or time-dependent? ###k I guess that's nim in current code!" ) #070115 comment if _arglist: # new feature 070321. The type is applied to each element, but the default value is for the entire list -- # OTOH, when would it ever be used, since even if no args are supplied, the list can be formed?? # Probably it would only be used when the list was 0 length, and could meaningfully be [], (), or another list-like thing... # this is all a guess and I probably won't even review this code for this issue now, unless it fails when tried. ####k type_expr = tuple_Expr( type_expr ) # type-coerce the value to a list of the given type [070321 guess] ###e or list_Expr??? if dflt_expr is _E_DFLT_FROM_TYPE_: dflt_expr = default_expr_from_type_expr(type_expr) ## note [070115], this would be impossible for time-dependent types! and for self-dep ones, possible but harder than current code. assert is_pure_expr(dflt_expr) #k guess 061105 else: dflt_expr = canon_expr( dflt_expr ) # hopefully this finally will fix dflt 10 bug, 061105 guesshope ###k [works for None, 061114] assert is_pure_expr( dflt_expr ) # not sure this is redundant, since not sure if canon_expr checks for Instance ###k printnim("not sure if canon_expr checks for Instance") # Note on why we use explicit call_Expr & getattr_Expr below, # rather than () and . notation like you can use in user-level formulae (which python turns into __call__ and getattr), # to construct Exprs like _self._i_grabarg( attr_expr, ...): # it's only to work around safety features which normally detect that kind of Expr-formation (getattr on _i_* or _e_*, # or getattr then call) as a likely error. These safety features are very important, catching errors that would often lead # to hard-to-diagnose bugs (when our code has an Expr but thinks it has an Instance), so it's worth the trouble. held_dflt_expr = hold_Expr(dflt_expr) # Note, this gets evalled back into dflt_expr (treated as inert, may or may not be an expr depending on what it is right here) # by the time _i_grabarg sees it (the eval is done when the call_Expr evals its args before doing the call). # So if we wanted _i_grabarg to want None rather than _E_REQUIRED_ARG_ as a special case, we could change to that (there & here). grabopts = {} if _arglist: grabopts.update(dict(_arglist=constant_Expr(_arglist))) grabarg_expr = call_Expr(getattr_Expr(_self, '_i_grabarg'), attr_expr, argpos_expr, held_dflt_expr, **grabopts) # comments 070115: # - This will eval to an expr which depends on self but not on time. We could optim by wrapping it # (or declaring it final) in a way which effectively replaced it with its value-expr when first used. # (But it's not obvious where to store the result of that, since the exprs being returned now are assigned to classes # and will never be specific to single selfs. Do we need an expr to use here, which can cache its own info in self?? # Note: AFAIK, self will be the same as what _self gets replaced with when this is used. (We ought to assert that.) ###e) # - Further, grabarg_expr is probably supposed to be wrapped *directly* by eval_Expr, not with type_expr inside. I think I'll # make that change right now and test it with EVAL_REFORM still False, since I think it's always been required, as said # in other comments here. DOING THIS NOW. if attr_expr is not None and argpos_expr is not None: # for ArgOrOption, use a tuple of a string and int (attr and argpos) as the index index_expr = tuple_Expr(attr_expr, argpos_expr) elif attr_expr is None and argpos_expr is None: assert 0, "attr_expr is None and argpos_expr is None ..." elif attr_expr is not None: # for Option, use a plain attr string as the index index_expr = attr_expr else: assert argpos_expr is not None # for Arg, use a plain int as the index # (note: ExprsMeta replaces argpos_expr with that int wrapped in constant_Expr, but later eval pulls out the raw int) index_expr = argpos_expr # see if this is fixed now, not that it means much since we were using a stub... but who knows, maybe the stub was buggy # and we compensated for that and this could if so cause a bug: ## printnim("I suspect type_expr (stub now) is included wrongly re eval_Expr in _ArgOption_helper, in hindsight 061117") ## ### I suspect the above, because grabarg expr needs to be evalled to get the expr whose type coercion we want to instantiate res = Instance(_type_coercion_expr(type_expr, eval_Expr(grabarg_expr)), _index_expr=index_expr, _lvalue_flag=_lvalue_flag, **moreopts) # note: moreopts might contain _noinstance = True, and if so, Instance normally returns its first arg unchanged # (depending on other options). # 070115 replaced eval_Expr( type_expr( grabarg_expr)) with _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) ) return res # from _ArgOption_helper
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
def _ArgOption_helper( attr_expr, argpos_expr, type_expr, dflt_expr, _lvalue_flag = False, _arglist = False, **moreopts ): """ [private helper for Arg, Option, and maybe ArgOrOption] attr_expr should be None, or some sort of expr (in practice always _E_ATTR so far) that will get replaced by a constant_Expr for the current attr (in ExprsMeta's FormulaScanner), according to whether the current attr should be part of the index and a public option-name for supplying the arg (we make sure those conditions are the same). [#e Note that if someday we wanted to include f(attr) in the index, but still use attr alone as an option name, we'd have to modify this to permit both f(attr) (or f) and attr to be passed.] argpos_expr should similarly be None, or some sort of expr (in practice a private subclass of internal_Expr) that will get replaced by a constant_Expr for the argument position (an int) that should be allocated to the current attr's arg (determined in ExprsMeta's FormulaScanner by allocating posns 0,1,2,etc to newly seen arg-attrs, whether or not the attr itself is public for that arg). type_expr ###doc, passed herein to canon_type dflt_expr ###doc, can also be _E_DFLT_FROM_TYPE_ or [handled in caller i think, but survives here unmatteringly] _E_REQUIRED_ARG_; will be passed through canon_expr _lvalue_flag is a private option used by LvalueArg. _arglist is a private option used by ArgList. """ if _lvalue_flag: printnim("_lvalue_flag's proper interaction with dflt_expr is nim") # in all cases below ### guess: we want it to be an expr for a default stateref global _self # fyi type_expr = canon_type( type_expr) printnim("can type_expr legally be self-dependent and/or time-dependent? ###k I guess that's nim in current code!")#070115 comment if _arglist: # new feature 070321. The type is applied to each element, but the default value is for the entire list -- # OTOH, when would it ever be used, since even if no args are supplied, the list can be formed?? # Probably it would only be used when the list was 0 length, and could meaningfully be [], (), or another list-like thing... # this is all a guess and I probably won't even review this code for this issue now, unless it fails when tried. ####k type_expr = tuple_Expr(type_expr) # type-coerce the value to a list of the given type [070321 guess] ###e or list_Expr??? if dflt_expr is _E_DFLT_FROM_TYPE_: dflt_expr = default_expr_from_type_expr( type_expr) ## note [070115], this would be impossible for time-dependent types! and for self-dep ones, possible but harder than current code. assert is_pure_expr(dflt_expr) #k guess 061105 else: dflt_expr = canon_expr(dflt_expr) # hopefully this finally will fix dflt 10 bug, 061105 guesshope ###k [works for None, 061114] assert is_pure_expr(dflt_expr) # not sure this is redundant, since not sure if canon_expr checks for Instance ###k printnim("not sure if canon_expr checks for Instance") # Note on why we use explicit call_Expr & getattr_Expr below, # rather than () and . notation like you can use in user-level formulae (which python turns into __call__ and getattr), # to construct Exprs like _self._i_grabarg( attr_expr, ...): # it's only to work around safety features which normally detect that kind of Expr-formation (getattr on _i_* or _e_*, # or getattr then call) as a likely error. These safety features are very important, catching errors that would often lead # to hard-to-diagnose bugs (when our code has an Expr but thinks it has an Instance), so it's worth the trouble. held_dflt_expr = hold_Expr(dflt_expr) # Note, this gets evalled back into dflt_expr (treated as inert, may or may not be an expr depending on what it is right here) # by the time _i_grabarg sees it (the eval is done when the call_Expr evals its args before doing the call). # So if we wanted _i_grabarg to want None rather than _E_REQUIRED_ARG_ as a special case, we could change to that (there & here). grabopts = {} if _arglist: grabopts.update(dict(_arglist = constant_Expr(_arglist))) grabarg_expr = call_Expr( getattr_Expr(_self, '_i_grabarg'), attr_expr, argpos_expr, held_dflt_expr, **grabopts ) # comments 070115: # - This will eval to an expr which depends on self but not on time. We could optim by wrapping it # (or declaring it final) in a way which effectively replaced it with its value-expr when first used. # (But it's not obvious where to store the result of that, since the exprs being returned now are assigned to classes # and will never be specific to single selfs. Do we need an expr to use here, which can cache its own info in self?? # Note: AFAIK, self will be the same as what _self gets replaced with when this is used. (We ought to assert that.) ###e) # - Further, grabarg_expr is probably supposed to be wrapped *directly* by eval_Expr, not with type_expr inside. I think I'll # make that change right now and test it with EVAL_REFORM still False, since I think it's always been required, as said # in other comments here. DOING THIS NOW. if attr_expr is not None and argpos_expr is not None: # for ArgOrOption, use a tuple of a string and int (attr and argpos) as the index index_expr = tuple_Expr( attr_expr, argpos_expr ) elif attr_expr is None and argpos_expr is None: assert 0, "attr_expr is None and argpos_expr is None ..." elif attr_expr is not None: # for Option, use a plain attr string as the index index_expr = attr_expr else: assert argpos_expr is not None # for Arg, use a plain int as the index # (note: ExprsMeta replaces argpos_expr with that int wrapped in constant_Expr, but later eval pulls out the raw int) index_expr = argpos_expr # see if this is fixed now, not that it means much since we were using a stub... but who knows, maybe the stub was buggy # and we compensated for that and this could if so cause a bug: ## printnim("I suspect type_expr (stub now) is included wrongly re eval_Expr in _ArgOption_helper, in hindsight 061117") ## ### I suspect the above, because grabarg expr needs to be evalled to get the expr whose type coercion we want to instantiate res = Instance( _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) ), _index_expr = index_expr, _lvalue_flag = _lvalue_flag, **moreopts ) # note: moreopts might contain _noinstance = True, and if so, Instance normally returns its first arg unchanged # (depending on other options). # 070115 replaced eval_Expr( type_expr( grabarg_expr)) with _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) ) return res # from _ArgOption_helper