Beispiel #1
0
class test_StateArrayRefs_3( DelegatingInstanceOrExpr): # testexpr_35b, _35c
    indices = range(3)
    heights = StateArrayRefs(Width, 0.0)
    direction = Arg(Vector, DX, "direction of permitted motion -- DZ is the goal but DX is easier for testing")
        ### DX for initial test (testexpr_35b), then DZ (testexpr_35c)
    range = Option(tuple_Expr, None, doc = "range limit of height")
    msg = Option(str, "drag along a line")
    def _height_dragger_for_index(self, index):
        stateref = StateArrayRefs_getitem_as_stateref( self.heights, index )
            #e change to self.heights.getitem_as_stateref(index)? self.heights._staterefs[index]?? self.heights[index]???
        newindex = ('_height_dragger_3_for_index', index) 
        return self.Instance( _height_dragger_3( stateref, self.direction,
                                                 sbar_text = "%s (#%r)" % (self.msg, index,),
                                                 range = self.range
                                                ), newindex )
    delegate = SimpleRow(
        MapListToExpr( _self._height_dragger_for_index, ###k _self needed??
                       indices,
                       KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(SimpleColumn) ),
                           #e SimpleGrid? 2d form of MapListToExpr?
        ActionButton( _self.printit, "button: print state") ###e idea: define on special attr, let UI assemble debug info viewer
     )
    def printit(self): #e can PrintAction do this for us?
        print [h.value for i,h in sorted_items(self.heights)] ###KLUGE, assumes they're StateRefs -- maybe just rename StateArray -> StateArrayRefs
    pass
Beispiel #2
0
class _height_dragger_3(DelegatingInstanceOrExpr):
    # args
    height_ref = Arg(StateRef, doc = "stateref to a height variable")
    direction = Arg(Vector)
    sbar_text = Option(str, "_height_dragger_3")
    range = Option(tuple_Expr, None, doc = "range limit of height")
    # appearance/behavior
    #e should draw some "walls" too, and maybe limit the height
    drag_handler = Instance( DragBehavior_AlongLine(
        _self._delegate,
        height_ref,
        ## Ray(ORIGIN, DX) # works
        ## Ray(ORIGIN, DZ) # works, but only if you trackball it (as expected)...
        ## Ray(ORIGIN, direction) # fails -- Ray is an ordinary class, not an expr! ###FIX
        call_Expr(Ray, ORIGIN, direction), # this workaround fixes it for now.
            # (in prior commit it didn't fix it, but only because of a typo in the testexpr defs
            #  in tests.py, which meant I passed DZ when I thought I passed DX.)
        range = range
     ))
        ### NOTE: drag_handler is also being used to compute the translation from the height, even between drags.
    delegate = Overlay(
        Highlightable(
            Translate(
                Image("blueflake.png"), ###e needs an option to be visible from both sides (default True, probably)
                drag_handler._translation ###k ok?? only if that thing hangs around even in between drags, i guess!
                    #e #k not sure if this code-commoning is good, but it's tempting. hmm.
             ),
            sbar_text = sbar_text,
            behavior = drag_handler
         ),
        Translate(Rect(2), direction * -0.01),
        Line(ORIGIN, ORIGIN + direction * height_ref.value, white)
     )
    pass
Beispiel #3
0
class SimpleColumn(Widget2D): #061115
    #e or use InstanceMacro using Overlay & Translate? Could work, but slower and not needed... might help in CL with fancier gaps.
    ## a0 = Arg(Widget2D) # note: this probably doesn't preclude caller from passing None, and even if it does, nothing enforces that yet;
        # if caller does pass None (to this or to Overlay), best thing is probably to replace this with Pass = Rect(0,0,white)
        # and use it as a drawable, but have special case to use no gap under it -- or the equiv, as a simple change
        # to our btop formula so it's 0 if not a0 -- which is already done, so maybe there's no need to worry about a0 = None.
        # Maybe it should be an optional arg like the others. [061115]
    a0 = Arg(Widget2D, None) # even the first arg can be missing, as when applying it to a list of no elts [061205]
    a1 = Arg(Widget2D, None)
    a2 = Arg(Widget2D, None)
    a3 = Arg(Widget2D, None)
    a4 = Arg(Widget2D, None)
    a5 = Arg(Widget2D, None) # use ArgList here when that works
    a6 = Arg(Widget2D, None)
    a7 = Arg(Widget2D, None)
    a8 = Arg(Widget2D, None)
    a9 = Arg(Widget2D, None)
    a10 = Arg(Widget2D, None)
    a11 = Arg(Widget2D, None) # the 12th arg is 1 too many
    toomany = Instance(TextRect("too many args to SimpleColumn"))
        #070212 added Instance to fix mysterious bug manifesting as this debug output:
        ## getattr_debugprint: <lexenv_ipath_Expr... <TextRect#2331(a)>> has no 'bleft'
    args = list_Expr(a0,a1,a2,a3,a4,a5, a6,a7,a8,a9,a10, # could say or_Expr(a0, Spacer(0)) but here is not where it matters
                     and_Expr(a11, toomany)
                     )
    
    ## gap = Option(Width, 3 * PIXELS)
    pixelgap = Option(float, 3) # 070104 int -> float
    gap = pixelgap * PIXELS

    print_lbox = Option(bool, False) #061127 for debugging; should be harmless; never tested (target bug got diagnosed in another way)
    
    drawables = call_Expr(lambda args: filter(None, args) , args)
    ## empty = not drawables ###e BUG: needs more Expr support, I bet; as it is, likely to silently be a constant False; not used internally
    empty = not_Expr(drawables)
    bleft = call_Expr(lambda drawables: max([arg.bleft for arg in drawables] + [0]) , drawables)
        # 070211 arg.bleft -> getattr_debugprint(arg, 'bleft')
    bright = call_Expr(lambda drawables: max([arg.bright for arg in drawables] + [0]) , drawables)
    height = call_Expr(lambda drawables, gap: sum([arg.height for arg in drawables]) + gap * max(len(drawables)-1,0) , drawables, gap)
    ## btop = a0 and a0.btop or 0  # bugfix -- use _Expr forms instead; i think this form silently turned into a0.btop [061205]
    btop = or_Expr( and_Expr( a0, a0.btop), 0)
    bbottom = height - btop
    def draw(self):
        if self.print_lbox:
            print "print_lbox: %r lbox attrs are %r" % (self, (self.bleft, self.bright, self.bbottom, self.btop))
        glPushMatrix()
        prior = None
        for a in self.drawables:
            if prior:
                # move from prior to a
                dy = prior.bbottom + self.gap + a.btop
                glTranslatef(0,-dy,0) # positive is up, but Column progresses down
            prior = a
            self.drawkid(a) ## a.draw()
        glPopMatrix()
        return
    pass # end of class SimpleColumn or SimpleColumn_OLD
Beispiel #4
0
class Vertex_new(
        ModelObject
):  #070105 ###e maybe it also needs an official type or typename for use in rules and files?
    pos0 = Arg(Position)
    pos = State(Position, pos0)
    color = Option(Color, black)
    pass
Beispiel #5
0
class Sphere(
        Widget2D
):  # the superclass is to give it a 2D lbox. We'll need to think about whether it needs renaming.
    # or maybe this super will be Widget3D and that will inherit Widget2D?? hmm...
    """
    Sphere(radius, color, center) represents a spherical surface
    of the given radius (default 1), color (default gray), and center (default the local origin) [partly nim if not #e].
    [There is also an undocumented option, detailLevel.]
    """
    # args
    radius = ArgOrOption(Width, 1)
    color = ArgOrOption(Color, gray)
    center = ArgOrOption(
        Position,
        ORIGIN)  # this is not yet supported in determining the layout box,
    # since I'm tempted to say, if this is supplied, turn into a macro, Translate(Sphere(...))
    # so I won't need to uglify those calcs. Not yet sure quite how to most easily organize that --
    # best would be a general way to make some options or uses of them "turn into macros",
    # sort of like in a math paper saying "w/o loss of generality, assume center == ORIGIN". ###e
    detailLevel = Option(
        int, 2
    )  #k guess: 1 or 2 or 3, i think -- undocumented since needs a better name and maybe arg meaning
    # formulae
    bright = _self.radius
    bleft = _self.radius
    btop = _self.radius
    bbottom = _self.radius

    def draw(self):
        drawsphere(self.fix_color(self.color), self.center, self.radius,
                   self.detailLevel)

    pass
Beispiel #6
0
class Arrow(Widget2D):
    """
    Arrow class provides a 3D arrow with a 'tail' and an arrow head.
    @see: B{DnaSegment_ResizeHandle} where this is used to draw a resize handle 
          while editing a DnaSegment.
    @see: exprs.Rect.py which defines some other 3 D objects. 
    @see: DirectionArrow.py -- Don't confuse it with this class. 
    """
    color = ArgOrOption(Color, gray)        
    tailPoint = ArgOrOption(Position, ORIGIN)
    arrowBasePoint = ArgOrOption(Position, ORIGIN + 2*DX)
    scale = ArgOrOption(float, 10.0)
    tailRadius = ArgOrOption(float, 0.4)
    #tailRadiusLimits ensure a min and max size for the arrow (including arrow 
    #tail and arrow head. drawDirectionArrow method uses tailRadius as a reference
    #to derive other params such as arrowhead base radius etc. Thats why this arg
    #is named this way.
    #@See DnaSegment_ResizeHandle to see how these limits are defined. 
    tailRadiusLimits = ArgOrOption(tuple, ())
    glpane = ArgOrOption(Anything, None)
    scale_to_glpane = Option(bool, False)
    
    def draw(self):        
        drawDirectionArrow(
            self.color, 
            self.tailPoint, 
            self.arrowBasePoint,
            self.tailRadius,
            self.scale,
            tailRadiusLimits = self.tailRadiusLimits,
            numberOfSides = 6,
            glpane = self.glpane,
            scale_to_glpane = self.scale_to_glpane
        )          
Beispiel #7
0
class checkbox_pref(InstanceMacro):
    #e rename -- Checkbox(...), with various kinds of args to say what state it uses in different ways?
    #e make it one of several prefs controls for other types of pref and control
    #e generalize to all named state -- e.g. see also LocalVariable_StateRef -- permit passing in the stateref?
    #e get dflt label from stateref??
    # note: this was split out of kluge_dragtool_state_checkbox_expr 061214,
    # extended here into a def (later renamed checkbox_pref_OLDER), then a class
    prefs_key = Arg(str)
    label = Arg(Anything)  # string or Widget2D
    dflt = ArgOrOption(bool, False)
    sbar_text = Option(str, '')
    use_label = If(call_Expr(lambda label: type(label) == type(""), label),
                   TextRect(label), label)  ## was TextRect(label,1,20)
    use_sbar_text = or_Expr(
        sbar_text,
        If(call_Expr(lambda label: type(label) == type(""), label), label, ""))
    stateref = Instance(PrefsKey_StateRef(prefs_key, dflt))
    # note: without Instance here, next line stateref.value says (correctly):
    ## AssertionError: compute method asked for on non-Instance <PrefsKey_StateRef#47221(a)>
    var = stateref.value
    checkbox = If(
        var,
        checkbox_image('mac_checkbox_on.png'),
        checkbox_image('mac_checkbox_off.png'),
    )
    _value = DisplayListChunk(
        Highlightable(
            SimpleRow(CenterY(checkbox),
                      CenterY(use_label)),  # align = CenterY is nim
            ## on_press = Set(debug_evals_of_Expr(stateref.value), not_Expr(var) ), #070119 debug_evals_of_Expr - worked
            on_press=_self.on_press,
            # the following works too, but I wanted to intercept it to add some py code [070305]:
            ## Set( stateref.value, not_Expr(var) ),
            sbar_text=use_sbar_text))
    # note: using DisplayListChunk in _value works & is faster [070103]
    #070124 comment: the order DisplayListChunk( Highlightable( )) presumably means that the selobj
    # (which draws the highlightable's delegate) doesn't include the displist; doesn't matter much;
    # that CenterY(use_label) inside might be ok, or might be a bug which is made up for by the +0.5 I'm adding to drawfont2
    # in testdraw.py today -- not sure.
    incr_drawable = Instance(
        Boxed(CenterY(checkbox), pixelgap=0, bordercolor=gray, borderwidth=2))

    # I tried orange as a warning color -- means the checkbox reflects an intention but not yet the reality.
    # But it was annoyingly too visible. So I'll try gray.
    # If all colorboxeds are unpopular, then try an image that has a little spray of lines coming from the center, instead.
    def on_press(self):
        self.stateref.value = not self.stateref.value  # was, in the expr: Set( stateref.value, not_Expr(var) )

        ###e revise this code to use self.draw_incrementally once that's refiled into Highlightable ###e
        def func(self=self):
            self.incr_drawable.draw()
            ## self.draw() # includes the label - probably a waste but who cares
            self.env.glpane.swapBuffers()  # update display [needed]

        ran_already_flag, funcres = self.run_OpenGL_in_local_coords(
            func)  # this method runs in the Highlightable made in _value
        assert ran_already_flag
        return

    pass  # end of class checkbox_pref
