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.
December 14, 2018 at 02:07 pm - Permalink
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
December 14, 2018 at 03:33 pm - Permalink
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.
December 14, 2018 at 05:09 pm - Permalink
In reply to Those coordinates are the… 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
December 17, 2018 at 12:20 pm - Permalink
The "SetDrawEnv origin" command offsets the drawing so that "DrawOval -5,-5,5,5" will draw a 10-point circle at any desired location.
December 17, 2018 at 01:23 pm - Permalink
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
December 17, 2018 at 02:48 pm - Permalink
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 :)
December 17, 2018 at 05:08 pm - Permalink
In reply to In order to do hit testing,… 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
December 18, 2018 at 11:44 am - Permalink
The numbers will stay at the centers of the arrows if you change the conditional block like this:
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
December 18, 2018 at 12:47 pm - Permalink
In reply to The numbers will stay at the… 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
December 18, 2018 at 03:40 pm - Permalink
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():
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...
December 18, 2018 at 04:56 pm - Permalink
I wonder if you'd save yourself a lot of trouble if you create a CustomControl instead of mimicking one with drawing tools.
December 19, 2018 at 09:26 am - Permalink
In reply to I wonder if you'd save… by JimProuty
Thanks John, for your guidance, and Jim for your suggestion.
I'm starting to test John's idea now, and if that falls short, then I'll investigate CustomControls.
Best,
bruce
December 20, 2018 at 08:35 am - Permalink
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...
December 20, 2018 at 10:09 am - Permalink
Just to say 'Thanks' to John for his perserverance with my question.
By following his suggestions, I got things working as I wanted.
January 8, 2019 at 09:38 am - Permalink
In reply to Just to say 'Thanks' to John… by BRUCE
You're welcome, glad you got it working. What route did you finally use for this?
January 9, 2019 at 10:09 am - Permalink
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!
April 3, 2019 at 12:04 pm - Permalink
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.
April 3, 2019 at 04:45 pm - Permalink
In reply to It's a drawing object, so… by johnweeks
DrawAction is exactly what I was looking for; thanks John!
April 4, 2019 at 06:40 am - Permalink