Programmatically modifying user-drawn-shape?

Hi,

Working with Igor 8.0.2.1, I'm using DrawUserShape( ) to place 4 shapes in a Control Panel.  I'm setting b1 of the options field (bitwise OR with 0x02) of each shape and then using the mouseUp action to detect when and where the user clicks on each shape.  All works well until the user enlarges the control panel. 

I can dynamically repaint the shapes to fit the enlarged control panel, but I can't seem to adjust the rectangular region over which each shape raises its mouseUp actions.  I suspect I need to adjust that rectangular region each time I redraw at a new size, but so far I can't find any guidelines for doing this.  Can anyone offer a suggestion? 

Do I need to call DrawUserShape( ) again?  If so, do I need to delete the old shape before re-calling DrawUserShape( )?  Do I need to use drawing groups to indivdually address each of the 4 shapes?  Can such steps be performed within the user-defined drawing function?

Thanks in advance for any help,

Bruce

I guess I'm having trouble figuring out the exact problem you're having. Presumably the hot rectangle is something you compute based on the size of the shape? I believe that the x0, x1, y0, y1 members of WMDrawUserShapeStruct are correctly set whenever your user shape proc is called, so you can re-compute the rectangle on the fly. Or if it is a user-configurable rectangle then the draw-mode actions would be the time to store that.

Have you found the demo experiment? File->Example Experiments->Programming->User Draw Shapes. Look at the Fat Arrows shape- it has user-set head and tail parameters, and in draw mode you can drag handles that are shown as small yellow circles.

Thanks for the quick reply.  Please note: I'm presently only concerned about handling 'Operate-Mode' conditions.  I'm not thinking about 'Draw-Mode' conditions right now.

I want to be able to change the size and position of each shape's hot rectangle on the fly, as the user changes the size of the control panel (and therefore the size and positions of the drawn shapes).