Beispiel #8
0
class Boxed(InstanceMacro):  # 070316 slightly revised
    """
    Boxed(widget) is a boxed version of widget -- it looks like widget, centered inside a rectangular frame.
    Default options are pixelgap = 4 (in pixels), borderwidth = 4 (in pixels), bordercolor = white.
    [#e These can be changed in the env in the usual way. [nim]]

    @warning: some deprecated but commonly used options are given in model units, not in pixels (probably a design flaw).
    """
    #e (Does Boxed want a clipped option, like DraggablyBoxed has? What about just Rect?)
    # WARNING: would not work if it inherited from Widget2D,
    # since it would pick up Widget2D default values for lbox attrs like btop. [unconfirmed but likely; 061127 comment]

    # args
    thing = Arg(Widget2D)

    # options
    borderwidth = Option(int, 4)  # 070305 new feature -- specified in pixels
    borderthickness = Option(Width, borderwidth * PIXELS)
    # old alternative (worse since caller has to multiply by PIXELS); commonly used, but deprecated as of 070305
    # (warning: borderthickness is still used as an internal formula when not supplied)
    # (WARNING: supplying both forms is an error, but is not detected;
    #  this might cause bugs that are hard for the user to figure out
    #  if the different option forms were used in successive customizations of the same expr)

    pixelgap = Option(
        int, 4
    )  # 070305 new feature [#e rename gap? bordergap? (change all gap options to being in pixels?)]
    # (maybe not yet tested with nonzero passed-in values)
    gap = Option(Width, pixelgap * PIXELS)
    # old alternative (worse since caller has to multiply by PIXELS), commonly used, but deprecated as of 070305
    # (see also the comments for borderthickness)
    # (warning: gap is still used as an internal formula when not supplied)

    bordercolor = Option(Color, white)

    # internal formulae
    extra1 = gap + borderthickness
    ww = thing.width + 2 * extra1  #k I'm not sure that all Widget2Ds have width -- if not, make it so ##e [061114]
    hh = thing.height + 2 * extra1
    rectframe = RectFrame(ww, hh, thickness=borderthickness, color=bordercolor)
    # appearance -- note, rectframe appearing first is significant, since lbox attrs are delegated to it.
    _value = Overlay(
        Translate(rectframe,
                  -V_expr(thing.bleft + extra1, thing.bbottom +
                          extra1)),  #e can't we clarify this somehow?
        thing)
    pass
Beispiel #9
0
class Line(InstanceOrExpr
           ):  #070211; revised 070419 (Widget2D -> IorE, more options)
    end1 = Arg(Point)
    end2 = Arg(Point)
    color = ArgOrOption(Color, black)
    width = Option(int, 1)  #e rename linewidth?
    dashed = Option(bool, False)

    def draw(self):
        color = self.fix_color(self.color)
        end1, end2 = self.end1, self.end2
        width = self.width
        dashed = self.dashed
        drawline(color[:3], end1, end2, width=width,
                 dashEnabled=dashed)  ###k dashEnabled untested here

    pass
Beispiel #10
0
class ChoiceButton(InstanceMacro):
    """ChoiceButton(choiceval, choiceref, content, background, background_off) [most args optional]
    displays and permits control of a choice variable stored externally in choiceref,
    looking like Overlay(background, content) or Overlay(background_off, content)
    when its own choice value is chosen or unchosen (ie equal or unequal to the stored one), respectively.
       Most args are optional with useful defaults, or can be given as simpler convenience types (eg colors or text);
    all args but choiceval can be given as named options, which is useful for customization.
       (Example: it's useful to put several of these with the same choiceref but different choicevals into a Column.
    This can be done by mapping one customized variant over a list of choicevals.)
       The choosing-action occurs on_press of entire thing -- this is not yet changeable
    (to other kinds of button actions), but should be. #e
    """
    # args
    choiceval = Arg(Anything)
        #e declare it as having to be constant per-Instance? Or can it legally vary?? I guess it could;
        # and I guess it's no error, just weird, for two of these (eg in a column) to share the same choiceval;
        # in fact, if they're physically separated it's not even weird.

    sbar_text = Option(str, format_Expr("%s", _self.choiceval)) # mouseover text for statusbar

    choiceref = ArgOrOption(StateRef) ###k need value-type??

    content = ArgOrOption(stubtype, TextRect(format_Expr("%s", _self.choiceval)) ) # Widget2D or something "displayable" in one (eg text or color); defaults to displayed choiceval;
        # can caller pass a formula in terms of the other options to _self?
        # Maybe, but not by saying _self! _this(ChoiceButton) == _my? [yes -- _my is now implemented, 061205]

    background = ArgOrOption(stubtype, Rect(_self.width, _self.height, lightblue) ) # Widget2D, or color (used in Rect a bit larger than content)
    
    background_off = ArgOrOption(stubtype, Spacer(_self.width, _self.height)) # ditto, defaults to transparent
        ##k problem: what we want is to compute an lbox and then use this here in the spacer... or align the content... or ....
        ##e note that a lot of people find it more convenient to pass around a size, or even pass around a rect,
        # than to always work with 4 or 6 rect-related attrs...

    # formulae
    chosen = eq_Expr( choiceref.value, choiceval) #k
    ## print "chosen is",chosen

    ###k assume useful conversions of named options happened already
    ###e use _value; is it as simple as renaming it delegate and using DelegatingMixin?? Can InstanceMacro do it for us??
    # [if we use one of those, be careful not to inherit from Widget2D here, due to its lbox defaults!]
    _value = Highlightable( Overlay( SpacerFor(Boxed(content)),
                                         # kluge to make room around content in _self.width and _self.height,
                                         # and make those non-circular; WRONG because won't be properly aligned with backgrounds,
                                         # even if content itself would be;
                                         # could fix by shifting, but better to figure out how to have better rectlike size options ###e
                                     Overlay( ###KLUGE since "Overlay is a stub which only works with exactly two args"
                                         If(chosen, background, background_off),
                                         content ),
                                     ),
                            ## old code: on_press = SetStateRefValue(choiceref, choiceval),
                            # try this code 061211 1113a -- it works, use it:
                            on_press = Set(choiceref.value, choiceval),
                                ##e probably best to say Set(choiceref.value, choiceval), but I think that's nim -- not sure --
                                # should retest it after Set is revised later today to work with arg1 being lval eg getattr_Expr [061204]
                            sbar_text = sbar_text
                           )
    pass # end of class ChoiceButton
Beispiel #11
0
class _cmd_DrawOnSurface_BG(Highlightable):
    "[private helper] background drag event bindings for cmd_DrawOnSurface; intended for use inside BackgroundObject()"
    # args [needed for sharing state with something]
    world = Option(World)

    #e code for event handlers during the drag, eg on_drag
    def on_press(self):
        point = self.current_event_mousepoint()
        newpos = point + DZ * DZFUZZ  # kluge: move it slightly closer so we can see it in spite of bg
        ###e needs more principled fix -- not yet sure what that should be -- is it to *draw* closer? (in a perp dir from surface)
        #e or just to create spheres (or anything else with thickness in Z) instead? (that should not always be required)

        ###BUG: DZ is not always the right direction!
        #e maybe scrap it here, and instead change class polyline3d to always draw itself closer to the screen
        # than its stored coords, but store coords identical to those of the underlying model object --
        # use a globally available displist (nim) for translating closer to the user -- hmm, that has to be different
        # for each rotational environment you might be inside! So only the graphical env knows its name...

        node_expr = Vertex(newpos, Center(Rect(0.2, 0.2, red)))

        newnode = self.world.make_and_add(
            node_expr, type="Vertex")  #070206 added type = "Vertex"
        ###e may want to make, but not add... let it be a kid of the polyline3d

        self.newnode = newnode
        return

    def on_drag(self):
        point = self.current_event_mousepoint()
        lastnode = self.newnode  # btw nothing clears this on mouseup, so in theory it could be left from a prior drag

        newpos = point + DZ * DZFUZZ

        # inside a drawable, this is where we'd let the current tool decide what to do,
        # but in this polyline3d-making command we already know what to do:

        if not lastnode:
            print "bug: no self.newnode!!!"
        else:
            if not isinstance(lastnode, polyline3d):
                lastnode = self.newnode = self.world.make_and_add(
                    polyline3d(lastnode), type="polyline3d")
                ###e should we make and add this polyline3d, but get world to put it inside DraggableObject or so?
            lastnode.add_point(newpos)

        return

    def on_release(self):  #070223 new hack
        import foundation.env as env  #FIX - make this unnecessary
        if isinstance(self.newnode, polyline3d) and env.prefs.get(
                kluge_dragtool_state_prefs_key + "bla2", False):
            self.newnode._closed_state = True  ####KLUGE, I'd rather say .closed but that won't work until I have OptionState
        return

    pass  # end of class _cmd_DrawOnSurface_BG
Beispiel #12
0
class PM_Command(
        DelegatingInstanceOrExpr
):  #e review name, etc ##e delegating?? ###k hmm, is it a command, or a CommandRun? can those be same class?
    "superclass for commands with their own property managers"
    # default ways to get the PM
    property_manager_groups = ()  # typically overridden by a subclass
    property_manager_message = _self.__class__.__name__  #e improve, use _C_ if needed # typically overridden by a subclass
    property_manager = Instance(
        PM_from_groups(_self.property_manager_groups,
                       message=_self.property_manager_message))
    # find the world -- maybe this belongs in a yet higher Command superclass? ###e
    world = Option(World)
    pass
Beispiel #13
0
class TextEditField(DelegatingInstanceOrExpr):
    text = State(
        str, "(empty)"
    )  #e dflt should be an arg, or more to the point, the text should come from a stateref ###DOIT
    label = Option(str, "(field label)")
    dialog_title = Option(str, "(title)")
    dflt_dialog_label = "enter some text; use @@@ for \\n"  # a public class attr (for clients to use in longer labels)
    dialog_label = Option(str, dflt_dialog_label)
    delegate = Highlightable(
        SimpleRow(
            Top(TextRect(label)), Top(TextRect(text))
        ),  #e long enough? too long? nlines ok? # Top is for when >1 line in text
        #e highlight form with blue border?
        on_doubleclick=_self.
        on_doubleclick,  # on_press -> on_doubleclick -- probably better
        sbar_text="double-click to edit textfield",
        #e cmenu binding too?
    )

    def on_doubleclick(self):
        ok, text = grab_text_using_dialog(default=self.text,
                                          title=self.dialog_title,
                                          label=self.dialog_label)
        if ok:
            #e unicode?? (see cad/src MT Node renaming code, or Node.name mmp code, or the Comment Node,
            # for example code that can handle it)
            #e filter through a func about what's acceptable?
            # if it fails, repeat dialog with more specific label?
            # strip whitespace? etc
            # eventually customize all that, for now just do what's most useful right here
            text = text.strip()
            self.text = text  # changes state
        else:
            print "cancelled text edit, old text remains: %r" % (
                self.text, )  #e some other place to put a message in the UI?
        return

    pass
Beispiel #14
0
class SimpleRow(Widget2D):
    # copy of SimpleColumn, but bbottom <-> bright, btop <-> bleft, width <- height, and 0,-dy -> dx,0, basically
    a0 = Arg(Widget2D, None)
    a1 = Arg(Widget2D, None)
    a2 = Arg(Widget2D, None)
    a3 = Arg(Widget2D, None)
    a4 = Arg(Widget2D, None)
    a5 = Arg(Widget2D, None)  # use ArgList here when that works
    a6 = Arg(Widget2D, None)
    a7 = Arg(Widget2D, None)
    a8 = Arg(Widget2D, None)
    a9 = Arg(Widget2D, None)
    a10 = Arg(Widget2D, None)
    a11 = Arg(Widget2D, None)
    toomany = Instance(TextRect("too many args to SimpleRow"))
    args = list_Expr(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
                     and_Expr(a11, toomany))

    pixelgap = Option(int, 3)
    gap = pixelgap * PIXELS

    drawables = call_Expr(lambda args: filter(None, args), args)
    empty = not_Expr(drawables)
    btop = call_Expr(
        lambda drawables: max([arg.btop for arg in drawables] + [0]),
        drawables)
    bbottom = call_Expr(
        lambda drawables: max([arg.bbottom for arg in drawables] + [0]),
        drawables)
    width = call_Expr(
        lambda drawables, gap: sum([arg.width for arg in drawables]) + gap *
        max(len(drawables) - 1, 0), drawables, gap)
    bleft = or_Expr(and_Expr(a0, a0.bleft), 0)
    bright = width - bleft

    def draw(self):
        glPushMatrix()
        prior = None
        for a in self.drawables:
            if prior:
                # move from prior to a
                dx = prior.bright + self.gap + a.bleft
                glTranslatef(dx, 0,
                             0)  # positive is right, and Row progresses right
            prior = a
            self.drawkid(a)  ## a.draw()
        glPopMatrix()
        return

    pass  # end of class SimpleRow
