Esempio n. 1
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
Esempio n. 2
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
Esempio n. 3
0
def Instance(expr,
             _index_expr=_E_ATTR,
             _lvalue_flag=False,
             _noinstance=False,
             doc=None):
    """
    This macro is assigned to a class attr to declare that its value should be a lazily-instantiated Instance of expr (by default).
    Assuming the arg is an expr (not yet checked?), turn into the expr _self._i_instance(hold_Expr(expr), _E_ATTR),
    which is free in the symbols _self and _E_ATTR. [#e _E_ATTR might be changed to _E_INDEX, or otherwise revised.]

    This function is also used internally to help implement the Arg and Option macros;
    for their use only, it has a private _index_expr option, giving an index expr other than _E_ATTR for the new Instance
    (which is used to suggest an ipath for the new instance, relative to that of self).

    Similarly, it helps implement ArgExpr etc, for whose sake it has a private option _noinstance.

    Devel scratch comment:
    Note that the Arg and Option macros may have handy, not expr itself, but a "grabarg" expr which needs to be evaluated
    (with _self bound) to produce the expr to be instantiated. What should they pass?? eval_Expr of the expr they have.
    [#doc - reword this]

    Other private options: _lvalue_flag, _noinstance (_noinstance is only supported when EVAL_REFORM is true)
    """
    if doc:
        printnim(
            "Instance doc is not saved anywhere; should turn into a note to formulascanner to save it as metainfo, maybe"
        )  #e
    printnim(
        "review: same index is used for a public Option and a private Instance on an attr; maybe ok if no overlap possible???"
    )  ##e
    global _self  # not needed, just fyi
    if EVAL_REFORM:
        if _lvalue_flag:
            assert not _noinstance  # since it makes no sense to ask for this then, AFAIK (if ok for one, ok for all -- see below)
            #070119 bugfix to make Set(var,val) work again (eg when clicking a checkbox_pref)
            # rather than evalling var to its current value. This means _lvalue_flag never gets passed to _i_instance.
            res = call_Expr(getattr_Expr(_self, '_i_instance'),
                            eval_to_lval_Expr(expr), _index_expr)
            ####e if this works, then try simplifying it to remove the _i_instance call! (assuming the lval is never needing make)
            # (or given this macro's name, maybe it makes more sense for LvalueArg to manage to not call it...)
        elif _noinstance:  #070122
            # we ignore _index_expr, but callers can't help but pass one, so don't complain when they do
            return expr  # i.e. Instance(expr) == expr -- not useful directly, but useful as a passthru option from Arg etc.
        else:
            res = call_Expr(getattr_Expr(_self, '_i_instance'), expr,
                            _index_expr)
    else:
        assert not _noinstance, "bug: _noinstance is only supported when EVAL_REFORM is true"
        res = call_Expr(getattr_Expr(_self, '_i_instance'),
                        hold_Expr(expr),
                        _index_expr,
                        _lvalue_flag=_lvalue_flag)
    return res
Esempio n. 4
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
class _color_toggler(DelegatingInstanceOrExpr):
    ###WRONGnesses:
    # - Q: shouldn't we be toggling a flag or enum or int, and separately mapping that to a color to show?
    #   A: Yes, unless we're for general raw color images -- but as a StateArray test this doesn't matter.
    # args
    color_ref = Arg(StateRef, doc="stateref to a color variable"
                    )  ###e can we tell StateRef what the value type should be?
    # appearance/behavior
    delegate = Boxed(
        Highlightable(
            Rect(1, 1, color_ref.value),
            on_press=Set(color_ref.value,
                         call_Expr(_self.toggle_color, color_ref.value)),
            sbar_text=
            "click to change color"  #e give index in StateArray, let that be an arg? (if so, is it a fancy stateref option?)
        ),
        pixelgap=
        0,  #e rename -- but to what? bordergap? just gap? (change all gap options to being in pixels?)
        borderwidth=
        1,  ###BUG: some of these border edges are 2 pixels, depending on pixel alignment with actual screen
        bordercolor=black)

    def toggle_color(self, color):
        r, g, b = color
        return (
            b, r, g
        )  # this permits toggling through the cyclic color sequence red, green, blue in that order
        #e or we could rotate that color-cube on the same diagonal axis but less than 1/3 turn,
        # to get more colors, if we didn't mind renormalizing them etc...

    pass
Esempio n. 6
0
def StateArrayRefs(type_expr, dfltval_expr, **kws):  #e refile?
    """An array (expandable, arbitrarily indexed -- really a dictionary) of individually tracked/resettable state variables --
    accessed as individual StateRefs to the variables.
    Usage: in a class definition that uses ExprsMeta,
      attr = StateArrayRefs( type_expr, dfltval_expr )
    creates an attribute in each Instance which is effectively a dictionary of individually change/usage-tracked values,
    each coerced to type_expr [nim] (which can't yet depend on the dictionary index ###FIX),
    and each set to the value of dfltval_expr if it is accessed before it's first set.
       Note: dfltval_expr is evaluated without usage tracking; it is not yet decided whether it's evaluated only once
    (and if so when) or each time it's needed (the latter is more useful, but less efficient in many cases).
    There is not yet any way for dfltval_expr to depend on the dictionary index. ###FIX
       ###e There is not yet a way to control the possible or actual dictionary indices, or to provide an initial default value
    for the array as a whole (it's always {}). See also _CK_ and _CV_ rules, which provide read-only versions of those abilities.
    A combo of a _CK_/_CV_ interface to a StateArray could, for now, do much of what a _CK_-like feature in StateArray should do,
    though much more clumsily. In my first use of this I said "i want an arg for the index set... maybe even the initial set,
    or total set, so i can iter over it..."
    [NOTE: at least one dictlike class has that feature - review the ones in py_utils, see what _CK_ uses]
       ###e there may not yet be a way to delete an element (effectively resetting it to the value of dfltval_expr, computed somewhen).
       ###e there is not yet an unambiguous record of which elements are present or not in the array.
       ###e There is not yet a way to subscribe to updates of the set of defined array element indices,
    nor to updates of all changes to the entire set of values,
    let alone the same features for specific subsets, defined by index preds or sets.
       ###e There is not yet an efficient way to invalidate a range of indices at once
    (letting smart-enoguh subscribers receive just one summary inval).
       ###e There is not yet a way for some of the array to be set to a virtual array (e.g. a view of part of another array or set).
    See also: MapListToExpr, ArgList [nim], InstanceDict [nim], StateArg [nim].
    """
    #e should change to descriptor class... [why is that not easier than it is?]
    #e implem of type_expr coercion: depending on type & options, wrap with glue code when set, or coerce in simple way when set
    #e options: doc, debug_name
    #e somehow the State finds out its argname -- i think (does it really?) -- we might want that in debug_name
    return State(StateArrayRefs_type,
                 call_Expr(_make_StateArrayRefs, type_expr, dfltval_expr,
                           _E_ATTR, _self, **kws))  #k this use of **kws
Esempio n. 7
0
def StateArrayRefs(type_expr, dfltval_expr, **kws): #e refile?
    """An array (expandable, arbitrarily indexed -- really a dictionary) of individually tracked/resettable state variables --
    accessed as individual StateRefs to the variables.
    Usage: in a class definition that uses ExprsMeta,
      attr = StateArrayRefs( type_expr, dfltval_expr )
    creates an attribute in each Instance which is effectively a dictionary of individually change/usage-tracked values,
    each coerced to type_expr [nim] (which can't yet depend on the dictionary index ###FIX),
    and each set to the value of dfltval_expr if it is accessed before it's first set.
       Note: dfltval_expr is evaluated without usage tracking; it is not yet decided whether it's evaluated only once
    (and if so when) or each time it's needed (the latter is more useful, but less efficient in many cases).
    There is not yet any way for dfltval_expr to depend on the dictionary index. ###FIX
       ###e There is not yet a way to control the possible or actual dictionary indices, or to provide an initial default value
    for the array as a whole (it's always {}). See also _CK_ and _CV_ rules, which provide read-only versions of those abilities.
    A combo of a _CK_/_CV_ interface to a StateArray could, for now, do much of what a _CK_-like feature in StateArray should do,
    though much more clumsily. In my first use of this I said "i want an arg for the index set... maybe even the initial set,
    or total set, so i can iter over it..."
    [NOTE: at least one dictlike class has that feature - review the ones in py_utils, see what _CK_ uses]
       ###e there may not yet be a way to delete an element (effectively resetting it to the value of dfltval_expr, computed somewhen).
       ###e there is not yet an unambiguous record of which elements are present or not in the array.
       ###e There is not yet a way to subscribe to updates of the set of defined array element indices,
    nor to updates of all changes to the entire set of values,
    let alone the same features for specific subsets, defined by index preds or sets.
       ###e There is not yet an efficient way to invalidate a range of indices at once
    (letting smart-enoguh subscribers receive just one summary inval).
       ###e There is not yet a way for some of the array to be set to a virtual array (e.g. a view of part of another array or set).
    See also: MapListToExpr, ArgList [nim], InstanceDict [nim], StateArg [nim].
    """
    #e should change to descriptor class... [why is that not easier than it is?]
    #e implem of type_expr coercion: depending on type & options, wrap with glue code when set, or coerce in simple way when set
    #e options: doc, debug_name
    #e somehow the State finds out its argname -- i think (does it really?) -- we might want that in debug_name
    return State( StateArrayRefs_type, call_Expr(_make_StateArrayRefs, type_expr, dfltval_expr, _E_ATTR, _self, **kws)) #k this use of **kws
Esempio n. 8
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
class dragverts_Polyline(DelegatingInstanceOrExpr):  # experimental 070308
    polyline = Arg(Polyline)
    func = call_Expr(draggable_polyline_point, polyline)
    delegate = OverlayList(
        polyline,  ##e make Overlay itself able to take a list arg mixed with a nonlist arg? Like Column?
        map_Expr(func, polyline.points)
    )  ### .points can be what we need, if .point_posns is what this used to be
    pass  ###stub
Esempio n. 10
0
class RotateTranslate(DelegatingInstanceOrExpr):  #070225
    """
    RotateTranslate(thing, quat, vector) draws as thing, rotated around its center by quat, then translated by vector.
    ###e other options for other kinds of motion? e.g. a different center of rotation?
    ###e lbox transforms?
    """
    # args
    delegate = Arg(Widget)
    quat = Arg(
        Quat
    )  ###e also permit 2-arg form like glRotate itself does? or only when we're called Rotate?
    #e also permit named options (rotation or quat? angle, amount? degrees or radians or turns?? various arg formats like Q takes?)
    vector = Arg(Vector, ORIGIN)
    # formulae
    motion = call_Expr(tuple3_from_vec, vector)
    center = delegate.center + motion

    ###e lbox transforms? could just use the same ones as Translate, but do we need them at all?
    # (bounding radius around center might be more useful -- it needs no transform beyond what we just did for center.)
    def draw(self):
        self.pushMatrix()
        self.drawkid(
            self.delegate)  # has exception protection (#e#k or will soon)
        self.popMatrix()
        return

    def pushMatrix(self):  # [modified from same method in class Chunk]
        """
        Do glPushMatrix(), and then transform from external to local coordsys.
        """
        # do most of the things that might cause exceptions before doing any OpenGL calls.
        x, y, z = self.motion
        cx, cy, cz = self.delegate.center
        q = self.quat
        try:
            a, b, c, d = q.angle * 180.0 / pi, q.x, q.y, q.z
        except:
            ###UNTESTED
            print "exception in a,b,c,d = q.angle*180.0/pi, q.x, q.y, q.z for q == %r" % (
                q, )
            # does my quat bug print this? no, it happily permits a quat to become Q(nan, nan, nan, nan) with no exception...
            a, b, c, d = 0, 1, 0, 0
        glPushMatrix()
        glTranslatef(x + cx, y + cy, z + cz)
        glRotatef(a, b, c, d)
        glTranslatef(-cx, -cy, -cz)
        return

    def popMatrix(self):  # [copied from same method in class Chunk]
        """
        Undo the effect of self.pushMatrix().
        """
        glPopMatrix()

    pass
Esempio n. 11
0
def Instance(expr, _index_expr = _E_ATTR, _lvalue_flag = False, _noinstance = False, doc = None):
    """
    This macro is assigned to a class attr to declare that its value should be a lazily-instantiated Instance of expr (by default).
    Assuming the arg is an expr (not yet checked?), turn into the expr _self._i_instance(hold_Expr(expr), _E_ATTR),
    which is free in the symbols _self and _E_ATTR. [#e _E_ATTR might be changed to _E_INDEX, or otherwise revised.]

    This function is also used internally to help implement the Arg and Option macros;
    for their use only, it has a private _index_expr option, giving an index expr other than _E_ATTR for the new Instance
    (which is used to suggest an ipath for the new instance, relative to that of self).

    Similarly, it helps implement ArgExpr etc, for whose sake it has a private option _noinstance.

    Devel scratch comment:
    Note that the Arg and Option macros may have handy, not expr itself, but a "grabarg" expr which needs to be evaluated
    (with _self bound) to produce the expr to be instantiated. What should they pass?? eval_Expr of the expr they have.
    [#doc - reword this]

    Other private options: _lvalue_flag, _noinstance (_noinstance is only supported when EVAL_REFORM is true)
    """
    if doc:
        printnim("Instance doc is not saved anywhere; should turn into a note to formulascanner to save it as metainfo, maybe")#e
    printnim("review: same index is used for a public Option and a private Instance on an attr; maybe ok if no overlap possible???")##e
    global _self # not needed, just fyi
    if EVAL_REFORM:
        if _lvalue_flag:
            assert not _noinstance # since it makes no sense to ask for this then, AFAIK (if ok for one, ok for all -- see below)
            #070119 bugfix to make Set(var,val) work again (eg when clicking a checkbox_pref)
            # rather than evalling var to its current value. This means _lvalue_flag never gets passed to _i_instance.
            res = call_Expr( getattr_Expr(_self, '_i_instance'), eval_to_lval_Expr(expr), _index_expr )
                ####e if this works, then try simplifying it to remove the _i_instance call! (assuming the lval is never needing make)
                # (or given this macro's name, maybe it makes more sense for LvalueArg to manage to not call it...)
        elif _noinstance:#070122
            # we ignore _index_expr, but callers can't help but pass one, so don't complain when they do
            return expr # i.e. Instance(expr) == expr -- not useful directly, but useful as a passthru option from Arg etc.
        else:
            res = call_Expr( getattr_Expr(_self, '_i_instance'),                   expr,  _index_expr )
    else:
            assert not _noinstance, "bug: _noinstance is only supported when EVAL_REFORM is true"
            res = call_Expr( getattr_Expr(_self, '_i_instance'),         hold_Expr(expr), _index_expr, _lvalue_flag = _lvalue_flag )
    return res
Esempio n. 12
0
def StatePlace(kind, ipath_expr=_self.ipath, tracked=True):  # revised 061117
    # experimental, but used and working in Highlightable [that use moved to InstanceOrExpr 061126]; related to Arg/Option/Instance
    """In a class definition for an InstanceOrExpr subclass, use the assignment

        <kind>_state = StatePlace(<kind>, <ipath_expr>)

    (for a short string <kind>, usually one of a small set of conventional kinds of storage)
    to cause self.<kind>_state in each Instance to refer to external state specific to
    <ipath_expr> as evaluated in that Instance (by default, _self.ipath, meaning specific
    to that Instance, though usually more persistent than it, perhaps depending on <kind>).

    By default, that state is fully usage-tracked and change-tracked; it should only be
    changed when processing a mouse click or keystroke or similar user action, and changing it
    will invalidate any model variables or drawing side effects that used the prior value of
    the same state. (Changing it *during* model computations or drawing code can cause undetected
    errors and/or detected bad bugs.)

    If tracked = False, then that state is not usage-tracked or change-tracked at all. This is
    appropriate for certain "kinds" of state, e.g. temporary per-frame variables used for drawing code.
    (Some such state could probably just be stored in each Instance, but this is not yet clear. #k)

    It's usually necessary to initialize the contents of the declared stateplaces
    using set_default_attrs in _init_instance, or the like. (Note that the stateplace is often
    found rather than created, and the same sometimes goes for individual attrs within it.
    Both this StatePlace  declaration and set_default_attrs take this into account by only initializing
    what's not already there.)

    See also the [nim] State declaration for use with individual attrs.
    """
    # Implem notes:
    #
    # StatePlace() works by turning into a formula which will eval to a permanent reference
    # to a (found or created) attrholder for storing state of the given kind, at the given ipath
    # (value of ipath_expr), relative to the env of the Instance this is used in (i.e. to _self.env).
    # [revision 061117: the persistent state itself needs usage and change tracking. The attrholder
    # could be permanent and own that state (and do that tracking), or be a transient accessor to it [did it that way].]
    #
    # WARNING/BTW: when I reload, the data should be persistent, but the subscriptions to it probably shouldn't be.
    # But that's really up to each subs -- clearing all of them would be wrong. So ignore it for now.
    # (It means old objects will get invalidated, but that loses them their subs, and nothing recomputes their attrs
    # and resubs them, so it actually doesn't end up causing a lasting performance problem, I think. Review sometime. #k)

    assert isinstance(tracked, bool) or tracked in (0, 1)
    assert isinstance(kind, str)
    res_expr = call_Expr(_StatePlace_helper, _self, kind, ipath_expr, tracked)
    return res_expr  # from StatePlace
