Floating panel in front of a graph, but more subtle

I would like to have a panel floating in front of a graph, but just a particular graph and not everything. Currently I use an external panel attached to the graph, but would rather like to have things on top of each other. Ideally I would like to have a panel while can be easily moved around and resized. A floating panel is almost what I want, but it is a bit obtrusive because it floats atop all windows. I would be also fine with having a sub-panel pasted into the graph, but I don't see how users could then move and resize things freely without resorting to the tools bar. I was thinking of constantly running DoWindow/B=mainGraph to keep things ordered, but this is maybe a bit fiddly as well. Are there any neat options?

You could make a panel subwindow moveable within the bounds of the graph by reacting to mousedown, mousemoved and mouseup events. Don't forget that subwindow events go to the parent window hook function. It's not obvious to me how to implement resizing. You could have the user adjust expansion (via right click), and resize the subwindow accordingly.

 

If I understand, you want something akin to this ...

if (front most graph window is my specific graph window)
    float my specific panel window on top of my specific graph window
else if (front most window is not my specific panel window)
     hide my specific panel window
end if

I believe that WinList returns windows in the order from front (index 0) to back (index infinity), and WinList can search on graph windows versus panel windows. Knowing this, I would wonder whether you could accomplish the above by setting hook functions on your specific graph and specific panel to detect active (versus inactive) status. I think the only thing missing is how to detect the case where both the graph window and the panel window become inactive (i.e. when a different graph window is made active) without adding hook functions to all other windows as well.

Hook code for moving a panel subwindow named P0 within the bounds of its parent:

function HookFunction(STRUCT WMWinHookStruct &s)

    int inPanel = 0
    string strPanel
    if (stringmatch(s.winName, "*#P0"))
        strPanel = s.winName
        inPanel = 1
    elseif (!stringmatch(s.winName, "*#*"))
        strPanel = s.winName + "#P0"
    else
        return 0
    endif

    switch(s.eventcode)
        case 3: // mousedown
            if (inPanel)
                SetWindow $strPanel userdata(drag)="1"
                STRUCT DragStructure d
                d.startMouse = s.mouseloc
                d.StartRect = s.winrect

                GetWindow $ParseFilePath(0, strPanel, "#", 0, 0) gsizeDC
                d.parentRect.top = V_top
                d.parentRect.left = V_left
                d.parentRect.bottom = V_bottom
                d.parentRect.right = V_right
                string str
                StructPut/S d, str
                SetWindow $strPanel userdata(dragstruct)=str
            endif
            break
        case 4: // mousemoved
            if (str2num(GetUserData(strPanel, "", "drag")))
                StructGet/S d, GetUserData(strPanel, "", "dragstruct")
                variable deltah = s.mouseloc.h - d.startmouse.h
                variable deltav = s.mouseloc.v - d.startmouse.v
                STRUCT rect newRect
                newRect.left = d.StartRect.left+deltah
                newRect.top = d.StartRect.top+deltav
                newRect.right = d.StartRect.right+deltah
                newRect.bottom = d.StartRect.bottom+deltav
                if (RectInRect(newRect, d.ParentRect))
                    // *** on Windows you will have to worry about units here ***
                    MoveSubwindow/W=$strPanel fnum=(newRect.left, newRect.top, newRect.right, newRect.bottom)
                    d.startrect = newRect
                endif
                d.startmouse = s.mouseloc
                StructPut/S d, str
                SetWindow $strPanel userdata(dragstruct)=str
            endif
            break
        case 5: // mouseup
            SetWindow $strPanel userdata(drag)="0"
            break
    endswitch

    return 0
end

structure DragStructure
    STRUCT point StartMouse
    STRUCT Rect StartRect
    STRUCT Rect ParentRect
endstructure

// point and rect structures must have same units
function pointInRect(STRUCT point &pnt, STRUCT rect &r)
    return ( pnt.h>r.left && pnt.h<r.right && pnt.v>r.top && pnt.v<r.bottom )
end

function RectInRect(STRUCT rect &r1, STRUCT rect &r2)
    return ( r1.left>r2.left && r1.top>r2.top && r1.right<r2.right && r1.bottom<r2.bottom )
end

This works on a mac where we don't have to worry about unit conversion for window coordinates.

Thanks for your replies.