Beispiel #15
0
class MapListToExpr(DelegatingInstanceOrExpr): #e rename? should this be what map_Expr does, or does that need to be incremental-update?
    # note: this was modified from _MT_try2_kids_helper, which was then rewritten in terms of it.
    # args (#e should some be ArgOrOption? Note: that doesn't work right if it comes before Arg.)
    ###KLUGE: use ArgExpr to avoid problems when passing expr classes as functions -- not sure if generally ok (eg when ordinary callable is passed)#####
    function = ArgExpr(Function, doc = "a python function which maps members of elements to Instances or Exprs")
    elements = Arg(list_Expr, doc = "a list of elements (of whatever data type you want, suitable for passing to _my.function)")
    exprhead = ArgExpr(Function, doc = """a python function or expr which can be applied to a sequence of Instances
                    to produce an Expr we should instantiate as our _delegate, whenever our input list of elements changes.
                    Typical values include expr classes like SimpleColumn, or customized ones like SimpleRow(pixelgap = 2).
                    WARNING: due to logic bugs at present, expr classes passed to this argument (or to the function argument)
                    need to be wrapped in KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr.
                    """
                )
    instance_function = Option(Function, _self.Instance,
                               doc = "a python function with the API of some IorE object's .Instance method, to produce the Instances we delegate to" )
        # I wanted to name this Instance, but I think that would cause infrecur when it used the default.
        # note: we pass it permit_expr_to_vary = True, which complicates the required API. But without this we could not pass someobj.Instance.
        # Maybe IorE needs a variant method of Instance with permit_expr_to_vary = True by default? VaryingInstance?
    # self._delegate recompute rule
    def _C__delegate(self):
        function = self.function
        elements = self.elements # usage tracked (that's important)
        exprhead = self.exprhead
        index = '_C__delegate'
        # compute the expr we need (see comment above for why we need a new one each time)
        elts = map( function, elements)
        def func2((elt, subindex)): #070320
            if is_expr_Instance(elt):
                res = elt
            elif is_pure_expr(elt):
                # new feature 070320: if function(element) gives an expr, instantiate it with a suitable choice of index
                eli = self.instance_function( elt, (subindex, index), permit_expr_to_vary = True)
                assert is_expr_Instance(eli) #e error message with elt and eli
                res = eli
            else:
                assert is_expr_Instance(elt) or is_pure_expr(elt) #e message
            assert is_expr_Instance(res) # sanity check, can remove when works
            return res
        elts = map( func2, zip(elts, range(len(elts))))
        expr = exprhead(*elts)
        # do we need to eval expr first? in theory i forget, but I think we do.
        # in practice it's very likely to eval to itself, so it doesn't matter for now. ###k
        ##e do we need to discard usage tracking during the following??
        res = self.instance_function( expr, ('notint', index), permit_expr_to_vary = True)
        return res
    pass # end of class MapListToExpr
Beispiel #16
0
class checkbox_v3(
        InstanceMacro
):  ##e rename # note: this can do something checkbox_pref can't yet do -- take an external stateref
    stateref = Arg(StateRef,
                   None)  ### default? might not work with a default val yet
    ### IMPLEM: specify what external state to use, eg a prefs variable, PrefsKey_StateRef(displayOriginAxis_prefs_key)
    defaultValue = Option(bool, False)  ###BUG -- not used! [noticed 061215]
    ## var = State(bool, defaultValue)
    var = stateref.value
    ##    # print "var = %r" % (var,) # TypeError: 'module' object is not callable - on line that says on_press = Set(var, not_Expr(var) )
    ##        # solved: it's probably from above: import Set; from Set import something else but not Set
    _value = Highlightable(
        If(
            var,
            checkbox_image('mac_checkbox_on.png'),
            checkbox_image('mac_checkbox_off.png'),
        ),
        ## on_press = Set(var, not_Expr(var) ) # wanted (sort of), but doesn't work yet, as explained here:
        ## AssertionError:... <C_rule_for_formula at 0xff2de10 for 'var' in 'checkbox_v3'> should implement set_for_our_cls
        # ah, I was assuming "var = stateref.value" would alias var to obj.attr permitting set of obj.attr by set of var,
        # but of course, that should probably never be permitted to work, since it looks like we're changing self.var instead!
        # Unless, I either:
        # - implem a variant of "var = stateref.value"
        # - decide that since var would normally be unsettable when defined by a formula, that always aliasing it like that
        #   would be ok (often wanted, not too confusing).
        # PROBLEM WITH PERMITTING IT: you might start out var = State(), and it works to change self.var,
        # then change var to a formula, and forget you were directly setting it... causing an unwanted actual working set
        # of other state which you thought you were not touching or even able to touch.
        # SOLUTION:
        # - implem a variant of "var = stateref.value, sort of a "state alias"
        # - have a clear error message from this Set, suggesting to change that assignment to that alias form.
        # as of 061204 418p: tentatively decided I like that, but the alias variant syntax is not yet designed.
        # Maybe: ####@@@@
        # - var = Alias(stateref.value) ?? [sounds good]
        # - or just var = State(stateref.value), or is that too confusing?
        #   [yes, too confusing, that arg is for the type, and State is for new state, not an alias... latter is lesser problem]
        # - or var = StateRef(stateref.value)? no, it's an lval, not a stateref.
        on_press=Set(stateref.value, not_Expr(var))
        # kluge: see if this works (I predict it will) -- workaround of not yet having that alias form of "var = stateref.value"
    )
    pass  # end of class checkbox_v3
Beispiel #17
0
class PM_from_groups(
        DelegatingInstanceOrExpr
):  ###e refile into demo_ui or so, and call it on [make_polyline3d_PG(somearg)]
    "Make a Property Manager UI from a list of groupbox content widgets (eg columns of field editors) and other info."
    # args
    groups = Arg(list_Expr)
    #e in future these groups need to come with more attrs, like group titles
    # (WAIT, they already do have a title attr which we don't use here!),
    # whether they're closable and if so whether initially closed...
    # and they might include their own Boxed already...
    # the type decl might say we want a list of PropertyGroupBoxes,
    # with autoconversion of ParameterGroups to those...
    message = Option(
        str, "(PM message goes here)"
    )  # this has to be already split into lines, for now; all indentation is stripped

    # formulae
    def _C_use_message(self):
        lines = self.message.split('\n')
        lines = [line.strip() for line in lines]
        lines = filter(None, lines)
        return '\n'.join(lines)

    use_message = _self.use_message
    # appearance
    message_box = Boxed(TextRect(use_message), gap=0, bordercolor=yellow)
    group_box_column = MapListToExpr(
        KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(
            Boxed),  ###e change to a GroupBox, with a title from the group...
        groups,
        KLUGE_for_passing_expr_classes_as_functions_to_ArgExpr(SimpleColumn))
    delegate = SimpleColumn(
        Left(message_box),
        Left(group_box_column),
    )
    pass
Beispiel #18
0
def OptionsDict(type=Anything):
    return Option(type)  ###STUB just so parsing works, totally wrong in effect
Beispiel #19
0
class DraggableObject(DelegatingInstanceOrExpr):
    """DraggableObject(obj) is a wrapper which makes any model object draggable (###doc the details),
    and also helps provides a context menu specific to obj.
    [##e It may be extended to make obj click-selectable or even region-selectable, at the proper times, too.]
       WARNING: Experimental -- API/organization will surely change,
    integrating not only rotation, but click to select, etc.
    The resulting wrapper will typically be applied by model->view macros.
       In fact, it's more complicated than that: the selection-click controller will wrap single objects,
    but the draggability wrapper is more likely to be organized something like this,
    where the named localvars refer to sets whose membership depends on selection:
      visibles = DisplayListChunk(fixed_stuff) + distortedly_moving_stuff +
        DraggableObject(DisplayListChunk(dragging_as_a_unit_stuff)).
    The distortedly_moving_stuff includes things like external bonds between fixed and being-dragged atoms,
    which have to stretch in individual ways during the drag.
    """
    # args
    obj = Arg(ModelObject)

    # options
    #e selectable = Option(bool, True, doc = "whether to let this object be click-selectable in the standard way") [see selected]
    rotatable = Option(
        bool,
        True,
        doc=
        "whether to let this object rotate about its center using MMB/Alt/Option drags"
    )
    # This is intended to implement an initial subset of the "New motion UI" [070225 new feature]
    # [###e default will change to False after testing]
    # WARNING: as an optim, we might require that this be True initially, or even always (i.e. be a constant),
    # if it will ever be True during the Instance's lifetime -- not sure. If so, this requirement must at least be documented,
    # and preferably error-detected. ###FIX (if we do require that)
    # experimental kluge 070314
    _kluge_drag_handler = Option(
        Anything,
        _self,
        doc=
        "object to receive our on_press/on_drag/on_release events, in place of _self"
    )

    # state
    selected = State(bool,
                     False)  ###KLUGE test stub, only set when debug070209
    translation = Option(
        Vector,
        V(0, 0, 0),  #070404
        doc=
        "initial translation [WARNING: might merge with misnamed self.motion (a State attr) to make a StateOption]"
    )
    motion = State(
        Vector, _self.translation
    )  # publicly visible and settable (but only with =, not +=).
    ##e rename to translation? (by making it a StateOption)
    ##e (or deprecate the concept of StateOption but make any State settable initially by a special option not just with same name?
    ##   eg either initial_attr or initial_data = [something with dict or attr access to the data] ??)
    ##e NOTE [070404]: I miscoded translation as Arg rather than Option, and said StateArg rather than StateOption in docstring,
    # though intending only named uses of it -- is this evidence that Arg / Option / Parameter should be the same,
    # that Option should be the default meaning, and positional arglists should be handled differently and as an extra thing
    # (eg like the old _args feature -- which leads to clearer code when subclassing)?? Guess: quite possibly, but needs more thought.
    # WARNING: use of += has two distinct bugs, neither error directly detectable:
    # - changes due to += (or the like) would not be change tracked.
    #   (But all changes to this need to be tracked, so our drawing effects are invalidated when it changes.)
    # - value might be a shared Numeric array -- right now use of = to set this doesn't copy the array to make us own it.
    rotation = State(Quat,
                     Q(1, 0, 0,
                       0))  #070225 new feature -- applied around object center

    # experiment 070312: works (see test_StateArrayRefs_2) ###doc ##e clean up ##k is it making the usual case slow in a significant way??
    delta_stateref = Option(StateRef,
                            call_Expr(LvalueFromObjAndAttr, _self, 'motion'),
                            doc="#doc")
    use_motion = delta_stateref.value

    # geometric attrs should delegate to obj, but be translated by motion as appropriate.
    ##e Someday we need to say that in two ways:
    # - the attrs in the "geometric object interface" delegate as a group (rather than listing each one of them here)
    # - but when they do, they get passed through a change-of-coords boundary, and they know their own coordsystems,
    #   so the right thing happens.
    # But for now we have no way to say either thing, so we'll add specific formulas for specific attrs as needed. [070208]
    ##e Note that before the obj types know how to translate due to type, the interface (which knows the attrs indivly)
    # could know it. So, delegation of all attrs in an interface can be done by special glue code which also knows
    # how to transform them in useful ways, by knowing about those attrs and what transforms are useful.
    # This is useful enough to keep, even once its default transforms can come from declared attr types &
    # values knowing their coordsys. It adds value to that since interfaces can always know special cases about specific attrs.

    if 0:
        # update 070209 late: try doing this in Translate below, with the other involved exprs delegating as usual... ####k
        center = obj.center + motion
        # following comments are from when the above was 'if 1' a day or two ago -- still relevant since general [##e refile??]:

        # Problem: won't work for objs with no center! Solution for now: don't try to eval the self attr then.
        # Not perfect, since what ought to be AttributeError will turn into some other exception.
        ##e One better solution would involve declared interfaces for obj, and delegation of all attrs in interfaces
        # of a certain kind (geometric), so if obj met more interfaces and had more attrs, those would be included,
        # but if not, we would not have them either.
        ##e Or alternatively, we could provide an easy way to modify the above formula
        # to specify a condition under which center should seem to exist here, with that cond being whether it exists on obj.
        ### A potential problem with both solutions: misleasing AttributeError messages, referring to self rather than obj,
        # would hurt debugging. So we probably want to reraise the original AttributeError in cases like that, whatever
        # the way in which we ask for that behavior. That means one construct for "passing along attr missingness",
        # but *not* a composition of one construct for saying when this attr is there, and one for asking whether another is.

        # Note: can't we delegate center (& other geometry) through the display delegate below, if Highlightable passes it through
        # and Translate does the coordinate transformation? ###e

    # appearance

    obj_name = call_Expr(node_name, obj)  #070216
    # Note: node_name is used in MT_try2; it's better than using _e_model_type_you_make (for our use in sbar_text, below).
    # BTW, node_name is a helper function associated with ModelTreeNodeInterface (informal so far).
    #
    # If you want to wrap an object with extra info which specifies its node_name, use ... what? ###k hmm, I forget if there
    # is a way partway through being implemented...
    # maybe WithAttributes( Center(Rect(0.4, 0.4, green)), mt_name = "green rect #n" )... i guess yes, ### TRY IT
    # should clean this situation up, use Adaptor term for that pattern
    # of interface conversion, etc... [070404 updated comment]

    # (Note [070216]: I had a bug when I had a comma after the above def. This made obj_name, included directly in another expr,
    #  turn into a singleton tuple of the call_Expr value, but when included as _self.obj_name (normally equivalent to obj_name),
    #  turn into something else (since eval of a tuple must not descend inside it -- guess, might have been a tuple_Expr).
    #  I'm not sure how to detect this error except to stop permitting tuple(expr) to be allowed as abbrev for a tuple_Expr --
    #  which seems too inconvenient -- or to figure out a way for the formula scanner to detect it (and make it illegal as the
    #  rhs of an assignment into a class namespace -- probably ok to make illegal). ##DOIT sometime)

    obj_drawn = If(
        selected,
        Overlay(obj, Rect(
            1, 1,
            blue)),  ##### WRONG LOOK for selected, but should work [070209]
        #BUG: Rect(1,lightblue) is gray, not light blue -- oh, it's that failure to use type to guess which arg it is!
        obj)

    sbar_text_for_maybe_selected = If(selected, " (selected)", "")

    delegate = Highlightable(
        # Note 070317: since Highlightable is outside of RotateTranslate, its coordsys doesn't change during a drag,
        # thus avoiding, here in DraggableObject, the bug that came up in the first implem of DraggablyBoxed,
        # whose highlightable rectframe was moving during the drag, but was also being used to supply the coordsys
        # for the drag events. This bug is actually in SimpleDragBehavior above, and the fix will be confined to that class.
        #
        # plain appearance
        RotateTranslate(obj_drawn, rotation, use_motion),
        # hover-highlighted appearance (also used when dragging, below)
        highlighted=RotateTranslate(
            DisplayListChunk(
                # This inner DisplayListChunk, in theory, might help make up for current implem of disabling them inside WarpColors...
                # in my tests, it didn't make a noticeable difference (probably since obj is fast to draw). [070216 2pm]
                #
                # Note: if obj has its own DisplayListChunk, does that notice the value of whatever dynenv var is altered by WarpColors??
                # We'll have to make it do so somehow -- perhaps by altering the displist name by that, or turning off displists due to it.
                # For this initial implem [070215 4pm], we did the latter.

                ## WarpColors( obj_drawn, lambda color: ave_colors( 0.3, white, color ) ), # whiten the color -- ugly
                ## WarpColors( obj_drawn, lambda color: yellow ), # "ignore color, use yellow" -- even uglier
                ## WarpColors( obj_drawn, lambda color: ave_colors( 0.2, white, color ) ), # whiten, but not as much -- less ugly
                WarpColors(
                    obj_drawn, lambda color: ave_colors(0.1, white, color)
                ),  # whiten, even less -- even less ugly [best so far]
                ## WarpColors( obj_drawn, lambda color: ave_colors( 0.2, gray, color ) ), # gray-end instead of whiten -- not quite as good
                ## WarpColors( obj_drawn, lambda color: (color[1],color[2],color[0]) ), # permute the hues...
            ),
            rotation,
            use_motion),
        pressed=_my.highlighted,  # pressed_in and pressed_out appearance
        ###BUG (when we gave pressed_in and pressed_out separately -- ###UNTESTED since then):
        # this pressed_out appearance seems to work for DNA cyls but not for draggable PalletteWell items! [070215 4pm]
        ## sbar_text = format_Expr( "Draggable %r", obj ),
        ##e should use %s on obj.name or obj.name_for_sbar, and add those attrs to ModelObject interface
        # (they would delegate through viewing wrappers on obj, if any, and get to the MT-visible name of the model object itself)
        ##e [Can we implem something like try_Expr( try1, try2, try3) which evals to the first one evalling without an exception??
        # But that doesn't seem safe unless you have to list the permissible exceptions (like in Python try/except).
        # The use of this here (temporary) would be to look for obj.name, then try a different format_Expr if that fails.
        # getattr(obj, 'name', dflt) would get us by, but would not as easily permit alternate format_Exprs in the two cases.]
        ##        # older highlighted or pressed_in appearance (not sure which it was before I inserted the args above this) -- zapping it 070216 late
        ##        If( eval_Expr(constant_Expr(constant_Expr(debug070209))),
        ##                ###e need option or variant of If to turn off warning that cond is a constant: warn_if_constant = False??
        ##                # also make the printed warning give a clue who we are -- even if we have to pass an option with the text of the clue??
        ##            Translate( Boxed(obj), motion),
        ##                #######070209 TEST THIS KLUGE -- note it does not include selected appearance
        ##                    # (but HL might incl it anyway? sometimes yes sometimes no, not sure why that would be -- ah, it depends on whether
        ##                    # mouse is over the moved object (which is silly but i recall it as happening in regular ne1 too -- ###BUG)
        ##                #e not good highlight form
        ##                ####BUG: the layout attrs (lbox attrs, eg bleft) are apparently not delegated, so the box is small and mostly obscured
        ##            Translate( obj, motion)
        ##         ),
        ## _obj_name = call_Expr(node_name, obj), #070216
        # that can't work yet -- it tries to define a new attr in an object (this Highlightable) from outside,
        # accessible in other option formulae as _this(Highlightable)._obj_name...
        # instead, I moved this def into _self (far above) for now.
        sbar_text=format_Expr("%s%s (can be dragged)", obj_name,
                              sbar_text_for_maybe_selected),  # revised 070216
        # This adds some info to sbar_text about what we can do with obj (drag, select, etc)...
        #e someday, maybe the dynenv wants to control how info of several kinds turns into actual sbar_text.
        ##        on_press = _self.on_press,
        ##        on_drag = _self.on_drag,
        ##        on_release = _self.on_release,
        on_press=_kluge_drag_handler.on_press,
        on_drag=_kluge_drag_handler.on_drag,
        on_release=_kluge_drag_handler.on_release,
        cmenu_maker=
        obj  ###e 070204 experimental, API very likely to be revised; makes Highlightable look for obj.make_selobj_cmenu_items
    )

    ### DESIGN Q: do we also include the actual event binding (on_press and on_drag) -- for now, we do --
    # or just supply the Draggable interface for moving self.obj
    # and let the caller supply the binding to our internal "cmd" drag_from_to?? ###e

    # has Draggable interface (see demo_polygon.py for explan) for changing self.motion

    def _cmd_drag_from_to(
            self, p1,
            p2):  #e rename drag_hitpoint_from_to? (in the Draggable Interface)
        """[part of the Draggable Interface; but this interface
        is not general enough if it only has this method -- some objects need more info eg a moving mouseray, screenrect, etc.
        Either this gets passed more info (eg a dragevent obj),
        or we keep the kluge of separate self dynenv queries (like for mousepoint and screenrect),
        or we provide glue code to look for this method but use more general ones if it's not there. ###e
        BTW, that interface is a myth at present; all actual dragging so far is done using on_press/on_drag/on_release,
        with this method at best used internally on some objs, like this one. [as of 070313]]
        """
        if self._delegate.altkey:
            assert 0, "should no longer be called"