Can I do that by recomputing x0, x1, y0, y1 in the user-defined draw function?  (The on-line documentation for WMDrawUserShapeStruct reports that these parameters are "Input", suggesting that Igor will not respond to changes made to those values in my draw function.

The 'Fat Arrows' example does show how to resize the user shape, but it doesn't show how to resize the hot rectangle.

I hope this clarifies what I'm trying to do.

Thanks,

Bruce

 

 

Those coordinates are the bounding rectangle of your shape. In the draw action, compute the hot rectangle, draw it, and store it in your user data (I'm working from memory here...).

I mentioned the Fat Arrows shape because it has a hot circle that is recomputed. I think it doesn't matter that it is in Draw Mode, the same technique should apply.

In reply to by johnweeks

Thanks again for pointing out the 'Fat Arrows' example.  In that example, each arrow's 'hot rectangle' gets updated when the user switches to draw-mode, selects an arrow, and resizes the blue bounding rectangle.

I've pasted here code from the function FatArrowFuncBase() that responds to the 'drawselected' action:

        case "drawSelected":
            DrawPoly/ABS originX,originY,1,1,polyX,polyY
            SetDrawEnv fillfgc= (65535,65535,0),fillpat=1,linethick=1,save    // prepare to draw yellow dots
            
            SetDrawEnv push        
            SetDrawEnv origin= shaftX, shaftY
            SetDrawEnv xcoord=abs,ycoord=abs
            DrawOval -5,-5,5,5        // draw yellow dot at arrow's tail
            SetDrawEnv pop

            SetDrawEnv push        
            SetDrawEnv origin= headX, headY
            SetDrawEnv xcoord=abs,ycoord=abs
            DrawOval -5,-5,5,5        // draw yellow dot at edge of arrowhead
            SetDrawEnv pop
            break

I can't see what part of this code changes the arrow's hot rectangle.  Does the code above make that change, or is that done elsewhere in Igor?  If it's done here, please tell me how.

My goal is to write Igor code that programmatically changes the hot rectangle for my shapes, without my users switching to draw-mode or having to manually resize the shapes.

bp

The "SetDrawEnv origin" command offsets the drawing so that "DrawOval -5,-5,5,5" will draw a 10-point circle at any desired location.

Hi John,

Sorry if I've misunderstood.  I'm not simply trying to draw an oval (or rectangle) during the shape's 'draw' action.  I need to change the size and position of the rectangular region over which the shape generates events (actions).  For example: as my code re-draws a larger copy of the shape, I need to enlarge the rectangular region which gives rise to events such as mousedown, mouseup, mousemove, etc.

My current code successfully changes the size of the shape drawn on the screen, but it doesn't change the size of that responsive rectangular region.  Thus when I programmatically enlarge my shape, I get mouse actions only when the mouse is over the original (and smaller) rectangular region -- not over the full extent of the enlarged shape.

As I understand it, SetDrawEnv origin=x0,y0 defines the origin for subsequent Draw operations, but doesn't set the size of the rectangular region which responds to mouse actions.  I don't understand how that will help solve this problem.

Thanks.

bp

In order to do hit testing, you must be storing parameters that tell you where the hot regions are. When you draw, you are also computing those regions because you draw a rectangle in that spot. So when you compute the new rectangle to draw, also store it in your data structures.

The Fat Arrow example stores and retrieves STRUCT MyFatArrowFuncSettings in the privateString member of the WMDrawUserShapeStruct. Don't get hung up on the fact that it is a string; StructGet and StructPut store a binary copy of a structure in a string; it treats the string simply as a bag of bytes. There are some restrictions on what you can have in a structure if you are going to use StructGet and StructPut.

Doing the hit testing does involve some non-intuitive programming :)

In reply to by johnweeks

Thanks for your reply, but I suspect I'm still not making my question clear.

I've attached a small Igor experiment that I hope will clarify my question.  Please open the experiment and follow the notes / questions in the comments at the top of the Procedure Window.  Let me know if anything remains unclear after testing that experiment.

Looking forward to your answers,

bp

The numbers will stay at the centers of the arrows if you change the conditional block like this:

            if( strlen(s.textString) != 0 )
                Variable xx = origXabs + arrowWidth/2
                Variable yy = origYabs + arrowHeight/2
                print "xx:", xx, "yy:",yy, "string:", s.textString
                setdrawenv xcoord=abs, ycoord=abs
                SetDrawEnv textxjust=1, textyjust=1, textrot= s.angle, origin=xx,yy
                DrawText 0.5,0.5, s.textString                  // draw text centered within the 'hot rectangle'
            endif

Unless you use SetDrawEnv push and SetDrawEnv pop, the settings established by SetDrawEnv only last for one drawing command. So in my code snippet here, I added SetDrawEnv commands before the DrawText command to re-establish the origin. And I computed the center from already-computed values from above.

This shows how to find the right coordinates, now you need to save them in the private struct so that you can use them for hit testing

In reply to by johnweeks

The code you provided does indeed keep the labels in the centers of the arrows.  Thanks for that.  Unfortunately that wasn't my question.

When the control panel is first displayed, each arrow is drawn within its 'hot rectangle'.   Because Igor gives me mouse events when clicks land anywhere in those rectangles, I get events every time the user clicks on each arrow.  This is correct behavior.

However, when a user enlarges the control panel, the 'hot rectangles' do not change size or position, even though the arrows get larger and move.  Igor continues to give me mouse-events only when those actions land anywhere in those (now too tiny) rectangles.  As a result, I don't get mouse events when users click on the larger arrows.  This is the problem.

So my original question remains:  How can I programmatically change the size and position of the 'hot rectangles' that determine which mouse events Igor sends to my code?  This is different than 'hit testing' and is different than painting the labels in the center of the arrows (although that's nice too).

I hope the pictures in the attached .pdf file will help explain the question.  The images are taken from the experiment I sent earlier today.

Thanks,

bp

HotRects.pdf

Ahh....

Thank you for your patience as I (willfully? no, not really) misunderstood your question. The answer to the question you actually asked is that these things are drawing objects, not controls even though they act like controls. So to update Igor's notion of the size of these things, erase and redraw them. When you drag a drawing object's handles in draw mode, that's effectively what you're doing, just without having to erase and redraw.

Here's my version, in which I extracted the code that actually draws the arrows from your PanelFatArrows() function. That makes it available to call from either PanelFatArrows(), or from HookProc_FatArrow():

Function DrawFatArrows(string panelName, struct pointf & pnlSize)
    SetDrawLayer/K UserBack
   
    struct      sFatArrow   thisFatArrow
    make        /O /N=(4,4)         arrowState

    SetArrowState (thisFatArrow, pnlSize, 0)   
    DrawUserShape thisFatArrow.x0, thisFatArrow.y0, thisFatArrow.x1,thisFatArrow.y1,        MyFatArrowFuncExR, "0", A"5MuMAz5K\\>=z!!!!\""
    structPut thisFatArrow, arrowState[0]
   
    SetArrowState (thisFatArrow, pnlSize, 1)   
    DrawUserShape thisFatArrow.x0, thisFatArrow.y0, thisFatArrow.x1,thisFatArrow.y1,        MyFatArrowFuncExL, "1", A"5M[,E^]4?75L**WJ,fQLz"
    structPut thisFatArrow, arrowState[1]
   
    SetArrowState (thisFatArrow, pnlSize, 2)   
    DrawUserShape thisFatArrow.x0, thisFatArrow.y0, thisFatArrow.x1,thisFatArrow.y1,        MyFatArrowFuncExU, "2", A"5MV7>^]4?75KY5]J,fQLz"
    structPut thisFatArrow, arrowState[2]
   
    SetArrowState (thisFatArrow, pnlSize, 3)   
    DrawUserShape thisFatArrow.x0, thisFatArrow.y0, thisFatArrow.x1,thisFatArrow.y1,        MyFatArrowFuncExD, "3", A"5Mh+X?iU0,5KPfXJ,fQLz"
    structPut thisFatArrow, arrowState[3]
end

function PanelFatArrows()
    struct      pointf      pnlSize
    struct      pointf      pnlOrigin
   
   
    pnlOrigin.h = 100
    pnlOrigin.v = 100
    pnlSize.h = 300
    pnlSize.v = 200
    NewPanel /N=FatArrows /W=(pnlOrigin.h, pnlOrigin.v, pnlOrigin.h+pnlSize.h+1, pnlOrigin.v+pnlSize.v+1)
    DrawFatArrows("FatArrows", pnlSize)
       
    SetWindow FatArrows hook(FatArrowHook)=HookProc_FatArrow            // Add a hook function to the panel so we can respond to resize events
End

function HookProc_FatArrow(STRUCT WMWinHookStruct &s)
    string          hostPnlName = s.winName
    wave                arrowState
    struct  sFatArrow thisFatArrow
    struct pointf   pnlSize
   
    switch (s.eventCode)
        case 6: // pnl resize event
            GetWindow $hostPnlName, wsizeDC

            pnlSize.h = V_right - V_left + 1
            pnlSize.v = V_bottom - V_top + 1
            DrawFatArrows(hostPnlName, pnlSize)
            break
           
        default:
            break
           
    endswitch
    return 0
end

I have used SetDrawLayer/K UserBack, which erases everything in the draw layer. To be more nuanced about it, use DrawAction with named groups, which allows you to extract, erase, and insert drawing objects into a draw layer that has other stuff in that you want to preserve. But it's difficult...

I wonder if you'd save yourself a lot of trouble if you create a CustomControl instead of mimicking one with drawing tools.

One of the strengths if Igor is that there are usually multiple ways to attack a problem. One of Igor's weaknesses is that there are usually multiple ways to attack a problem...

Just to say 'Thanks' to John for his perserverance with my question. 

By following his suggestions, I got things working as I wanted.

Hi all,

I hope my question is a relevant extension of the original: would it be possible to programatically delete a shape once drawn? Or must you use the drawing tools to select the shape and delete it.

Thanks!

It's a drawing object, so you have to use the DrawAction operation to do it. Probably most convenient to use SetDrawEnv to make your user shape a named group (yeah, just one object in the group, but it gets a name that you can reference). Then use DrawAction to find your "group" and delete it.

In reply to by johnweeks

johnweeks wrote:

It's a drawing object, so you have to use the DrawAction operation to do it. Probably most convenient to use SetDrawEnv to make your user shape a named group (yeah, just one object in the group, but it gets a name that you can reference). Then use DrawAction to find your "group" and delete it.

DrawAction is exactly what I was looking for; thanks John!