@JJ: Hmm... interesting idea. So if I hide the floating panel as long as the graph is not on top I may be able to manage. Lately, one can autohide them as well when Igor is not the active application. I think I only need to check for the deactivate event on the relevant windows and don't need to involve all other graphs in this (that would be quite overkill). I will try to play around with this idea and see how well this works. The only problem I see is when somehow the deactivate event is unreliable.

A deactivate event on the graph window would happen by activating the panel window or any other window. A deactivate event on the panel window would happen by activating the graph window or any other window. Perhaps Tony's approach using a semaphore userData(...) parameter on the graph and/or panel is needed as well???

After trying the floating-panel approach for a few hours I gave up: It is too messy to detect whether the relevant windows are in the front or not to hide/show the floating panel. Also, floating panels interfere too much with the operation of Igor (copy-pasting is disabled etc.). I now try Tony's approach (thanks a lot for that). Since I had already split-axis dragging implemented for the graph in question, this was straight-forward to implement and works reasonably good. I try to implement resizing as well by detecting whether the mouse is close to the corners. Let's see how well this turns out to work. An external panel involves definitely much less head scratching, but why not try new things once in a while ...

OK, I got it to work nicely. Please find a MWE in the attachment based on Tony's snippet. I just fixed a few things and added edge detection for resizing to the code. Note that this is only useful for small additional panels and graphs, since the sub-window is of course obstructing the view for the plots below.

Try out with:

Make/d/o test1=sqrt(p), test2=p^2
buildMainGraph(test1)
buildSubGraph("Graph0",test2)

Let me know if you have any suggestions for changes.

  • EDIT1: Added a few minor fixes.
  • EDIT2: Now with changing mouse indicators and proper graph-expansion support on Windows (and Mac hopefully).
  • EDIT3: Now dragging is disabled for the plot area of the sub-graph.
Float Panel_v5.ipf

Looks great.

I think you need to avoid reacting to resize event for host window. Something like this:

        case "resize":
            if (cmpstr(sName, s.winName))
                break
            endif

Otherwise weird stuff happens when you resize a host containing a minimum size subwindow.

Tony, thanks for having a look at the code! I am not sure I understand your comment. What weird things happen? If I would add your snippet, then the whole resize section would become useless and could be cut. The hook function never reacts to the subwindow, since it has no hook. But the resize section is specifically there to shift the sub-window around in case it is in danger of leaving the host window area. This works nicely on windows as far as I can tell. Maybe there is a unit conversion problem on Mac?

OK, then I misunderstood the purpose of the code.

For me on the mac, subwindows get resized sensibly when you resize the host. Without the resizing code everything works quite intuitively. But now I see what you're trying to do.

Perhaps you need

GetWindow $sName wsizeDC

rather than gsizeDC? The subwindow after creation has gsize 2 pixels smaller than the minimum size, so any resize of the host moves the subwindow up and to the left. This may be mac-specific.

note also that a subwindow can be 'squashed' by resizing the host (even with your code active), after which your code prevents the subwindow from expanding back to its unsquashed size. Once the subwindow has been squeezed below the minimum size, CheckRectPosAndSize() is always false and prevents resizing by dragging. In addition, your code fires when Igor tries to expand the subwindow (after it has been 'squashed' by the host) and keeps resizing the subwindow back to its reduced size.

I think this means you need to check that the host is larger than minimum subwindow size before the MoveSubwindow.

OK, I see. Yes, this part might be a bit fiddly. I haven't tested it too much. I basically don't want the subwindow to completely disappear when you resize the host window to a size small enough. The reason is that I save the window position for recreating the subwindow, but if the user somehow managed to resize the host window, then there is no way to get the subwindow back in view other than increasing the host-window size again. Maybe that is not really the right way to do it. I would love to have something like anchor points analogous to annotations here where the subwindow moves with a corner other than top left, but there seem to be nothing like this for subpanels.

Regarding the locked resize, I guess I need to make sure that the subwindow expands back to the minimal size upon active or something to prevent that. I will have a look tomorrow and play around with this a bit more.

Tony, here is a version which addresses all these issue, i.e., the 'squishiness' is preserved and you cannot compress the subwindow beyond its minimal size. Let me know if you find other (e.g., mac related issues).

Float Panel_v6.ipf