##            ###KLUGE, just a hack for testing Highlightable.altkey [070224]; later, do rotation instead (per "New motion UI")
##            # (Is it also a ###KLUGE to detect altkey within this method, rather than caller detecting it and passing a flag
##            #  or calling a different method? YES.)
##            ## self.motion = self.motion + (p2 - p1) * -1
##            # change self.rotation... by a quat which depends on p2 - p1 projected onto the screen... or the similar mouse x,y delta...
##            ###KLUGE: assume DZ is toward screen and scale is standard....
##            # wait, ###BUG, we don't even have enough info to do this right, or not simply, starting from p1, rather than startpoint...
##            dx,dy,dz = p2 - p1
##            rotby = Q(p1,p2) ###WRONG but ought to be legal and visible and might even pretend to be a trackball in some cases and ways
##            self.rotation = self.rotation + rotby
##            # print "%r motion = %r rotation = %r" % (self, self.motion, self.rotation)
        else:
            ## self.motion = self.motion + (p2 - p1)
            self.delta_stateref.value = self.delta_stateref.value + (p2 - p1)
        return

    ##e something to start & end the drag? that could include flush if desired...

    # can push changes into the object

    def flush(self, newmotion=V(0, 0, 0)):
        self.delegate.move(
            self.use_motion + newmotion
        )  ###k ASSUMES ModelObject always supports move (even if it's a noop) ###IMPLEM
        # note, nothing wrong with modelobjects usually having one coordsys state which this affects
        # and storing the rest of their data relative to that, if they want to -- but only some do.
        ## self.motion = V(0,0,0)
        self.delta_stateref.value = V(0, 0, 0)

    # if told to move, flush at the same time

    def move(self, motion):
        self.flush(motion)
        return

    # on_press etc methods are modified from demo_polygon.py class typical_DragCommand

    #e note: it may happen that we add an option to pass something other than self to supply these methods.
    # then these methods would be just the default for when that was not passed
    # (or we might move them into a helper class, one of which can be made to delegate to self and be the default obj). [070313]

    def on_press(self):
        point = self.current_event_mousepoint(
        )  # the touched point on the visible object (hitpoint)
        # (this method is defined in the Highlightable which is self.delegate)
        self.oldpoint = self.startpoint = point
        # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place.
        # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].)
        if self._delegate.altkey:
            self._this_drag = 'free x-y rotate'
            #e more options later, and/or more flags like this (maybe some should be booleans)
            ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance!
            ##e or should that be renamed DragOperation??
            self._screenrect = (ll, lr, ur,
                                ul) = self.screenrect(self.startpoint)
            # these points should be valid in our delegate's coords == self's coords
            self._dx = _dx = norm(lr - ll)
            self._dy = _dy = norm(ur - lr)
            self._dz = cross(
                _dx, _dy
            )  # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think)
            ###k check cross direction sign
            self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4
            # New motion UI suggests that 40% of that distance means 180 degrees of rotation.
            # We'll draw an axis whose length is chosen so that dragging on a sphere of that size
            # would have the same effect. (Maybe.)
            self._objcenter = self._delegate.center
            self.startrot = +self.rotation
        else:
            self._this_drag = 'free x-y translate'
        if debug070209:
            self.ndrags = 0
        return

    def on_drag(self):
        # Note: we can assume this is a "real drag", since the caller (ultimately a selectMode method in testmode, as of 070209)
        # is tracking mouse motion and not calling this until it becomes large enough, as the debug070209 prints show.
        oldpoint = self.oldpoint  # was saved by prior on_drag or by on_press
        point = self.current_event_mousepoint(plane=self.startpoint)
        if debug070209:
            self.ndrags += 1


##            if (self.ndrags == 1) or 1:
##                print "drag event %d, model distance = %r, pixel dist not computed" % (self.ndrags, vlen(oldpoint - point),)
        if self._this_drag == 'free x-y rotate':
            # rotate using New motion UI
            #  [probably works for this specific kind of rotation, one of 4 that UI proposes;
            #   doesn't yet have fancy cursors or during-rotation graphics; add those only after it's a DragCommand]
            # two implem choices:
            # 1. know the eye direction and the screen dims in plane of startpoint, in model coords; compute in model coords
            # 2. get the mouse positions (startpoint and point) and screen dims in window x,y coords, compute rotation in eye coords,
            #   but remember to reorient it to correspond with model if model coords are rotated already.
            # Not sure which one is better.
            #   In general, placing user into model coords (or more precisely, into object local coords) seems more general --
            # for example, what if there were several interacting users, each visible to the others?
            # We'd want each user's eye & screen to be visible! (Maybe even an image of their face & screen, properly scaled and aligned?)
            # And we'd want their posns to be used in the computations here, all in model coords.
            # (Even if zoom had occurred, which means, even the user's *size* is quite variable!)
            #   I need "user in model coords" for other reasons too, so ok, I'll do it that way.
            #
            # [Hey, I might as well fix the bug in current_event_mousepoint which fakes the center of view, at the same time.
            # (I can't remember its details right now, but I think it assumed the local origin was the cov, which is obviously wrong.)
            # (But I didn't look at that code or fix that bug now.)]
            vec = point - self.startpoint
            uvec = norm(vec)  #k needed??
            axisvec = cross(
                self._dz, uvec
            )  # unit length (suitable for glRotate -- but we need to use it to make a quat!)
            axisvec = norm(
                axisvec)  # just to be sure (or to reduce numerical errors)
            scale = self._scale
            draw_axisvec = axisvec * scale  #e times some other length constant too?
            center = self._objcenter
            self.axisends = (center - axisvec, center + axisvec
                             )  # draw a rotation axis here ###e
            self.degrees = degrees = vlen(
                vec
            ) / scale * 180.0  # draw a textual indicator with degrees (and axisvec angle too) ###e
            ###e or print that info into sbar? or somewhere fixed in glpane? or in glpane near mouse?
            # now set self.rotation to a quat made from axisvec and degrees
            theta = degrees / 360.0 * 2 * pi
            # print "axisvec %r, degrees %r, theta %r" % (axisvec ,degrees,theta)
            rot = Q(axisvec, theta)
            self.rotation = self.startrot + rot  # note use of self.startrot rather than self.rotation on rhs
            # avoid += to make sure it gets changed-tracked -- and since it would be the wrong op!

        elif self._this_drag == 'free x-y translate':
            self._cmd_drag_from_to(
                oldpoint, point)  # use Draggable interface cmd on self
        else:
            assert 0
        self.oldpoint = point
        return

    def on_release(self):
        #e here is where we'd decide if this was really just a "click", and if so, do something like select the object,
        # if we are generalized to become the wrapper which handles that too.
        if debug070209:
            if not self.ndrags:
                # print "release (no drags)" # ie a click
                self.selected = not self.selected  ###KLUGE test stub
            else:
                pass  # print "release after %d drags" % self.ndrags
            self.ndrags = 0
        pass

    pass  # end of class DraggableObject
Beispiel #20
0
class polyline3d(
        InstanceOrExpr
):  # WARNING [070319]: duplicated code, demo_drag.py and demo_draw_on_surface.py [modified a bit]
    """A graphical object with an extendable (or resettable from outside I guess) list of points,
    and a kid (end1) (supplied, but optional (leaving it out is ###UNTESTED)) that can serve as a drag handle by default.
    (And which also picks up a cmenu from self. (kluge!))
    Also some Options, see the code.
    """
    # Note [070307]: this class will be superceded by class Polyline in demo_polyline.py,
    # and the code that uses it below (eg the add_point call) will be superseded by other code in that file.
    #
    ###BUGS: doesn't set color, depends on luck. end1 is not fully part of it so putting cmenu on it will be hard.
    # could use cmenu to set the options.
    # end1 might be in MT directly and also be our kid (not necessarily wrong, caller needn't add it to MT).
    # when drawing on sphere, long line segs can go inside it and not be seen.
    points = State(
        list_Expr,
        [])  ###k probably wrong way to say the value should be a list
    ##    end1arg = Arg(StubType,None)
    ##    end1 = Instance(eval_Expr( call_Expr(end1arg.copy,)( color = green) ))###HACK - works except color is ignored and orig obscures copy
    end1 = Arg(
        StubType,
        None,
        doc=
        "KLUGE arg: optional externally supplied drag-handle, also is given our cmenu by direct mod"
    )
    closed = Option(bool, False, doc="whether to draw it as a closed loop")
    _closed_state = State(bool, closed)  ####KLUGE, needs OptionState
    relative = Option(
        bool,
        True,
        doc=
        "whether to position it relative to the center of self.end1 (if it has one)"
    )

    def _init_instance(self):
        end1 = self.end1
        if end1:
            end1.cmenu_maker = self

    def _C__use_relative(self):
        "compute self._use_relative (whether to use the relative option)"
        if self.relative:
            try:
                center = self.end1.center
                return True
            except:
                #e print?
                return False
        return False

    def _C_origin(self):  #070307 renamed this to origin from center
        if self._use_relative:
            return self.end1.center  # this can vary!
        return ORIGIN