Esempio n. 13
0
def StatePlace(kind, ipath_expr = _self.ipath, tracked = True): # revised 061117
    # experimental, but used and working in Highlightable [that use moved to InstanceOrExpr 061126]; related to Arg/Option/Instance
    """In a class definition for an InstanceOrExpr subclass, use the assignment

        <kind>_state = StatePlace(<kind>, <ipath_expr>)

    (for a short string <kind>, usually one of a small set of conventional kinds of storage)
    to cause self.<kind>_state in each Instance to refer to external state specific to
    <ipath_expr> as evaluated in that Instance (by default, _self.ipath, meaning specific
    to that Instance, though usually more persistent than it, perhaps depending on <kind>).

    By default, that state is fully usage-tracked and change-tracked; it should only be
    changed when processing a mouse click or keystroke or similar user action, and changing it
    will invalidate any model variables or drawing side effects that used the prior value of
    the same state. (Changing it *during* model computations or drawing code can cause undetected
    errors and/or detected bad bugs.)

    If tracked = False, then that state is not usage-tracked or change-tracked at all. This is
    appropriate for certain "kinds" of state, e.g. temporary per-frame variables used for drawing code.
    (Some such state could probably just be stored in each Instance, but this is not yet clear. #k)

    It's usually necessary to initialize the contents of the declared stateplaces
    using set_default_attrs in _init_instance, or the like. (Note that the stateplace is often
    found rather than created, and the same sometimes goes for individual attrs within it.
    Both this StatePlace  declaration and set_default_attrs take this into account by only initializing
    what's not already there.)

    See also the [nim] State declaration for use with individual attrs.
    """
    # Implem notes:
    #  
    # StatePlace() works by turning into a formula which will eval to a permanent reference
    # to a (found or created) attrholder for storing state of the given kind, at the given ipath
    # (value of ipath_expr), relative to the env of the Instance this is used in (i.e. to _self.env).
    # [revision 061117: the persistent state itself needs usage and change tracking. The attrholder
    # could be permanent and own that state (and do that tracking), or be a transient accessor to it [did it that way].]
    #
    # WARNING/BTW: when I reload, the data should be persistent, but the subscriptions to it probably shouldn't be.
    # But that's really up to each subs -- clearing all of them would be wrong. So ignore it for now.
    # (It means old objects will get invalidated, but that loses them their subs, and nothing recomputes their attrs
    # and resubs them, so it actually doesn't end up causing a lasting performance problem, I think. Review sometime. #k)

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

    attr_expr should be None, or some sort of expr (in practice always _E_ATTR so far)
      that will get replaced by a constant_Expr for the current attr (in ExprsMeta's FormulaScanner),
      according to whether the current attr should be part of the index and a public option-name for supplying the arg
      (we make sure those conditions are the same). [#e Note that if someday we wanted to include f(attr) in the index,
      but still use attr alone as an option name, we'd have to modify this to permit both f(attr) (or f) and attr to be passed.]

    argpos_expr should similarly be None, or some sort of expr (in practice a private subclass of internal_Expr)
      that will get replaced by a constant_Expr for the argument position (an int) that should be allocated to the current attr's arg
      (determined in ExprsMeta's FormulaScanner by allocating posns 0,1,2,etc to newly seen arg-attrs, whether or not the attr itself
      is public for that arg).

    type_expr ###doc, passed herein to canon_type

    dflt_expr ###doc, can also be _E_DFLT_FROM_TYPE_ or [handled in caller i think, but survives here unmatteringly] _E_REQUIRED_ARG_;
        will be passed through canon_expr

    _lvalue_flag is a private option used by LvalueArg.

    _arglist is a private option used by ArgList.
    """
    if _lvalue_flag:
        printnim("_lvalue_flag's proper interaction with dflt_expr is nim"
                 )  # in all cases below
        ### guess: we want it to be an expr for a default stateref
    global _self  # fyi
    type_expr = canon_type(type_expr)
    printnim(
        "can type_expr legally be self-dependent and/or time-dependent? ###k I guess that's nim in current code!"
    )  #070115 comment
    if _arglist:
        # new feature 070321. The type is applied to each element, but the default value is for the entire list --
        # OTOH, when would it ever be used, since even if no args are supplied, the list can be formed??
        # Probably it would only be used when the list was 0 length, and could meaningfully be [], (), or another list-like thing...
        # this is all a guess and I probably won't even review this code for this issue now, unless it fails when tried. ####k
        type_expr = tuple_Expr(
            type_expr
        )  # type-coerce the value to a list of the given type [070321 guess] ###e or list_Expr???
    if dflt_expr is _E_DFLT_FROM_TYPE_:
        dflt_expr = default_expr_from_type_expr(type_expr)
        ## note [070115], this would be impossible for time-dependent types! and for self-dep ones, possible but harder than current code.
        assert is_pure_expr(dflt_expr)  #k guess 061105
    else:
        dflt_expr = canon_expr(
            dflt_expr
        )  # hopefully this finally will fix dflt 10 bug, 061105 guesshope ###k [works for None, 061114]
        assert is_pure_expr(
            dflt_expr
        )  # not sure this is redundant, since not sure if canon_expr checks for Instance ###k
        printnim("not sure if canon_expr checks for Instance")
    # Note on why we use explicit call_Expr & getattr_Expr below,
    # rather than () and . notation like you can use in user-level formulae (which python turns into __call__ and getattr),
    # to construct Exprs like _self._i_grabarg( attr_expr, ...):
    # it's only to work around safety features which normally detect that kind of Expr-formation (getattr on _i_* or _e_*,
    # or getattr then call) as a likely error. These safety features are very important, catching errors that would often lead
    # to hard-to-diagnose bugs (when our code has an Expr but thinks it has an Instance), so it's worth the trouble.
    held_dflt_expr = hold_Expr(dflt_expr)
    # Note, this gets evalled back into dflt_expr (treated as inert, may or may not be an expr depending on what it is right here)
    # by the time _i_grabarg sees it (the eval is done when the call_Expr evals its args before doing the call).
    # So if we wanted _i_grabarg to want None rather than _E_REQUIRED_ARG_ as a special case, we could change to that (there & here).
    grabopts = {}
    if _arglist:
        grabopts.update(dict(_arglist=constant_Expr(_arglist)))
    grabarg_expr = call_Expr(getattr_Expr(_self, '_i_grabarg'), attr_expr,
                             argpos_expr, held_dflt_expr, **grabopts)
    # comments 070115:
    # - This will eval to an expr which depends on self but not on time. We could optim by wrapping it
    # (or declaring it final) in a way which effectively replaced it with its value-expr when first used.
    # (But it's not obvious where to store the result of that, since the exprs being returned now are assigned to classes
    #  and will never be specific to single selfs. Do we need an expr to use here, which can cache its own info in self??
    #  Note: AFAIK, self will be the same as what _self gets replaced with when this is used. (We ought to assert that.) ###e)
    # - Further, grabarg_expr is probably supposed to be wrapped *directly* by eval_Expr, not with type_expr inside. I think I'll
    # make that change right now and test it with EVAL_REFORM still False, since I think it's always been required, as said
    # in other comments here. DOING THIS NOW.
    if attr_expr is not None and argpos_expr is not None:
        # for ArgOrOption, use a tuple of a string and int (attr and argpos) as the index
        index_expr = tuple_Expr(attr_expr, argpos_expr)
    elif attr_expr is None and argpos_expr is None:
        assert 0, "attr_expr is None and argpos_expr is None ..."
    elif attr_expr is not None:
        # for Option, use a plain attr string as the index
        index_expr = attr_expr
    else:
        assert argpos_expr is not None
        # for Arg, use a plain int as the index
        # (note: ExprsMeta replaces argpos_expr with that int wrapped in constant_Expr, but later eval pulls out the raw int)
        index_expr = argpos_expr


# see if this is fixed now, not that it means much since we were using a stub... but who knows, maybe the stub was buggy
# and we compensated for that and this could if so cause a bug:
##    printnim("I suspect type_expr (stub now) is included wrongly re eval_Expr in _ArgOption_helper, in hindsight 061117")
##        ### I suspect the above, because grabarg expr needs to be evalled to get the expr whose type coercion we want to instantiate
    res = Instance(_type_coercion_expr(type_expr, eval_Expr(grabarg_expr)),
                   _index_expr=index_expr,
                   _lvalue_flag=_lvalue_flag,
                   **moreopts)
    # note: moreopts might contain _noinstance = True, and if so, Instance normally returns its first arg unchanged
    # (depending on other options).
    # 070115 replaced eval_Expr( type_expr( grabarg_expr)) with _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) )
    return res  # from _ArgOption_helper
class NanotubeSegment_EditCommand(State_preMixin, EditCommand):
    """
    Command to edit a NanotubeSegment object. 
    To edit a segment, first enter BuildNanotube_EditCommand (accessed using 
    Build > Nanotube) then, select an existing NanotubeSegment within the 
    NanotubeGroup you are editing. When you select the NanotubeSegment, it 
    enters NanotubeSegment_Editcommand and shows the property manager with its
    widgets showing the properties of selected segment.
    """
    cmd              =  'Nanotube Segment'
    sponsor_keyword  =  'Nanotube'
    prefix           =  'NanotubeSegment' # used for gensym
    cmdname          = "NANOTUBE_SEGMENT"
    commandName      = 'NANOTUBE_SEGMENT'
    featurename      = 'Edit Nanotube Segment'

    command_should_resume_prevMode = True
    command_has_its_own_gui = True
    command_can_be_suspended = False

    create_name_from_prefix  =  True 

    call_makeMenus_for_each_event = True 

    #Graphics Mode 
    GraphicsMode_class = NanotubeSegment_GraphicsMode

    #This is set to BuildDna_EditCommand.flyoutToolbar (as of 2008-01-14, 
    #it only uses 
    flyoutToolbar = None

    _parentNanotubeGroup = None    

    handlePoint1 = State( Point, ORIGIN)
    handlePoint2 = State( Point, ORIGIN)
    #The minimum 'stopper'length used for resize handles
    #@see: self._update_resizeHandle_stopper_length for details. 
    _resizeHandle_stopper_length = State(Width, -100000)

    rotationHandleBasePoint1 = State( Point, ORIGIN)
    rotationHandleBasePoint2 = State( Point, ORIGIN)

    #See self._update_resizeHandle_radius where this gets changed. 
    #also see NanotubeSegment_ResizeHandle to see how its implemented. 
    handleSphereRadius1 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)
    handleSphereRadius2 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)

    cylinderWidth = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 
    cylinderWidth2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 


    #@TODO: modify the 'State params for rotation_distance 
    rotation_distance1 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    rotation_distance2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    leftHandle = Instance(         
        NanotubeSegment_ResizeHandle(    
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth'),
            origin = handlePoint1,
            fixedEndOfStructure = handlePoint2,
            direction = norm_Expr(handlePoint1 - handlePoint2),
            sphereRadius = handleSphereRadius1, 
            range = (_resizeHandle_stopper_length, 10000)                               
        ))

    rightHandle = Instance( 
        NanotubeSegment_ResizeHandle(
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth2'),
            origin = handlePoint2,
            fixedEndOfStructure = handlePoint1,
            direction = norm_Expr(handlePoint2 - handlePoint1),
            sphereRadius = handleSphereRadius2,
            range = (_resizeHandle_stopper_length, 10000)
        ))

    rotationHandle1 = Instance(         
        RotationHandle(    
            command = _self,
            rotationDistanceRef = call_Expr( ObjAttr_StateRef,
                                             _self, 
                                             'rotation_distance1'),
                                             center = handlePoint1,
                                             axis = norm_Expr(handlePoint1 - handlePoint2),
                                             origin = rotationHandleBasePoint1,
                                             radiusVector = norm_Expr(rotationHandleBasePoint1 - handlePoint1)

                                         ))

    rotationHandle2 = Instance(         
        RotationHandle(    
            command = _self,
            rotationDistanceRef = call_Expr( ObjAttr_StateRef,
                                             _self, 
                                             'rotation_distance2'),
                                             center = handlePoint2,
                                             axis = norm_Expr(handlePoint2 - handlePoint1),
                                             origin = rotationHandleBasePoint2,
                                             radiusVector = norm_Expr(rotationHandleBasePoint2 - handlePoint2)

                                         ))

    def __init__(self, commandSequencer, struct = None):
        """
        Constructor for DnaDuplex_EditCommand
        """

        glpane = commandSequencer
        State_preMixin.__init__(self, glpane)        
        EditCommand.__init__(self, commandSequencer)
        self.struct = struct

        #Graphics handles for editing the structure . 
        self.handles = []        
        self.grabbedHandle = None

        #Initialize DEBUG preference
        pref_nt_segment_resize_by_recreating_nanotube()
        return

    def init_gui(self):
        """
        Initialize gui. 
        """

        #Note that NanotubeSegment_EditCommand only act as an edit command for an 
        #existing structure. The call to self.propMgr.show() is done only during
        #the call to self.editStructure ..i .e. only after self.struct is 
        #updated. This is done because of the following reason:
        # - self.init_gui is called immediately after entering the command. 
        # - self.init_gui in turn, initialized propMgr object and may also 
        #  show the property manager. The self.propMgr.show routine calls 
        #  an update widget method just before the show. This update method 
        #  updates the widgets based on the parameters from the existing 
        #  structure of the command (self.editCommand.struct)
        #  Although, it checks whether this structure exists, the editCommand
        #  could still have a self.struct attr from a previous run. (Note that 
        #  EditCommand API was written before the command sequencer API and 
        #  it has some loose ends like this. ) -- Ninad 2008-01-22
        self.create_and_or_show_PM_if_wanted(showPropMgr = False)
        return

    def editStructure(self, struct = None):
        EditCommand.editStructure(self, struct)        
        if self.hasValidStructure():         
            #When the structure (segment) is finalized (after the  modifications)
            #it will be added to the original NanotubeGroup to which it belonged 
            #before we began editing (modifying) it. 
            self._parentNanotubeGroup = self.struct.getNanotubeGroup() 
            #Set the endpoints
            #@ DOES THIS DO ANYTHING? I don't think so. --Mark 2008-04-01
            #@endPoint1, endPoint2 = self.struct.nanotube.getEndPoints()
            #@params_for_propMgr = (endPoint1, endPoint2)

            #TODO 2008-03-25: better to get all parameters from self.struct and
            #set it in propMgr?  This will mostly work except that reverse is 
            #not true. i.e. we can not specify same set of params for 
            #self.struct.setProps ...because endPoint1 and endPoint2 are derived.
            #by the structure when needed. Commenting out following line of code
            self.propMgr.setParameters(self.struct.getProps())

            #Store the previous parameters. Important to set it after you 
            #set nanotube attrs in the propMgr. 
            #self.previousParams is used in self._previewStructure and 
            #self._finalizeStructure to check if self.struct changed.
            self.previousParams = self._gatherParameters()
            self._updateHandleList()
            self.updateHandlePositions()
        return

    def keep_empty_group(self, group):
        """
        Returns True if the empty group should not be automatically deleted. 
        otherwise returns False. The default implementation always returns 
        False. Subclasses should override this method if it needs to keep the
        empty group for some reasons. Note that this method will only get called
        when a group has a class constant autdelete_when_empty set to True. 
        (and as of 2008-03-06, it is proposed that dna_updater calls this method
        when needed. 
        @see: Command.keep_empty_group() which is overridden here. 
        @see: BreakStrands_Command.keep_empty_group
        @see: Group.autodelete_when_empty.. a class constant used by the 
              dna_updater (the dna updater then decides whether to call this 
              method to see which empty groups need to be deleted)
        """

        bool_keep = EditCommand.keep_empty_group(self, group)

        if not bool_keep:     
            if self.hasValidStructure():                
                if group is self.struct:
                    bool_keep = True
                elif group is self.struct.parent_node_of_class(self.assy.NanotubeGroup):
                    bool_keep = True
            #If this command doesn't have a valid structure, as a fall back, 
            #lets instruct it to keep ALL the NanotubeGroup objects even when empty
            #Reason? ..see explanation in BreakStrands_Command.keep_empty_group
            elif isinstance(group, self.assy.NanotubeGroup):
                bool_keep = True

        return bool_keep

    def hasValidStructure(self):
        """
        Tells the caller if this edit command has a valid structure. 
        Overrides EditCommand.hasValidStructure()
        """
        #(By Bruce 2008-02-13)

        isValid = EditCommand.hasValidStructure(self)

        if not isValid:
            return isValid 

        # would like to check here whether it's empty of axis chunks;
        # instead, this will do for now (probably too slow, though):
        p1, p2 = self.struct.nanotube.getEndPoints()
        return (p1 is not None)

    def _getStructureType(self):
        """
        Subclasses override this method to define their own structure type. 
        Returns the type of the structure this editCommand supports. 
        This is used in isinstance test. 
        @see: EditCommand._getStructureType() (overridden here)
        """
        return self.win.assy.NanotubeSegment

    def _updateHandleList(self):
        """        
        Updates the list of handles (self.handles) 
        @see: self.editStructure
        @see: NanotubeSegment_GraphicsMode._drawHandles()
        """   
        # note: if handlePoint1 and/or handlePoint2 can change more often than this 
        # runs, we'll need to rerun the two assignments above whenever they 
        # change and before the handle is drawn. An easy way would be to rerun
        # these assignments in the draw method of our GM. [bruce 080128]
        self.handles = [] # guess, but seems like a good idea [bruce 080128]
        self.handles.append(self.leftHandle)
        self.handles.append(self.rightHandle)
        if DEBUG_ROTATION_HANDLES:
            self.handles.append(self.rotationHandle1)
            self.handles.append(self.rotationHandle2)
        return

    def updateHandlePositions(self):
        """
        Update handle positions and also update the resize handle radii and
        their 'stopper' lengths. 
        @see: self._update_resizeHandle_radius()
        @see: self._update_resizeHandle_stopper_length()
        @see: NanotubeSegment_GraphicsMode._drawHandles()
        """
        self.handlePoint1 = None # Needed!
        self.handlePoint2 = None

        #TODO: Call this method less often by implementing model_changed
        #see bug 2729 for a planned optimization
        self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE
        self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE      

        self._update_resizeHandle_radius()

        handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints()

        if 0: # Debug prints
            print "updateHandlePositions(): handlePoint1=", handlePoint1
            print "updateHandlePositions(): handlePoint2=", handlePoint2

        if handlePoint1 is not None and handlePoint2 is not None:
            # (that condition is bugfix for deleted axis segment, bruce 080213)

            self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2            

            #Update the 'stopper'  length where the resize handle being dragged 
            #should stop. See self._update_resizeHandle_stopper_length()
            #for more details
            self._update_resizeHandle_stopper_length()            

            if DEBUG_ROTATION_HANDLES:
                self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE
                self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE
                #Following computes the base points for rotation handles. 
                #to be revised -- Ninad 2008-02-13
                unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2)

                v  = cross(self.glpane.lineOfSight, unitVectorAlongAxis)

                self.rotationHandleBasePoint1 = self.handlePoint1 + norm(v) * 4.0  
                self.rotationHandleBasePoint2 = self.handlePoint2 + norm(v) * 4.0
        return

    def _update_resizeHandle_radius(self):
        """
        Finds out the sphere radius to use for the resize handles, based on 
        atom /chunk or glpane display (whichever decides the display of the end 
        atoms. The default value is 1.2.

        @see: self.updateHandlePositions()
        """
        self.handleSphereRadius1 = HANDLE_RADIUS_DEFAULT_VALUE
        self.handleSphereRadius2 = HANDLE_RADIUS_DEFAULT_VALUE
        return

    def _update_resizeHandle_stopper_length(self):
        """
        Update the limiting length at which the resize handle being dragged
        should 'stop'  without proceeding further in the drag direction. 
        The segment resize handle stops when you are dragging it towards the 
        other resizeend and the distance between the two ends reaches two 
        duplexes. 

        The self._resizeHandle_stopper_length computed in this method is 
        used as a lower limit of the 'range' option provided in declaration
        of resize handle objects (see class definition for the details)
        @see: self.updateHandlePositions()
        """

        total_length = vlen(self.handlePoint1 - self.handlePoint2)        
        nanotubeRise = self.struct.nanotube.getRise()
        self._resizeHandle_stopper_length = - total_length + nanotubeRise
        return

    def _createPropMgrObject(self):
        """
        Creates a property manager object (that defines UI things) for this 
        editCommand. 
        """
        assert not self.propMgr
        propMgr = self.win.createNanotubeSegmentPropMgr_if_needed(self)
        return propMgr

    def _gatherParameters(self):
        """
        Return the parameters from the property manager UI.

        @return: The endpoints of the nanotube.
        @rtype:  tuple (endPoint1, endPoint2).
        """     
        return self.propMgr.getParameters()

    def _createStructure(self):
        """
        Creates and returns the structure (in this case a L{NanotubeSegment} 
        object. 
        @return : Nanotube segment that include the nanotube chunk.
        @rtype: L{NanotubeSegment}        
        """
        # self.name needed for done message
        if self.create_name_from_prefix:
            # create a new name
            name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct)
            self._gensym_data_for_reusing_name = (self.prefix, name)
        else:
            # use externally created name
            self._gensym_data_for_reusing_name = None
                # (can't reuse name in this case -- not sure what prefix it was
                #  made with)
            name = self.name


        # Create the model tree group node. 
        # Make sure that the 'topnode'  of this part is a Group (under which the
        # DNa group will be placed), if the topnode is not a group, make it a
        # a 'Group' (applicable to Clipboard parts).See part.py
        # --Part.ensure_toplevel_group method. This is an important line
        # and it fixes bug 2585
        self.win.assy.part.ensure_toplevel_group()
        ntSegment = NanotubeSegment(self.name, 
                                    self.win.assy,
                                    self.win.assy.part.topnode,
                                    editCommand = self  )
        try:
            # Make the NanotubeSegment.

            n, m, type, endings, endPoint1, endPoint2 = self._gatherParameters()

            from cnt.model.Nanotube import Nanotube
            self.nanotube = Nanotube()
            nanotube  =  self.nanotube
            nanotube.setChirality(n, m)
            nanotube.setType(type)
            nanotube.setEndings(endings)
            nanotube.setEndPoints(endPoint1, endPoint2)
            position = V(0.0, 0.0, 0.0)
            ntChunk = nanotube.build(self.name, self.win.assy, position)

            nanotube.computeEndPointsFromChunk(ntChunk)

            ntSegment.addchild(ntChunk)

            #set some properties such as nanotubeRise
            #This information will be stored on the NanotubeSegment object so that
            #it can be retrieved while editing this object. 
            #Should these props be assigned to the NanotubeSegment in 
            #Nanotube.build() itself? This needs to be answered while modifying
            #build() method to fit in the dna data model. --Ninad 2008-03-05

            #WARNING 2008-03-05: Since self._modifyStructure calls 
            #self._createStructure() 
            #If in the near future, we actually permit modifying a
            #structure (such as dna) without actually recreating the whole 
            #structure, then the following properties must be set in 
            #self._modifyStructure as well. Needs more thought.
            props =(nanotube.getChirality(),
                    nanotube.getType(),
                    nanotube.getEndings(),
                    nanotube.getEndPoints())

            ntSegment.setProps(props)

            return ntSegment

        except (PluginBug, UserError):
            # Why do we need UserError here? Mark 2007-08-28
            ntSegment.kill()
            raise PluginBug("Internal error while trying to create a NanotubeSegment.")
        return

    def _modifyStructure(self, params):
        """
        Modify the structure based on the parameters specified. 
        Overrides EditCommand._modifystructure. This method removes the old 
        structure and creates a new one using self._createStructure. This 
        was needed for the structures like this (Dna, Nanotube etc) . .
        See more comments in the method.
        """    
        if not pref_nt_segment_resize_by_recreating_nanotube():
            self._modifyStructure_NEW_SEGMENT_RESIZE(params)
            return

        assert self.struct
        # parameters have changed, update existing structure
        self._revertNumber()

        # self.name needed for done message
        if self.create_name_from_prefix:
            # create a new name
            name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct)
            self._gensym_data_for_reusing_name = (self.prefix, name)
        else:
            # use externally created name
            self._gensym_data_for_reusing_name = None
                # (can't reuse name in this case -- not sure what prefix it was
                #  made with)
            name = self.name

        #@NOTE: Unlike editcommands such as Plane_EditCommand, this 
        #editCommand actually removes the structure and creates a new one 
        #when its modified. -- Ninad 2007-10-24

        self._removeStructure()

        self.previousParams = params

        self.struct = self._createStructure()
        # Now append the new structure in self._segmentList (this list of 
        # segments will be provided to the previous command 
        # (BuildDna_EditCommand)
        # TODO: Should self._createStructure does the job of appending the 
        # structure to the list of segments? This fixes bug 2599 
        # (see also BuildDna_PropertyManager.Ok 

        if self._parentNanotubeGroup is not None:
            #Should this be an assertion? (assert self._parentNanotubeGroup is not 
            #None. For now lets just print a warning if parentNanotubeGroup is None 
            self._parentNanotubeGroup.addSegment(self.struct)
        return  


    def _modifyStructure_NEW_SEGMENT_RESIZE(self, params): #@ NOT FIXED
        """
        Modify the structure based on the parameters specified. 
        Overrides EditCommand._modifystructure. This method removes the old 
        structure and creates a new one using self._createStructure. This 
        was needed for the structures like this (Dna, Nanotube etc) . .
        See more comments in the method.

        @attention: is not implemented.
        """        

        #@TODO: - rename this method from _modifyStructure_NEW_SEGMENT_RESIZE
        #to self._modifyStructure, after more testing
        #This method is used for debug prefence: 
        #'Nanotube Segment: resize without recreating whole duplex'
        #see also self.modifyStructure_NEW_SEGMENT_RESIZE

        assert self.struct      

        from utilities.debug import print_compact_stack
        print_compact_stack("_modifyStructure_NEW_SEGMENT_RESIZE() not fixed!" )
        print "Params =", params

        self.nanotube = params #@

        length_diff =  self._determine_how_to_change_length() #@

        if length_diff == 0:
            print_compact_stack("BUG: length_diff is always ZERO." )
            return
        elif length_diff > 0:
            print "Nanotube longer by ", length_diff, ", angstroms."
        else:
            print "Nanotube shorter by ", length_diff, ", angstroms."

        return

        if numberOfBasePairsToAddOrRemove != 0:   #@@@@ Not reached.

            resizeEnd_final_position = self._get_resizeEnd_final_position(
                ladderEndAxisAtom, 
                abs(numberOfBasePairsToAddOrRemove),
                nanotubeRise )

            self.nanotube.modify(self.struct,
                                 length_diff,
                                 ladderEndAxisAtom.posn(),
                                 resizeEnd_final_position)

        #Find new end points of structure parameters after modification 
        #and set these values in the propMgr. 
        new_end1 , new_end2 = self.struct.nanotube.getEndPoints() #@

        params_to_set_in_propMgr = (new_end1,
                                    new_end2)

        #TODO: Need to set these params in the PM 
        #and then self.previousParams = params_to_set_in_propMgr

        self.previousParams = params
        return  

    def _get_resizeEnd_final_position(self, 
                                      ladderEndAxisAtom, 
                                      numberOfBases, 
                                      nanotubeRise):

        final_position = None   
        if self.grabbedHandle:
            final_position = self.grabbedHandle.currentPosition
        else:
            other_axisEndAtom = self.struct.getOtherAxisEndAtom(ladderEndAxisAtom)
            axis_vector = ladderEndAxisAtom.posn() - other_axisEndAtom.posn()
            segment_length_to_add = 0 #@
            final_position = ladderEndAxisAtom.posn() + norm(axis_vector)*segment_length_to_add

        return final_position

    def getStructureName(self):
        """
        Returns the name string of self.struct if there is a valid structure. 
        Otherwise returns None. This information is used by the name edit field 
        of  this command's PM when we call self.propMgr.show()
        @see: NanotubeSegment_PropertyManager.show()
        @see: self.setStructureName
        """
        if self.hasValidStructure():
            return self.struct.name
        return None

    def setStructureName(self, name):
        """
        Sets the name of self.struct to param <name> (if there is a valid 
        structure. 
        The PM of this command callss this method while closing itself 
        @param name: name of the structure to be set.
        @type name: string
        @see: NanotubeSegment_PropertyManager.close()
        @see: self.getStructureName()

        """
        #@BUG: We call this method in self.propMgr.close(). But propMgr.close() 
                #is called even when the command is 'cancelled'. That means the 
                #structure will get changed even when user hits cancel button or
                #exits the command by clicking on empty space. 
                #This should really be done in self._finalizeStructure but that 
                #method doesn't get called when you click on empty space to exit 
                #the command. See NanotubeSegment_GraphicsMode.leftUp for a detailed 
                #comment. 

        if self.hasValidStructure():
            self.struct.name = name
        return

    def getCursorText(self):
        """
        This is used as a callback method in NanotubeLine mode 
        @see: NanotubeLineMode.setParams, NanotubeLineMode_GM.Draw
        """
        if self.grabbedHandle is None:
            return
        
        if not env.prefs[nanotubeSegmentEditCommand_showCursorTextCheckBox_prefs_key]:
            return '', black

        text = ""
        textColor = black

        currentPosition = self.grabbedHandle.currentPosition
        fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure

        nanotubeLength = vlen( currentPosition - fixedEndOfStructure )

        nanotubeLengthString = self._getCursorText_length(nanotubeLength)
        
        text = nanotubeLengthString
   
        #@TODO: The following updates the PM as the cursor moves. 
        #Need to rename this method so that you that it also does more things 
        #than just to return a textString -- Ninad 2007-12-20
        self.propMgr.ntLengthLineEdit.setText(nanotubeLengthString)

        return text, textColor
    
    def _getCursorText_length(self, nanotubeLength):
        """
        Returns a string that gives the length of the Nanotube for the cursor 
        text
        """
        nanotubeLengthString = ''
        if env.prefs[nanotubeSegmentEditCommand_cursorTextCheckBox_length_prefs_key]:
            lengthUnitString = 'A'
            #change the unit of length to nanometers if the length is > 10A
            #fixes part of bug 2856
            if nanotubeLength > 10.0:
                lengthUnitString = 'nm'
                nanotubeLength = nanotubeLength * 0.1
                
            nanotubeLengthString = "%5.3f%s"%(nanotubeLength, lengthUnitString)
        
        return nanotubeLengthString
    
    def modifyStructure(self):
        """
        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{NanotubeSegment_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}        

        As of 2008-02-01 it recreates the structure
        @see: a note in self._createStructure() about use of ntSegment.setProps 
        """

        if not pref_nt_segment_resize_by_recreating_nanotube():
            self.modifyStructure_NEW_SEGMENT_RESIZE()
            return

        if self.grabbedHandle is None:
            return        

        self.propMgr.endPoint1 = self.grabbedHandle.fixedEndOfStructure
        self.propMgr.endPoint2 = self.grabbedHandle.currentPosition
        #@length = vlen(self.propMgr.endPoint1 - self.propMgr.endPoint2 ) #@  

        self.preview_or_finalize_structure(previewing = True)  

        self.updateHandlePositions()
        self.glpane.gl_update()
        return

    def modifyStructure_NEW_SEGMENT_RESIZE(self): #@ NOT FIXED
        """
        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{NanotubeSegment_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}        

        As of 2008-02-01 it recreates the structure
        @see: a note in self._createStructure() about use of ntSegment.setProps 
        """
        #TODO: need to cleanup this and may be use use something like
        #self.previousParams = params in the end -- 2008-03-24 (midnight)


        #@TODO: - rename this method from modifyStructure_NEW_SEGMENT_RESIZE
        #to self.modifyStructure, after more testing
        #This method is used for debug prefence: 
        #'Nanotube Segment: resize without recreating whole duplex'
        #see also self._modifyStructure_NEW_SEGMENT_RESIZE

        if self.grabbedHandle is None:
            return   

        self.propMgr.endPoint1 = self.grabbedHandle.fixedEndOfStructure
        self.propMgr.endPoint2 = self.grabbedHandle.currentPosition

        DEBUG_DO_EVERYTHING_INSIDE_MODIFYSTRUCTURE_METHOD = False

        if DEBUG_DO_EVERYTHING_INSIDE_MODIFYSTRUCTURE_METHOD:

            # TO DO: this entire block of code.  --Mark 2008-04-03
            print_compact_stack("modifyStructure_NEW_SEGMENT_RESIZE(): NOT FIXED")

            length = vlen(self.grabbedHandle.fixedEndOfStructure - \
                          self.grabbedHandle.currentPosition )

            endAtom1, endAtom2 = self.struct.getAxisEndAtoms() #@

            for atm in (endAtom1, endAtom2):
                if not same_vals(self.grabbedHandle.fixedEndOfStructure, atm.posn()):
                    ladderEndAxisAtom = atm
                    break

                endPoint1, endPoint2 = self.struct.nanotube.getEndPoints()
                old_dulex_length = vlen(endPoint1 - endPoint2)

                nanotubeRise = self.struct.getProps()      #@ 

                params_to_set_in_propMgr = (
                    self.grabbedHandle.origin,
                    self.grabbedHandle.currentPosition,
                )

                ##self._modifyStructure(params)
                ############################################

                self.nanotube = Nanotube() #@ Creates 5x5 CNT. Miisng PM params.

                length_diff =  self._determine_how_to_change_length()  
                ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end() #@

                #@ Nanotube class needs modify() method.
                self.nanotube.modify(self.struct, 
                                     length_diff,
                                     ladderEndAxisAtom.posn(),
                                     self.grabbedHandle.currentPosition)

        #TODO: Important note: How does NE1 know that structure is modified? 
        #Because number of base pairs parameter in the PropMgr changes as you 
        #drag the handle . This is done in self.getCursorText() ... not the 
        #right place to do it. OR that method needs to be renamed to reflect
        #this as suggested in that method -- Ninad 2008-03-25

        self.preview_or_finalize_structure(previewing = True) 

        ##self.previousParams = params_to_set_in_propMgr

        self.glpane.gl_update()
        return

    def get_axisEndAtom_at_resize_end(self):
        ladderEndAxisAtom = None
        if self.grabbedHandle is not None:
            ladderEndAxisAtom = self.struct.getAxisEndAtomAtPosition(self.grabbedHandle.origin)
        else:
            endAtom1, endAtom2 = self.struct.getAxisEndAtoms()
            ladderEndAxisAtom = endAtom2

        return ladderEndAxisAtom

    def _determine_how_to_change_length(self): #@ NEEDS WORK
        """
        Returns the difference in length between the original nanotube and the
        modified nanotube, where:
           0 = no change in length
         > 0 = lengthen
         < 0 = trim
        """
        nanotubeRise = self.struct.nanotube.getRise()
        endPoint1, endPoint2 = self.struct.nanotube.getEndPoints() #@ 
        original_nanotube_length = vlen(endPoint1 - endPoint2)
        new_nanotube_length      = vlen(endPoint1 - endPoint2) #@
        return new_nanotube_length - original_nanotube_length #@ ALWAYS RETURNS ZERO

    def makeMenus(self): 
        """
        Create context menu for this command. (Build Dna mode)
        """
        if not hasattr(self, 'graphicsMode'):
            return

        selobj = self.glpane.selobj

        if selobj is None:
            return

        self.Menu_spec = []

        highlightedChunk = None
        if isinstance(selobj, Chunk):
            highlightedChunk = selobj
        if isinstance(selobj, Atom):
            highlightedChunk = selobj.molecule
        elif isinstance(selobj, Bond):
            chunk1 = selobj.atom1.molecule
            chunk2 = selobj.atom2.molecule
            if chunk1 is chunk2 and chunk1 is not None:
                highlightedChunk = chunk1

        if highlightedChunk is None:
            return

        if self.hasValidStructure():        

            nanotubeGroup = self.struct.parent_node_of_class(self.assy.NanotubeGroup)
            if nanotubeGroup is None:
                return
            #following should be self.struct.getNanotubeGroup or self.struct.getNanotubeGroup
            #need to formalize method name and then make change.
            if not nanotubeGroup is highlightedChunk.parent_node_of_class(self.assy.NanotubeGroup):
                item = ("Edit unavailable: Member of a different NanotubeGroup",
                        noop, 'disabled')
                self.Menu_spec.append(item)
                return

        highlightedChunk.make_glpane_context_menu_items(self.Menu_spec,
                                                        command = self)
        return
Esempio n. 18
0
class DnaStrand_EditCommand(State_preMixin, EditCommand):
    """
    Command to edit a DnaStrand (chunk object as of 2008-02-14)

    To edit a Strand, first enter BuildDna_EditCommand (accessed using 
    Build> Dna) then, select a strand chunk of an existing DnaSegment within the 
    DnaGroup you are editing. When you select the strand chunk, it enters 
    DnaStrand_Editcommand, shows its property manager and also shows the 
    resize handles if any.
    """
    cmd              =  'Dna Strand'
    sponsor_keyword  =  'DNA'
    prefix           =  'Strand '   # used for gensym
    cmdname          = "DNA_STRAND"
    commandName       = 'DNA_STRAND'
    featurename       = 'Edit_Dna_Strand'


    command_should_resume_prevMode = True
    command_has_its_own_gui = True
    command_can_be_suspended = False

    # Generators for DNA, nanotubes and graphene have their MT name 
    # generated (in GeneratorBaseClass) from the prefix.
    create_name_from_prefix  =  True 

    call_makeMenus_for_each_event = True 

    #Graphics Mode 
    GraphicsMode_class = DnaStrand_GraphicsMode

    #@see: self.updateHandlePositions for details on how these variables are 
    #used in computation. 
    #@see: DnaStrand_ResizeHandle and handle declaration in this class 
    #definition
    handlePoint1 = State( Point, ORIGIN)
    handlePoint2 = State( Point, ORIGIN)    

    handleDirection1 = State(Vector, ORIGIN)
    handleDirection2 = State(Vector, ORIGIN)

    #See self._update_resizeHandle_radius where this gets changed. 
    #also see DnaSegment_ResizeHandle to see how its implemented. 
    handleSphereRadius1 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)
    handleSphereRadius2 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)

    #handleColors 
    handleColor1 = State(Color, purple)
    handleColor2 = State(Color, purple)

    #TODO: 'cylinderWidth attr used for resize handles -- needs to be renamed 
    #along with 'height_ref attr in exprs.DraggableHandle_AlongLine
    cylinderWidth = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 
    cylinderWidth2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 

    #Resize Handles for Strand. See self.updateHandlePositions()
    leftHandle = Instance(         
        DnaStrand_ResizeHandle( 
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth'),
            origin = handlePoint1,
            fixedEndOfStructure = handlePoint2,
            direction = handleDirection1,
            sphereRadius = handleSphereRadius1,
            handleColor = handleColor1
        ))


    rightHandle = Instance( 
        DnaStrand_ResizeHandle(
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth2'),
            origin = handlePoint2,
            fixedEndOfStructure = handlePoint1,
            direction = handleDirection2,
            sphereRadius = handleSphereRadius2,
            handleColor = handleColor2
        ))


    def __init__(self, commandSequencer, struct = None):
        """
        Constructor for DnaDuplex_EditCommand
        """

        glpane = commandSequencer
        State_preMixin.__init__(self, glpane)        
        EditCommand.__init__(self, commandSequencer)
        self.struct = struct

        #DnaSegment object to which this strand belongs 
        self._parentDnaSegment = None

        #It uses BuildDna_EditCommand.flyoutToolbar ( in other words, that 
        #toolbar is still accessible and not changes while in this command)
        flyoutToolbar = None

        #Graphics handles for editing the structure . 
        self.handles = []        
        self.grabbedHandle = None

        #This is used for comarison purpose in model_changed method to decide
        #whether to update the sequence. 
        self._previousNumberOfBases = None

    def init_gui(self):
        """
        Initialize gui. 
        """
        #@see DnaSegment_EditCommand.init_gui() for a detailed note. 
        #This command implements similar thing 
        self.create_and_or_show_PM_if_wanted(showPropMgr = False)

    def model_changed(self):
        #This MAY HAVE BUG. WHEN --
        #debug pref 'call model_changed only when needed' is ON
        #See related bug 2729 for details. 

        #The following code that updates te handle positions and the strand 
        #sequence fixes bugs like 2745 and updating the handle positions
        #updating handle positions in model_changed instead of in 
        #self.graphicsMode._draw_handles() is also a minor optimization
        #This can be further optimized by debug pref 
        #'call model_changed only when needed' but its NOT done because of an 
        # issue menitoned in bug 2729   - Ninad 2008-04-07    

        EditCommand.model_changed(self) #This also calls the 
                                        #propMgr.model_changed 

        if self.grabbedHandle is not None:
            return

        #For Rattlesnake, PAM5 segment resizing  is not supported. 
        #@see: self.hasResizableStructure()        
        if self.hasValidStructure():
            isStructResizable, why_not = self.hasResizableStructure()
            if not isStructResizable:
                self.handles = []
                return
            elif len(self.handles) == 0:
                self._updateHandleList()

            self.updateHandlePositions()
            #NOTE: The following also updates self._previousParams
            self._updateStrandSequence_if_needed()


    def keep_empty_group(self, group):
        """
        Returns True if the empty group should not be automatically deleted. 
        otherwise returns False. The default implementation always returns 
        False. Subclasses should override this method if it needs to keep the
        empty group for some reasons. Note that this method will only get called
        when a group has a class constant autdelete_when_empty set to True. 
        (and as of 2008-03-06, it is proposed that dna_updater calls this method
        when needed. 
        @see: Command.keep_empty_group() which is overridden here. 
        """

        bool_keep = EditCommand.keep_empty_group(self, group)

        if not bool_keep:     
            if self.hasValidStructure():                
                if group is self.struct:
                    bool_keep = True
                elif group is self.struct.parent_node_of_class(self.assy.DnaGroup):
                    bool_keep = True

        return bool_keep

    def _createPropMgrObject(self):
        """
        Creates a property manager  object (that defines UI things) for this 
        editCommand. 
        """
        assert not self.propMgr
        propMgr = self.win.createDnaStrandPropMgr_if_needed(self)
        return propMgr

    def _gatherParameters(self):
        """
        Return the parameters from the property manager UI.

        @return: All the parameters (get those from the property manager):
                 - numberOfBases
                 - dnaForm
                 - basesPerTurn
                 - endPoint1
                 - endPoint2
        @rtype:  tuple
        """     
        return self.propMgr.getParameters()

    def _createStructure(self):
        """
        Creates and returns the structure -- TO BE IMPLEMENTED ONCE 
        DNA_MODEL IS READY FOR USE.
        @return : DnaStrand.
        @rtype: L{Group}  
        @note: This needs to return a DNA object once that model is implemented        
        """
        pass

    def _modifyStructure(self, params):
        """
        Modify the structure based on the parameters specified. 
        Overrides EditCommand._modifystructure. This method removes the old 
        structure and creates a new one using self._createStructure. This 
        was needed for the structures like this (Dna, Nanotube etc) . .
        See more comments in the method.
        """        


        assert self.struct
        # parameters have changed, update existing structure
        self._revertNumber()


        # self.name needed for done message
        if self.create_name_from_prefix:
            # create a new name
            name = self.name = gensym(self.prefix, self.assy) # (in _build_struct)
            self._gensym_data_for_reusing_name = (self.prefix, name)
        else:
            # use externally created name
            self._gensym_data_for_reusing_name = None
                # (can't reuse name in this case -- not sure what prefix it was
                #  made with)
            name = self.name

        self.dna = B_Dna_PAM3_SingleStrand()

        numberOfBases, \
                     dnaForm, \
                     dnaModel, \
                     basesPerTurn, \
                     duplexRise, \
                     color_junk = params
        #see a note about color_junk in DnaSegment_EditCommand._modifyStructure()


        numberOfBasesToAddOrRemove =  self._determine_numberOfBases_to_change()

        if numberOfBasesToAddOrRemove != 0: 
            resizeEndStrandAtom, resizeEndAxisAtom = \
                               self.get_strand_and_axis_endAtoms_at_resize_end()

            if resizeEndAxisAtom:
                dnaSegment = resizeEndAxisAtom.molecule.parent_node_of_class(
                    self.assy.DnaSegment)

                resizeEnd_final_position = self._get_resizeEnd_final_position(
                    resizeEndAxisAtom, 
                    abs(numberOfBasesToAddOrRemove),
                    duplexRise )

                self.dna.modify(dnaSegment, 
                                resizeEndAxisAtom,
                                numberOfBasesToAddOrRemove, 
                                basesPerTurn, 
                                duplexRise,
                                resizeEndAxisAtom.posn(),
                                resizeEnd_final_position,
                                resizeEndStrandAtom = resizeEndStrandAtom
                            )                        

        return  

    def _finalizeStructure(self):
        """
        Overrides EditCommand._finalizeStructure. 
        @see: EditCommand.preview_or_finalize_structure
        """     
        if self.struct is not None:
            #@TODO 2008-03-19:Should we always do this even when strand sequence
            #is not changed?? Should it waste time in comparing the current 
            #sequence with a previous one? Assigning sequence while leaving the 
            #command will be slow for large files.Need to be optimized if 
            #problematic.
            #What if a flag is set in self.propMgr.updateSequence() when it 
            #updates the seq for the second time and we check that here. 
            #Thats  not good if the method gets called multiple times for some 
            #reason other than the user entered sequence. So not doing here. 
            #Better fix would be to check if sequence gets changed (textChanged)
            #in DnaSequence editor class .... Need to be revised
            EditCommand._finalizeStructure(self) 
            self._updateStrandSequence_if_needed()
            self.assignStrandSequence()

    def _previewStructure(self):        
        EditCommand._previewStructure(self)
        self.updateHandlePositions()
        self._updateStrandSequence_if_needed()


    def _updateStrandSequence_if_needed(self):
        if self.hasValidStructure():            
            new_numberOfBases = self.struct.getNumberOfBases()

            #@TODO Update self._previousParams again? 
            #This NEEDS TO BE REVISED. BUG MENTIONED BELOW----
            #we already update this in EditCommand class. But, it doesn't work for 
            #the following bug -- 1. create a duplex with 5 basepairs, 2. resize 
            #red strand to 2 bases. 3. Undo 4. Redo 5. Try to resize it again to 
            #2 bases -- it doesn't work because self.previousParams stil stores 
            #bases as 2 and thinks nothing got changed! Can we declare 
            #self._previewStructure as a undiable state? Or better self._previousParams
            #should store self.struct and should check method return value 
            #like self.struct.getNumberOfBases()--Ninad 2008-04-07
            if self.previousParams is not None:
                if new_numberOfBases != self.previousParams[0]:
                    self.propMgr.numberOfBasesSpinBox.setValue(new_numberOfBases)
                    self.previousParams = self._gatherParameters()
            if not same_vals(new_numberOfBases, self._previousNumberOfBases):
                self.propMgr.updateSequence()
                self._previousNumberOfBases = new_numberOfBases    

    def _get_resizeEnd_final_position(self, 
                                      resizeEndAxisAtom, 
                                      numberOfBases, 
                                      duplexRise):

        final_position = None   
        if self.grabbedHandle:
            final_position = self.grabbedHandle.currentPosition
        else:
            dnaSegment = resizeEndAxisAtom.molecule.parent_node_of_class(self.assy.DnaSegment)
            other_axisEndAtom = dnaSegment.getOtherAxisEndAtom(resizeEndAxisAtom)
            axis_vector = resizeEndAxisAtom.posn() - other_axisEndAtom.posn()
            segment_length_to_add = getDuplexLength('B-DNA', 
                                                    numberOfBases, 
                                                    duplexRise = duplexRise)

            final_position = resizeEndAxisAtom.posn() + norm(axis_vector)*segment_length_to_add

        return final_position


    def getStructureName(self):
        """
        Returns the name string of self.struct if there is a valid structure. 
        Otherwise returns None. This information is used by the name edit field 
        of  this command's PM when we call self.propMgr.show()
        @see: DnaStrand_PropertyManager.show()        
        @see: self.setStructureName
        """
        if self.hasValidStructure():
            return self.struct.name
        else:
            return None

    def setStructureName(self, name):
        """
        Sets the name of self.struct to param <name> (if there is a valid 
        structure. 
        The PM of this command callss this method while closing itself 
        @param name: name of the structure to be set.
        @type name: string
        @see: DnaStrand_PropertyManager.close()
        @see: self.getStructureName()
        @see: DnaSegment_GraphicsMode.leftUp , 
              DnaSegment_editCommand.setStructureName for comments about some 
              issues.         
        """

        if self.hasValidStructure():
            self.struct.name = name

    def assignStrandSequence(self):
        """
        Assigns the sequence typed in the sequence editor text field to 
        the selected strand chunk. The method it invokes also assigns 
        a complimentary sequence to the mate strand.
        @see: Chunk.setStrandSequence
        """
        sequenceString = self.propMgr.sequenceEditor.getPlainSequence()
        sequenceString = str(sequenceString)      
        #assign strand sequence only if it not the same as the current sequence
        seq = self.struct.getStrandSequence()
        
        if seq != sequenceString:
            self.struct.setStrandSequence(sequenceString) 


    def editStructure(self, struct = None):
        """
        Edit the structure 
        @param struct: structure to be edited (in this case its a strand chunk)
        @type struct: chunk or None (this will change post dna data model)
        """
        EditCommand.editStructure(self, struct)        
        if self.hasValidStructure():
            self._updatePropMgrParams()            

            #TO BE REVISED post dna data model - 2008-02-14
            if isinstance(self.struct.dad , self.assy.DnaSegment):
                self._parentDnaSegment = self.struct.dad   

            #For Rattlesnake, we do not support resizing of PAM5 model. 
            #So don't append the exprs handles to the handle list (and thus 
            #don't draw those handles. See self.model_changed() 
            isStructResizable, why_not = self.hasResizableStructure()
            if not isStructResizable:
                self.handles = []
            else:
                self._updateHandleList()
                self.updateHandlePositions()
                
    def _updatePropMgrParams(self):
        """
        Subclasses may override this method. 
        Update some property manager parameters with the parameters of
        self.struct (which is being edited)
        @see: self.editStructure()
        """

        #Format in which params need to be provided to the Property manager
        
                #numberOfBases, 
                #dnaForm,
                #dnaModel,
                #basesPerTurn,
                #duplexRise, 
                #color

        self._previousNumberOfBases = self.struct.getNumberOfBases()
        numberOfBases = self._previousNumberOfBases        
        color = self.struct.getColor()

        params_for_propMgr = ( numberOfBases,
                               None, 
                               None,
                               None, 
                               None,                               
                               color )


        #TODO 2008-03-25: better to get all parameters from self.struct and
        #set it in propMgr?  This will mostly work except that reverse is 
        #not true. i.e. we can not specify same set of params for 
        #self.struct.setProps ...because endPoint1 and endPoint2 are derived.
        #by the structure when needed. Commenting out following line of code
        #UPDATE 2008-05-06 Fixes a bug due to which the parameters in propMGr
        #of DnaSegment_EditCommand are not same as the original structure
        #(e.g. bases per turn and duplexrise)
        self.propMgr.setParameters(params_for_propMgr)
        


    def _getStructureType(self):
        """
        Subclasses override this method to define their own structure type. 
        Returns the type of the structure this editCommand supports. 
        This is used in isinstance test. 
        @see: EditCommand._getStructureType() (overridden here)
        """
        return self.win.assy.DnaStrand
    
    def updateSequence(self):
        """
        Public method provided for convenience. If any callers outside of this 
        command need to update the sequence in the sequence editor, they can simply 
        do currentCommand.updateSequence() rather than 
        currentCommand.propMgr.updateSequence()
        @see: DnaStrand_ProprtyManager.updateSequence() which does the actual 
        job of updating the sequence string in the sequence editor.
        """
        if self.propMgr is not None:
            self.propMgr.updateSequence()


    def hasResizableStructure(self):
        """
        For Rattlesnake release, we dont support strand resizing for PAM5 
        models. If the structure is not resizable, the handles won't be drawn
        @see:self.model_changed()
        @see:DnaStrand_PropertyManager.model_changed()
        @see: self.editStructure()
        @see: DnaSegment.is_PAM3_DnaStrand()
        """
        #Note: This method fixes bugs similar to (and including) bug 2812 but 
        #the changes didn't made it to Rattlesnake rc2 -- Ninad 2008-04-16
        isResizable = True
        why_not = ''

        if not self.hasValidStructure():
            isResizable = False
            why_not     = 'It is invalid.'
            return isResizable, why_not        

        isResizable = self.struct.is_PAM3_DnaStrand()

        if not isResizable:
            why_not = 'It needs to be converted to PAM3 model.'
            return isResizable, why_not

        #The following fixes bug 2812
        strandEndBaseAtom1, strandEndBaseAtom2 = self.struct.get_strand_end_base_atoms()

        if strandEndBaseAtom1 is strandEndBaseAtom2:
            isResizable = False
            why_not = "It is probably a \'closed loop\'."
            return isResizable, why_not


        return True, ''


    def _updateHandleList(self):
        """        
        Updates the list of handles (self.handles) 
        @see: self.editStructure
        @see: DnaSegment_GraphicsMode._drawHandles()
        """        
        # note: if handlePoint1 and/or handlePoint2 can change more often than 
        # this runs, we'll need to rerun the two assignments above whenever they
        # change and before the handle is drawn. An easy way would be to rerun
        # these assignments in the draw method of our GM. [bruce 080128]
        self.handles = [] # guess, but seems like a good idea [bruce 080128]
        self.handles.append(self.leftHandle)
        self.handles.append(self.rightHandle)

    def updateHandlePositions(self):
        """
        Update handle positions
        """
        if len(self.handles) == 0:
            #No handles are appended to self.handles list. 
            #@See self.model_changed() and self._updateHandleList()
            return

        self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE
        self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE 

        axisAtom1 = None
        axisAtom2 = None
        strandEndBaseAtom1 = None
        strandEndBaseAtom2 = None  

        #It could happen (baecause of bugs) that standEndBaseAtom1 and 
        #strandEndBaseAtom2 are one and the same! i.e strand end atom is 
        #not bonded. If this happens, we should throw a traceback bug 
        #exit gracefully. The possible cause of this bug may be ---
        #(just an example): For some reason, the modify code is unable to 
        #determine the correct bond direction of the resized structure
        #and therefore, while fusing the original strand with the new one created
        #by DnaDuplex.modify, it is unable to find bondable pairs! 
        # [- Ninad 2008-03-31]
        strandEndBaseAtom1, strandEndBaseAtom2 = self.struct.get_strand_end_base_atoms()
        if strandEndBaseAtom1 is strandEndBaseAtom2:
            print_compact_stack("bug in updating handle positions, some resized DnaStrand " \
                                "has only one atom")
            return


        if strandEndBaseAtom1:
            axisAtom1 = strandEndBaseAtom1.axis_neighbor()
            if axisAtom1:
                self.handleDirection1 = self._get_handle_direction(axisAtom1, 
                                                                   strandEndBaseAtom1)                
                self.handlePoint1 = axisAtom1.posn()                

        if strandEndBaseAtom2:
            axisAtom2 = strandEndBaseAtom2.axis_neighbor()

            if axisAtom2:
                self.handleDirection2 = self._get_handle_direction(axisAtom2, 
                                                                   strandEndBaseAtom2)
                self.handlePoint2 = axisAtom2.posn()   


        # UPDATE 2008-04-15:
        # Before 2008-04-15 the state attrs for exprs handles were always reset to None
        #at the beginning of the method. But it is calling model_changed signal
        #recursively. (se also bug 2729) So, reset thos state attrs only when 
        #needed  -- Ninad  [ this fix was not in RattleSnake rc1] 

        # Set handlePoints (i.e. their origins) and the handle directions to 
        # None if the atoms used to compute these state attrs are missing. 
        # The GraphicsMode checks if the handles have valid placement 
        # attributes set before drawing it.    

        if strandEndBaseAtom1 is None and strandEndBaseAtom2 is None:
            #probably a ring
            self.handles = []


        if strandEndBaseAtom1 is None or axisAtom1 is None:            
            self.handleDirection1 = None
            self.handlePoint1 = None

        if strandEndBaseAtom2 is None or axisAtom2 is None:            
            self.handleDirection2 = None
            self.handlePoint2 = None

        #update the radius of resize handle 
        self._update_resizeHandle_radius(axisAtom1, axisAtom2)

        #update the display color of the handles
        self._update_resizeHandle_color(strandEndBaseAtom1, strandEndBaseAtom2)

    def _update_resizeHandle_radius(self, axisAtom1, axisAtom2):
        """
        Finds out the sphere radius to use for the resize handles, based on 
        atom /chunk or glpane display (whichever decides the display of the end 
        atoms.  The default  value is 1.2.


        @see: self.updateHandlePositions()
        @see: B{Atom.drawing_radius()}
        """               

        if axisAtom1 is not None:
            self.handleSphereRadius1 = max(1.005*axisAtom1.drawing_radius(), 
                                           1.005*HANDLE_RADIUS_DEFAULT_VALUE)
        if axisAtom2 is not None: 
            self.handleSphereRadius2 =  max(1.005*axisAtom2.drawing_radius(), 
                                            1.005*HANDLE_RADIUS_DEFAULT_VALUE) 

    def _update_resizeHandle_color(self, 
                                   strandEndBaseAtom1, 
                                   strandEndBaseAtom2):
        """
        Update the color of resize handles using the current color 
        of the *chunk* of the corresponding strand end atoms. 
        @Note: If no specific color is set to the chunk, it uses 'purple' as
        the default color (and doesn't use the atom.element.color) . This 
        can be changed if needed.
        """
        if strandEndBaseAtom1 is not None:
            color = strandEndBaseAtom1.molecule.color 
            if color: 
                self.handleColor1 = color

        if strandEndBaseAtom2 is not None:
            color = strandEndBaseAtom2.molecule.color 
            if color: 
                self.handleColor2 = color

    def _get_handle_direction(self, axisAtom, strandAtom):
        """
        Get the handle direction.
        """

        handle_direction = None

        strand_rail = strandAtom.molecule.get_ladder_rail() 

        for bond_direction in (1, -1):
            next_strand_atom = strandAtom.strand_next_baseatom(bond_direction)
            if next_strand_atom:
                break

        if next_strand_atom:
            next_axis_atom = next_strand_atom.axis_neighbor()
            if next_axis_atom:
                handle_direction = norm(axisAtom.posn() - next_axis_atom.posn())

        return handle_direction

    def getDnaRibbonParams(self):
        """
        Returns parameters for drawing the dna ribbon. 

        If the dna rubberband line should NOT be drawn (example when you are 
        removing bases from the strand or  if its unable to get dnaSegment) , 
        it retuns None. So the caller should check if the method return value
        is not None. 
        @see: DnaStrand_GraphicsMode._draw_handles()
        """

        if self.grabbedHandle is None:
            return None

        if self.grabbedHandle.origin is None:
            return None

        direction_of_drag = norm(self.grabbedHandle.currentPosition - \
                                 self.grabbedHandle.origin)

        #If the strand bases are being removed (determined by checking the 
        #direction of drag) , no need to draw the rubberband line. 
        if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
            return None

        strandEndAtom, axisEndAtom = self.get_strand_and_axis_endAtoms_at_resize_end()

        #DnaStrand.get_DnaSegment_with_content_atom saely handles the case where
        #strandEndAtom is None. 
        dnaSegment = self.struct.get_DnaSegment_with_content_atom(strandEndAtom)

        ribbon1_direction = None

        if dnaSegment:     
            basesPerTurn = dnaSegment.getBasesPerTurn()
            duplexRise = dnaSegment.getDuplexRise()        
            ribbon1_start_point = strandEndAtom.posn()

            if strandEndAtom:
                ribbon1_start_point = strandEndAtom.posn()
                for bond_direction, neighbor in strandEndAtom.bond_directions_to_neighbors():
                    if neighbor and neighbor.is_singlet():
                        ribbon1_direction = bond_direction
                        break

            ribbon1Color = strandEndAtom.molecule.color
            if not ribbon1Color:
                ribbon1Color = strandEndAtom.element.color

            return (self.grabbedHandle.origin,
                    self.grabbedHandle.currentPosition,
                    basesPerTurn, 
                    duplexRise, 
                    ribbon1_start_point,
                    ribbon1_direction,
                    ribbon1Color
                )

        return None



    def get_strand_and_axis_endAtoms_at_resize_end(self):
        resizeEndStrandAtom = None        
        resizeEndAxisAtom = None

        strandEndAtom1, strandEndAtom2 = self.struct.get_strand_end_base_atoms()

        if self.grabbedHandle is not None:
            for atm in (strandEndAtom1, strandEndAtom2):
                axisEndAtom = atm.axis_neighbor()
                if axisEndAtom:
                    if same_vals(axisEndAtom.posn(), self.grabbedHandle.origin):
                        resizeEndStrandAtom = atm
                        resizeEndAxisAtom = axisEndAtom


        return (resizeEndStrandAtom, resizeEndAxisAtom)


    def getCursorText(self):
        """
        Used by DnaStrand_GraphicsMode._draw_handles()
        @TODO: It also updates the number of bases
        """
        if self.grabbedHandle is None:
            return

        #@TODO: This updates the PM as the cursor moves. 
        #Need to rename this method so that you that it also does more things 
        #than just to return a textString. Even better if its called in 
        #self.model_changed but at the moment there is a bug thats why we 
        #are doing this update by calling getCursorText in the 
        #GraphicscMode._draw_handles-- Ninad 2008-04-05
        self.update_numberOfBases()
        
        if not env.prefs[dnaStrandEditCommand_showCursorTextCheckBox_prefs_key]:
            return '', black


        #Note: for Rattlesnake rc2, the text color is green when bases are added
        #, red when subtracted black when no change. But this implementation is 
        #changed based on Mark's user experience. The text is now always shown
        #in black color. -- Ninad 2008-04-17
        textColor = black     

        text = ""  
        
        numberOfBases = self.propMgr.numberOfBasesSpinBox.value()
        
        numberOfBasesString = self._getCursorText_numberOfBases(
            numberOfBases)
        
        changedBases = self._getCursorText_changedBases(
            numberOfBases)
        
        text = numberOfBasesString 
        
        if text and changedBases:
            text += " " # Removed comma. Mark 2008-07-03
        
        text += changedBases                   

        return (text , textColor)
    
    def _getCursorText_numberOfBases(self, numberOfBases):
        """
        Return the cursor textstring that gives information about the number 
        of bases if the corresponding prefs_key returns True.
        """
        numberOfBasesString = ''

        if env.prefs[
            dnaStrandEditCommand_cursorTextCheckBox_numberOfBases_prefs_key]:
            numberOfBasesString = "%db"%numberOfBases

        return numberOfBasesString

    def _getCursorText_changedBases(self, numberOfBases):
        """
        @see: self.getCursorText()
        """
        changedBasesString = ''

        if env.prefs[
            dnaStrandEditCommand_cursorTextCheckBox_changedBases_prefs_key]:
            
            original_numberOfBases = self.struct.getNumberOfBases()
            changed_bases = numberOfBases - original_numberOfBases            

            if changed_bases > 0:
                changedBasesString = "(" + "+" + str(changed_bases) + ")"
            else:
                changedBasesString = "(" + str(changed_bases) + ")"

        return changedBasesString


    def modifyStructure(self):
        """

        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{DnaStrand_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}         
        """
        #TODO: need to cleanup this and may be use use something like
        #self.previousParams = params in the end -- 2008-03-24 (midnight)


        if self.grabbedHandle is None:
            return   

        #TODO: Important note: How does NE1 know that structure is modified? 
        #Because number of base pairs parameter in the PropMgr changes as you 
        #drag the handle . This is done in self.getCursorText() ... not the 
        #right place to do it. OR that method needs to be renamed to reflect
        #this as suggested in that method -- Ninad 2008-03-25        
        self.preview_or_finalize_structure(previewing = True) 

        self.glpane.gl_update()

    def update_numberOfBases(self):
        """
        Updates the numberOfBases in the PM while a resize handle is being 
        dragged. 
        @see: self.getCursorText() where it is called. 
        """
        #@Note: originally (before 2008-04-05, it was called in 
        #DnaStrand_ResizeHandle.on_drag() but that 'may' have some bugs 
        #(not verified) also see self.getCursorText() to know why it is
        #called there (basically a workaround for bug 2729
        if self.grabbedHandle is None:
            return

        currentPosition = self.grabbedHandle.currentPosition
        resize_end = self.grabbedHandle.origin

        new_duplexLength = vlen( currentPosition - resize_end )

        numberOfBasePairs_to_change = getNumberOfBasePairsFromDuplexLength('B-DNA', 
                                                                           new_duplexLength)

        original_numberOfBases = self.struct.getNumberOfBases()
        #If the dot product of handle direction and the direction in which it 
        #is dragged is negative, this means we need to subtract bases
        direction_of_drag = norm(currentPosition - resize_end)
        if dot(self.grabbedHandle.direction, direction_of_drag ) < 0:
            total_number_of_bases = original_numberOfBases - numberOfBasePairs_to_change
            self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases)
        else:
            total_number_of_bases = original_numberOfBases + numberOfBasePairs_to_change
            self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases - 1)


    def _determine_numberOfBases_to_change(self):
        """
        """       
        #The Property manager will be showing the current number 
        #of base pairs (w. May be we can use that number directly here? 
        #The following is  safer to do so lets just recompute the 
        #number of base pairs. (if it turns out to be slow, we will consider
        #using the already computed calue from the property manager

        original_numberOfBases = self.struct.getNumberOfBases()

        numberOfBasesToAddOrRemove = self.propMgr.numberOfBasesSpinBox.value()\
                                   - original_numberOfBases 

        if numberOfBasesToAddOrRemove > 0:
            #dna.modify will remove the first base pair it creates 
            #(that basepair will only be used for proper alignment of the 
            #duplex with the existing structure) So we need to compensate for
            #this basepair by adding 1 to the new number of base pairs. 
            numberOfBasesToAddOrRemove += 1


        return numberOfBasesToAddOrRemove


    def makeMenus(self): 
        """
        Create context menu for this command. (Build Dna mode)
        """
        if not hasattr(self, 'graphicsMode'):
            return

        selobj = self.glpane.selobj

        if selobj is None:
            return

        self.Menu_spec = []

        highlightedChunk = None
        if isinstance(selobj, Chunk):
            highlightedChunk = selobj
        if isinstance(selobj, Atom):
            highlightedChunk = selobj.molecule
        elif isinstance(selobj, Bond):
            chunk1 = selobj.atom1.molecule
            chunk2 = selobj.atom2.molecule
            if chunk1 is chunk2 and chunk1 is not None:
                highlightedChunk = chunk1

        if highlightedChunk is None:
            return

        if self.hasValidStructure():
            if (self.struct is highlightedChunk) or \
               (self.struct is highlightedChunk.parent_node_of_class(
                   self.assy.DnaStrand)):
                item = (("Currently editing %r"%self.struct.name), 
                        noop, 'disabled')
                self.Menu_spec.append(item)
                return	 
            #following should be self.struct.getDnaGroup or self.struct.getDnaGroup
            #need to formalize method name and then make change.
            dnaGroup = self.struct.parent_node_of_class(self.assy.DnaGroup)
            if dnaGroup is None:
                return            
            if not dnaGroup is highlightedChunk.parent_node_of_class(self.assy.DnaGroup):                
                item = ("Edit unavailable: Member of a different DnaGroup",
                        noop, 'disabled')
                self.Menu_spec.append(item)
                return

        highlightedChunk.make_glpane_context_menu_items(self.Menu_spec,
                                                        command = self)
Esempio n. 19
0
class GraphDrawDemo_FixedToolOnArg1(
        InstanceMacro
):  # see also class World_dna_holder -- need to unify them as a ui-provider framework
    # args
    background = Arg(Widget2D, Rect(10))
    # testexpr_19a, 061207 morn -- see if arb objects work here, and try drawing on a curved surface --
    # works! (when the surface was the default here -- now also tested/works when it's the arg supplied by that testexpr)
    #
    # ... except for desirable improvements:
    # - we should replace DZ with "local perp to surface" to make the drawn things more visible.
    # - And we *might* want to replace depth with "computed true depth", since for a sphere, as we rotate the view
    #   the drawn radius slightly changes due to where the triangle faces are located, and this can bury the drawing of marks
    #   if they are really close to the sphere --
    # - either that, or record their posns relative to the sphere surface,
    #   which might be most correct anyway -- and especially useful if we change the radius of the sphere! (making it balloon-like)
    # - Also those things are oriented in global coords rather than coords based on the clicked surface.
    #   Fixing that would also make them "look oriented" and help you perceive their depth when they're over a curved surface.
    #
    # Note that all these ideas would require asking the object for the surface orientation, and for how to store coords (relative
    # to what), and in the given eg, getting quite different answers (incl about how to transform coords for storage, re scaling)
    # depending on whether the rect or sphere was clicked --
    # which the current code does not even detect, since it gives them the same glname. ###e
    # options
    highlight_color = Option(
        Color, None
    )  # suggest: ave_colors(0.9,gray,white)) # use this only if background takes a color option
    use_VertexView = Option(
        bool, False
    )  # 070105 so I can try out new code w/o breaking old code #### TRYIT
    world = Option(
        World, World(),
        doc="the set of model objects")  # revised 070228 for use in _19j
    test_background_object = Option(
        bool, False,
        doc="test the new testmode._background_object feature")  #070322
    hide_background_object = Option(bool, False)
    # internals
    highlightable_background = \
        Highlightable( background, #######   WAIT A MINUTE,   how can we do that -- background is already an instance?!? ######@@@@@@
                       ## background(color=green),####KLUGE, causes various bugs or weirdnesses... not yet fully understood,
                       ## e.g. AssertionError: compute method asked for on non-Instance <Rect#10415(a)>
                       ## [GLPane_overrider.py:455] [Highlightable.py:275] [Rect.py:52]
                       ##Rect(5,5,green),###KLUGE2 - works now that highlightable is not broken by projection=True [also works 061213]
                       ## background.copy(color=green), # oops i mean:
                       ## call_Expr(background.copy,)( color=green), # oops, i have to include eval_Expr:
                       If( highlight_color,
                          eval_Expr( call_Expr(background.copy,)( color = highlight_color) ),
                               # can't work unless background is simple like a Rect,
                               # but does work then! (edit _19d to include _19 not _19b)
                          background ## None -- I hoped None would be equivalent, noticed in HL and replaced, but that would be hard,
                           # would require it to notice each time whether the opt was suppied or not, do default each time
                           # rather than per-time, also require supplying None to be same as not supplying the arg (not true now)
                           # (maybe passing some other symbol should be same as that?? and permitted each time? But If->None is so
                           #  easy, by leaving out the arg... ###e decide)
                        ),
                       #e want this to work too: call_Expr(background.copy, color=green),
                       # -- just let copy take **kws and pass them on, or let it call a customize helper
                       # review of the kluges:
                       # - the explicit call_Expr remains annoying but the lack of simple fix still seems true; not sure;
                       #   could we experiment by turning off that check within certain classes? not sure if that's possible
                       #   since it happens during expr-building. ##k
                       # - the need for eval_Expr seems wrong somehow, in fact i'm not sure I understand why we need it.
                       #   Will it go away in new planned eval/instantiation scheme? not sure. I think so.
                       # - copy needs **kws.
                       # - does the .copy itself need to be explicit? that is, could supplying args/opts to an instance copy it implicitly??
                       #   this might fit with other instances doing other stuff when those were supplied.... ###e
                       # - should Overlay take color option and pass it on into leaves (subexprs)? what if some leaves won't take it?
                       #   that has been discussed elsewhere... i forget if dynenv or _optional_options = dict() seemed best.
                       on_press = _self.on_press_bg,
                       on_drag = _self.on_drag_bg,
                       on_release = _self.on_release_bg,
                       sbar_text = "gray bg"
                       )
    use_highlightable_background = If(
        test_background_object,
        BackgroundObject(highlightable_background,
                         hide=hide_background_object),
        DisplayListChunk(  # new feature as of 070103; works, and seems to be faster (hard to be sure)
            highlightable_background))
    ## world = Instance( World() ) # maintains the set of objects in the model
    _value = Overlay(
        use_highlightable_background,
        If(
            _self.use_VertexView,
            DisplayListChunk(WithViewerFunc(world, viewerfunc)),
            # try DisplayListChunk, 070103 later -- works, doesn't break dragging of contained old nodes.
            DisplayListChunk(world)  ##, debug_prints = "World")
            ## world # zap DisplayListChunk to see if it fixes new 070115 bug about dragging old nodes -- nope
        ))

    newnode = None  # note: name conflict(?) with one of those not yet used Command classes

    def on_press_bg(self):
        if 0:
            print "compare:"
            print self, self.delegate, self.delegate.delegate, self.delegate.delegate.plain  # the background
            print self.background
            print "hl.highlighted =", self.delegate.delegate.highlighted
            # self.background is the same as the .plain printed above, which means, as of 061208 941pm anyway,
            # instantiating an instance gives exactly that instance. (Reasonable for now...)

        point = self.current_event_mousepoint()
        #e note: current_event_mousepoint is defined only on Highlightable, for now (see comments there for how we need to fix that),
        # but works here because we delegate ultimately to a Highlightable, without changing local coords as we do.
        # note: lots of devel scratch & debug comments removed 061207; see cvs rev 1.5 for them.

        # for initial test, don't use those Command classes above, just do a side effect right here ###kluge

        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! [more comment on that in demo_draw_on_surface.py]

        if not self.use_VertexView:
            # old code
            ## print "make node in old way (not using VertexView)" # still running as of 070115 at least in testexpr_19f
            node_expr = Vertex(
                newpos,
                Center(
                    Rect(
                        0.2,
                        0.2,
                        ## 'green', -- now we cycle through several colors: (colors,...)[counter % 6]
                        ## tuple_Expr(green,yellow,red,blue,white,black)[mod_Expr(_this(Vertex).ipath[0],6)]
                        red  # the above worked ok until tested 070121 morn -- ipath now starts with string.
                        # it was a kluge anyway, so disable it until we can rework it to be sensible.
                    )))
        else:
            # new code, being written 070105, just getting started -- mostly nim
            node_expr = Vertex_new(
                newpos,
                # cycle through several colors: (colors,...)[counter % 6]
                color=tuple_Expr(green, yellow, red, blue, white,
                                 black)[mod_Expr(_this(Vertex).ipath[0], 6)])
            pass

        ## draggable_node_expr = Highlightable(node_expr, on_drag = _self.on_drag_node, sbar_text = "dne")
        ###BUG: this breaks dragging of the new node; it fails to print the call message from on_drag_node;
        # if you try to drag an old node made this way, it doesn't work but says
        # debug fyi: len(names) == 2 (names = (268L, 269L))
        # Guess: limitation in current rendering code makes it not work for any nested glnames, but just print this instead...
        # (note: even after reload, the node objects in the world have their old Vertex class, and the old expr used to make them)
        #
        # [later 061213:] IIRC the status was: I made GLPane_overrider so I could fix that 2-glname issue in it,
        # but never got to that yet. Meanwhile I commented out the use of this expr, and thus on_drag_node is never used...
        # and Vertexes dragged directly do nothing -- they're highlightable but with no actions.
        # And World could probably draw them highlightable even if they weren't, but it doesn't.
        # BTW the disabled nonworking draggable_node_expr is not well-designed -- it does not add a Vertex to World, it adds a
        # draggable one -- but even World is not perfect, since it contains Vertexes (not just their data)
        # and they inherently have (a lack of) action bindings since they are Highlightable.
        # Probably better would be if World contained data-nodes and had access to (or had its own) display rules for them
        # which added commands/actions based on the currently active tools. That would help with tool-code-reloading too.
        # Probably some other comments here say this too.
        #
        # So does a World need a formula or function arg for how to map its data objects to display objects, at the moment?
        # Or is there some scheme of a global map for that, to be applied when "drawing" any data object?
        # And do some data objs have their own positions, or is that always supplied by the world or other data obj they're in?
        # In theory, we might display atoms at posns unrelated to atom.pos, e.g. as a row in a table which includes their coords.
        # So it's more like we have ways of "drawing a set of things" which can say "at posns given by func(thing)"
        # or "at successive posns in a column", corresponding to two display forms with different exprs,
        # with the map from thing to individual display form also needing to be specified.
        # So a World is more like a set of things, and it can have a display mode (or more than one), given a thing-display-function.
        # We can ask it or anything else how it recommends displaying itself given display style options,
        # but we can choose to use that display function (from it to a more directly displayable object) or use another one.
        # Or we can probably just "draw it" and have it pick up the current display style from the env (including the
        # currently active tools). Is there any reason not to permit both? (draw using current style, draw using given style,
        # give me function from you to drawables using given style, use specific function and draw the results -- all possible.)
        #
        # If a thing in a world has standard mouse actions of its own, can it also have "grabbable areas" for use in dragging it
        # when it has a posn as displayed in some world? Or did that world have to explicitly turn it into a draggable thing?
        # Answer: both. The world turns it into that by adding a drag binding for those "overall handles" the thing has.
        # It might draw them with glnames in some set it knows... ie as named subobjs of itself. The overall thing might also
        # have a single name. Then we have a sequence of two glnames meaning obj/subobj which we want to use to determine the action.
        # For some subobjs that's within the object and supplied by it (perhaps depending on tool); for others,
        # it's supplied by the World it's in (also dep on a tool) and handled by it (eg move the obj, select the obj).
        #
        # For the simple things we have there, there are no subobjects, and no actions except drag or later select the whole thing.
        # A simple model is "one thing was hit, but some things are specified by a specific series of two or more glnames".
        # In general the outer name decides how to interpret (or whether to ignore) the inner names.
        # It can map the inner ones somehow... not sure how. This will relate a lot to DisplayListChunk when we have that.
        # Mere nested Highlightables might push two names but both would be unique. Outer name might just defer to inner one then.

        if 0:
            ## MAKE THIS WORK:
            draggable_node_expr = 'define this'
            newnode = self.world.make_and_add(draggable_node_expr,
                                              type="Vertex")
        else:
            newnode = self.world.make_and_add(
                node_expr, type="Vertex")  #070206 added type = "Vertex"

        self.newnode = newnode  ###KLUGE that we store it directly in self; might work tho; we store it only for use by on_drag_bg
        return  # from on_press_bg


##    def on_drag_node(self):
##        print "on_drag_node called -- how can we know *which* node it was called on??"
##        # 070103 status guess: this is not called; old cmts above seem to say that the only problem with it working is nested glnames.
##        return

    def on_drag_bg(self):
        # note: so far, anyway, called only for drag after click on empty space, not from drag after click on existing node
        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
        ##        try:
        ##            lastipath = lastnode.ipath[0]
        ##        except:
        ##            lastipath = -1
        ##        # print "on_drag_bg %d" % lastipath, point###  # this shows no error in retaining correct lastnode -- that's not the bug
        ## print "on_drag_bg"
        newpos = point + DZ * DZFUZZ  # used for different things, depending

        what = kluge_dragtool_state()  ###IMPLEM better
        if what == 'draw':
            # make a blue dot showing the drag path, without moving the main new node (from the click)
            node_expr = Vertex(newpos, Center(Rect(0.1, 0.1, blue)))
            self.world.make_and_add(
                node_expr, type="dot"
            )  #070206 added type = "dot" -- note, not deducible from the expr!!
        elif what == 'polyline':
            if not lastnode:
                print "bug: no self.newnode!!!"
            else:
                if not isinstance(lastnode, polyline):
                    lastnode = self.newnode = self.world.make_and_add(
                        polyline(lastnode), type="polyline")
                lastnode.add_point(newpos)
        elif what == 'drag':
            # drag the new node made by the click
            if not lastnode:
                print "bug: no self.newnode!!!"
            else:
                lastnode.pos = newpos
            pass
        return

    def on_release_bg(self):  #070223 new hack
        import foundation.env as env  #FIX
        if isinstance(self.newnode, polyline) 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

    # == methods make, make_and_add have been moved from here into class World [070202]

    pass  # end of class GraphDrawDemo_FixedToolOnArg1
Esempio n. 20
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
Esempio n. 21
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
class EditNanotube_EditCommand(State_preMixin, EditCommand):
    """
    Command to edit a NanotubeSegment (nanotube).
    """
    # class constants
    GraphicsMode_class = EditNanotube_GraphicsMode
    PM_class = EditNanotube_PropertyManager
    
    commandName      = 'EDIT_NANOTUBE'
    featurename      = "Edit Nanotube"
    from utilities.constants import CL_SUBCOMMAND
    command_level = CL_SUBCOMMAND
    command_parent = 'BUILD_NANOTUBE'
    
    command_should_resume_prevMode = True
    command_has_its_own_PM = True
    
    flyoutToolbar = None

    call_makeMenus_for_each_event = True 

    handlePoint1 = State( Point, ORIGIN)
    handlePoint2 = State( Point, ORIGIN)
    #The minimum 'stopper'length used for resize handles
    #@see: self._update_resizeHandle_stopper_length for details. 
    _resizeHandle_stopper_length = State(Width, -100000)

    rotationHandleBasePoint1 = State( Point, ORIGIN)
    rotationHandleBasePoint2 = State( Point, ORIGIN)

    #See self._update_resizeHandle_radius where this gets changed. 
    #also see EditNanotube_ResizeHandle to see how its implemented. 
    handleSphereRadius1 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)
    handleSphereRadius2 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)

    cylinderWidth = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 
    cylinderWidth2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE) 

    #@TODO: modify the 'State params for rotation_distance 
    rotation_distance1 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    rotation_distance2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    leftHandle = Instance(         
        EditNanotube_ResizeHandle(    
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth'),
            origin = handlePoint1,
            fixedEndOfStructure = handlePoint2,
            direction = norm_Expr(handlePoint1 - handlePoint2),
            sphereRadius = handleSphereRadius1, 
            range = (_resizeHandle_stopper_length, 10000)                               
        ))

    rightHandle = Instance( 
        EditNanotube_ResizeHandle(
            command = _self,
            height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth2'),
            origin = handlePoint2,
            fixedEndOfStructure = handlePoint1,
            direction = norm_Expr(handlePoint2 - handlePoint1),
            sphereRadius = handleSphereRadius2,
            range = (_resizeHandle_stopper_length, 10000)
        ))

    rotationHandle1 = Instance(         
        RotationHandle(    
            command = _self,
            rotationDistanceRef = call_Expr( ObjAttr_StateRef,
                                             _self, 
                                             'rotation_distance1'),
                                             center = handlePoint1,
                                             axis = norm_Expr(handlePoint1 - handlePoint2),
                                             origin = rotationHandleBasePoint1,
                                             radiusVector = norm_Expr(rotationHandleBasePoint1 - handlePoint1)

                                         ))

    rotationHandle2 = Instance(         
        RotationHandle(    
            command = _self,
            rotationDistanceRef = call_Expr( ObjAttr_StateRef,
                                             _self, 
                                             'rotation_distance2'),
                                             center = handlePoint2,
                                             axis = norm_Expr(handlePoint2 - handlePoint1),
                                             origin = rotationHandleBasePoint2,
                                             radiusVector = norm_Expr(rotationHandleBasePoint2 - handlePoint2)

                                         ))

    def __init__(self, commandSequencer):
        """
        Constructor for InsertDna_EditCommand
        """
        glpane = commandSequencer.assy.glpane
        State_preMixin.__init__(self, glpane)        
        EditCommand.__init__(self, commandSequencer)
        
        #Graphics handles for editing the structure . 
        self.handles = []        
        self.grabbedHandle = None

        #Initialize DEBUG preference
        pref_nt_segment_resize_by_recreating_nanotube()
        return
    
    def editStructure(self, struct = None):
        EditCommand.editStructure(self, struct)        
        if self.hasValidStructure():

            #TODO 2008-03-25: better to get all parameters from self.struct and
            #set it in propMgr?  This will mostly work except that reverse is 
            #not true. i.e. we can not specify same set of params for 
            #self.struct.setProps ...because endPoint1 and endPoint2 are derived.
            #by the structure when needed.
            self.propMgr.setParameters(self.struct.getProps())

            #Store the previous parameters. Important to set it after you 
            #set nanotube attrs in the propMgr. 
            #self.previousParams is used in self._previewStructure and 
            #self._finalizeStructure to check if self.struct changed.
            self.previousParams = self._gatherParameters()
            self._updateHandleList()
            self.updateHandlePositions()
        return

    def hasValidStructure(self):
        """
        Tells the caller if this edit command has a valid structure. 
        Overrides EditCommand.hasValidStructure()
        """
        #(By Bruce 2008-02-13)

        isValid = EditCommand.hasValidStructure(self)

        if not isValid:
            return isValid 

        # would like to check here whether it's empty of axis chunks;
        # instead, this will do for now (probably too slow, though):
        p1, p2 = self.struct.nanotube.getEndPoints()
        return (p1 is not None)

    def _getStructureType(self):
        """
        Subclasses override this method to define their own structure type. 
        Returns the type of the structure this editCommand supports. 
        This is used in isinstance test. 
        @see: EditCommand._getStructureType() (overridden here)
        """
        return self.win.assy.NanotubeSegment

    def _updateHandleList(self):
        """        
        Updates the list of handles (self.handles) 
        @see: self.editStructure
        @see: EditNanotube_GraphicsMode._drawHandles()
        """   
        # note: if handlePoint1 and/or handlePoint2 can change more often than this 
        # runs, we'll need to rerun the two assignments above whenever they 
        # change and before the handle is drawn. An easy way would be to rerun
        # these assignments in the draw method of our GM. [bruce 080128]
        self.handles = [] # guess, but seems like a good idea [bruce 080128]
        self.handles.append(self.leftHandle)
        self.handles.append(self.rightHandle)
        if DEBUG_ROTATION_HANDLES:
            self.handles.append(self.rotationHandle1)
            self.handles.append(self.rotationHandle2)
        return

    def updateHandlePositions(self):
        """
        Update handle positions and also update the resize handle radii and
        their 'stopper' lengths. 
        @see: self._update_resizeHandle_radius()
        @see: self._update_resizeHandle_stopper_length()
        @see: EditNanotube_GraphicsMode._drawHandles()
        """
        self.handlePoint1 = None # Needed!
        self.handlePoint2 = None

        #TODO: Call this method less often by implementing model_changed
        #see bug 2729 for a planned optimization
        self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE
        self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE      

        self._update_resizeHandle_radius()

        handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints()

        if 0: # Debug prints
            print "updateHandlePositions(): handlePoint1=", handlePoint1
            print "updateHandlePositions(): handlePoint2=", handlePoint2

        if handlePoint1 is not None and handlePoint2 is not None:
            # (that condition is bugfix for deleted axis segment, bruce 080213)

            self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2            

            #Update the 'stopper'  length where the resize handle being dragged 
            #should stop. See self._update_resizeHandle_stopper_length()
            #for more details
            self._update_resizeHandle_stopper_length()            

            if DEBUG_ROTATION_HANDLES:
                self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE
                self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE
                #Following computes the base points for rotation handles. 
                #to be revised -- Ninad 2008-02-13
                unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2)

                v  = cross(self.glpane.lineOfSight, unitVectorAlongAxis)

                self.rotationHandleBasePoint1 = self.handlePoint1 + norm(v) * 4.0  
                self.rotationHandleBasePoint2 = self.handlePoint2 + norm(v) * 4.0
        return

    def _update_resizeHandle_radius(self):
        """
        Finds out the sphere radius to use for the resize handles, based on 
        atom /chunk or glpane display (whichever decides the display of the end 
        atoms. The default value is 1.2.

        @see: self.updateHandlePositions()
        """
        self.handleSphereRadius1 = HANDLE_RADIUS_DEFAULT_VALUE
        self.handleSphereRadius2 = HANDLE_RADIUS_DEFAULT_VALUE
        return

    def _update_resizeHandle_stopper_length(self):
        """
        Update the limiting length at which the resize handle being dragged
        should 'stop'  without proceeding further in the drag direction. 
        The segment resize handle stops when you are dragging it towards the 
        other resizeend and the distance between the two ends reaches two 
        duplexes. 

        The self._resizeHandle_stopper_length computed in this method is 
        used as a lower limit of the 'range' option provided in declaration
        of resize handle objects (see class definition for the details)
        @see: self.updateHandlePositions()
        """

        total_length = vlen(self.handlePoint1 - self.handlePoint2)        
        nanotubeRise = self.struct.nanotube.getRise()
        self._resizeHandle_stopper_length = - total_length + nanotubeRise
        return

    
    def _gatherParameters(self):
        """
        Return the parameters from the property manager UI.

        @return: The endpoints of the nanotube.
        @rtype:  tuple (endPoint1, endPoint2).
        """     
        return self.propMgr.getParameters()

    def _createStructure(self):
        """
        Returns the current NanotubeSegment being edited with a new nanotube 
        chunk.
        @return : Nanotube segment that include the new nanotube chunk.
        @rtype: L{NanotubeSegment}        
        """
        
        try:
            # Create a new nanotube chunk using new params.
            n, m, type, endings, endPoint1, endPoint2 = self._gatherParameters()
            from cnt.model.NanotubeParameters import NanotubeParameters
            self.nanotube = NanotubeParameters()
            nanotube  =  self.nanotube
            nanotube.setChirality(n, m)
            nanotube.setType(type)
            nanotube.setEndings(endings)
            nanotube.setEndPoints(endPoint1, endPoint2)
            position = V(0.0, 0.0, 0.0)
            ntChunk = nanotube.build(self.struct.name, self.win.assy, position)
            nanotube.computeEndPointsFromChunk(ntChunk) # Needed.
            self.struct.addchild(ntChunk)
            
            #WARNING 2008-03-05:
            #When we actually permit modifying a nanotube without recreating it,
            #then the following properties must be set in self._modifyStructure 
            #as well. Needs more thought.
            props =(nanotube.getChirality(),
                    nanotube.getType(),
                    nanotube.getEndings(),
                    nanotube.getEndPoints())

            self.struct.setProps(props)

            return self.struct

        except (PluginBug, UserError):
            self.struct.kill()
            raise PluginBug("Internal error while trying to recreate a NanotubeSegment.")
        return None

    def _modifyStructure(self, params):
        """
        Modify the structure based on the parameters specified. 
        Overrides EditCommand._modifystructure. This method removes the old 
        nanotube and replaces it with a new one using self._createStructure.
        """    
        if not pref_nt_segment_resize_by_recreating_nanotube():
            self._modifyStructure_NEW_SEGMENT_RESIZE(params)
            return

        # To modify the structure, we need to:
        # 1. Delete the current nanotube chunk inside the NanotubeSegment group.
        # 2. Create a new new nanotube chunk using nanotube.build()
        # 3. Add the newly generated nanotube to this NanotubeSegment.
        # 4. Update all the nanotube parameters.
        # Note: Steps 2-4 are done in self._createStructure()
        assert self.struct
        self.struct.members[0].kill() # Delete the current nanotube chunk.
        self.previousParams = params
        self.struct = self._createStructure()
        return

    def _modifyStructure_NEW_SEGMENT_RESIZE(self, params): #@ NOT FIXED
        """
        This resizes without recreating whole nanotube 
        Overrides EditCommand._modifystructure.
        @attention: is not implemented.
        """        

        #@TODO: - rename this method from _modifyStructure_NEW_SEGMENT_RESIZE
        #to self._modifyStructure, after more testing
        #This method is used for debug prefence: 
        #'Nanotube Segment: resize without recreating whole nanotube'
        #see also self.modifyStructure_NEW_SEGMENT_RESIZE

        assert self.struct      

        from utilities.debug import print_compact_stack
        print_compact_stack("_modifyStructure_NEW_SEGMENT_RESIZE() not fixed!" )
        print "Params =", params

        self.nanotube = params #@

        length_diff =  self._determine_how_to_change_length() #@

        if length_diff == 0:
            print_compact_stack("BUG: length_diff is always ZERO." )
            return
        elif length_diff > 0:
            print "Nanotube longer by ", length_diff, ", angstroms."
        else:
            print "Nanotube shorter by ", length_diff, ", angstroms."

        return

        if numberOfBasePairsToAddOrRemove != 0:   #@@@@ Not reached.

            resizeEnd_final_position = self._get_resizeEnd_final_position(
                ladderEndAxisAtom, 
                abs(numberOfBasePairsToAddOrRemove),
                nanotubeRise )

            self.nanotube.modify(self.struct,
                                 length_diff,
                                 ladderEndAxisAtom.posn(),
                                 resizeEnd_final_position)

        #Find new end points of structure parameters after modification 
        #and set these values in the propMgr. 
        new_end1 , new_end2 = self.struct.nanotube.getEndPoints() #@

        params_to_set_in_propMgr = (new_end1,
                                    new_end2)

        #TODO: Need to set these params in the PM 
        #and then self.previousParams = params_to_set_in_propMgr

        self.previousParams = params
        return  

    def _get_resizeEnd_final_position(self, 
                                      ladderEndAxisAtom, 
                                      numberOfBases, 
                                      nanotubeRise):

        final_position = None   
        if self.grabbedHandle:
            final_position = self.grabbedHandle.currentPosition
        else:
            other_axisEndAtom = self.struct.getOtherAxisEndAtom(ladderEndAxisAtom)
            axis_vector = ladderEndAxisAtom.posn() - other_axisEndAtom.posn()
            segment_length_to_add = 0 #@
            final_position = ladderEndAxisAtom.posn() + norm(axis_vector)*segment_length_to_add

        return final_position

    def getStructureName(self):
        """
        Returns the name string of self.struct if there is a valid structure. 
        Otherwise returns None. This information is used by the name edit field 
        of this command's PM when we call self.propMgr.show()
        @see: EditNanotube_PropertyManager.show()
        @see: self.setStructureName
        """
        if self.hasValidStructure():
            return self.struct.name
        return None

    def setStructureName(self, name):
        """
        Sets the name of self.struct to param <name> (if there is a valid 
        structure. 
        The PM of this command callss this method while closing itself 
        @param name: name of the structure to be set.
        @type name: string
        @see: EditNanotube_PropertyManager.close()
        @see: self.getStructureName()

        """
        #@BUG: We call this method in self.propMgr.close(). But propMgr.close() 
                #is called even when the command is 'cancelled'. That means the 
                #structure will get changed even when user hits cancel button or
                #exits the command by clicking on empty space. 
                #This should really be done in self._finalizeStructure but that 
                #method doesn't get called when you click on empty space to exit 
                #the command. See EditNanotube_GraphicsMode.leftUp for a detailed 
                #comment. 

        if self.hasValidStructure():
            self.struct.name = name
        return

    def getCursorText(self):
        """
        This is used as a callback method in NanotubeLine mode 
        @see: NanotubeLineMode.setParams, NanotubeLineMode_GM.Draw
        """
        if self.grabbedHandle is None:
            return
        
        text = ''
        textColor = env.prefs[cursorTextColor_prefs_key]
        
        if not env.prefs[editNanotubeEditCommand_showCursorTextCheckBox_prefs_key]:
            return text, textColor

        currentPosition = self.grabbedHandle.currentPosition
        fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure

        nanotubeLength = vlen( currentPosition - fixedEndOfStructure )

        nanotubeLengthString = self._getCursorText_length(nanotubeLength)
        
        text = nanotubeLengthString
   
        #@TODO: The following updates the PM as the cursor moves. 
        #Need to rename this method so that you that it also does more things 
        #than just to return a textString -- Ninad 2007-12-20
        self.propMgr.ntLengthLineEdit.setText(nanotubeLengthString)

        return text, textColor
    
    def _getCursorText_length(self, nanotubeLength):
        """
        Returns a string that gives the length of the Nanotube for the cursor 
        text
        """
        nanotubeLengthString = ''
        if env.prefs[editNanotubeEditCommand_cursorTextCheckBox_length_prefs_key]:
            lengthUnitString = 'A'
            #change the unit of length to nanometers if the length is > 10A
            #fixes part of bug 2856
            if nanotubeLength > 10.0:
                lengthUnitString = 'nm'
                nanotubeLength = nanotubeLength * 0.1
                
            nanotubeLengthString = "%5.3f%s"%(nanotubeLength, lengthUnitString)
        
        return nanotubeLengthString
    
    def modifyStructure(self):
        """
        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{EditNanotube_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}        

        As of 2008-02-01 it recreates the structure
        @see: a note in self._createStructure() about use of ntSegment.setProps 
        """

        if not pref_nt_segment_resize_by_recreating_nanotube():
            self.modifyStructure_NEW_SEGMENT_RESIZE()
            return

        if self.grabbedHandle is None:
            return        

        self.propMgr.endPoint1 = self.grabbedHandle.fixedEndOfStructure
        self.propMgr.endPoint2 = self.grabbedHandle.currentPosition
        #@length = vlen(self.propMgr.endPoint1 - self.propMgr.endPoint2 ) #@  

        self.preview_or_finalize_structure(previewing = True)  

        self.updateHandlePositions()
        self.glpane.gl_update()
        return

    def modifyStructure_NEW_SEGMENT_RESIZE(self): #@ NOT FIXED
        """
        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{EditNanotube_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}        

        As of 2008-02-01 it recreates the structure
        @see: a note in self._createStructure() about use of ntSegment.setProps 
        """
        #TODO: need to cleanup this and may be use use something like
        #self.previousParams = params in the end -- 2008-03-24 (midnight)


        #@TODO: - rename this method from modifyStructure_NEW_SEGMENT_RESIZE
        #to self.modifyStructure, after more testing
        #This method is used for debug prefence: 
        #'Nanotube Segment: resize without recreating whole duplex'
        #see also self._modifyStructure_NEW_SEGMENT_RESIZE

        if self.grabbedHandle is None:
            return   

        self.propMgr.endPoint1 = self.grabbedHandle.fixedEndOfStructure
        self.propMgr.endPoint2 = self.grabbedHandle.currentPosition

        DEBUG_DO_EVERYTHING_INSIDE_MODIFYSTRUCTURE_METHOD = False

        if DEBUG_DO_EVERYTHING_INSIDE_MODIFYSTRUCTURE_METHOD:

            # TO DO: this entire block of code.  --Mark 2008-04-03
            print_compact_stack("modifyStructure_NEW_SEGMENT_RESIZE(): NOT FIXED")

            length = vlen(self.grabbedHandle.fixedEndOfStructure - \
                          self.grabbedHandle.currentPosition )

            endAtom1, endAtom2 = self.struct.getAxisEndAtoms() #@

            for atm in (endAtom1, endAtom2):
                if not same_vals(self.grabbedHandle.fixedEndOfStructure, atm.posn()):
                    ladderEndAxisAtom = atm
                    break

                endPoint1, endPoint2 = self.struct.nanotube.getEndPoints()
                old_dulex_length = vlen(endPoint1 - endPoint2)

                nanotubeRise = self.struct.getProps()      #@ 

                params_to_set_in_propMgr = (
                    self.grabbedHandle.origin,
                    self.grabbedHandle.currentPosition,
                )

                ##self._modifyStructure(params)
                ############################################

                self.nanotube = NanotubeParameters() #@ Creates 5x5 CNT. Missing PM params.

                length_diff =  self._determine_how_to_change_length()  
                ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end() #@

                #@ Nanotube class needs modify() method.
                self.nanotube.modify(self.struct, 
                                     length_diff,
                                     ladderEndAxisAtom.posn(),
                                     self.grabbedHandle.currentPosition)

        #TODO: Important note: How does NE1 know that structure is modified? 
        #Because number of base pairs parameter in the PropMgr changes as you 
        #drag the handle . This is done in self.getCursorText() ... not the 
        #right place to do it. OR that method needs to be renamed to reflect
        #this as suggested in that method -- Ninad 2008-03-25

        self.preview_or_finalize_structure(previewing = True) 

        ##self.previousParams = params_to_set_in_propMgr

        self.glpane.gl_update()
        return

    def get_axisEndAtom_at_resize_end(self):
        ladderEndAxisAtom = None
        if self.grabbedHandle is not None:
            ladderEndAxisAtom = self.struct.getAxisEndAtomAtPosition(self.grabbedHandle.origin)
        else:
            endAtom1, endAtom2 = self.struct.getAxisEndAtoms()
            ladderEndAxisAtom = endAtom2

        return ladderEndAxisAtom

    def _determine_how_to_change_length(self): #@ NEEDS WORK
        """
        Returns the difference in length between the original nanotube and the
        modified nanotube, where:
           0 = no change in length
         > 0 = lengthen
         < 0 = trim
        """
        nanotubeRise = self.struct.nanotube.getRise()
        endPoint1, endPoint2 = self.struct.nanotube.getEndPoints() #@ 
        original_nanotube_length = vlen(endPoint1 - endPoint2)
        new_nanotube_length      = vlen(endPoint1 - endPoint2) #@
        return new_nanotube_length - original_nanotube_length #@ ALWAYS RETURNS ZERO

    def makeMenus(self): 
        """
        Create context menu for this command.
        """
        if not hasattr(self, 'graphicsMode'):
            return

        selobj = self.glpane.selobj

        if selobj is None:
            return

        self.Menu_spec = []

        highlightedChunk = None
        if isinstance(selobj, Chunk):
            highlightedChunk = selobj
        if isinstance(selobj, Atom):
            highlightedChunk = selobj.molecule
        elif isinstance(selobj, Bond):
            chunk1 = selobj.atom1.molecule
            chunk2 = selobj.atom2.molecule
            if chunk1 is chunk2 and chunk1 is not None:
                highlightedChunk = chunk1

        if highlightedChunk is None:
            return

        highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self)
        return
class DnaSegment_EditCommand(State_preMixin, EditCommand):
    """
    Command to edit a DnaSegment object. 
    To edit a segment, first enter BuildDna_EditCommand (accessed using Build> Dna) 
    then, select an axis chunk of an existing DnaSegment  within the DnaGroup you
    are editing. When you select the axis chunk, it enters DnaSegment_Editcommand
    and shows the property manager with its widgets showing the properties of 
    selected segment.
    """
    cmd = 'Dna Segment'
    sponsor_keyword = 'DNA'
    prefix = 'Segment '  # used for gensym
    cmdname = "DNA_SEGMENT"
    commandName = 'DNA_SEGMENT'
    featurename = 'Edit Dna Segment'

    command_should_resume_prevMode = True
    command_has_its_own_gui = True
    command_can_be_suspended = False

    # Generators for DNA, nanotubes and graphene have their MT name
    # generated (in GeneratorBaseClass) from the prefix.
    create_name_from_prefix = True

    call_makeMenus_for_each_event = True

    #Graphics Mode
    GraphicsMode_class = DnaSegment_GraphicsMode

    #This is set to BuildDna_EditCommand.flyoutToolbar (as of 2008-01-14,
    #it only uses
    flyoutToolbar = None

    handlePoint1 = State(Point, ORIGIN)
    handlePoint2 = State(Point, ORIGIN)
    #The minimum 'stopper'length used for resize handles
    #@see: self._update_resizeHandle_stopper_length for details.
    _resizeHandle_stopper_length = State(Width, -100000)

    rotationHandleBasePoint1 = State(Point, ORIGIN)
    rotationHandleBasePoint2 = State(Point, ORIGIN)

    #See self._update_resizeHandle_radius where this gets changed.
    #also see DnaSegment_ResizeHandle to see how its implemented.
    handleSphereRadius1 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)
    handleSphereRadius2 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)

    cylinderWidth = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    cylinderWidth2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    #@TODO: modify the 'State params for rotation_distance
    rotation_distance1 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    rotation_distance2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    duplexRise = getDuplexRise('B-DNA')

    leftHandle = Instance(
        DnaSegment_ResizeHandle(
            command=_self,
            height_ref=call_Expr(ObjAttr_StateRef, _self, 'cylinderWidth'),
            origin=handlePoint1,
            fixedEndOfStructure=handlePoint2,
            direction=norm_Expr(handlePoint1 - handlePoint2),
            sphereRadius=handleSphereRadius1,
            discRadius=env.prefs[dnaSegmentResizeHandle_discRadius_prefs_key],
            discThickness=env.
            prefs[dnaSegmentResizeHandle_discThickness_prefs_key],
            range=(_resizeHandle_stopper_length, 10000)))

    rightHandle = Instance(
        DnaSegment_ResizeHandle(
            command=_self,
            height_ref=call_Expr(ObjAttr_StateRef, _self, 'cylinderWidth2'),
            origin=handlePoint2,
            fixedEndOfStructure=handlePoint1,
            direction=norm_Expr(handlePoint2 - handlePoint1),
            sphereRadius=handleSphereRadius2,
            discRadius=env.prefs[dnaSegmentResizeHandle_discRadius_prefs_key],
            discThickness=env.
            prefs[dnaSegmentResizeHandle_discThickness_prefs_key],
            range=(_resizeHandle_stopper_length, 10000)))

    rotationHandle1 = Instance(
        RotationHandle(command=_self,
                       rotationDistanceRef=call_Expr(ObjAttr_StateRef, _self,
                                                     'rotation_distance1'),
                       center=handlePoint1,
                       axis=norm_Expr(handlePoint1 - handlePoint2),
                       origin=rotationHandleBasePoint1,
                       radiusVector=norm_Expr(rotationHandleBasePoint1 -
                                              handlePoint1)))

    rotationHandle2 = Instance(
        RotationHandle(command=_self,
                       rotationDistanceRef=call_Expr(ObjAttr_StateRef, _self,
                                                     'rotation_distance2'),
                       center=handlePoint2,
                       axis=norm_Expr(handlePoint2 - handlePoint1),
                       origin=rotationHandleBasePoint2,
                       radiusVector=norm_Expr(rotationHandleBasePoint2 -
                                              handlePoint2)))

    def __init__(self, commandSequencer, struct=None):
        """
        Constructor for DnaDuplex_EditCommand
        """

        glpane = commandSequencer
        State_preMixin.__init__(self, glpane)
        EditCommand.__init__(self, commandSequencer)
        self.struct = struct

        #Graphics handles for editing the structure .
        self.handles = []
        self.grabbedHandle = None

    def init_gui(self):
        """
        Initialize gui. 
        """

        #Note that DnaSegment_EditCommand only act as an edit command for an
        #existing structure. The call to self.propMgr.show() is done only during
        #the call to self.editStructure ..i .e. only after self.struct is
        #updated. This is done because of the following reason:
        # - self.init_gui is called immediately after entering the command.
        # - self.init_gui in turn, initialized propMgr object and may also
        #  show the property manager. The self.propMgr.show routine calls
        #  an update widget method just before the show. This update method
        #  updates the widgets based on the parameters from the existing
        #  structure of the command (self.editCommand.struct)
        #  Although, it checks whether this structure exists, the editCommand
        #  could still have a self.struct attr from a previous run. (Note that
        #  EditCommand API was written before the command sequencer API and
        #  it has some loose ends like this. ) -- Ninad 2008-01-22
        self.create_and_or_show_PM_if_wanted(showPropMgr=False)

    def model_changed(self):
        #This MAY HAVE BUG. WHEN --
        #debug pref 'call model_changed only when needed' is ON
        #See related bug 2729 for details.

        #The following code that updates te handle positions and the strand
        #sequence fixes bugs like 2745 and updating the handle positions
        #updating handle positions in model_changed instead of in
        #self.graphicsMode._draw_handles() is also a minor optimization
        #This can be further optimized by debug pref
        #'call model_changed only when needed' but its NOT done because of an
        # issue menitoned in bug 2729   - Ninad 2008-04-07
        EditCommand.model_changed(self)  #This also calls the
        #propMgr.model_changed

        if self.grabbedHandle is not None:
            return

        #For Rattlesnake, PAM5 segment resizing  is not supported.
        #@see: self.hasResizableStructure()
        if self.hasValidStructure():
            isStructResizable, why_not = self.hasResizableStructure()
            if not isStructResizable:
                self.handles = []
                return
            elif len(self.handles) == 0:
                self._updateHandleList()

            self.updateHandlePositions()

            self._update_previousParams_in_model_changed()

    def _update_previousParams_in_model_changed(self):
        #The following fixes bug 2802. The bug comment has details of what
        #it does. Copying some portion of it below--
        #We have fixed similar problem for strand resizing, by updating the
        #self.previousParams attr in model_changed method (and also updating
        #the numberOfBasePairs spinbox in the PM. But here, user can even
        #change the number of basepairs from the PM. When he does that,
        #the model_changed is called and it resets the number of basepairs
        #spinbox value with  the ones currently on the structure! Thereby
        #making it impossible to upate structure using spinbox.  To fix this
        #we introduce a new parameter in propMgr.getParameters() which
        #reports the actual number of bases on the structure.
        #-- Ninad 2008-04-12
        if self.previousParams is not None:
            new_numberOfBasePairs = self.struct.getNumberOfBasePairs()
            if new_numberOfBasePairs != self.previousParams[0]:
                self.propMgr.numberOfBasePairsSpinBox.setValue(
                    new_numberOfBasePairs)
                self.previousParams = self.propMgr.getParameters()

    def editStructure(self, struct=None):
        EditCommand.editStructure(self, struct)
        if self.hasValidStructure():
            self._updatePropMgrParams()

            #Store the previous parameters. Important to set it after you
            #set duplexRise and basesPerTurn attrs in the propMgr.
            #self.previousParams is used in self._previewStructure and
            #self._finalizeStructure to check if self.struct changed.
            self.previousParams = self._gatherParameters()

            #For Rattlesnake, we do not support resizing of PAM5 model.
            #So don't append the exprs handles to the handle list (and thus
            #don't draw those handles. See self.model_changed()
            isStructResizable, why_not = self.hasResizableStructure()
            if not isStructResizable:
                self.handles = []
            else:
                self._updateHandleList()
                self.updateHandlePositions()

    def _updatePropMgrParams(self):
        """
        Subclasses may override this method. 
        Update some property manager parameters with the parameters of
        self.struct (which is being edited)
        @see: self.editStructure()
        """

        #Format in which params need to be provided to the Property manager
        #(i.e. in propMgr.setParameters():
        #numberOfBasePairs,
        #dnaForm,
        #dnaModel,
        #basesPerTurn,
        #duplexRise,
        #endPoint1,
        #endPoint2

        basesPerTurn, duplexRise = self.struct.getProps()
        endPoint1, endPoint2 = self.struct.getAxisEndPoints()
        numberOfBasePairs = self.struct.getNumberOfBasePairs()
        color = self.struct.getColor()

        params_for_propMgr = (numberOfBasePairs, None, None, basesPerTurn,
                              duplexRise, endPoint1, endPoint2, color)

        #TODO 2008-03-25: better to get all parameters from self.struct and
        #set it in propMgr?  This will mostly work except that reverse is
        #not true. i.e. we can not specify same set of params for
        #self.struct.setProps ...because endPoint1 and endPoint2 are derived.
        #by the structure when needed. Commenting out following line of code
        #UPDATE 2008-05-06 Fixes a bug due to which the parameters in propMGr
        #of DnaSegment_EditCommand are not same as the original structure
        #(e.g. bases per turn and duplexrise)
        self.propMgr.setParameters(params_for_propMgr)

    def keep_empty_group(self, group):
        """
        Returns True if the empty group should not be automatically deleted. 
        otherwise returns False. The default implementation always returns 
        False. Subclasses should override this method if it needs to keep the
        empty group for some reasons. Note that this method will only get called
        when a group has a class constant autdelete_when_empty set to True. 
        (and as of 2008-03-06, it is proposed that dna_updater calls this method
        when needed. 
        @see: Command.keep_empty_group() which is overridden here. 
        @see: BreakStrands_Command.keep_empty_group
        @see: Group.autodelete_when_empty.. a class constant used by the 
              dna_updater (the dna updater then decides whether to call this 
              method to see which empty groups need to be deleted)
        """

        bool_keep = EditCommand.keep_empty_group(self, group)

        if not bool_keep:
            if self.hasValidStructure():
                if group is self.struct:
                    bool_keep = True
                elif group is self.struct.parent_node_of_class(
                        self.assy.DnaGroup):
                    bool_keep = True
            #If this command doesn't have a valid structure, as a fall back,
            #lets instruct it to keep ALL the DnaGroup objects even when empty
            #Reason? ..see explanation in BreakStrands_Command.keep_empty_group
            elif isinstance(group, self.assy.DnaGroup):
                bool_keep = True

        return bool_keep

    def hasResizableStructure(self):
        """
        For Rattlesnake release, we dont support segment resizing for PAM5 
        models. If the structure is not resizable, the handles won't be drawn
        @see:self.model_changed()
        @see:DnaSegment_PropertyManager.model_changed()
        @see: self.editStructure()
        @see: DnaSegment.is_PAM3_DnaSegment()
        """
        #Note: This method fixes bugs similar to bug 2812 but the changes
        #didn't made it to Rattlesnake rc2 -- Ninad 2008-04-16
        isResizable = True
        why_not = ''

        if not self.hasValidStructure():
            isResizable = False
            why_not = 'It is invalid.'
            return isResizable, why_not

        isResizable = self.struct.is_PAM3_DnaSegment()
        if not isResizable:
            why_not = 'It needs to be converted to PAM3 model'
            return isResizable, why_not

        endAtom1, endAtom2 = self.struct.getAxisEndAtoms()

        if endAtom1 is None or endAtom2 is None:
            isResizable = False
            why_not = "Unable to determine one or both end atoms of the segment"
            return isResizable, why_not

        if endAtom1 is endAtom2:
            isResizable = False
            why_not = "Resizing a segment with single atom is unsupported"
            return isResizable, why_not

        return isResizable, why_not

    def hasValidStructure(self):
        """
        Tells the caller if this edit command has a valid structure. 
        Overrides EditCommand.hasValidStructure()
        """
        #(By Bruce 2008-02-13)

        isValid = EditCommand.hasValidStructure(self)

        if not isValid:
            return isValid

        # would like to check here whether it's empty of axis chunks;
        # instead, this will do for now (probably too slow, though):
        p1, p2 = self.struct.getAxisEndPoints()
        return (p1 is not None)

    def _getStructureType(self):
        """
        Subclasses override this method to define their own structure type. 
        Returns the type of the structure this editCommand supports. 
        This is used in isinstance test. 
        @see: EditCommand._getStructureType() (overridden here)
        """
        return self.win.assy.DnaSegment

    def _updateHandleList(self):
        """        
        Updates the list of handles (self.handles) 
        @see: self.editStructure
        @see: DnaSegment_GraphicsMode._drawHandles()
        """
        # note: if handlePoint1 and/or handlePoint2 can change more often than this
        # runs, we'll need to rerun the two assignments above whenever they
        # change and before the handle is drawn. An easy way would be to rerun
        # these assignments in the draw method of our GM. [bruce 080128]
        self.handles = []  # guess, but seems like a good idea [bruce 080128]
        self.handles.append(self.leftHandle)
        self.handles.append(self.rightHandle)
        if DEBUG_ROTATION_HANDLES:
            self.handles.append(self.rotationHandle1)
            self.handles.append(self.rotationHandle2)

    def updateHandlePositions(self):
        """
        Update handle positions and also update the resize handle radii and
        their 'stopper' lengths. 
        @see: self._update_resizeHandle_radius()
        @see: self._update_resizeHandle_stopper_length()     
        @see: DnaSegment_GraphicsMode._drawHandles()
        """

        if len(self.handles) == 0:
            #No handles are appended to self.handles list.
            #@See self.model_changed() and self._updateHandleList()
            return

        #TODO: Call this method less often by implementing model_changed
        #see bug 2729 for a planned optimization
        self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE
        self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE

        self._update_resizeHandle_radius()

        handlePoint1, handlePoint2 = self.struct.getAxisEndPoints()

        if handlePoint1 is not None and handlePoint2 is not None:
            # (that condition is bugfix for deleted axis segment, bruce 080213)

            self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2

            #Update the 'stopper'  length where the resize handle being dragged
            #should stop. See self._update_resizeHandle_stopper_length()
            #for more details
            self._update_resizeHandle_stopper_length()

            if DEBUG_ROTATION_HANDLES:
                self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE
                self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE
                #Following computes the base points for rotation handles.
                #to be revised -- Ninad 2008-02-13
                unitVectorAlongAxis = norm(self.handlePoint1 -
                                           self.handlePoint2)

                v = cross(self.glpane.lineOfSight, unitVectorAlongAxis)

                self.rotationHandleBasePoint1 = self.handlePoint1 + norm(
                    v) * 4.0
                self.rotationHandleBasePoint2 = self.handlePoint2 + norm(
                    v) * 4.0

    def _update_resizeHandle_radius(self):
        """
        Finds out the sphere radius to use for the resize handles, based on 
        atom /chunk or glpane display (whichever decides the display of the end 
        atoms.  


        @see: self.updateHandlePositions()
        @see: B{Atom.drawing_radius()}
        """
        atm1, atm2 = self.struct.getAxisEndAtoms()
        if atm1 is not None:
            self.handleSphereRadius1 = max(1.25 * atm1.drawing_radius(),
                                           1.25 * HANDLE_RADIUS_DEFAULT_VALUE)
        if atm2 is not None:
            self.handleSphereRadius2 = max(1.25 * atm2.drawing_radius(),
                                           1.25 * HANDLE_RADIUS_DEFAULT_VALUE)

    def _update_resizeHandle_stopper_length(self):
        """
        Update the limiting length at which the resize handle being dragged
        should 'stop'  without proceeding further in the drag direction. 
        The segment resize handle stops when you are dragging it towards the 
        other resizeend and the distance between the two ends reaches two 
        duplexes. 

        The self._resizeHandle_stopper_length computed in this method is 
        used as a lower limit of the 'range' option provided in declaration
        of resize handle objects (see class definition for the details)
        @see: self.updateHandlePositions()
        """

        total_length = vlen(self.handlePoint1 - self.handlePoint2)
        duplexRise = self.struct.getDuplexRise()

        #Length of the duplex for 2 base pairs
        two_bases_length = getDuplexLength('B-DNA', 2, duplexRise=duplexRise)

        self._resizeHandle_stopper_length = -total_length + two_bases_length

    def _createPropMgrObject(self):
        """
        Creates a property manager object (that defines UI things) for this 
        editCommand. 
        """
        assert not self.propMgr
        propMgr = self.win.createDnaSegmentPropMgr_if_needed(self)
        return propMgr

    def _gatherParameters(self):
        """
        Return the parameters from the property manager UI.

        @return: All the parameters (get those from the property manager):
                 - numberOfBases
                 - dnaForm
                 - basesPerTurn
                 - endPoint1
                 - endPoint2
        @rtype:  tuple
        """
        return self.propMgr.getParameters()

    def _createStructure(self):
        """
        Creates and returns the structure (in this case a L{Group} object that 
        contains the DNA strand and axis chunks. 
        @return : group containing that contains the DNA strand and axis chunks.
        @rtype: L{Group}  
        @note: This needs to return a DNA object once that model is implemented        
        """

        params = self._gatherParameters()

        # No error checking in build_struct, do all your error
        # checking in gather_parameters
        number_of_basePairs_from_struct,\
                                       numberOfBases, \
                                       dnaForm, \
                                       dnaModel, \
                                       basesPerTurn, \
                                       duplexRise, \
                                       endPoint1, \
                                       endPoint2, \
                                       color_junk = params
        #Note: color_junk is not used. Ideally it should do struct.setColor(color)
        #but the color combobox in the PM directly sets the color of the
        #structure to the specified one when the current index in the combobx
        #changes

        #If user enters the number of basepairs and hits preview i.e. endPoint1
        #and endPoint2 are not entered by the user and thus have default value
        #of V(0, 0, 0), then enter the endPoint1 as V(0, 0, 0) and compute
        #endPoint2 using the duplex length.
        #Do not use '==' equality check on vectors! its a bug. Use same_vals
        # or Veq instead.
        if Veq(endPoint1, endPoint2) and Veq(endPoint1, V(0, 0, 0)):
            endPoint2 = endPoint1 + \
                      self.win.glpane.right*getDuplexLength('B-DNA',
                                                            numberOfBases)

        if numberOfBases < 1:
            msg = redmsg("Cannot preview/insert a DNA duplex with 0 bases.")
            self.propMgr.updateMessage(msg)
            self.dna = None  # Fixes bug 2530. Mark 2007-09-02
            return None

        if dnaForm == 'B-DNA':
            if dnaModel == 'PAM3':
                dna = B_Dna_PAM3()
            elif dnaModel == 'PAM5':
                dna = B_Dna_PAM5()
            else:
                print "bug: unknown dnaModel type: ", dnaModel
        else:
            raise PluginBug("Unsupported DNA Form: " + dnaForm)

        self.dna = dna  # needed for done msg

        # self.name needed for done message
        if self.create_name_from_prefix:
            # create a new name
            name = self.name = gensym(self.prefix,
                                      self.win.assy)  # (in _build_struct)
            self._gensym_data_for_reusing_name = (self.prefix, name)
        else:
            # use externally created name
            self._gensym_data_for_reusing_name = None
            # (can't reuse name in this case -- not sure what prefix it was
            #  made with)
            name = self.name

        # Create the model tree group node.
        # Make sure that the 'topnode'  of this part is a Group (under which the
        # DNa group will be placed), if the topnode is not a group, make it a
        # a 'Group' (applicable to Clipboard parts).See part.py
        # --Part.ensure_toplevel_group method. This is an important line
        # and it fixes bug 2585
        self.win.assy.part.ensure_toplevel_group()
        dnaSegment = DnaSegment(self.name,
                                self.win.assy,
                                self.win.assy.part.topnode,
                                editCommand=self)
        try:
            # Make the DNA duplex. <dnaGroup> will contain three chunks:
            #  - Strand1
            #  - Strand2
            #  - Axis

            dna.make(dnaSegment, numberOfBases, basesPerTurn, duplexRise,
                     endPoint1, endPoint2)

            #set some properties such as duplexRise and number of bases per turn
            #This information will be stored on the DnaSegment object so that
            #it can be retrieved while editing this object.
            #This works with or without dna_updater. Now the question is
            #should these props be assigned to the DnaSegment in
            #dnaDuplex.make() itself ? This needs to be answered while modifying
            #make() method to fit in the dna data model. --Ninad 2008-03-05

            #WARNING 2008-03-05: Since self._modifyStructure calls
            #self._createStructure()
            #If in the near future, we actually permit modifying a
            #structure (such as dna) without actually recreating the whole
            #structre, then the following properties must be set in
            #self._modifyStructure as well. Needs more thought.
            props = (duplexRise, basesPerTurn)
            dnaSegment.setProps(props)

            return dnaSegment

        except (PluginBug, UserError):
            # Why do we need UserError here? Mark 2007-08-28
            dnaSegment.kill()
            raise PluginBug(
                "Internal error while trying to create DNA duplex.")

    def _modifyStructure(self, params):
        """
        Modify the structure based on the parameters specified. 
        Overrides EditCommand._modifystructure. This method removes the old 
        structure and creates a new one using self._createStructure. This 
        was needed for the structures like this (Dna, Nanotube etc) . .
        See more comments in the method.
        """
        assert self.struct

        self.dna = B_Dna_PAM3()

        number_of_basePairs_from_struct,\
                                       numberOfBases, \
                                       dnaForm, \
                                       dnaModel, \
                                       basesPerTurn, \
                                       duplexRise, \
                                       endPoint1, \
                                       endPoint2 , \
                                       color = params

        #Delete unused parameters.
        del endPoint1
        del endPoint2
        del number_of_basePairs_from_struct

        numberOfBasePairsToAddOrRemove = self._determine_numberOfBasePairs_to_change(
        )

        ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end()

        if numberOfBasePairsToAddOrRemove != 0:

            resizeEnd_final_position = self._get_resizeEnd_final_position(
                ladderEndAxisAtom, abs(numberOfBasePairsToAddOrRemove),
                duplexRise)

            self.dna.modify(self.struct, ladderEndAxisAtom,
                            numberOfBasePairsToAddOrRemove, basesPerTurn,
                            duplexRise, ladderEndAxisAtom.posn(),
                            resizeEnd_final_position)

        #Find new end points of structure parameters after modification
        #and set these values in the propMgr.
        new_end1, new_end2 = self.struct.getAxisEndPoints()

        params_to_set_in_propMgr = (numberOfBases, dnaForm, dnaModel,
                                    basesPerTurn, duplexRise, new_end1,
                                    new_end2, color)

        #TODO: Need to set these params in the PM
        #and then self.previousParams = params_to_set_in_propMgr

        self.previousParams = params

        return

    def _get_resizeEnd_final_position(self, ladderEndAxisAtom, numberOfBases,
                                      duplexRise):

        final_position = None
        if self.grabbedHandle:
            final_position = self.grabbedHandle.currentPosition
        else:
            other_axisEndAtom = self.struct.getOtherAxisEndAtom(
                ladderEndAxisAtom)
            axis_vector = ladderEndAxisAtom.posn() - other_axisEndAtom.posn()
            segment_length_to_add = getDuplexLength('B-DNA',
                                                    numberOfBases,
                                                    duplexRise=duplexRise)

            final_position = ladderEndAxisAtom.posn(
            ) + norm(axis_vector) * segment_length_to_add

        return final_position

    def getStructureName(self):
        """
        Returns the name string of self.struct if there is a valid structure. 
        Otherwise returns None. This information is used by the name edit field 
        of  this command's PM when we call self.propMgr.show()
        @see: DnaSegment_PropertyManager.show()
        @see: self.setStructureName
        """
        if self.hasValidStructure():
            return self.struct.name
        else:
            return None

    def setStructureName(self, name):
        """
        Sets the name of self.struct to param <name> (if there is a valid 
        structure. 
        The PM of this command callss this method while closing itself 
        @param name: name of the structure to be set.
        @type name: string
        @see: DnaSegment_PropertyManager.close()
        @see: self.getStructureName()

        """
        #@BUG: We call this method in self.propMgr.close(). But propMgr.close()
        #is called even when the command is 'cancelled'. That means the
        #structure will get changed even when user hits cancel button or
        #exits the command by clicking on empty space.
        #This should really be done in self._finalizeStructure but that
        #method doesn't get called when you click on empty space to exit
        #the command. See DnaSegment_GraphicsMode.leftUp for a detailed
        #comment.

        if self.hasValidStructure():
            self.struct.name = name

    def getCursorText(self):
        """
        This is used as a callback method in DnaLine mode 
        @see: DnaLineMode.setParams, DnaLineMode_GM.Draw
        """
        #@TODO: Refactor this. Similar code exists in
        #DnaStrand_EditCommand.getCursorText() -- Ninad 2008-04-12
        if self.grabbedHandle is None:
            return

        currentPosition = self.grabbedHandle.currentPosition
        fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure

        duplexRise = self.struct.getDuplexRise()

        #############

        raw_numberOfBasePairsToAddOrRemove = self._determine_numberOfBasePairs_to_change(
        )

        #Following fixes bugs like 2904 and 2906
        #Note that we are using numberOfBasePairsToAddOrRemove in self._modifyStructure()
        #if self._determine_numberOfBasePairs_to_change() returns the number of basepairs
        #to add, it returns 1 more than the actual number of basepairs. Because
        #while creating the dna, it removes the first base pair of the newly created
        #dna. So, for cursor text and for PM spinbox, we should make adjustments to the
        #raw_numberOfBasePairsToAddOrRemove so that it reflects the correct value
        #in the spinbox and in the PM

        if raw_numberOfBasePairsToAddOrRemove > 1:
            numberOfBasePairsToAddOrRemove = raw_numberOfBasePairsToAddOrRemove - 1

        else:
            numberOfBasePairsToAddOrRemove = raw_numberOfBasePairsToAddOrRemove

        ##############

        current_numberOfBasePairs = self.struct.getNumberOfBasePairs()

        numberOfBasePairs = current_numberOfBasePairs + numberOfBasePairsToAddOrRemove
        #@TODO: The following updates the PM as the cursor moves.
        #Need to rename this method so that you that it also does more things
        #than just to return a textString -- Ninad 2007-12-20
        self.propMgr.numberOfBasePairsSpinBox.setValue(numberOfBasePairs)

        #Note: for Rattlesnake rc2, the text color is green when bases are added
        #, red when subtracted black when no change. But this implementation is
        #changed based on Mark's user experience. The text is now always shown
        #in black color. -- Ninad 2008-04-17
        textColor = black

        text = ""

        if not env.prefs[
                dnaSegmentEditCommand_showCursorTextCheckBox_prefs_key]:
            return '', black

        #@@TODO: refactor.
        #this duplex length canculation fixes bug 2906
        duplexLength = getDuplexLength('B-DNA',
                                       numberOfBasePairs,
                                       duplexRise=duplexRise)

        #Cursor text strings --
        duplexLengthString = str(round(duplexLength, 3))

        numberOfBasePairsString = self._getCursorText_numberOfBasePairs(
            numberOfBasePairs)

        duplexLengthString = self._getCursorText_length(duplexLength)

        changedBasePairsString = self._getCursorText_changedBasePairs(
            numberOfBasePairs)

        #Add commas (to be refactored)
        commaString = ", "
        text = numberOfBasePairsString

        if text and changedBasePairsString:
            text += " "  # commaString not needed here. Mark 2008-07-03

        text += changedBasePairsString

        if text and duplexLengthString:
            text += commaString

        text += duplexLengthString

        return (text, textColor)

    def _getCursorText_numberOfBasePairs(self, numberOfBasePairs):
        """
        Return the cursor textstring that gives information about the number 
        of basepairs if the corresponding prefs_key returns True.
        """
        numberOfBasePairsString = ''

        if env.prefs[
                dnaSegmentEditCommand_cursorTextCheckBox_numberOfBasePairs_prefs_key]:
            numberOfBasePairsString = "%db" % numberOfBasePairs

        return numberOfBasePairsString

    def _getCursorText_length(self, duplexLength):
        """
        """
        duplexLengthString = ''
        if env.prefs[
                dnaSegmentEditCommand_cursorTextCheckBox_length_prefs_key]:
            lengthUnitString = 'A'
            #change the unit of length to nanometers if the length is > 10A
            #fixes part of bug 2856
            if duplexLength > 10.0:
                lengthUnitString = 'nm'
                duplexLength = duplexLength * 0.1
            duplexLengthString = "%5.3f%s" % (duplexLength, lengthUnitString)

        return duplexLengthString

    def _getCursorText_changedBasePairs(self, numberOfBasePairs):
        """
        """
        changedBasePairsString = ''

        if env.prefs[
                dnaSegmentEditCommand_cursorTextCheckBox_changedBasePairs_prefs_key]:

            original_numberOfBasePairs = self.struct.getNumberOfBasePairs()

            changed_basePairs = numberOfBasePairs - original_numberOfBasePairs

            if changed_basePairs > 0:
                changedBasePairsString = "(" + "+" + str(
                    changed_basePairs) + ")"
            else:
                changedBasePairsString = "(" + str(changed_basePairs) + ")"

        return changedBasePairsString

    def getDnaRibbonParams(self):
        """
        Returns parameters for drawing the dna ribbon. 

        If the dna rubberband line should NOT be drawn (example when you are 
        removing basepairs from the segment 
        So the caller should check if the method return value is not None. 
        @see: DnaSegment_GraphicsMode._draw_handles()
        """

        if self.grabbedHandle is None:
            return None

        if self.grabbedHandle.origin is None:
            return None

        direction_of_drag = norm(self.grabbedHandle.currentPosition - \
                                 self.grabbedHandle.origin)

        #If the segment is being shortened (determined by checking the
        #direction of drag) , no need to draw the rubberband line.
        if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
            return None

        basesPerTurn = self.struct.getBasesPerTurn()
        duplexRise = self.struct.getDuplexRise()

        ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end()
        ladder = ladderEndAxisAtom.molecule.ladder
        endBaseAtomList = ladder.get_endBaseAtoms_containing_atom(
            ladderEndAxisAtom)
        ribbon1_start_point = None
        ribbon2_start_point = None
        ribbon1_direction = None
        ribbon2_direction = None

        ribbon1Color = applegreen
        ribbon2Color = applegreen

        if endBaseAtomList and len(endBaseAtomList) > 2:
            strand_atom1 = endBaseAtomList[0]
            strand_atom2 = endBaseAtomList[2]

            if strand_atom1:
                ribbon1_start_point = strand_atom1.posn()
                for bond_direction, neighbor in strand_atom1.bond_directions_to_neighbors(
                ):
                    if neighbor and neighbor.is_singlet():
                        ribbon1_direction = bond_direction
                        break

                ribbon1Color = strand_atom1.molecule.color
                if not ribbon1Color:
                    ribbon1Color = strand_atom1.element.color

            if strand_atom2:
                ribbon2_start_point = strand_atom2.posn()
                for bond_direction, neighbor in strand_atom2.bond_directions_to_neighbors(
                ):
                    if neighbor and neighbor.is_singlet():
                        ribbon2_direction = bond_direction
                        break
                ribbon2Color = strand_atom2.molecule.color
                if not ribbon2Color:
                    ribbon2Color = strand_atom2.element.color

        return (self.grabbedHandle.origin, self.grabbedHandle.currentPosition,
                basesPerTurn, duplexRise, ribbon1_start_point,
                ribbon2_start_point, ribbon1_direction, ribbon2_direction,
                ribbon1Color, ribbon2Color)

    def modifyStructure(self):
        """

        Called when a resize handle is dragged to change the length of the 
        segment. (Called upon leftUp) . This method assigns the new parameters 
        for the segment after it is resized and calls 
        preview_or_finalize_structure which does the rest of the job. 
        Note that Client should call this public method and should never call
        the private method self._modifyStructure. self._modifyStructure is 
        called only by self.preview_or_finalize_structure

        @see: B{DnaSegment_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the 
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}
        @see: B{self.preview_or_finalize_structure }
        @see: B{self._modifyStructure}        

        As of 2008-02-01 it recreates the structure
        @see: a note in self._createStructure() about use of dnaSegment.setProps 
        """
        #TODO: need to cleanup this and may be use use something like
        #self.previousParams = params in the end -- 2008-03-24 (midnight)

        if self.grabbedHandle is None:
            return

        ##self.propMgr.setParameters(params_to_set_in_propMgr)
        #TODO: Important note: How does NE1 know that structure is modified?
        #Because number of base pairs parameter in the PropMgr changes as you
        #drag the handle . This is done in self.getCursorText() ... not the
        #right place to do it. OR that method needs to be renamed to reflect
        #this as suggested in that method -- Ninad 2008-03-25

        self.preview_or_finalize_structure(previewing=True)

        ##self.previousParams = params_to_set_in_propMgr

        self.glpane.gl_update()

    def get_axisEndAtom_at_resize_end(self):
        ladderEndAxisAtom = None
        if self.grabbedHandle is not None:
            ladderEndAxisAtom = self.struct.getAxisEndAtomAtPosition(
                self.grabbedHandle.origin)
        else:
            endAtom1, endAtom2 = self.struct.getAxisEndAtoms()
            ladderEndAxisAtom = endAtom2

        return ladderEndAxisAtom

    def _determine_numberOfBasePairs_to_change(self):
        """
        """

        duplexRise = self.struct.getDuplexRise()
        numberOfBasesToAddOrRemove = 0
        #Following helps fixing bugs like 2904 and 2906 see also self.getCursorText()
        #and TODO items in that method. Also note that the grabbed handle case
        #is similar to the one in MultipleDnaSegmentResize_EditCommand.
        #needs refactoring and overall cleanup.
        if self.grabbedHandle is not None:

            currentPosition = self.grabbedHandle.currentPosition
            fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure

            changedLength = vlen(currentPosition - self.grabbedHandle.origin)

            direction_of_drag = norm(self.grabbedHandle.currentPosition - \
                                     self.grabbedHandle.origin)

            #Even when the direction of drag is negative (i.e. the basepairs being
            #removed), make sure not to remove base pairs for very small movement
            #of the grabbed handle
            if changedLength < 0.2 * duplexRise:
                return 0

            #This check quickly determines if the grabbed handle moved by a distance
            #more than the duplexRise and avoids further computations
            #This condition is applicable only when the direction of drag is
            #positive..i.e. bases bing added to the segment.
            if changedLength < duplexRise and \
               dot(self.grabbedHandle.direction, direction_of_drag) > 0:
                return 0

            #If the segment is being shortened (determined by checking the
            #direction of drag)

            numberOfBasesToAddOrRemove =  \
                                       getNumberOfBasePairsFromDuplexLength(
                                           'B-DNA',
                                           changedLength,
                                           duplexRise = duplexRise)

            if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
                numberOfBasesToAddOrRemove = -numberOfBasesToAddOrRemove

            if numberOfBasesToAddOrRemove > 0:
                #dna.modify will remove the first base pair it creates
                #(that basepair will only be used for proper alignment of the
                #duplex with the existing structure) So we need to compensate for
                #this basepair by adding 1 to the new number of base pairs.

                #UPDATE 2008-05-14: The following commented out code
                #i.e. "##numberOfBasesToAddOrRemove += 1" is not required in this
                #class , because the way we compute the number of base pairs to
                #be added is different than than how its done at the moment in the
                #superclass. In this method, we compute bases to be added from
                #the resize end and that computation INCLUDES the resize end.
                #so the number that it returns is already one more than the actual
                #bases to be added. so commenting out the following line
                # -- Ninad 2008-05-14
                ##numberOfBasesToAddOrRemove += 1
                pass
        else:
            #The Property manager will be showing the current number
            #of base pairs (w. May be we can use that number directly here?
            #The following is  safer to do so lets just recompute the
            #number of base pairs. (if it turns out to be slow, we will consider
            #using the already computed calue from the property manager
            new_numberOfBasePairs = self.propMgr.numberOfBasePairsSpinBox.value(
            )

            endPoint1, endPoint2 = self.struct.getAxisEndPoints()
            if endPoint1 is None or endPoint2 is None:
                return 0

            original_duplex_length = vlen(endPoint1 - endPoint2)

            original_numberOfBasePairs = self.struct.getNumberOfBasePairs()

            numberOfBasesToAddOrRemove = new_numberOfBasePairs - original_numberOfBasePairs

            if numberOfBasesToAddOrRemove > 0:
                #dna.modify will remove the first base pair it creates
                #(that basepair will only be used for proper alignment of the
                #duplex with the existing structure) So we need to compensate for
                #this basepair by adding 1 to the new number of base pairs.
                numberOfBasesToAddOrRemove += 1

        return numberOfBasesToAddOrRemove

    def makeMenus(self):
        """
        Create context menu for this command. (Build Dna mode)
        """
        if not hasattr(self, 'graphicsMode'):
            return

        selobj = self.glpane.selobj

        if selobj is None:
            return

        self.Menu_spec = []

        highlightedChunk = None
        if isinstance(selobj, Chunk):
            highlightedChunk = selobj
        if isinstance(selobj, Atom):
            highlightedChunk = selobj.molecule
        elif isinstance(selobj, Bond):
            chunk1 = selobj.atom1.molecule
            chunk2 = selobj.atom2.molecule
            if chunk1 is chunk2 and chunk1 is not None:
                highlightedChunk = chunk1

        if highlightedChunk is None:
            return

        if self.hasValidStructure():

            dnaGroup = self.struct.parent_node_of_class(self.assy.DnaGroup)
            if dnaGroup is None:
                return
            #following should be self.struct.getDnaGroup or self.struct.getDnaGroup
            #need to formalize method name and then make change.
            if not dnaGroup is highlightedChunk.parent_node_of_class(
                    self.assy.DnaGroup):
                item = ("Edit unavailable: Member of a different DnaGroup",
                        noop, 'disabled')
                self.Menu_spec.append(item)
                return

        highlightedChunk.make_glpane_context_menu_items(self.Menu_spec,
                                                        command=self)
Esempio n. 24
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
Esempio n. 25
0
class MakeCrossovers_Handle(DelegatingInstanceOrExpr):

    should_draw = State(bool, True)
    radius = 0.8

    point1 = Arg(Point)
    point2 = Arg(Point)

    scale = Arg(float)

    crossoverSite_marker = Option(
        Action,
        doc='The CrossoverSite Marker class which instantiates this handle')

    #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')

    crossoverPairs = Option(tuple, ())

    #Stateusbar text. Variable needs to be renamed in superclass.
    sbar_text = Option(str,
                       "Click on the handle to create this crossover",
                       doc="Statusbar text on mouseover")

    delegate = If_expr(
        _self.should_draw,
        Highlightable(Overlay(
            Cylinder((call_Expr(_self.crossoverPairs[0].posn),
                      call_Expr(_self.crossoverPairs[3].posn)),
                     radius=radius,
                     color=silver),
            Cylinder((call_Expr(_self.crossoverPairs[1].posn),
                      call_Expr(_self.crossoverPairs[2].posn)),
                     radius=radius,
                     color=silver)),
                      Overlay(
                          Cylinder((call_Expr(_self.crossoverPairs[0].posn),
                                    call_Expr(_self.crossoverPairs[3].posn)),
                                   radius=radius,
                                   color=banana),
                          Cylinder((call_Expr(_self.crossoverPairs[1].posn),
                                    call_Expr(_self.crossoverPairs[2].posn)),
                                   radius=radius,
                                   color=banana)),
                      on_press=_self.on_press,
                      on_release=_self.on_release,
                      sbar_text=sbar_text))

    ##delegate = If_expr(_self.should_draw,
    ##Highlightable(Cylinder((point1, point2),
    ##radius = radius,
    ##color = silver),
    ##Cylinder((point1, point2),
    ##radius = radius,
    ##color = banana),
    ##on_press = _self.on_press,
    ##on_release = _self.on_release))

    ##delegate = \
    ##If_expr(
    ##_self.should_draw,
    ##Highlightable(Arrow(
    ##color = silver,
    ##arrowBasePoint = point1,
    ####tailPoint = norm(vector)*1.0,
    ##tailPoint = point2,
    ##radius = radius,
    ##scale = scale),

    ##Arrow(
    ##color = banana,
    ##arrowBasePoint =  point1,
    ####tailPoint = norm(vector)*1.0,
    ##tailPoint =  point2,
    ##radius = radius,
    ##scale = scale),
    ##on_press = _self.on_press,
    ##on_release = _self.on_release ) ) #

    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

    def on_press(self):
        pass

    def on_release(self):
        self.command.makeCrossover(self.crossoverPairs)
        self.crossoverSite_marker.removeHandle(self)
Esempio n. 26
0
class SimpleColumn_NEW(Widget2D): #061115, revised 070321 to use new ArgList -- but disabled 070322,
    # since too slow for routine use (eg in testexpr_19g),
    # until we fix that update speed bug which means a change in any element remakes them all.
    ### fyi: a test that puts too many elts for SimpleColumn_OLD into the MT: _30i, dna x 3, rects x 2

    #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]
    args = ArgList(Widget2D, (), doc = "0 or more things (that can participate in 2d layout) to show in a column")
    def _C_a0(self):
        args = self.args
        args[0:] # make sure this works (i.e. args is a sequence)
        if len(args):
            return args[0]
        else:
            return None
        pass
    a0 = _self.a0 # kluge, but legal (due to special case in ExprsMeta, documented in comments there):
        # get a0 into the class-def namespace for direct use below

    def _init_instance(self):##### debug only
        super(SimpleColumn, self)._init_instance()
        try:
            args = 'bug' # for use in error message, in case of exception
            args = self.args
            len(args) # make sure this works!
            if debug070321:
                print "fyi: this SimpleColumn has %d args" % len(args)
                print "fyi: the args i mentioned are: %r" % (args,) #####
        except:
            print "following exception concerns self = %r, args = %r" % (self, args)
            raise
        return
    
    ## 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_NEW or SimpleColumn
Esempio n. 27
0
class Translate(InstanceOrExpr, DelegatingMixin):
    "Translate(thing, vec) translates thing (and its bounding box, 2D or 3D) by vec (2 or 3 components). [3D aspect is nim]"
    # 3D aspect might be nim
    #061127 fixed unnoticed bug, Widget -> IorE, to avoid failing to delegate fix_color etc in Widget
    thing = Arg(Widget)
    delegate = _self.thing
    vec = Arg(Vector)
    # translation of layout box
    # [#e This should be handled differently later, since the current approach requires knowing
    #  all attrs/methods that take or return geometric info in the object's local coords!
    #     A better way might be to have object-local methods to turn number-coords into Points etc, and vice versa,
    #  with Points knowing their coords and their coord-frame identifier. Subtleties include the semantics
    #  of time-variable coord-frames -- it matters which frame a Point is understood to be relative to,
    #  to know whether it moves or not when it doesn't change in value.
    #     If that's too hard to work out, another better way might be to query the object, or our
    #  understanding of its API-type, for type-signatures of methods/attrs needing geometric transformations,
    #  and setting those up here, perhaps in _init_instance (tho this scheme would need optims to move it
    #  at least as far (in being shared between instances) as into _init_expr).
    # ]
    ## for useful debug/example code, _C_debugfactor and call_Expr(_self._C_debugfactor), see rev 1.6:
    ## "_C_debugfactor - temporary eg of debug code, counter-dependent drawing, mixing _C_ and formulae"

    dx = vec[0]
    dy = vec[1]
    #k Interesting Q: would it work here to say dx, dy = vec? I doubt it, since len(vec) (an expr) is not defined.
    # (Besides (an academic point), its length when defined is 3, so we'd need to include dz even if we don't use it.)
    bleft = thing.bleft - dx  # might become negative; probably ok, but origin outside lbox will affect Column layout, etc ##e ok?
    bright = thing.bright + dx
    bbottom = thing.bbottom - dy  # might become negative; see comment above
    btop = thing.btop + dy

    motion = call_Expr(tuple3_from_vec, vec)  #070209 late -- works
    center = delegate.center + motion  #070209 late -- moved from draggable to here -- works

    ###e the following move method will probably need to go (not hard since not in any non-obs api),
    # to make room for a move method which alters the posn of a model object. [070209 comment about old code]

    # methods needed by all layout primitives: move & draw (see Column) & maybe kid (needed for highlighting, maybe not yet called)
    def move(
        self, i, j
    ):  # note: this separate move/draw API is obsolete, but still used, tho only locally (see paper notes circa 091113)
        "move from i to j, where both indices are encoded as None = self and 0 = self.thing"
        #e we might decide to only bother defining the i is None cases, in the API for this, only required for highlighting;
        # OTOH if we use it internally we might need both cases here
        assert self._e_is_instance
        x, y, z = tuple3_from_vec(self.vec)
        if i is None and j == 0:
            glTranslatef(
                x, y, z
            )  ##e we should inline this method (leaving only this statement) into draw, before thing.draw ...
        elif i == 0 and j is None:
            glTranslatef(
                -x, -y,
                -z)  ##e ... and leaving only this statement, after thing.draw
        return

    def kid(
        self, i
    ):  # never called, but (for nim hover highlighting) i forget whether it's obs (see paper notes circa 091113)
        assert i == 0
        return self.thing

    ###e note: as said in notesfile, the indices for these drawn kids *could differ* from these arg indices if I wanted...
    # or I could instead define a dict...
    def draw(self):
        ##        print "start drawing in %r, ipath %r" % (self, self.ipath,)
        assert self._e_is_instance
        self.move(None, 0)
        self.drawkid(self.thing)  ## self.thing.draw()
        # draw kid number 0 -- ##k but how did we at some point tell that kid that it's number 0, so it knows its ipath??
        ##k is it worth adding index or ipath as a draw-arg? (I doubt it, seems inefficient for repeated drawing)
        self.move(0, None)
        ##        print "done drawing in %r, ipath %r" % (self, self.ipath,)
        return

    pass  # end of class Translate
Esempio n. 28
0
def _ArgOption_helper( attr_expr, argpos_expr, type_expr, dflt_expr, _lvalue_flag = False, _arglist = False, **moreopts ):
    """
    [private helper for Arg, Option, and maybe ArgOrOption]

    attr_expr should be None, or some sort of expr (in practice always _E_ATTR so far)
      that will get replaced by a constant_Expr for the current attr (in ExprsMeta's FormulaScanner),
      according to whether the current attr should be part of the index and a public option-name for supplying the arg
      (we make sure those conditions are the same). [#e Note that if someday we wanted to include f(attr) in the index,
      but still use attr alone as an option name, we'd have to modify this to permit both f(attr) (or f) and attr to be passed.]

    argpos_expr should similarly be None, or some sort of expr (in practice a private subclass of internal_Expr)
      that will get replaced by a constant_Expr for the argument position (an int) that should be allocated to the current attr's arg
      (determined in ExprsMeta's FormulaScanner by allocating posns 0,1,2,etc to newly seen arg-attrs, whether or not the attr itself
      is public for that arg).

    type_expr ###doc, passed herein to canon_type

    dflt_expr ###doc, can also be _E_DFLT_FROM_TYPE_ or [handled in caller i think, but survives here unmatteringly] _E_REQUIRED_ARG_;
        will be passed through canon_expr

    _lvalue_flag is a private option used by LvalueArg.

    _arglist is a private option used by ArgList.
    """
    if _lvalue_flag:
        printnim("_lvalue_flag's proper interaction with dflt_expr is nim") # in all cases below
            ### guess: we want it to be an expr for a default stateref
    global _self # fyi
    type_expr = canon_type( type_expr)
    printnim("can type_expr legally be self-dependent and/or time-dependent? ###k I guess that's nim in current code!")#070115 comment
    if _arglist:
        # new feature 070321. The type is applied to each element, but the default value is for the entire list --
        # OTOH, when would it ever be used, since even if no args are supplied, the list can be formed??
        # Probably it would only be used when the list was 0 length, and could meaningfully be [], (), or another list-like thing...
        # this is all a guess and I probably won't even review this code for this issue now, unless it fails when tried. ####k
        type_expr = tuple_Expr(type_expr) # type-coerce the value to a list of the given type [070321 guess] ###e or list_Expr???
    if dflt_expr is _E_DFLT_FROM_TYPE_:
        dflt_expr = default_expr_from_type_expr( type_expr)
            ## note [070115], this would be impossible for time-dependent types! and for self-dep ones, possible but harder than current code.
        assert is_pure_expr(dflt_expr) #k guess 061105
    else:
        dflt_expr = canon_expr(dflt_expr) # hopefully this finally will fix dflt 10 bug, 061105 guesshope ###k [works for None, 061114]
        assert is_pure_expr(dflt_expr) # not sure this is redundant, since not sure if canon_expr checks for Instance ###k
        printnim("not sure if canon_expr checks for Instance")
    # Note on why we use explicit call_Expr & getattr_Expr below,
    # rather than () and . notation like you can use in user-level formulae (which python turns into __call__ and getattr),
    # to construct Exprs like _self._i_grabarg( attr_expr, ...):
    # it's only to work around safety features which normally detect that kind of Expr-formation (getattr on _i_* or _e_*,
    # or getattr then call) as a likely error. These safety features are very important, catching errors that would often lead
    # to hard-to-diagnose bugs (when our code has an Expr but thinks it has an Instance), so it's worth the trouble.
    held_dflt_expr = hold_Expr(dflt_expr) 
        # Note, this gets evalled back into dflt_expr (treated as inert, may or may not be an expr depending on what it is right here)
        # by the time _i_grabarg sees it (the eval is done when the call_Expr evals its args before doing the call).
        # So if we wanted _i_grabarg to want None rather than _E_REQUIRED_ARG_ as a special case, we could change to that (there & here).
    grabopts = {}
    if _arglist:
        grabopts.update(dict(_arglist = constant_Expr(_arglist)))
    grabarg_expr = call_Expr( getattr_Expr(_self, '_i_grabarg'), attr_expr, argpos_expr, held_dflt_expr, **grabopts )
        # comments 070115:
        # - This will eval to an expr which depends on self but not on time. We could optim by wrapping it
        # (or declaring it final) in a way which effectively replaced it with its value-expr when first used.
        # (But it's not obvious where to store the result of that, since the exprs being returned now are assigned to classes
        #  and will never be specific to single selfs. Do we need an expr to use here, which can cache its own info in self??
        #  Note: AFAIK, self will be the same as what _self gets replaced with when this is used. (We ought to assert that.) ###e)
        # - Further, grabarg_expr is probably supposed to be wrapped *directly* by eval_Expr, not with type_expr inside. I think I'll
        # make that change right now and test it with EVAL_REFORM still False, since I think it's always been required, as said
        # in other comments here. DOING THIS NOW.
    if attr_expr is not None and argpos_expr is not None:
        # for ArgOrOption, use a tuple of a string and int (attr and argpos) as the index
        index_expr = tuple_Expr( attr_expr, argpos_expr )
    elif attr_expr is None and argpos_expr is None:
        assert 0, "attr_expr is None and argpos_expr is None ..."
    elif attr_expr is not None:
        # for Option, use a plain attr string as the index
        index_expr = attr_expr
    else:
        assert argpos_expr is not None
        # for Arg, use a plain int as the index
        # (note: ExprsMeta replaces argpos_expr with that int wrapped in constant_Expr, but later eval pulls out the raw int)
        index_expr = argpos_expr
# see if this is fixed now, not that it means much since we were using a stub... but who knows, maybe the stub was buggy
# and we compensated for that and this could if so cause a bug:
##    printnim("I suspect type_expr (stub now) is included wrongly re eval_Expr in _ArgOption_helper, in hindsight 061117")
##        ### I suspect the above, because grabarg expr needs to be evalled to get the expr whose type coercion we want to instantiate
    res = Instance( _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) ),
                     _index_expr = index_expr, _lvalue_flag = _lvalue_flag, **moreopts )
        # note: moreopts might contain _noinstance = True, and if so, Instance normally returns its first arg unchanged
        # (depending on other options).
        # 070115 replaced eval_Expr( type_expr( grabarg_expr)) with _type_coercion_expr( type_expr, eval_Expr(grabarg_expr) )
    return res # from _ArgOption_helper
Esempio n. 29
0
class test_connectWithState(State_preMixin, ExampleCommand):

    # class constants needed by mode API for example commands
    commandName = 'test_connectWithState-commandName'
    default_mode_status_text = "test_connectWithState"
    featurename = "Prototype: Test connectWithState"
    PM_class = test_connectWithState_PM

    # tracked state -- this initializes specially defined instance variables
    # which will track all their uses and changes so that connectWithState
    # works for them:
    cylinderVertical = State(bool, False)
    cylinderWidth = State(float, CYLINDER_WIDTH_DEFAULT_VALUE)
        # TODO: soon this will be the only use of this constant, so it can be inlined
    cylinderColor = State('color-stub', pink) # type should be Color (nim), but type is not yet used
    
        # note: you can add _e_debug = True to one or more of these State definitions
        # to see debug prints about some accesses to this state.

    GraphicsMode_class = _test_connectWithState_GM
    
    # init methods
    
    def __init__(self, glpane):
        # I don't know why this method is needed. ##### REVIEW (super semantics), FIX or clean up
        super(test_connectWithState, self).__init__(glpane) # State_preMixin.__init__
        ExampleCommand.__init__(self, glpane) # (especially this part)
        return

##    def __init__(self, glpane):
##        super(test_connectWithState, self).__init__(glpane)
####            # that only calls some mode's init method,
####            # so (for now) call this separately:
####        IorE_guest_mixin.__init__(self, glpane)
##        return

    # exprs-based formulae (and some compute methods)
    direction = If_expr( cylinderVertical, DY, DX )
    def _C_width_direction(self):
        """
        compute self.width_direction
        """
        # Note: to do this with a formula expr instead
        # would require cross_Expr to be defined,
        # and glpane.lineOfSight to be tracked.
        return cross( self.direction, self.env.glpane.lineOfSight )
    width_direction = _self.width_direction # so it can be used in formulae below

    # stub for handle test code [070912]
    
    widthHandleEnabled = True # stub
    ## widthHandle = Instance(Rect()) # stub
    h_offset = 0.5 + 0.2 # get it from handle? nah (not good if that changes with time); just make it fit.
        # or we could decide that handles ought to have useful fixed bounding boxes...
##    widthHandle = Instance(Translate(Center(Rect(0.5)),
##                                     width_direction * (cylinderWidth / 2.0 + h_offset) )) #stub
    widthHandle = Instance( DraggableHandle_AlongLine(
        appearance = Center(Rect(0.5, 0.5, white)),
        ### REVIEW:
        # Can't we just replace the following with something based on the formula for the position,
        #   width_direction * (cylinderWidth / 2.0 + h_offset)
        # ?
        # As it is, I have to manually solve that formula for origin and direction to pass in,
        # i.e. rewrite it as
        #   position = origin + direction * cylinderWidth
        ## height_ref = cylinderWidth, ###WRONG
##        height_ref = ObjAttr_StateRef( _self, 'cylinderWidth'),
##            ## AssertionError: ObjAttr_StateRef fallback is nim -- needed for S._self
        height_ref = call_Expr( ObjAttr_StateRef, _self, 'cylinderWidth'), # guess at workaround; #e we need a more principled way!
            ### REVIEW: efficient enough? (guess: overhead only happens once, so yes)
            # could we say instead something like: height_ref = Variable(cylinderWidth) ?? Or VariableRef? Or StateRef_to ?
        origin = width_direction * h_offset, # note: also includes cylinder center, but that's hardcoded at ORIGIN
        direction = width_direction / 2.0,
        sbar_text = "cylinder width", ### TODO: make it a formula, include printed value of width?
        range = (0.1, 10),
            ### TODO: DraggableHandle_AlongLine should take values from the stateref if this option is not provided;
            # meanwhile, we ought to pass a consistent value!
    ))
        # Note: the Instance is required; but I'm not sure if it would be
        # if we were using a fuller exprs superclass or init code. [bruce 070912]

    def cmd_Bigger(self):
        self.cylinderWidth += 0.5
        set_cylinder_height( cylinder_height() + 0.5)
        # TODO: enforce maxima
        return

    def cmd_Smaller(self):
        self.cylinderWidth -= 0.5
        set_cylinder_height( cylinder_height() - 0.5)
        # enforce minima (###BUG: not the same ones as declared in the PM)
        ### REVISE: min & max should be declared in State macro and (optionally) enforced by it
        if self.cylinderWidth < 0.1:
            self.cylinderWidth = 0.1
        if cylinder_height() < 0.1:
            set_cylinder_height(0.1)
        return
    
    pass
Esempio n. 30
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
Esempio n. 31
0
class Image(Widget2D):
    """
    Image(filename, size = Rect(2)) draws a rectangular textured image based on the given image file,
    using the same size and position in model space as the lbox of an instance of self.size
    (a Widget2D, usually a Rect, Rect(2) by default, i.e. about 30 pixels square in home view).
       It uses an OpenGL texture size (resolution) given by options ideal_width and ideal_height
    [#e which need renaming, making into one option, and other improvements; see code comments for details].
       [It doesn't yet work in POV-Ray but it ought to someday. #e]
       The other options are not fully documented here, and need improvements in design and sometimes in implem,
    but are summarized here:
       Options that affect how the file gets loaded into a PIL Image include rescale, convert, _tmpmode [#doc these],
    as well as the texture size options mentioned.
    (The PIL Image object is available as self._image even if our OpenGL texture is not used. [#doc more details?]
    [###WRONG or REVIEW -- isn't it the nEImageOps object which has that name??])
       Options that affect how the texture gets made from the loaded image include... none yet, I think. Someday
    we'll want make_mipmaps (with filtering options for its use) and probably others. [###k need to verify none are used for this]

    @warning: variations in the above options (between instances, or over time [if that's implemented -- untested,
              unsure ##k]) cause both a new OpenGL texture to be created, and (even if it would not be necessarily in a smarter
              implem [i think] -- but the lack of image->texture options makes this academic for now) a new PIL Image to be created
              from the image file on disk.

       Options that affect how the texture is drawn (in any instance, at any moment) include:
    clamp, pixmap [#e misnamed], use_mipmaps, decal, tex_origin, nreps [#doc these].
    [#e More options of this kind are needed.]
    All the texture-drawing options can be varied, either in different instances or over time in one instance
    (by passing them as formulae), without causing a new texture or PIL Image to be loaded as they vary.

    @warning: the image is not visible from the back by default,
              which is only ok for some uses, such as 2D widgets
              or solid-object faces or decals. Use two_sided = True
              to make it visible from both sides.
    """
    ##e about options ideal_width and ideal_height:
    #e should be a single option, resolution or tex_size, number or pair, or smth to pick size based on image native size
    #e should let you specify an image subrect too, or more generally an expr to compute the image -- or this can be part of one...
    # note we do have tex_origin and nreps
    #
    #e size option is misnamed since it also controls position -- maybe give it another name
    # like where, place, footprint, lbox, box? none of those good enough.
    # Also we need a way to change the size but preserve the natural aspect ratio.
    # One way: let size and tex_size both be passed easily as formulas of native image size.
    # The main new thing that requires is an abbreviation for _this(Image), e.g. _my. [note: _my is now implemented, 061205]

    # args
    filename = Arg(str)
    use_filename = call_Expr(canon_image_filename, filename)
    # named options -- clamp = False, use_mipmaps = True, decal = False, pixmap = False [redundant with defaults in bind_texture]
    clamp = Option(bool, False)  # clamp or (default) repeat
    pixmap = Option(bool,
                    False)  #e misnamed -- whether to use GL_NEAREST filtering
    use_mipmaps = Option(
        bool, True
    )  # whether to use mipmaps, if present in loaded texture object; only matters if pixmap is False
    #e what determines whether mipmaps are present? For now, they always are;
    # later, it might depend on whether we had RAM, or on a more global pref, or ....
    decal = Option(
        bool,
        False,
        doc=
        "combine texture with color using GL_DECAL? (by default, use GL_REPLACE)"
    )
    # as of 070403, False works and means GL_REPLACE
    # 070404 changing default to False; not sure if this can affect blend = false cases -- maybe yes beyond edges? ###UNTESTED
    #e should probably rename this and change it from a bool to a choice or to more bools
    # (since it has 5 or 6 possible values; see code comments)
    blend = Option(
        bool, False,
        doc="whether to blend translucent images with background")  #070403
    # Note: blend doesn't turn off depth buffer writing, but does reject fully transparent fragments (via GL_ALPHA_TEST),
    # so only the translucent (i.e. partly transparent) pixels can obscure things if drawn first,
    # or can be hover-highlighted (affect sbar_text, act as drag-grip-point, etc).
    # This behavior is fine if translucency is used for antialiased edges.
    # (Except for images that have very small nonzero alphas that really ought to be zero instead,
    #  like the one used in testexpr_11pd3 (fyi, see screenshot 'alpha fluctuations.jpg', not in cvs) --
    #  maybe this comes in through the rescaling and/or mipmap filtering?)
    # See also slightly related glStencilFunc, glDepthFunc.
    alpha_test = Option(
        bool,
        _self.blend,
        doc=
        "whether to use GL_ALPHA_TEST (by default, use it when blend option is true)"
    )  #070404
    # this is effectively an option to not use GL_ALPHA_TEST when blend is True (as we'd normally do then)

    two_sided = Option(
        bool,
        False,
        doc="whether to disable GL_CULL_FACE so that both sides get drawn"
    )  #080223

    ###e should add option to turn off depth buffer writing -- see warning below

    ###e should add option to turn off color buffer writing -- glColorMask -- see warning below
    # see also disable_color (widget2d.py, maybe move to GLPane.py?)

    # [or find a more modular way to control things like that -- wrappers? std options?]

    ### WARNING: hard to disable those correctly (re restoring state)
    # if we ever get drawn in a larger thing that disables one of them -- as we might,
    #  due to selobj highlighting! ###k CHECK THIS for other new disables too, alpha and blend...

    nreps = Option(
        float, 1.0
    )  #e rename - repeat count; mostly only useful when clamp is False, but ought to work otherwise too
    ##e generalize to let caller supply tex_dx and tex_dy vectors, for rotating the texture within the drawing region;
    # (Can that be done as a more general value for this option? Unclear whether that's natural, tho passing in a matrix might be...)
    tex_origin = Option(
        Vector, ORIGIN
    )  # offset option, to shift tex_origin; can be 2d or 3d, though we use only two dims of it
    #e design Qs:
    # - is it really Point rather than Vector?
    # - does it interact with [nim] drawing-region-origin so as to line up if we use the same one for adjacent regions?
    size = Option(
        Widget2D, Rect(2)
    )  ##e also permit number or pair, ie args to Rect also should be ok # [experiment 061130]
    shape = Option(Anything,
                   None,
                   doc="""shape name ('upper-left-half' or 'lower-right-half'),
                           or sequence of 3 2d points (intrinsic coords, CCW),
                           to draw only a sub-triangle of the textured rect image"""
                   )
    ###e also permit shape option to specify polygon, not just triangle, or, any geometry-drawing expr
    # (see comments in helper function for more on that)
    # (has design issues re tex/model coord correspondence [tho current scheme is well-defined and might be best for many uses],
    #  and possible embedded textured parts)

    bleft = size.bleft
    bright = size.bright
    bbottom = size.bbottom
    btop = size.btop

    # more options, which affect initial image loading from file, thus are part of the texture-cache key [061127]
    rescale = Option(
        bool, True
    )  # whether to resize by rescaling or padding (default might be changed after testing #e)
    ideal_width = Option(
        int, 256
    )  ###e let them be a func of image size, as a pair? (eg so they can be next greater 2pow?) someday.
    ideal_height = Option(int, 256)
    convert = Option(
        bool, False)  #061128, whether to convert image to DESIRED_MODE RGBX.
    ### NOTE: type bool is wrong, since later [but long before 070404] it became able to let you specify another mode,
    # and in that case it also affects getTextureData retval mode. This is now routinely used for transparent texture images.
    _tmpmode = Option(
        str, None)  #k None is not str, is that ok? #doc [might be temp kluge]

    #e these are not fully implem -- at best, when rescale = False, you'll see black padding when drawing;
    # what we need to do is pass a reduced tex coord so you don't. I hope the image (not padding) will be at the lower left corner
    # of what's drawn. [as of 061127 1022p] [it's not -- this is commented on elsewhere and explained, probably in ImageUtils.py]

    # formulae
    # THIS SHOULD WORK (I think), but doesn't, don't know why ####BUG: [is my syntax wrong for passing the kws to call_Expr???]
    ## texture_options = call_Expr( dict, clamp = clamp, pixmap = pixmap, use_mipmaps = use_mipmaps, decal = decal )
    ## __get__ is nim in the Expr <type 'dict'>(*(), **{'clamp': <call_Expr#5175: .....

    def _C__texture_holder(self):
        # pil_kws added 061127, doc in nEImageOps;
        # current defaults are ideal_width = None, ideal_height = None, rescale = True, convert = False, _tmpmode = None.
        # Note: don't include texture_options here, since they don't affect the PIL image object itself.
        pil_kws = dict(rescale=self.rescale,
                       ideal_width=self.ideal_width,
                       ideal_height=self.ideal_height,
                       convert=self.convert,
                       _tmpmode=self._tmpmode)
        items = pil_kws.items()
        items.sort()
        pil_kws_items = tuple(items)  # make that dict hashable
        tex_key = (
            self.use_filename, pil_kws_items
        )  # must be compatible with the single arg to _texture_holder.__init__
        return texture_holder_for_filename[
            tex_key]  # this shared global MemoDict is defined above

    _image = _self._texture_holder._image

    def bind_texture(self, **kws):
        """
        bind our texture (and set other GL params needed to draw with it).

        Anything that calls this should eventually call
        self.kluge_reset_texture_mode_to_work_around_renderText_bug(),
        but only after all drawing using the texture is done.
        """
        self._texture_holder.bind_texture(**kws)

    def kluge_reset_texture_mode_to_work_around_renderText_bug(self):
        """
        This needs to be called after calling self.bind_texture,
        but only after drawing using the texture is done.
        """
        self._texture_holder.kluge_reset_texture_mode_to_work_around_renderText_bug(
            self.env.glpane)

    def draw(self):
        # bind texture for image filename [#e or other image object],
        # doing whatever is needed of allocating texture name, loading image object, loading texture data;
        ###e optim: don't call glBindTexture if it's already bound, and/or have a "raw draw" which assumes it's already bound
        if 'workaround bug in formula for texture_options':
            texture_options = dict(clamp=self.clamp,
                                   pixmap=self.pixmap,
                                   use_mipmaps=self.use_mipmaps,
                                   decal=self.decal)
        else:
            texture_options = self.texture_options  # never used, but desired once bug is fixed
        self.bind_texture(**texture_options)

        try:

            # figure out texture coords (from optional args, not yet defined ###e) -- stub for now
            nreps = float(
                self.nreps
            )  # float won't be needed once we have type coercion; not analyzed whether int vs float matters in subr
            ## tex_origin = ORIGIN2 # see also testdraw's drawtest1, still used in testmode to draw whole font texture rect
            tex_origin = V(self.tex_origin[0], self.tex_origin[1])
            ## tex_dx = D2X ; tex_dx *= nreps # this modifies a shared, mutable Numeric array object, namely D2X! Not what I wanted.
            tex_dx = D2X * nreps
            tex_dy = D2Y * nreps

            # where to draw it -- act like a 2D Rect for now, determined by self's lbox,
            # which presently comes from self.size
            origin = V(-self.bleft, -self.bbottom, 0)
            # see also the code in drawfont2 which tweaks the drawing position to improve the pixel alignment
            # (in a way which won't work right inside a display list if any translation occurred before now in that display list)
            # in case we want to offer that option here someday [070124 comment]
            ##        dx = DX * self.bright
            ##        dy = DY * self.btop
            dx = DX * (self.bleft + self.bright
                       )  # bugfix 070304: include bleft, bbottom here
            dy = DY * (self.bbottom + self.btop)

            blend = self.blend
            alpha_test = self.alpha_test
            two_sided = self.two_sided
            shape = self.shape  # for now, None or a symbolic string (choices are hardcoded below)

            if blend:
                glEnable(GL_BLEND)
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            if alpha_test:
                glEnable(GL_ALPHA_TEST)  # (red book p.462-463)
                glAlphaFunc(
                    GL_GREATER, 0.0
                )  # don't draw the fully transparent parts into the depth or stencil buffers
                ##e maybe let that 0.0 be an option? eg the value of alpha_test itself? Right now, it can be False ~== 0 (not None).
            if two_sided:
                glDisable(GL_CULL_FACE)

            if not shape:
                draw_textured_rect(origin, dx, dy, tex_origin, tex_dx, tex_dy)
            else:
                # support sub-shapes of the image rect, but with unchanged texture coords relative to the whole rect [070404 ###UNTESTED]
                if type(shape) == type(""):
                    ##e use an external shape name->value mapping?
                    # in fact, should such a mapping be part of the dynamic graphics-instance env (self.env)??
                    if shape == 'upper-left-half':
                        shape = ((0, 0), (1, 1), (0, 1))
                    elif shape == 'lower-right-half':
                        shape = ((0, 0), (1, 0), (1, 1))
                    elif shape == 'upper-right-half':  #070628
                        shape = ((0, 1), (1, 0), (1, 1))
                    elif shape == 'lower-left-half':  #070628; untested
                        shape = ((0, 0), (1, 0), (0, 1))
                    else:
                        assert 0, "in %r, don't know this shape name: %r" % (
                            self, shape)
                # otherwise assume it might be the right form to pass directly
                # (list of 3 2d points in [0,1] x [0,1] relative coords -- might need to be in CCW winding order)
                draw_textured_rect_subtriangle(origin, dx, dy, tex_origin,
                                               tex_dx, tex_dy, shape)
                pass
            if blend:
                glDisable(GL_BLEND)
            if alpha_test:
                glDisable(GL_ALPHA_TEST)
            if two_sided:
                glEnable(GL_CULL_FACE)
        finally:
            self.kluge_reset_texture_mode_to_work_around_renderText_bug()
        return  # from Image.draw

    # note the suboptimal error message from this mistake:
    #   bright = DX * 2
    #   ## TypeError: only rank-0 arrays can be converted to Python scalars. ...
    #   ## [test.py:419 inst.draw()] [Column.py:91 glTranslatef(dx,0,0)]
    #e Ideally we'd have a typecheck on _self.bright (etc) in Widget2D
    # which would catch this error whenever self.bright was computed,
    # or even better, when it's a constant for the class (as in this case),
    # right when that constant formula is defined.

    pass  # end of class Image
Esempio n. 32
0
class MultipleDnaSegmentResize_EditCommand(DnaSegment_EditCommand):

    #Graphics Mode
    GraphicsMode_class = MultipleDnaSegmentResize_GraphicsMode

    #Property Manager
    PM_class = MultipleDnaSegmentResize_PropertyManager

    cmdname = 'MULTIPLE_DNA_SEGMENT_RESIZE'  # REVIEW: needed? correct?

    commandName = 'MULTIPLE_DNA_SEGMENT_RESIZE'
    featurename = "Edit Multiple Dna Segments"
    from utilities.constants import CL_SUBCOMMAND
    command_level = CL_SUBCOMMAND
    command_parent = 'BUILD_DNA'

    #This command operates on (resizes) multiple DnaSegments at once.
    #It does that by  looping through the DnaSegments and resizing them
    #individually. self.currentStruct is set to the 'current' DnaSegment
    #in that loop and then it is used in the resizing code.
    #@see: DnaSegmentList class
    #@see: self.modifyStructure()
    currentStruct = None

    handlePoint1 = State(Point, ORIGIN)
    handlePoint2 = State(Point, ORIGIN)
    #The minimum 'stopper'length used for resize handles
    #@see: self._update_resizeHandle_stopper_length for details.
    _resizeHandle_stopper_length = State(Width, -100000)

    rotationHandleBasePoint1 = State(Point, ORIGIN)
    rotationHandleBasePoint2 = State(Point, ORIGIN)

    #See self._update_resizeHandle_radius where this gets changed.
    #also see DnaSegment_ResizeHandle to see how its implemented.
    handleSphereRadius1 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)
    handleSphereRadius2 = State(Width, HANDLE_RADIUS_DEFAULT_VALUE)

    cylinderWidth = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    cylinderWidth2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    #@TODO: modify the 'State params for rotation_distance
    rotation_distance1 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)
    rotation_distance2 = State(Width, CYLINDER_WIDTH_DEFAULT_VALUE)

    leftHandle = Instance(
        DnaSegment_ResizeHandle(
            command=_self,
            height_ref=call_Expr(ObjAttr_StateRef, _self, 'cylinderWidth'),
            origin=handlePoint1,
            fixedEndOfStructure=handlePoint2,
            direction=norm_Expr(handlePoint1 - handlePoint2),
            sphereRadius=handleSphereRadius1,
            range=(_resizeHandle_stopper_length, 10000)))

    rightHandle = Instance(
        DnaSegment_ResizeHandle(
            command=_self,
            height_ref=call_Expr(ObjAttr_StateRef, _self, 'cylinderWidth2'),
            origin=handlePoint2,
            fixedEndOfStructure=handlePoint1,
            direction=norm_Expr(handlePoint2 - handlePoint1),
            sphereRadius=handleSphereRadius2,
            range=(_resizeHandle_stopper_length, 10000)))

    def _update_previousParams_in_model_changed(self):
        """
        Overrides superclass method. Does nothing in this class.
        @see: self.model_changed() which calls this method.
        """
        pass

    def isAddSegmentsToolActive(self):
        """
        Returns True if the Add segments tool in the PM, that allows adding
        dna segments from the resize segment list,  is active.
        @see: MultipleDnaSegmentResize_GraphicsMode.chunkLeftUp()
        @see: MultipleDnaSegmentResize_GraphicsMode.end_selection_from_GLPane()
        @see: self.isRemoveSegmentsToolActive()
        @see: self.addSegmentToResizeSegmentList()
        """
        if self.propMgr is None:
            return False

        return self.propMgr.isAddSegmentsToolActive()

    def isRemoveSegmentsToolActive(self):
        """
        Returns True if the Remove Segments tool in the PM, that allows removing
        dna segments from the resize segment list, is active.
        @see: MultipleDnaSegmentResize_GraphicsMode.chunkLeftUp()
        @see: MultipleDnaSegmentResize_GraphicsMode.end_selection_from_GLPane()
        @see: self.isAddSegmentsToolActive()
        @see: self.removeSegmentFromResizeSegmentList()
        """
        if self.propMgr is None:
            return False

        return self.propMgr.isRemoveSegmentsToolActive()

    def addSegmentToResizeSegmentList(self, segment):
        """
        Adds the given segment to the resize segment list
        @param segment: The DnaSegment to be added to the resize segment list.
        Also does other things such as updating resize handles etc.
        @type sement: B{DnaSegment}
        @see: self.isAddSegmentsToolActive()
        """
        assert isinstance(segment, self.win.assy.DnaSegment)
        self.struct.addToStructList(segment)

    def removeSegmentFromResizeSegmentList(self, segment):
        """
        Removes the given segment from the resize segment list
        @param segment: The DnaSegment to be removed from the resize segment
        list. Also does other things such as updating resize handles etc.
        @type sement: B{DnaSegment}
        @see: self.isRemoveSegmentsToolActive()
        """
        assert isinstance(segment, self.win.assy.DnaSegment)
        self.struct.removeFromStructList(segment)

    def setResizeStructList(self, structList):
        """
        Replaces the list of segments to be resized with the
        given segmentList. Calls the related method of self.struct.
        @param structList: New list of segments to be resized.
        @type  structList: list
        @see: DnaSegmentList.setResizeStructList()
        """
        if self.hasValidStructure():
            self.struct.setStructList(structList)
            self.updateHandlePositions()

    def getResizeSegmentList(self):
        """
        Returns a list of segments to be resized at once.
        @see: DnaSegmentList.getStructList()
        """
        if self.hasValidStructure():
            return self.struct.getStructList()
        return ()

    def updateResizeSegmentList(self):
        """
        Update the list of resize segments (maintained by self.struct)
        """
        if self.hasValidStructure():
            self.struct.updateStructList()

    def editStructure(self, struct=None):
        """
        Overrides superclass method. It first creates a B{DnaSegmentList} object
        using the provided list (struct argument) The DnaSegmentList object
        acts as self.struct.
        @param struct: The caller of this method should provide a list of
               containing all the structurs that need to be edited (resized) at
               once. Caller also needs to make sure that this list contains
               objects of class DnaSegment.
        @type  struct: list
        @see: B{DnaSegmentList}
        """
        if isinstance(struct, list):
            dnaSegmentListObject = DnaSegmentList(self, structList=struct)

        _superclass.editStructure(self, struct=dnaSegmentListObject)

    def _updatePropMgrParams(self):
        """
        Subclasses may override this method.
        Update some property manager parameters with the parameters of
        self.struct (which is being edited)
        @see: self.editStructure()
        @TODO: For multiple Dna segment resizing, this method does nothing as
           of 2008-05-14. Need to be revised.
        """

        #Commented out code as of 2008-05-14 ===========

        #endPoint1, endPoint2 = self.struct.getAxisEndPoints()
        #params_for_propMgr = (None,
        #endPoint1,
        #endPoint2)

        ##TODO 2008-03-25: better to get all parameters from self.struct and
        ##set it in propMgr?  This will mostly work except that reverse is
        ##not true. i.e. we can not specify same set of params for
        ##self.struct.setProps ...because endPoint1 and endPoint2 are derived.
        ##by the structure when needed. Commenting out following line of code
        ##UPDATE 2008-05-06 Fixes a bug due to which the parameters in propMGr
        ##of DnaSegment_EditCommand are not same as the original structure
        ##(e.g. bases per turn and duplexrise)
        ###self.propMgr.setParameters(params_for_propMgr)

        pass

    def _update_resizeHandle_radius(self):
        """
        Finds out the sphere radius to use for the resize handles, based on
        atom /chunk or glpane display (whichever decides the display of the end
        atoms.  The default  value is 1.2.


        @see: self.updateHandlePositions()
        @see: B{Atom.drawing_radius()}
        @TODO: Refactor this. It does essentially same thing as the
        superclass method. The problem is due to the use of the global constant
        HANDLE_RADIUS_DEFAULT_VALUE which needs to be different (bigger) in this
        class. So better to make it a class constant (need similar changes
        in several commands. To be done in a refactoring project.
        """
        atm1, atm2 = self.struct.getAxisEndAtoms()
        if atm1 is not None:
            self.handleSphereRadius1 = max(1.005 * atm1.drawing_radius(),
                                           1.005 * HANDLE_RADIUS_DEFAULT_VALUE)
        if atm2 is not None:
            self.handleSphereRadius2 = max(1.005 * atm2.drawing_radius(),
                                           1.005 * HANDLE_RADIUS_DEFAULT_VALUE)

    def _modifyStructure(self, params):
        """
        Modify the structure based on the parameters specified.
        Overrides EditCommand._modifystructure. This method removes the old
        structure and creates a new one using self._createStructure. This
        was needed for the structures like this (Dna, Nanotube etc) . .
        See more comments in the method.
        """

        if self.currentStruct is None:
            print_compact_stack("bug: self.currentStruct doesn't exist"\
                                "exiting self._modifyStructure")
            return

        assert isinstance(self.currentStruct, self.win.assy.DnaSegment)

        self.dna = B_Dna_PAM3_Generator()

        duplexRise = self.currentStruct.getDuplexRise()

        basesPerTurn = self.currentStruct.getBasesPerTurn()

        numberOfBasePairsToAddOrRemove = self._determine_numberOfBasePairs_to_change(
        )

        ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end()

        if ladderEndAxisAtom is None:
            print_compact_stack("bug: unable to resize the DnaSegment"\
                                "%r, end axis ato not found"%self.currentStruct)
            return

        if numberOfBasePairsToAddOrRemove != 0:

            resizeEnd_final_position = self._get_resizeEnd_final_position(
                ladderEndAxisAtom, abs(numberOfBasePairsToAddOrRemove),
                duplexRise)

            self.dna.modify(self.currentStruct, ladderEndAxisAtom,
                            numberOfBasePairsToAddOrRemove, basesPerTurn,
                            duplexRise, ladderEndAxisAtom.posn(),
                            resizeEnd_final_position)

        self.previousParams = params

        return

    def modifyStructure(self):
        """

        Called when a resize handle is dragged to change the length of one
        or more DnaSegments (to be resized together)  Called upon leftUp .

        To acheive this resizing, it loops through the DnaSegments to be resized
        and calls self._modifyStructure() for individual DnaSegments.

        Note that Client should call this public method and should never call
        the private method self._modifyStructure.

        @see: B{DnaSegment_ResizeHandle.on_release} (the caller)
        @see: B{SelectChunks_GraphicsMode.leftUp} (which calls the
              the relevent method in DragHandler API. )
        @see: B{exprs.DraggableHandle_AlongLine}, B{exprs.DragBehavior}

        @see: B{self._modifyStructure}
        @see: B{DnaSegmentList.updateAverageEndPoints()}

        """

        if self.grabbedHandle is None:
            return

        for segment in self.getResizeSegmentList():
            #TODO 2008-05-14: DnaSegmentList class relies on self.currentStruct.
            #See if there is a better way. Ok for now
            self.currentStruct = segment
            #self._modifyStructure needs 'param' argument. As of 2008-05-14
            #it is not supported/needed for multiple dna segment resizing.
            #So just pass an empty tuple.
            params = ()
            self._modifyStructure(params)
            #Reset the self.currentStruct after use
            self.currentStruct = None

        #Update the average end points of the self.struct so that resize handles
        #are placed at proper positions.
        self.struct.updateAverageEndPoints()

        self.win.assy.changed()
        self.win.win_update()

    def _get_resizeEnd_final_position(self, ladderEndAxisAtom, numberOfBases,
                                      duplexRise):
        """
        Returns the final position of the resize end.
        Note that we can not use the grabbedHandle's currentPosition as the
        final position because resize handle is placed at an 'average' position.
        So we must compute the correct vector using the 'self.currentStruct'
        """

        final_position = None

        other_axisEndAtom = self.struct.getOtherAxisEndAtom(ladderEndAxisAtom)

        if other_axisEndAtom is None:
            return None

        axis_vector = ladderEndAxisAtom.posn() - other_axisEndAtom.posn()
        segment_length_to_add = getDuplexLength('B-DNA',
                                                numberOfBases,
                                                duplexRise=duplexRise)

        signFactor = +1

        ##if self.grabbedHandle is not None:
        ##vec = self.grabbedHandle.currentPosition - \
        ##       self.grabbedHandle.fixedEndOfStructure

        ##if dot(vec, axis_vector) < 0:
        ##signFactor = -1
        ##else:
        ##signFactor = +1

        axis_vector = axis_vector * signFactor


        final_position = ladderEndAxisAtom.posn() + \
                       norm(axis_vector)*segment_length_to_add

        return final_position

    def hasResizableStructure(self):
        """
        """
        if len(self.struct.getStructList()) == 0:
            why_not = 'Segment list is empty'
            isResizable = False
            return isResizable, why_not

        return _superclass.hasResizableStructure(self)

    def _getStructureType(self):
        """
        Returns the type of the structure this command supports.
        This is used in isinstance test.
        @see: EditCommand._getStructureType()
        """
        return DnaSegmentList

    def getDnaRibbonParams(self):
        """
        Overrides superclass method.
        @TODO:[2008-05-14]  This could SLOW things up! Its called multiple times
        from the graphics mode (for all the DnaSegments to be resized at once).
        There is no apparent solution to this, but we could try optimizing the
        following code. Also needs some refactoring.
        @see: MultipleDnaSegmentResize_GraphicsMode._drawHandles()
        @see: self._determine_numberOfBasePairs_to_change()
        """

        #Optimization: First test a few basic things to see if the dna ribbon
        #should be drawn, before doing more computations -- Ninad 2008-05-14

        params_when_adding_bases = None
        params_when_removing_bases = None

        if self.grabbedHandle is None:
            return None, None

        if self.grabbedHandle.origin is None:
            return None, None

        ladderEndAxisAtom = self.get_axisEndAtom_at_resize_end()

        if ladderEndAxisAtom is None:
            return None, None

        numberOfBasePairsToAddOrRemove = self._determine_numberOfBasePairs_to_change(
        )

        if numberOfBasePairsToAddOrRemove == 0:
            return None, None


        direction_of_drag = norm(self.grabbedHandle.currentPosition - \
                                 self.grabbedHandle.origin)

        #@TODO: BUG: DO NOT use self._get_resizeEnd_final_position to compute
        #the resizeEnd_final_position. It computes the segment length to add
        #using the number of base pairs to add ... and it uses
        #getDuplexLength method which in turn rounds off the length as an integer
        #multiple of duplexRise. Although this is what we want in self._modify()
        #the drawDnaRibbon draws the base pair only if step size is > 3.18!
        #so no basepair is drawn at exact val of 3.18. This is BUG, harder to
        #fix. for drawing dna ribbon, lets justcompute the resize end final
        #position using the following code. -- Ninad 2008-05-14
        other_axisEndAtom = self.struct.getOtherAxisEndAtom(ladderEndAxisAtom)

        if other_axisEndAtom is None:
            return None, None

        axis_vector = ladderEndAxisAtom.posn() - other_axisEndAtom.posn()
        currentPosition = self.grabbedHandle.currentPosition
        changedLength = vlen(currentPosition - self.grabbedHandle.origin)

        #NOTE: (TODO) we call self._determine_numberOfBasePairs_to_change() at the
        #beginning of this method which checks various things such as
        #total distance moved by the handle etc to determine whether to draw
        #the ribbon. So those checks are not done here. If that call is removed
        #then we need to do those checks.

        if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
            signFactor = -1.0
        else:
            signFactor = 1.0

        resizeEnd_final_position = ladderEndAxisAtom.posn() + \
                                 norm(axis_vector)*changedLength*signFactor

        #If the segment is being shortened (determined by checking the
        #direction of drag) , no need to draw the rubberband line.
        if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
            params_when_adding_bases = None
            params_when_removing_bases = (resizeEnd_final_position)

            return params_when_adding_bases, \
                   params_when_removing_bases

        basesPerTurn = self.struct.getBasesPerTurn()
        duplexRise = self.struct.getDuplexRise()

        ladder = ladderEndAxisAtom.molecule.ladder
        endBaseAtomList = ladder.get_endBaseAtoms_containing_atom(
            ladderEndAxisAtom)

        ribbon1_start_point = None
        ribbon2_start_point = None
        ribbon1_direction = None
        ribbon2_direction = None

        ribbon1Color = applegreen
        ribbon2Color = applegreen

        if endBaseAtomList and len(endBaseAtomList) > 2:
            strand_atom1 = endBaseAtomList[0]
            strand_atom2 = endBaseAtomList[2]

            if strand_atom1:
                ribbon1_start_point = strand_atom1.posn()
                for bond_direction, neighbor in strand_atom1.bond_directions_to_neighbors(
                ):
                    if neighbor and neighbor.is_singlet():
                        ribbon1_direction = bond_direction
                        break

                ribbon1Color = strand_atom1.molecule.color
                if not ribbon1Color:
                    ribbon1Color = strand_atom1.element.color

            if strand_atom2:
                ribbon2_start_point = strand_atom2.posn()
                for bond_direction, neighbor in strand_atom2.bond_directions_to_neighbors(
                ):
                    if neighbor and neighbor.is_singlet():
                        ribbon2_direction = bond_direction
                        break
                ribbon2Color = strand_atom2.molecule.color
                if not ribbon2Color:
                    ribbon2Color = strand_atom2.element.color

        params_when_adding_bases = (ladderEndAxisAtom.posn(),
                                    resizeEnd_final_position, basesPerTurn,
                                    duplexRise, ribbon1_start_point,
                                    ribbon2_start_point, ribbon1_direction,
                                    ribbon2_direction, ribbon1Color,
                                    ribbon2Color)

        params_when_removing_bases = None

        return params_when_adding_bases, params_when_removing_bases

    def _determine_numberOfBasePairs_to_change(self):
        """
        Overrides superclass method
        @TODO: This is significantly different (and perhaps better)
               than the superclass method. See how to incorporate changes in
               this method in superclass.

        @see: self.getDnaRibbonParams()
        """

        currentPosition = self.grabbedHandle.currentPosition
        fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure
        duplexRise = self.struct.getDuplexRise()

        changedLength = vlen(currentPosition - self.grabbedHandle.origin)

        direction_of_drag = norm(self.grabbedHandle.currentPosition - \
                                 self.grabbedHandle.origin)

        #Even when the direction of drag is negative (i.e. the basepairs being
        #removed), make sure not to remove base pairs for very small movement
        #of the grabbed handle
        if changedLength < 0.2 * duplexRise:
            return 0

        #This check quickly determines if the grabbed handle moved by a distance
        #more than the duplexRise and avoids further computations
        #This condition is applicable only when the direction of drag is
        #positive..i.e. bases bing added to the segment.
        if changedLength < duplexRise and \
           dot(self.grabbedHandle.direction, direction_of_drag) > 0:
            return 0

        #If the segment is being shortened (determined by checking the
        #direction of drag)

        numberOfBasesToAddOrRemove =  \
                                   getNumberOfBasePairsFromDuplexLength(
                                       'B-DNA',
                                       changedLength,
                                       duplexRise = duplexRise)

        if dot(self.grabbedHandle.direction, direction_of_drag) < 0:
            numberOfBasesToAddOrRemove = -numberOfBasesToAddOrRemove

        if numberOfBasesToAddOrRemove > 0:
            #dna.modify will remove the first base pair it creates
            #(that basepair will only be used for proper alignment of the
            #duplex with the existing structure) So we need to compensate for
            #this basepair by adding 1 to the new number of base pairs.

            #UPDATE 2008-05-14: The following commented out code
            #i.e. "##numberOfBasesToAddOrRemove += 1" is not required in this
            #class , because the way we compute the number of base pairs to
            #be added is different than than how its done at the moment in the
            #superclass. In this method, we compute bases to be added from
            #the resize end and that computation INCLUDES the resize end.
            #so the number that it returns is already one more than the actual
            #bases to be added. so commenting out the following line
            # -- Ninad 2008-05-14
            ##numberOfBasesToAddOrRemove += 1
            ##print "*** numberOfBasesToAddOrRemove = ", numberOfBasesToAddOrRemove
            ##print "**** changedLength =", changedLength
            pass
            ###UPDATE 2008-06-26: for some reason, when the number of base pairs
            ###to be added (i.e. value of numberOfBasesToAddOrRemove) is  1 more
            ###than the actual number of base pairs to be added. So subtract 1
            ###from this number. Cause not debugged. -- Ninad
            ##if numberOfBasesToAddOrRemove > 1:
            ##numberOfBasesToAddOrRemove -= 1
            #UPDATE 2008-08-20: Note that DnaSegment_EditCommand.getCursorText()
            #does the job of removing the extra basepair from numberOfBasesToAddOrRemove
            #It(subtracting 1 basePair) is not done here as
            #self._modifyStructure() needs it without the subtraction. This is
            #prone to bugs and need to be cleaned up. -- Ninad

        return numberOfBasesToAddOrRemove