in your demo the subwindow is created with one dimension equal to the minimum size, which makes it unmoveable (because CheckRectPosAndSize() is always false).

You are right. I think I had this worked out before. Anyway, setting the check to >= should fix that. See the attached version.

Float Panel_v7.ipf

Here now the snippet:

https://www.wavemetrics.com/code-snippet/insert-dragable-and-resizeable…

I have also extended the code to work with multiple subwindows on the same graph, i.e., each subwindow can be dragged and resized individually. Here I noticed a quirk (or even bug?) with window hook functions: If two subwindows are overlapping then s.winName reports the name of the subwindow with lower hierarchy and not the topmost one. This leads to problems with drawing the correct mouse cursor and one is likely to grab a lower subwindow behind other subwindows, but is otherwise unproblematic. I haven't found a workaround yet.

are you sure you have the units right for all Windows screen resolutions?

GetWindow gsizeDC gives you pixels.

MoveSubwindow takes control panel units.

The conversion is

(ScreenResolution > 96 ? pixel * 72 / ScreenResolution : pixel) / expansion

In Igor 8, MoveSubwindow has a bug: if screenresolution is > 96, you need pixel/expansion rather than control panel units.

Tony, thanks for your critical look. As far as I can tell, the posted code works fine for any combination of ScreenResolution > 96 and active graph expansion here on Windows 10 both with Igor 8.05 and Igor 9.05. So I guess I got it right, maybe by accident. How does it look on Mac?

Now I remember that I had this more complicated conversion you mentioned and I was not right. Maybe I have both builds where this was fixed?

Ah, maybe because the host is a graph you can use points rather than control panel units, even though the subwindow is a panel? It works, so no worries.

One more question:

Why do you embed a graph subwindow in a panel subwindow, rather than just using a graph subwindow? I thought at first that you were going to add more controls.

Thanks for checking. Great that it works on your side, too.

Yes, at first I wanted to have a multipurpose panel, where you can add multiple graphs and controls. But even if it is a simple graph, the panel makes sure that the graph is opaque which helps visibility in this case. I couldn't find another solution other than making the graph background 'almost white', i.e., gbRGB=(65534,65534,65534), but I found that somewhat unsatisfactory. Maybe there is another way, but I didn't bother too much with graphs only. The code works also fine with just graphs of course, i.e., replacing this:

NewPanel/HOST=$mainGName/N=floatPanel/W=(wL,wT,wR,wB)
Display/HOST=#/FG=(FL,FT,FR,FB) plotThis

with this:

Display/HOST=$mainGName/W=(wL,wT,wR,wB) plotThis

 

In reply to by chozo

chozo wrote:

Here I noticed a quirk (or even bug?) with window hook functions: If two subwindows are overlapping then s.winName reports the name of the subwindow with lower hierarchy and not the topmost one. This leads to problems with drawing the correct mouse cursor and one is likely to grab a lower subwindow behind other subwindows, but is otherwise unproblematic. I haven't found a workaround yet.

Seems like a bug to me. These sorts of bugs have cropped up in other places, too- it's just natural when iterating through a list to do it in the forwards direction. I would hesitate to fix it in Igor 9 because it would change behavior of existing code. But maybe Igor 10...

So I made a test case:

Make junk=sin(x/8)

Window Panel0() : Panel
    PauseUpdate; Silent 1       // building window...
    NewPanel /W=(150,77,450,277)
    Display/W=(103,67,253,167)/HOST=#  junk
    ModifyGraph wbRGB=(65535,60076,49151),gbRGB=(65535,60076,49151)
    RenameWindow #,G0
    SetActiveSubwindow ##
    Display/W=(33,22,183,122)/HOST=#  junk
    ModifyGraph wbRGB=(49151,65535,65535),gbRGB=(49151,65535,65535)
    RenameWindow #,G1
    SetActiveSubwindow ##
EndMacro

And guess what: clicking in the overlap area selects the window that's underneath. So this is more than just a window hook problem.

John, I see. On the one hand, I am happy to hear that I didn't screw up the code. On the other hand, I guess this means you have gotten yourself another task on your desk then? I hope this is not to difficult to fix.

The fix for the hit testing on subwindows was easy- in a loop, I changed iterators to reverse_iterator. The other problems I found with subwindow activation while working the hit testing were harder!