##    center = _self.origin #k might not be needed -- guessing it's not, 070307

    def add_point(self, pos):
        "add a point at the given 3d position"
        pos = pos - self.origin
        self.points = self.points + [
            pos
        ]  ### INEFFICIENT, but need to use '+' for now to make sure it's change-tracked

    def draw(self):
        ###k is it wrong to draw both a kid and some opengl stuff?? not really, just suboptimal if opengl stuff takes long (re rendering.py)
        self.drawkid(self.end1)  # end1 node
        if self._closed_state:
            glBegin(GL_LINE_LOOP
                    )  # note: nothing sets color. see drawline for how.
            # in practice it's thin and dark gray - just by luck.
        else:
            glBegin(GL_LINE_STRIP)
        if self._use_relative:
            # general case - but also include origin (end1.center) as first point!
            origin = self.origin
            glVertex3fv(origin)
            for pos in self.points:
                glVertex3fv(pos + origin)
        else:
            # optim - but not equivalent since don't include origin!
            for pos in self.points:
                glVertex3fv(pos)
        glEnd()
        #e option to also draw dots on the points [useful if we're made differently, one click per point; and they might be draggable #e]
        return

    def make_selobj_cmenu_items(self, menu_spec, highlightable):
        """Add self-specific context menu items to <menu_spec> list when self is the selobj (or its delegate(?)... ###doc better).
        Only works if this obj (self) gets passed to Highlightable's cmenu_maker option (which DraggableObject(self) will do).
        [For more examples, see this method as implemented in chem.py, jigs*.py in cad/src.]
        """
        menu_spec.extend([
            (
                "polyline3d", noop, 'disabled'
            ),  # or 'checked' or 'unchecked'; item = None for separator; submenu possible
        ])

    pass  # end of class polyline3d
Beispiel #21
0
class DrawInCorner(DelegatingInstanceOrExpr):
    """
    DrawInCorner( thing, (-1,-1)) draws thing in the lower left corner of the screen
    (positioning thing so that its layout box's lower left corner nests directly into that corner of the screen).
    The depth can be specified by the option want_depth (between 0.0 and 1.0), which by default is 0.01 (very near the front).
    [###UNTESTED: it may be that non-default depths have never been tested.]

    The "corner" can be any corner, or any edge, or the center of the screen;
    it can be specified as a 2nd argument (x,y), or by the option corner = (x,y),
    where x can be -1, 0, or 1 (for left, center, or right respectively)
    and y can be -1, 0, or 1 (for bottom, center, or top).

    For the corners, the abbreviations defined in prefs_constants (small ints called UPPER_RIGHT, UPPER_LEFT,
    LOWER_LEFT, LOWER_RIGHT) are also permitted (and for convenience can also be imported from this class's source file).

    When thing does not need to touch a screen boundary (in one or both dimensions),
    it is not shifted at all, meaning its local origin is aligned with the specified position in that dimension.
    For drawing in an edge or the center, consider wrapping thing in Center or the like to modify this.
    (Without this feature, DrawInCorner( thing, (0,0)) would be equivalent to DrawInCorner( Center(thing), (0,0)).)

    ###BUG: The current implem (as of 070210) probably doesn't work properly after coordinate changes inside display lists.
    """
    ##e should we reverse the arg order? [recent suggestion as of 070302]
    delegate = Arg(Widget2D)
    corner = ArgOrOption(
        int,
        LOWER_RIGHT,
        doc=
        "the corner/edge/center to draw in, as named int or 2-tuple; see class docstring for details"
    )
    # note: semi-misnamed option, since it can also be an edge or the center
    ###KLUGE: type spec of 'int' is wrong -- we also allow it to be a pair of ints for x,y "symbolic posn" respectively
    want_depth = Option(
        float, 0.01
    )  # this choice is nearer than cov_depth (I think!) but doesn't preclude 3D effects (I hope).

    def draw(self):
        if self.delegate is None:
            # 070210 -- but I'm not sure if this is a complete ###KLUGE, or good but ought to be done more generally,
            # or if it would be better to do it totally differently by instantiating None into something like Spacer(). ##k
            return

        glMatrixMode(GL_MODELVIEW)  # not needed
        glPushMatrix()
        glLoadIdentity()
        try:
            glpane = self.env.glpane
            aspect = glpane.aspect  # revised by bruce 070919
            corner = self.corner
            delegate = self.delegate
            want_depth = self.want_depth
            # note about cov_depth:
            ## self.near = 0.25
            ## self.far = 12.0
            # so I predict cov_depth is 0.75 / 11.75 == 0.063829787234042548
            # but let's print it from a place that computes it, and see.
            # ... hmm, only place under that name is in selectMode.py, and that prints 0.765957458814 -- I bet it's something
            # different, but not sure. ###k (doesn't matter for now)

            # modified from _setup_modelview:

            saveplace = self.transient_state  # see if this fixes the bug 061211 1117a mentioned below -- it does, use it.
            # BUG (probably not related to this code, but not known for sure):
            # mousewheel zoom makes checkboxes inside DrawInCorner
            # either not highlight, or if they are close enough to
            # the center of the screen (I guess), highlight in the
            # wrong size and place. For more info and a partial theory,
            # see the 081202 update in DisplayListChunk.py docstring.
            # Before I realized the connection to DisplayListChunk,
            # I tried using saveplace = self.per_frame_state instead of
            # self.transient_state above, but that had no effect on the bug.
            # That was before I fixed some bugs in same_vals related to
            # numpy.ndarray, when I was plagued with mysterious behavior
            # from those in my debug code (since solved), but I tried it
            # again afterwards and it still doesn't fix this bug, which
            # makes sense if my partial theory about it is true.
            #
            # In principle (unrelated to this bug), I'm dubious we're
            # storing this state in the right place, but I won't change
            # it for now (see related older comments below).
            #
            # Note: I fixed the bug in another way, by changing
            # Highlightable's projection option to default True.
            # [bruce 081202]

            if glpane.current_glselect or (0 and 'KLUGE' and hasattr(
                    saveplace, '_saved_stuff')):
                # kluge did make it faster; still slow, and confounded by the highlighting-delay bug;
                # now I fixed that bug, and now it seems only normally slow for this module -- ok for now.

                # when that cond is false, we have a nonstandard projection matrix
                # (see also the related comments in save_coords_if_safe in Highlightable.py)
                # [bruce 081204 comment]

                x1, y1, z1 = saveplace._saved_stuff  # this is needed to make highlighting work!
                ###BUG [061211 1117a; fixed now, see above, using saveplace = self.transient_state]:
                # if we click on an object in testexpr_15d (with DrawInCorner used for other objs in the testbed)
                # before it has a chance to show its highlighted form, at least after a recent reload, we get an attrerror here.
                # Easy to repeat in the test conditions mentioned (on g5). Not sure how it can affect a different obj (self)
                # than the one clicked on too quickly. Best fix would be to let glpane give us the requested info,
                # which is usually the same for all callers anyway, and the same across reloads (just not across resizes).
                # But it does depend on want_depth, and (via gluUnProject) on the current modelview coords
                # (and projection coords if we ever changed them). So it's not completely clear how to combine ease, efficiency,
                # and safety, for that optim in general, even w/o needing this bugfix.
                #    But the bug is easy to hit, so needs a soon fix... maybe memoize it with a key corresponding to your own
                # assumed choice of modelview coords and want_depth? Or maybe enough to put it into the transient_state? TRY THAT. works.
                if _DEBUG_SAVED_STUFF:
                    print "_DEBUG_SAVED_STUFF: retrieved", x1, y1, z1
            else:
                x1, y1, z1 = saveplace._saved_stuff = \
                             gluUnProject(glpane.width, glpane.height, want_depth)
                # max x and y, i.e. right top
                # (note, to get the min x and y we'd want to pass (0, 0, want_depth),
                #  since these are windows coords -- (0, 0) is bottom left corner (not center))
                #
                # Note: Using gluUnProject is probably better than knowing and reversing _setup_projection,
                # since it doesn't depend on knowing the setup code, except meaning of glpane height & width attrs,
                # and knowing that origin is centered between them and 0.
                if _DEBUG_SAVED_STUFF:
                    print "_DEBUG_SAVED_STUFF: saved", x1, y1, z1


##            print x1,y1,z1
# use glScale to compensate for zoom * scale in _setup_projection,
# for error in PIXELS, and for want_depth != cov_depth
            x1wish = glpane.width / 2.0 * PIXELS  # / 2.0 because in these coords, screen center indeed has x == y == 0
            r = x1 / x1wish
            glScale(r, r, r)
            ##            x1 /= r
            ##            y1 /= r
            z1 /= r
            # now the following might work except for z, so fix z here
            glTranslatef(0.0, 0.0, z1)
            del x1, y1  # not presently used
            if _DEBUG_SAVED_STUFF:
                print "_DEBUG_SAVED_STUFF:     r = %r, translated z by z1 == %r" % (
                    r, z1)

            # I don't think we need to usage-track glpane height & width (or scale or zoomFactor etc)
            # since we'll redraw when those change, and redo this calc every time we draw.
            # The only exception would be if we're rendering into a display list.
            # I don't know if this code (gluUnProject) would even work in that case.
            # [I think I wrote a similar comment in some other file earlier today. #k]

            # move to desired corner, and align it with same corner of lbox
            # (#e could use an alignment prim for the corner if we had one)

            if corner in corner_abbrevs:
                # normalize how corner is expressed, into a 2-tuple of +-1's
                corner = corner_abbrevs[corner]

            x, y = corner

            if x == -1:  # left
                x_offset = -glpane.width / 2.0 * PIXELS + delegate.bleft
            elif x == +1:  # right
                x_offset = +glpane.width / 2.0 * PIXELS - delegate.bright
            elif x == 0:  # center(x)
                x_offset = 0
                # note: before 070210 this was (+ delegate.bleft - delegate.bright) / 2.0,
                # which has an unwanted (since unavoidable) centering effect; use explicit Center if desired.
            else:
                print "invalid corner", corner  ###
                raise ValueError, "invalid corner %r" % (corner, )

            if y == -1:  # bottom
                y_offset = -glpane.height / 2.0 * PIXELS + delegate.bbottom
            elif y == +1:  # top
                y_offset = +glpane.height / 2.0 * PIXELS - delegate.btop
            elif y == 0:  # center(y)
                y_offset = 0
                # note: # note: before 070210 this was (+ delegate.bbottom - delegate.btop) / 2.0
            else:
                print "invalid corner", corner  ###
                raise ValueError, "invalid corner %r" % (corner, )

            offset = (x_offset, y_offset)
            glTranslatef(offset[0], offset[1], 0.0)

            if _DEBUG_SAVED_STUFF:
                print "_DEBUG_SAVED_STUFF:     offset =", offset

            self.drawkid(delegate)  ## delegate.draw()

        finally:
            glMatrixMode(GL_MODELVIEW)  # not needed
            glPopMatrix()

        return

    pass  # end of class DrawInCorner
Beispiel #22
0
class TextRect(Widget2D):
    """TextRect(msg, nlines, ncols) renders as a rect of ncols by nlines chars,
    taken from str(msg) (typically a formula in _self or _this(something)),
    origin on bottomleft.
       Arg nlines defaults to lines in msg, limited by option max_lines, default 6;
    ncols defaults to cols in msg, limited by option max_cols, default 45.
    #doc textsize issues, lbox issues, arg order reason (some caller comment explains it, i think, maybe in test.py).
    """
    from graphics.drawing.texture_fonts import tex_width, tex_height  # constants (#e shouldn't be; see comments where they're defined)
    # args
    msg = Arg(str)
    nlines = Arg(int,
                 min_Expr(_self.msg_lines,
                          _self.max_lines))  # related to height, but in chars
    ###e try default of _self.msg_lines, etc -- trying this 061116
    ## ncols = Arg(int, 16) # related to width, but in chars
    ncols = Arg(int,
                min_Expr(_self.msg_cols,
                         _self.max_cols))  # related to width, but in chars
    # options
    max_lines = Option(int, 6)
    max_cols = Option(
        int, 45)  # 16 -> 45 (guess), 061211, was never used before btw
    margin = Option(int,
                    2)  # in pixels -- should this be said in the type? ###k
    # formulae for arg defaults, from other args and options (take care to not make them circular!) [061116]
    msg_lines = call_Expr(call_Expr(msg.rstrip).count, '\n') + 1
    # i.e. msg.rstrip().count('\n') + 1, but x.y(z) syntax-combo is not allowed, as a safety feature --
    # we'll need to find a way to sometimes allow it, I think.
    msg_cols = call_Expr(lambda msg: max(map(len, msg.split('\n'))),
                         msg)  # finally implemented 061211
    # formulae
    ###e msg_lines, msg_cols, and make sure those can be used in the default formulae for the args
    # lbox attrs -- in the wrong units, not pixelwidth, so we need to kluge them for now
    margin1 = margin * PIXELS  # bugs: option might want to know type PIXELS, and it all shows up on the right, none on the left
    bright = ncols * PIXELS * tex_width + 2 * margin1
    btop = nlines * PIXELS * tex_height + 2 * margin1

    def draw(self):
        assert self._e_is_instance, "draw called on non-Instance of TextRect"  #061211
        glpane = self.env.glpane
        msg = str(
            self.msg
        )  #k str() won't always be needed, maybe isn't now ##e guess: need __mod__ in Expr
        width = self.ncols  # in chars
        # WARNING: every Widget2D has self.width in native Width units; don't assign ncols to self.width or you'll mess that up.
        #e probably we should rename this localvar to ncols, same for height/nlines.
        height = self.nlines  # in chars

        if 1:
            # until we have Arg type coercion, see if this detects caller errors -- it does:
            ## ValueError: invalid literal for int(): line 1
            ## line 2
            width = int(width)
            height = int(height)

        from graphics.drawing.texture_fonts import drawfont2
        glPushMatrix()  ####k guess, not sure needed
        #e translate by margin
        drawfont2(glpane, msg, width, height, pixelwidth=PIXELS)
        #061211 pass pixelwidth so rotation won't matter (warning: different value than before, even w/o rotation)
        glPopMatrix()
        return

    pass  # TextRect
Beispiel #23
0
class _cmd_MakeRect_BG(Highlightable):
    """Background event bindings for dragging out new Rects.
    (Not involved with selecting/moving/editing/resizing rects after they're made,
     even if that happens immediately to a rect this code makes from a single drag.)
    """
    # args [needed for sharing state with something]
    world = Option(World)
    #e something to share state with a control panel in the PM - unless we just use prefs for that

    # The drag event handlers modify the following state and/or derived objects (some documented with their defs):
    # self.rubber_rect = None or a newly made Rect we're dragging out right now
    #  (implemented as a rubberband-object drawable -- NOT a model object -- since it has formulae to our state, not its own state)
    #
    # nim: self.newnode = None or a Model Object version of the Rect we're dragging out now, or last dragged out...
    #  in general this may be redundant with "the selection", if dragging out a rect selects it, or shift-dragging it adds it to sel...
    # note, which of these are change-tracked? the ones used in drawing. that means all of them i guess.

    startpoint = State(Point,
                       None,
                       doc="mousedown position; one corner of the rect"
                       )  #e (or its center, for variants of this cmd)
    curpoint = State(
        Point,
        None,
        doc="last mouse position used for the other corner of the rect")
    making_a_rect_now = State(
        bool,
        False,
        doc="whether we're making a rect right now, using the current drag")

    # formulae
    whj = curpoint - startpoint  # contains dims of current rect; only valid while we're making one, or after it's done before next press
    w = whj[
        0]  ###k what if negative?? evidently we make a neg-width Rect and nothing complains and it draws properly... ###REVIEW
    h = whj[1]  # ditto
    color = purple  # for now -- will be a pref or PM control for the color to use for new rects
    rubber_rect = Instance(
        If(making_a_rect_now, Translate(Rect(w, h, color), startpoint)))
    # this Instance is needed, at least by Highlightable
    # appearance -- note, for superclass Highlightable, this is plain, not delegate, and must be an Instance.
    # This means it's not good to subclass Highlightable rather than delegating to it. (Especially in example code!) ###FIX
    ## delegate = _self.rubber_rect
    plain = _self.rubber_rect

    # code for event handlers during the drag.
    def on_press(self):
        self.startpoint = self.current_event_mousepoint()
        self.making_a_rect_now = False  #k probably not needed
        # don't make until we drag!
        self.curpoint = None  #k probably not needed, but might help to catch bugs where whj is used when it shouldn't be
        #e change cursor, someday; sooner, change state to name the desired cursor, and display that cursorname in the PM
        return

    def on_drag(self):
        self.curpoint = self.current_event_mousepoint(plane=self.startpoint)
        self.making_a_rect_now = True
        return

    def on_release(self):
        ##e decide whether to really make one... here, assume we do, as long as we started one:
        if self.making_a_rect_now:
            node_expr = DraggableObject(
                StatefulRect(self.rubber_rect)
            )  #e StatefulMovableRect? StatefulSnapshot(self.rubber_rect)??
            ###k can we just pass the rubber rect and assume StatefulRect can grab its state from it by taking a snapshot??
            ###WRONG for DraggableObject to be here, I think -- it's how we display it -- not sure, at least movability might be here...
            ### the posn is in the rubber_rect since it has Translate... this seems potentially bad/klugy tho...
            # older cmts, related: ... also its state needs to include abs posn...
            # maybe this means, split DraggableObject into event binding part (for this UI) and Movable part (for abs posn state). #k
            self.newnode = self.world.make_and_add(node_expr, type="Rect")
            ###BUG (understood): type = "Rect" is not affecting sbar_text in DraggableObject. Need to add it in StatefulRect itself.
            self.newnode.motion = self.startpoint  ###KLUGE
            self.making_a_rect_now = False  # hide the rubber rect now that the real one is there
        else:
            print "fyi: click with no drag did nothing"  ##e remove after debug
        return

    pass  # end of class _cmd_MakeRect_BG
Beispiel #24
0
class DragBehavior_AlongLine(DragBehavior
                             ):  #070318 (compare to SimpleDragBehavior)
    """
    A drag behavior which moves the original hitpoint along a line,
    storing only its 1d-position-offset along the line's direction
    [noting that the hitpoint is not necessarily equal to the moved object's origin]
    [#doc better]
    """
    # args [#e replace 'height' with 'posn_parameter' or 'posn_param' in these comments & docstrings]
    highlightable = Arg(
        Anything
    )  ###e is there a way we can require that we're passed an Instance rather than making it ourselves?
    # I suspect that violating that caused the bug in example 2.
    # (A way that would work: a specialcase check in _init_instance.
    #  But I'd rather have an option on Arg to do that. require_already_instance?? Or ArgInstance? that latter invites confusion
    #  since it's not analogous to ArgExpr.)
    posn_parameter_ref = Arg(StateRef,
                             doc="where the variable height is stored")
    constrain_to_line = Arg(
        Ray,
        doc="the line/ray on which the height is interpreted as a position")
    ###e rename: constraint_line? line_to_constrain_to? constrain_to_this_line?
    # note: the position of the height on this line is typically used as the position of the drawn movable object's origin;
    # we shouldn't assume the drag startpoint is on the line, since the drawn movable object might be touched at any of its points.
    ##e rename Ray -> MarkedLine? (a line, with real number markings on it) ParametricLine? (no, suggests they can be distorted/curved)
    range = Option(tuple_Expr, None, doc="range limit of height")  #nim
    ##e drag event object can be passed to us... as a delegate! [In recent code, it seems like the Highlightable is taking this role.]

    # (if this delegates or supers to something that knows all about the drag in a lower level way,
    #  then maybe it's not so bad to be getting dragevent info from self rather than a dragevent arg... hmm.
    #  An argument in favor of that: self is storing state related to one drag anyway, so nothing is lost
    #  by assuming some other level of self stores some other level of that state. So maybe we create a standard
    #  DragHandling object, then create a behavior-specific delegate to it, to which *it* delegates on_press etc;
    #  and we also get it to delegate state-modifying calls to some external object -- or maybe that's not needed
    #  if things like this one's stateref are enough. ##k)
    #
    # related: Maybe DraggableObject gets split into the MovableObject and the DragBehavior...

    # same as in SimpleDragBehavior: saved_coordsys & current_event_mousepoint

    # state:
    saved_coordsys = Instance(
        SavedCoordsys()
    )  # provides transient state for saving a fixed coordsys to use throughout a drag

    # helper methods (these probably belong in a superclass):
    def current_event_mousepoint(
        self, *args, **kws
    ):  #e zap this and inline it, for clarity? or move it into DragBehavior superclass??
        return self.saved_coordsys.current_event_mousepoint(*args, **kws)

    def current_event_mouseray(self):
        p0 = self.current_event_mousepoint(depth=0.0)  # nearest depth ###k
        p1 = self.current_event_mousepoint(depth=1.0)  # farthest depth ###k
        return Ray(
            p0, p1 - p0
        )  #e passing just p1 should be ok too, but both forms can't work unless p0,p1 are typed objects...

    # specific methods
    def _C__translation(
        self
    ):  ### WARNING: used externally too -- rename to be not private if we decide that's ok ###e
        """
        compute self._translation from the externally stored height
        """
        k = self.posn_parameter_ref.value
        return self.constrain_to_line.posn_from_params(
            k)  #e review renaming, since we are asking it for a 3-tuple

    def on_press(self):
        self.saved_coordsys.copy_from(
            self.highlightable
        )  # needed before using current_event_mousepoint or current_event_mouseray
        # (since self.highlightable's coordsys changes during the drag)
        self.startpoint = self.current_event_mousepoint(
        )  # the touched point on the visible object (hitpoint)
        self.offset = self.startpoint - (
            ORIGIN + self._translation
        )  #k maybe ok for now, but check whether sensible in long run
        self.line = self.constrain_to_line + self.offset  # the line the hitpoint is on (and constrained to, if handle is rigid)
        # (this is parallel to self.constrain_to_line and intersects the hitpoint)
    def on_drag(self):
        # Note: we can assume this is a "real drag" (not one which is too short to count), due to how we are called.
        mouseray = self.current_event_mouseray()
        k = self.line.closest_pt_params_to_ray(mouseray)
        #
        # BUG: for lines with a lot of Z direction, in perspective view,
        # this is wrong -- we want the closest point on the screen,
        # not in space. The current code (closest point in space)
        # would tend to come up with a point too near the screen,
        # if the constraint line and the mouseray are diverging away
        # from each other (in model space) with depth.
        #
        # TODO: fix this. Possible fixes include:
        # - common code, using a projection matrix & its inverse (seems hard)
        # - separate code for Ortho vs Perspective case (needs eyepoint)
        #   (might be clearest & simplest; it might turn out we'd want other
        #    behavior tweaks which differed in these cases -- for example,
        #    stop dragging and turn the handle red when it gets so far away
        #    in model space (near the "point at infinity" visible on the screen
        #    for an infinite line -- aka the "point of convergence" for a family
        #    of parallel lines) that its motion would be too sensitive, since
        #    the constraint line is too close to perpendicular to the screen)
        # - save the initial mouseray too, since it gives us enough info
        #   (along with current one) to know the entire projection
        #   (except when they coincide, in which case no motion is needed).
        #   Alg for this is not yet worked out. Q: is an ortho projection
        #   along initial mouseray a good approximation? Probably not,
        #   if we approach the "point at infinity".
        #
        # Also we need a refactoring, so that one external object can store
        # both the geometric info about the constraint, and the state of the
        # dragpoint along it, accessible as parameter-along-line or point or both,
        # with either being the underlying state. (Unless nothing less than a
        # DragBehavior can actually do all those things, in which case,
        # we need variants depending on whether the point or the parameter
        # is the underlying state. But more likely, we want a DragState which
        # knows how to make the underlying state convenient, and a DragBehavior
        # which knows how to connect that to mouse gestures, so these can vary
        # separately.)
        #
        # [bruce 070913 comment]
        #
        if k is not None:
            # store k, after range-limiting
            range = self.range  # don't use the python builtin of the same name,
            #in this method! (#e or rename the option?)
            if range is not None:
                low, high = range
                if low is not None and k < low:
                    k = low
                if high is not None and k > high:
                    k = high
            self.posn_parameter_ref.value = k
            ##e by analogy with DraggableObject, should we perhaps save this
            ##side effect until the end?
        return

    def on_release(self):
        pass

    pass  # end of class DragBehavior_AlongLine
Beispiel #25
0
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
Beispiel #26
0
class DraggablyBoxed(
        Boxed
):  # 070316; works 070317 [testexpr_36] before ww,hh State or resizable, and again (_36b) after them
    # inherit args, options, formulae from Boxed
    thing = _self.thing  ###k WONT WORK unless we kluge ExprsMeta to remove this assignment from the namespace -- which we did.
    ###e not sure this is best syntax though. attr = _super.attr implies it'd work inside larger formulae, but it can't;
    # attr = Boxed.attr might be ok, whether it can work is not reviewed; it too might imply what _super does, falsely I think.
    extra1 = _self.extra1
    borderthickness = _self.borderthickness
    rectframe = _self.rectframe  # a pure expr
    # new options
    resizable = Option(bool,
                       False,
                       doc="whether to make it resizable at lower right")
    # works 070317 10pm (testexpr_36b) except for a few ###BUGS [updated info 070318 7pm]:
    # + [fixed] the wrong corner resizes (top right) (logic bug)
    # + [fixed] resizer doesn't move (understood -- wrong expr for its posn; commented below)
    # - negative sizes allowed (missing feature - limit the drag - need new DragBehavior feature)
    # - no clipping to interior of rectframe (missing feature - draw something clipped)
    # - perspective view ought to work, but entirely ###UNTESTED.
    # also, cosmetic bugs:
    # - resizer doesn't follow mouse in rotated coordsys, even in ortho view (though it's still useable).
    #   (This is not surprising -- we're using the wrong kind of DragBehavior as a simple kluge.)
    # - the resizer is ugly, in shape & color.
    clipped = Option(
        bool, False,
        doc="###doc")  #070322 new feature ### make True default after testing?
    # state
    # WARNING: due to ipath persistence, if you revise dflt_expr you apparently need to restart ne1 to see the change.
    ##    ww = State(Width, thing.width  + 2 * extra1) # replaces non-state formula in superclass -- seems to work
    ##    hh = State(Width, thing.height + 2 * extra1)
    ##        # now we just need a way to get a stateref to, effectively, the 3-tuple (ww,hh,set-value-discarder) ... instead, use whj:
    whj = State(Vector,
                V_expr(thing.width + 2 * extra1, -thing.height - 2 * extra1,
                       0))  #e not sure this is sound in rotated coordsys
    translation = State(Vector, ORIGIN)
    # override super formulae
    ww = whj[0]  # seems to work
    hh = neg_Expr(
        whj[1]
    )  # negative is needed since drag down (negative Y direction) needs to increase height
    # (guess: neg_Expr wouldn't be needed if we used an appropriate new DragBehavior in resizer,
    #  rather than our current klugy use of SimpleDragBehavior)
    # appearance
    rectframe_h = Instance(
        Highlightable(
            ## rectframe(bordercolor=green),####### cust is just to see if it works -- it doesn't, i guess i sort of know why
            ##bug: __call__ of <getattr_Expr#8243: (S._self, <constant_Expr#8242: 'rectframe'>)> with: () {'bordercolor': (0.0, 1.0, 0.0)}
            ##AssertionError: getattr exprs are not callable
            TopLeft(rectframe),
            #e different colored hover-highlighted version?? for now, just use sbar_text to know you're there.
            sbar_text=
            "draggable box frame",  # this disappears on press -- is that intended? ###k
            behavior=SimpleDragBehavior(
                # arg1: the highlightable
                _self.rectframe_h,
                # arg2: a write-capable reference to _self.translation
                ## fails - evalled at compile time, not an expr: LvalueFromObjAndAttr( _self, 'translation'),
                ###BUG: why didn't anything complain when that bug caused the state value to be an add_Expr, not a number-array?
                call_Expr(LvalueFromObjAndAttr, _self, 'translation'),
                #e alternate forms for this that we might want to make work:
                #  - getattr_StateRef(_self, 'translation') # simple def of the above
                #  - StateRef_for( _self.translation ) # turns any lvalue into a stateref! Name is not good enough, though.
            )))
    resizer = Instance(
        Highlightable(
            Center(Rect(extra1, extra1)),  #e also try BottomRight
            highlighted=Center(Rect(extra1, extra1, white)),
            pressed=_my.highlighted,
            sbar_text="resize the box frame",
            behavior=SimpleDragBehavior(
                _self.resizer, call_Expr(LvalueFromObjAndAttr, _self, 'whj'))))
    ###BUG: in Boxed, rectframe comes first, so lbox attrs are delegated to it. We should do that too --
    # but right now we draw it later in order to obscure the thing if they overlap. With clipping we won't need that --
    # but without clipping we will. If the latter still matters, we need a version of Overlay with delegation != drawing order,
    # or, to delegate appearance and layout to different instances ourselves. (Or just to define new formulae for lbox -- easiest.) #e
    drawme = Instance(
        Overlay(
            If(
                clipped,
                Clipped(
                    thing,
                    planes=[
                        call_Expr(clip_to_right_of_x0, -thing.bleft - extra1 +
                                  ww - borderthickness),
                        # note: the (- borderthickness) term makes the clipping reach exactly
                        # to the inner rectframe edge. Without it, clipping would reach to the outer edge,
                        # which for 3d objects inside the frame can cause them to obscure it.
                        # (Other interesting values are (- extra1) and (- borderthickness/2.0),
                        #  but they both look worse, IMHO.)
                        call_Expr(clip_below_y0,
                                  thing.btop + extra1 - hh + borderthickness)
                    ]),
                thing,
            ),
            Translate(rectframe_h,
                      V_expr(-thing.bleft - extra1, thing.btop + extra1)),
            If(
                resizable,
                ## Translate( resizer, V_expr( thing.bright + extra1, - thing.bbottom - extra1))
                ###WRONG - this posn is fixed by thing's lbox dims, not affected by ww, hh;
                # will the fix be clearer if we use a TopLeft alignment expr?
                # It'd be hard to use it while maintaining thing's origin for use by external alignment --
                # but maybe there's no point in doing that.
                Translate(
                    resizer,
                    V_expr(-thing.bleft - extra1 + ww,
                           thing.btop + extra1 - hh)))))
    _value = Translate(
        drawme,  ## DisplayListChunk( drawme), ###k this DisplayListChunk might break the Highlightable in rectframe_h #####
        translation)
    pass  # end of class DraggablyBoxed
class DraggableHandle_AlongCircle(DelegatingInstanceOrExpr): 
    """
    A kind of draggable handle which can be dragged along a line
    to change the value of a single floating point parameter
    (representing position along the line, using a scale and origin
    determined by our arguments).

    ### TODO: add epydoc parameters to this docstring? not sure that will work for a class!
    Maybe we need to synthesize those (from the doc options below) into a fake __init__ method for its sake??
    """
    # == args and options
    
    # options for appearance of handle
    appearance = Option( Drawable,
                         Center(Rect(0.2, 0.2, white)),
                         doc = "appearance, when not highlighted") 
    
    appearance_highlighted = Option( Drawable,
                                     appearance,
                                     doc = "appearance, when highlighted")
    
    sbar_text = Option(str, 
                       "draggable handle along circle", 
                       doc = "statusbar text on mouseover")

    # state variable controlled by dragging
    rotationDistanceRef = Option(StateRef, 
                                doc = "stateref to a dragged disctance variable") 

    
    # action options, for Highlightable to do after the ones that come from
    # DragBehavior_AlongLine [new feature of this and Highlightable, 080129]
    on_press = Option(Action)
    on_drag = Option(Action)
    on_release = Option(Action)
    on_release_in = Option(Action, on_release)
    on_release_out = Option(Action, on_release)
    on_doubleclick = Option(Action)
    
    #origin of the handle itself
    origin = Option( Point, ORIGIN)
    
    # Center of the circle whose perimeter serves as a path along which to 
    # drag 
    center = Option( Point, ORIGIN)
    
    #Axis of the circle. 
    axis  = Option( Vector, DX, doc = "vector giving direction and scale")
    
    #radius Vector
    radiusVector = Option( Vector, 
                           DX, 
                           doc = "vector giving direction and scale")
    
    #angle range for the rotation 
    range_for_rotation = Option(tuple_Expr, 
                                None, 
                                doc = "range limit of angle (tuple)")
    

    # == internal instances and formulae (modified from test_statearray_3.py)
    
    _drag_handler = Instance( 
        DragBehavior_AlongCircle(
            _self._delegate,
            rotationDistanceRef,
            origin,
            center,
            axis,
            radiusVector,
            range_for_rotation = range_for_rotation))
    
    
    #QUESTION: Should the 'RotateTranslate' transform from exprs.transforms be 
    #used here? -- Ninad 2008-02-13
    delegate = \
        Highlightable(
            Translate(
                appearance,
                _drag_handler._rotation #k ok?? only if that thing hangs around even in between drags, i guess!
                    #e #k not sure if this code-commoning is good, but it's tempting. hmm.
             ),
            highlighted = Translate(
                appearance_highlighted,
                _drag_handler._rotation
             ),
            sbar_text = sbar_text,
            behavior = _drag_handler,
            on_press = _self.on_press,
            on_drag = _self.on_drag,
            # (note: no need to pass on_release)
            on_release_in = _self.on_release_in,
            on_release_out = _self.on_release_out,
            on_doubleclick = _self.on_doubleclick,
         )
    
 
    pass # end of class
Beispiel #28
0
class DraggableHandle_AlongLine(DelegatingInstanceOrExpr): ### TODO: all options might need renaming! replace "height" everywhere.
    """
    A kind of draggable handle which can be dragged along a line
    to change the value of a single floating point parameter
    (representing position along the line, using a scale and origin
    determined by our arguments).

    ### TODO: add epydoc parameters to this docstring? not sure that will work for a class!
    Maybe we need to synthesize those (from the doc options below) into a fake __init__ method for its sake??
    """
    # == args and options
    
    # options for appearance of handle
    appearance = Option( Drawable,
                         Center(Rect(0.2, 0.2, white)),
                         doc = "our appearance, when not highlighted") ###e take other args of Highlightable?
    appearance_highlighted = Option( Drawable,
                                     appearance,
                                     doc = "our appearance, when highlighted")
    sbar_text = Option(str, "draggable handle", doc = "our statusbar text on mouseover")

    # state variable controlled by dragging
    height_ref = Option(StateRef, doc = "stateref to a height variable") # TODO: rename, redoc

    range = Option(tuple_Expr, None, doc = "range limit of height (tuple)")
        ### MAYBE: RENAME to avoid conflict with python range in this code
        ### TODO: take values from the stateref if this option is not provided

    # action options, for Highlightable to do after the ones that come from
    # DragBehavior_AlongLine [new feature of this and Highlightable, 080129]
    on_press = Option(Action)
    on_drag = Option(Action)
    on_release = Option(Action)
    on_release_in = Option(Action, on_release)
    on_release_out = Option(Action, on_release)
    on_doubleclick = Option(Action)
        
    # line along which to drag it, and how to interpret the state as a position along that line (origin and scale)
    origin = Option( Point, ORIGIN)
    direction = Option( Vector, DX, doc = "vector giving direction and scale")
        # providing a default value is mainly just for testing
        
    #If this is false, the 'highlightable' object i.e. this handle 
    #won't be drawn. The delegate (that defines a Highlightable) 
    #We define an If_expr to check whether to draw the highlightable object.
    #[by Ninad]
    # [this should probably be revised, with hasValidParamsForDrawing replaced
    #  with an overridable compute method, for robustness -- current implem
    #  is suspected of causing tracebacks from insufficient update of this
    #  state. Need to review whether methodname needs to be hasValidParamsForDrawing
    #  to conform with any API. -- bruce 080409 comment]
    should_draw = State(bool, True) 

    # == internal instances and formulae (modified from test_statearray_3.py)
    
    _drag_handler = Instance( DragBehavior_AlongLine(
        _self._delegate,
        height_ref,
        call_Expr(Ray, origin, direction),
            # note: we need call_Expr since Ray is an ordinary class, not an expr! ###FIX
        range = range
     ))
    # note: _drag_handler is also being used to compute the translation from the height, even between drags.
    #@see: DnaStrand_ResizeHandle.hasValidParamsForDrawing
    #@see: definition of State attr should_draw
    delegate = \
        If_expr(
            _self.should_draw,
            Highlightable(
                Translate(
                    appearance,
                    _drag_handler._translation #k ok?? only if that thing hangs around even in between drags, i guess!
                        #e #k not sure if this code-commoning is good, but it's tempting. hmm.
                 ),
                highlighted = Translate(
                    appearance_highlighted,
                    _drag_handler._translation
                 ),
                sbar_text = sbar_text,
                behavior = _drag_handler,
                on_press = _self.on_press,
                on_drag = _self.on_drag,
                # (note: no need to pass on_release)
                on_release_in = _self.on_release_in,
                on_release_out = _self.on_release_out,
                on_doubleclick = _self.on_doubleclick            
            ) #end of Highlightable
        ) #end of If_expr
    
    def hasValidParamsForDrawing(self):
        """
        Overridden in subclasses. Default implementation returns True
        if this object (the highlightable) can be drawn without any known
        issues
        @see: DnaStrand_ResizeHandle.hasValidParamsForDrawing for more notes.
        """
        self.should_draw = True
        return self.should_draw
 
    pass # end of class
Beispiel #29
0
class ActionButton(DelegatingInstanceOrExpr):  # 070104 quick prototype
    "ActionButton(command, text) is something the user can press to run command, which looks like a button."
    # args/options
    command = Arg(Action)  #e default which prints?
    text = Arg(str, "<do it>"
               )  #e default text should be extracted from the command somehow
    button = Arg(
        Widget2D, Rect(15. * PIXELS)
    )  # can it be left out so only text label is used? ideally we'd have text with special border...
    enabled = Option(
        bool, True
    )  # whether the button should look enabled, and whether the command will run when the button is operated
    # formulae
    use_label = TextRect(text)  ## TextRect(text,1,20)###e revise
    plain_button = CenterY(button)
    highlighted_button = Boxed(
        plain_button,  # note: despite the name, this is only shown as the highlighted form when enabled is true
        bordercolor=
        blue,  # should color adapt to bg? is it a bad idea to put this over bg rather than over button?
        borderthickness=1.5 * PIXELS,
        gap=1 * PIXELS,
    )  ###k ????   -- note, this doesn't include the label -- ok?
    plain = DisplayListChunk(SimpleRow(
        plain_button, CenterY(use_label)))  # align = CenterY is nim
    highlighted = DisplayListChunk(
        SimpleRow(highlighted_button, CenterY(use_label), pixelgap=0.5))
    #k ok to wrap with DisplayListChunk? [seems so]
    ### KLUGE: without the pixelgap adjustment (to this particular weird-looking value, i guess),
    # the label moves to the right when highlighted, due to the Boxed being used to position it in the row.
    ### BUG: CenterY is not perfectly working. Guess -- lbox for TextRect is slightly wrong.
    ### IDEA: make the borderthickness for Boxed negative so the border is over the edge of the plain button. Might look better.
    ##e Note: we have no "pressed" appearance, since by the next time we get drawn, the command is already drawn and we ought to be
    # back to normal. Someday we should do a transient incremental redraw of just this button, with a "pressed and acting" appearance,
    # which can then go back to normal when the operation completes and does a regular full redraw.
    # Alternatively, we could switch to using buttons with an on_release_in action only,
    # and then have ordinarily-drawn pressed etc looks. [070227 comment]
    # update 070305: let's try to fix that:

    # appearances for optional willdoit-flicker (confirms unambiguously that the button was hit and will do something) [070307]
    # [ideally the computation & side effects could overlap the willdoit flicker in time,
    #  but they don't now, which is one reason the flicker is optional]
    incr_drawable_willdo1 = Instance(
        SimpleRow(highlighted_button(bordercolor=yellow),
                  pixelgap=0.5))  # label not needed here
    incr_drawable_willdo2 = Instance(
        SimpleRow(highlighted_button(bordercolor=blue), pixelgap=0.5))
    # note: yellow/blue (matching the usual ending & starting colors which bracket the flicker) looks much better than black/white

    # what it looks like while we're computing/doing its effects:
    incr_drawable_doing = Instance(
        SimpleRow(
            highlighted_button(bordercolor=orange),
            ## CenterY(use_label), [removed -- see comment for why -- might be added back]
            pixelgap=0.5))
    # orange warns you that it's not yet done, is also bright & active for action;
    ### UI FLAW: the orange/yellow distinction is annoying, so it's really only desirable for debugging,
    # since it shows that the instantiation time is significant but only happens on the first use of a button.
    # Probably the distinction (and its redraw happening at all) should be a debug_pref or so. ###FIX
    # (But if there is no distinction, we may want to be sure to redraw the label now if there is any chance it can be different --
    #  but in current code there's not, since we haven't changed state it might depend on by the time we draw it.
    #  BTW I wonder if redrawing the label (i.e. instantiating this instance of it) ever takes significant time itself?? #k)

    # what it looks like while we're redrawing (after finishing its internal effects):
    incr_drawable_done = Instance(
        SimpleRow(highlighted_button(bordercolor=yellow),
                  pixelgap=0.5))  # label not needed here

    # yellow means done -- not sure makes sense -- note green means "can do" in some controls

    def doit(self):
        """This runs when the user clicks on the button.
        WARNING: it's NOT just self.do_action() (the public method self's clients, like scripts, can call to do the same action) --
        it calls that, but it also has graphical effects.
        [It may or may not be public (in the Action interface) in the end. If it is, it'll be renamed. #e]
        """
        if self.enabled:
            # do some incremental drawing [new feature 070305, revised 070307]
            ###BUG (in some client code of this class):
            # this won't be able to make clear button quickly show it's disabled until client code is revised and maybe selobj-bugfixed ###DOIT
            if debug_pref(
                    "testmode: ActionButton willdoit-flicker?",
                    # When set, this flickers the button, like how the mac confirms a menu item choice.
                    # Conclusion after testing: it works fine, and usually looks ok,
                    # but is redundant with "yellow during redraw",
                    # so as long as that's slow enough to see, this has no point and is also making it even slower,
                    # so leave it turned off by default.
                    Choice_boolean_False,
                    prefs_key='A9 devel/exprs/action flicker'):
                # works [retest###], but I won't make it True by default [070307]
                ##e someday do this in a way that does not tie up the thread during this, e.g. by letting paintGL do it;
                # for now it's just experimental for its graphics effects and as a speed test,
                # and will probably be turned off after testing
                for i in range(4):
                    if i % 2 == 0:
                        self.draw_incrementally(self.incr_drawable_willdo1)
                    else:
                        self.draw_incrementally(self.incr_drawable_willdo2)
                    # print i, # very fast
                    # todo: delay, if needed to make this visible -- using time.time to delay only if draw timing was not long enough
                    # (with no delay it's almost too fast too see -- sometime I should write the code to measure the actual speed)
                    # (for now assume it's always very fast, so just delay a fixed amount using time.sleep)
                    time.sleep(1.0 / 3 / 4)  # 1/3 sec, spread over 4 sleeps
            self.draw_incrementally(
                self.incr_drawable_doing
            )  # this method runs in the Highlightable made in delegate
            print "ActionButton: doing %r for %r" % (self.text, self
                                                     )  ### remove self?
            ##e optim note: this shows self is a different obj each time (at least for make dna cyl button)...
            # I guess this is due to dna_ribbon_view_toolcorner_expr_maker being a function that makes an expr
            # which runs again at least on every use of the button (maybe more -- not sure exactly how often).
            # Should fix that (and it's not this file's fault -- just that the print stmt above reveals the problem).
            self.do_action()
            self.draw_incrementally(self.incr_drawable_done)
            pass
        else:
            print "ActionButton: not enabled, so not doing %r for %r" % (
                self.text, self)  # remove when works [reenabled 070307 ####]
            pass
        return

    def do_action(self):
        "#doc -- public, also used internally; see doit comment for doc, for now"
        res = self.command()
        if res is not None:
            print "unexpected: %r cmd %r retval was not None: %r" % (
                self,
                self.text,
                res,
            )  #e remove if happens legitimately
        return

    ###e refile these:
    def draw_incrementally(
            self, thing):  #070307 #e refile (as for next method below)
        "#doc"
        self._incrementally_draw_OpenGL(
            thing.draw
        )  #e or call a variant method of thing, which defaults to thing.draw?? nah, use an env var?

    def _incrementally_draw_OpenGL(
        self, func
    ):  #070307 #e rename ###e refile into IorE someday, and into Highlightable for now, i think
        """helper method for incremental drawing by user event handling methods (not part of self.draw called by paintGL).
        [#doc better]
        func should contain OpenGL commands for incrementally drawing, in self's coords (but not the swapbuffers at the end).
           Guess at a requirement within func: [which should be removed and is prob not real now, see below]
        # don't use drawkid! (because we're not inside a draw method)
        # (but will this cause trouble for draw methods inside this?? ### NEEDS REVIEW)
        [but instead we might as well make sure that drawkid's parent-seeing alg will not be messed up, since it'll be used
        inside whatever subthing draws we call, anyway]
        """

        ###e undefined in API so far: what if func says "draw later" (eg for transparency) -- do we do all that too, before we return??
        # guess: yes, but we'll need special drawing-env settings to tell primitives inside func that we're doing incremental drawing,
        # since it'll affect things like whether it's ok to write into the depth buffer for transparent objs obscuring visible ones
        # (useful for glselect code but would mess up subsequent incr drawing).
        def func1(self=self, func=func):
            res = func()
            self.env.glpane.swapBuffers()  # update display [needed]
            return res

        ran_already_flag, funcres = self.run_OpenGL_in_local_coords(func1)
        # note: this runs in self or first delegate that's a Highlightable, for now; that determines its gl state & coordsys
        assert ran_already_flag
        return funcres

    #e should we change to doing the action on_release_in, rather than on_press?
    delegate = Highlightable(
        plain,  ##e should this depend on enabled? probably yes, but probably the caller has to pass in the disabled form.
        ###e at least for the Mac, maybe it also ought to depend on whether the application is active (frontmost) and will respond to clicks.
        If(
            enabled, highlighted, plain
        ),  # revised 070109 to depend on enabled [#k does this cause the delegate expr itself to be remade??]
        on_press=_self.doit,
        # note: there was a bug in the prior form of this, "on_press = command" -- command to do should depend on enabled --
        ##e but i'm not sure if If(enabled,command,None) will work properly ###k TRY IT -- nevermind, using _self.doit now [070208]
        sbar_text=text
        #e should sbar_text depend on enabled?? yes, but need to revise callers then too -- some of which make the text depend on it
    )
    pass  # end of class ActionButton
class DnaStrand_ResizeHandle(DraggableHandle_AlongLine):
    """
    Provides a resize handle for editing the length of an existing Dna Strand.
    """

    #Handle color will be changed depending on whether the handle is grabbed
    # [bruce 080409 revised some details of this to fix bug 2747]

    handleColor = Option(Color, purple) # formula from caller
        # (in current usage, a state variable in caller)

    handleIsGrabbed = State(Boolean, False)
        # Note: this might not be needed if we passed more args to
        # Highlightable (namely, the appearance when pressed);
        # that would also be more reliable, since as it is, any failure for
        # on_release to be called would leave the handle stuck in the
        # grabbed state; client code would be wise to sometimes reset
        # this state. Also, it seems rare to ever see this selection color
        # since the handle is usually highlighted and yellow while dragging it.
        # [Depending on the highlight and selection drawing mode.  Russ 080530]
        # So this state could probably just be removed, with all uses of
        # _currentHandleColor changes to uses of handleColor.
        # [bruce 080409]

    _currentHandleColor = If_expr( handleIsGrabbed,
                                   env.prefs[selectionColor_prefs_key],
                                   _self.handleColor)

    #The caller-specified formula that determines the radius (of the sphere) of this handle.
    #See DnaStrand_EditCommand._determine_resize_handle_radius() for more
    #details
    sphereRadius = Option(Width, 1.5)

    #Appearance of the handle. (note that it uses all the code from exprs module
    # and needs more documentation there).
    #See exprs.Rect.Sphere for definition of a drawable 'Sphere' object.

    appearance = Overlay(
            Sphere(_self.sphereRadius,
                   _self._currentHandleColor,
                   center = ORIGIN + _self.direction * 3.0 * _self.sphereRadius),

            Cylinder((ORIGIN,
                      ORIGIN + _self.direction * 2.2 * _self.sphereRadius),
                      0.6 * _self.sphereRadius,
                      _self._currentHandleColor))

    #Handle appearance when highlighted
    # [this probably doesn't need to be an Option, since the client never
    #  passes it [bruce 080409 comment]]
    HHColor = env.prefs[hoverHighlightingColor_prefs_key]
    appearance_highlighted = Option(
        Drawable,
        Overlay(
            Sphere(_self.sphereRadius,
                   HHColor,
                   center = ORIGIN + _self.direction * 3.0 * _self.sphereRadius),

            Cylinder((ORIGIN,
                      ORIGIN + _self.direction * 2.2 * _self.sphereRadius),
                     0.6* _self.sphereRadius ,
                     HHColor)),
            doc = "handle appearance when highlighted")


    #Stateusbar text. Variable needs to be renamed in superclass.
    sbar_text = Option(str,
                       "Drag the handle to resize the strand",
                       doc = "Statusbar text on mouseover")

    #Command object specified as an 'Option' during instantiation of the class
    #see DnaSegment_EditCommand class definition.
    command =  Option(Action,
                      doc = 'The Command which instantiates this handle')

    #Current position of the handle. i.e. it is the position of the handle
    #under the mouse. (its differert than the 'orifinal position)
    #This variable is used in self.command.graphicsMode to draw a rubberband
    #line  and also to specify the endPoint2 of the structure while modifying
    #it. See DnaSegment_EditCommand.modifyStructure for details.
    if _self.origin is not None:
        currentPosition = _self.origin + _self.direction * _self.height_ref.value
    else:
        currentPosition = ORIGIN


    #Fixed end of the structure (self.command.struct) ..meaning that end won't
    #move while user grabbs and draggs this handle (attached to a the other
    #'moving endPoint) . This variable is used to specify endPoint1 of the
    #structure while modifyin it.  See DnaSegment_EditCommand.modifyStructure
    #and self.on_release for details.
    fixedEndOfStructure = Option(Point,
                                 V(0, 0, 0))

    #If this is false, the 'highlightable' object i.e. this handle
    #won't be drawn. See DraggableHandle.py for the declararion of
    #the delegate(that defines a Highlightable) We define a If_Exprs to check
    #whether to draw the highlightable object.
    should_draw = State(bool, True)

    def ORIG_NOT_USED_hasValidParamsForDrawing(self):
        """
        NOT USED AS OF 2008-04-02
        Returns True if the handles origin and direction are not 'None'.

        @see: DnaStrand_GraphicsMode._draw_handles() where the caller
              uses this to decide whether this handle can be drawn without
              a problem.
        """
        #NOTE: Better to do it in the drawing code of this class?
        #But it uses a delegate to draw stuff (see class Highlightable)
        #May be we should pass this method to that delegate as an optional
        #argument -- Ninad 2008-04-02
        if self.origin is None or self.direction is None:
            return  False

        return True


    def hasValidParamsForDrawing(self):
        """
        Returns True if the handles origin and direction are not 'None'.

        @see: DnaStrand_GraphicsMode._draw_handles() where the caller
              uses this to decide whether this handle can be drawn without
              a problem.

        """

        #NOTE: Better to do it in the drawing code of this class?
        #But it uses a delegate to draw stuff (see class Highlightable)
        #May be we should pass this method to that delegate as an optional
        #argument -- Ninad 2008-04-02

        #@Bug: Create a duplex; Enter Dna strand edit command,
        # then shorten it such that it removes some bases of the strand from the
        #original duplex. Hit undo; click on the right handle, and shorten it again
        #sometimes it gives a traceback. in drawing the highlightable
        #this could be because self.should_draw flag is not getting updated.


        #NOTES: If this method is used, you will also need to define the
        #delegate in class DraggableHandle as --
        #delegate = If_Exprs(_self.should_draw, Highlightable(....))
        if self.origin is None or self.direction is None:
            self.should_draw = False
        else:
            self.should_draw = True

        return self.should_draw


    def on_press(self):
        """
        Actions when handle is pressed (grabbed, during leftDown event)
        @see: B{SelectChunks.GraphicsMode.leftDown}
        @see: B{DnaStrand_EditCommand.grabbedHandle}
        @see: B{DnaStrand_GraphicsMode.Draw} (which uses some attributes of
             the current grabbed handle of the command.
        @see: B{DragHandle_API}
        """
        #Change the handle color when handle is grabbed. See declaration of
        #self.handleColor in the class definition.

        ## self._currentHandleColor = env.prefs[selectionColor_prefs_key]
        self.handleIsGrabbed = True

        #assign 'self' as the curent grabbed handle of the command.
        self.command.grabbedHandle = self

    def on_drag(self):
        """
        Method called while dragging this handle .
        @see: B{DragHandle_API}
        """
        pass
        #The following call is disabled. Instead updating this spinbox
        #is done by the command.getCursorText method . See that method for
        #details
        ##self.command.update_numberOfBases()

    def on_release(self):
        """
        This method gets called during leftUp (when the handle is released)
        @see: B{DnaStrand_EditCommand.modifyStructure}
        @see: self.on_press
        @see: B{SelectChunks.GraphicsMode.leftUp}
        @see: B{DragHandle_API}
        """
        ## self._currentHandleColor = self.handleColor
        self.handleIsGrabbed = False

        if self.command and hasattr(self.command, 'modifyStructure'):
            self.command.modifyStructure()
            #Clear the grabbed handle attribute (the handle is no longer
            #grabbed)
            self.command.grabbedHandle = None