Trouble with title bar if ResizeableWindow is maximized


#1

The problem can be reproduced with the JUCE-demo by

  1. enable the native title bar
  2. maximize the ResizeableWindow
  3. disable the native title bar
    => window is now longer maximized
  • or -
  1. disable the native title bar
  2. maximize the ResizeableWindow
  3. enable the native title bar
    => native title bar gets lost offscreen

#2

ok, thanks - I’ll get that sorted out…


#3

Jules, you fixed this issue in version 1.38, but there are still some problems.

First of all the last-non-fullscreen-position gets messed up if you do the following:

  1. disable the native title bar
  2. maximize the ResizeableWindow
  3. enable the native title bar
  4. demaximize the ResizeableWindow
    => the window has now an other position and size than before the maximize.

This doesn’t happen if it’s done the other way around:

  1. enable the native title bar
  2. maximize the ResizeableWindow
  3. disable the native title bar
  4. demaximize the ResizeableWindow
    => everything is okay

The next issues are about the basic handling of maximize/minimize in the platform specific code.

First is the juce_updateMultiMonitorInfo(…) in juce_linux_Windowing.cpp. The handling of clipToWorkArea is totally missing and if no Xinerama is present or not used it falls back to only add the defaultscreen even if more screens are present. So I suggest to change:

if (monitorCoords.size() == 0) #endif { monitorCoords.add (Rectangle (0, 0, DisplayWidth (display, DefaultScreen (display)), DisplayHeight (display, DefaultScreen (display)))); }
to this:

[code] if (monitorCoords.size() == 0)
#endif
{
int numMonitors = ScreenCount(display);
Atom hints = None;

    if (clipToWorkArea)
        hints = XInternAtom (display, "_NET_WORKAREA", True);
    
    for (int i = 0; i < numMonitors; i++)
    {
        if (clipToWorkArea && hints != None)
        {
            Window root = RootWindow (display, i);
            unsigned long nitems, bytesLeft;
            Atom actualType;
            int actualFormat;
            long* workarea = 0;

            if (XGetWindowProperty (display, root, hints, 0, 4, False,
                                    XA_CARDINAL, &actualType, &actualFormat, &nitems, &bytesLeft,
                                    (unsigned char**) &workarea) == Success)
            {
                if (actualType == XA_CARDINAL && actualFormat == 32 && nitems == 4)
                    monitorCoords.add(Rectangle(workarea[0], workarea[1], workarea[2], workarea[3]));

                XFree (workarea);
            }
        }
        else
            monitorCoords.add(Rectangle(0, 0, DisplayWidth(display, i), DisplayHeight(display, i)));
    }
}[/code]

With this change LinuxComponentPeer::setFullScreen(…) would no longer expand the window to the total screensize but to the workarea like it’s supposed to. A better way may be to maximize the window would be to set _NET_WM_STATE_MAXIMIZED_VERT and _NET_WM_STATE_MAXIMIZED_HORZ for it, like the ShowWindow(hwnd, SW_SHOWMAXIMIZED) on windows if the window has a native title bar.
I tried to do this but couldn’t get it work like I want it to, perhaps because I play around with Xlib just since 3 days.

Second is the XSetTransientForHint(display, wndH, RootWindow(display, DefaultScreen(display))) in LinuxComponentPeer::createWindow(). If you set all windows to be transient for the rootwindow some window manager will handle the window wrong. Gnome seems to be unaffected but wmii will get it wrong. They way JUCE currently handles it is not conform with the ICCCM. For example in the JUCE case only modal dialogs or similar should get the transient-for hint. But they should not be transient for the rootwindow but for the applications mainwindow.
I know that this is not easy to solve, may involve many changes in the code and it is not that important, but I want to point out at this.

Third is the sysmenu on windows. With the following changes the sysmenu will be full functional for minimize/maximize with and without native title bar:

DocumentWindow::getDesktopWindowStyleFlags()

[code] int flags = ResizableWindow::getDesktopWindowStyleFlags();

// photon:
// at least win32 needs this, because these flags define the 
// items in the sysmenu

#if (JUCE_WIN32 == 0)
if ((flags & ComponentPeer::windowHasTitleBar) != 0)
#endif
{[/code]

Win32ComponentPeer::setFullScreen(…)

[code] if (! fullScreen)
{
// photon:
// remove SW_SHOWMAXIMIZED from the window
if ((styleFlags & windowHasTitleBar) != 0)
ShowWindow (hwnd, SW_SHOWNORMAL);

            if (! lastNonFullscreenBounds.isEmpty())[/code]

Win32ComponentPeer::createWindow()

[code] if ((styleFlags & windowHasTitleBar) != 0)
{
[…]
}
else
{
// photon:
// set type corresponding styleFlags to get the right items in the sysmenu
type |= WS_POPUP | WS_SYSMENU;

        if ((styleFlags & windowHasMinimiseButton) != 0)
            type |= WS_MINIMIZEBOX;

        if ((styleFlags & windowHasMaximiseButton) != 0)
            type |= WS_MAXIMIZEBOX;
            
        if ((styleFlags & windowAppearsOnTaskbar) == 0)
            exstyle |= WS_EX_TOOLWINDOW;
        else
            exstyle |= WS_EX_APPWINDOW;
    }[/code]

Win32ComponentPeer::peerWindowProc(…)

[code] case WM_INITMENU:
// photon:
// need to update state of some sysmenu items if there is no native title bar
// because then the os is not aware that this window is maximized in a fake way
if ((styleFlags & windowHasTitleBar) == 0)
{
// xxx the first popup of the sysmenu is unaffected by this after that it works like it should
HMENU hmenu = GetSystemMenu (hwnd, false);

                if (isFullScreen())
                {
                    EnableMenuItem (hmenu, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
                    EnableMenuItem (hmenu, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
                    
                    if (! isMinimised())
                        EnableMenuItem (hmenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
                }
            }
            break;

        case WM_SYSCOMMAND:
            if ((styleFlags & windowHasTitleBar) != 0)
            {
                switch (wParam & 0xfff0)
                {
                case SC_CLOSE:
                    PostMessage (h, WM_CLOSE, 0, 0);
                    return 0;
                
                // photon:
                // handle this through the peer
                case SC_MAXIMIZE:
                    setFullScreen(true);
                    return 0;

                case SC_RESTORE:
                    if (isFullScreen())
                    {
                        setFullScreen(false);
                        return 0;
                    }
                    break;
                
                case SC_KEYMENU:
                    if (h == GetCapture())
                        ReleaseCapture();
                    break;
                }
            }
            else
            {
                // photon:
                // Now windows can be minimized/maximized/restored via 
                // sysmenu if window has no native title bar
                switch (wParam & 0xfff0)
                {
                case SC_MINIMIZE:
                    setMinimised(true);
                    return 0;
                    
                case SC_MAXIMIZE:
                    setFullScreen(true);
                    return 0;

                case SC_RESTORE:
                    if (isMinimised())
                        setMinimised(false);
                    else if (isFullScreen())
                        setFullScreen(false);
                
                    return 0;
                }
            }

            break;[/code]

Sorry for this long post, perhaps I should have attached a diff rather than post it like this.


#4

Cool. Thanks for those Windows tips - I’ve been doing a bit of merging, and it all looks good. Can’t seem to get the restore menu option to work though - even when you enable the menu item it seems to come up grey. No idea why that might be.

I’m not particularly bothered about the positions not matching up quite correctly when you turn the title bar on and off, as I doubt if any real apps other than the demo will ever enable or disable their title bar…

Good suggestion about linux without xinerama, though it won’t work if the workarea hint isn’t being used, as all the screens would be on top of each other at (0, 0). I think maybe the best plan is just to do this:

[code] if (monitorCoords.size() == 0)
#endif
{
Atom hints = clipToWorkArea ? XInternAtom (display, “_NET_WORKAREA”, True)
: None

    if (hints != None)
    {
        const int numMonitors = ScreenCount (display);

        for (int i = 0; i < numMonitors; ++i)
        {
            Window root = RootWindow (display, i);

            unsigned long nitems, bytesLeft;
            Atom actualType;
            int actualFormat;
            long* position = 0;

            if (XGetWindowProperty (display, root, hints, 0, 4, False,
                                    XA_CARDINAL, &actualType, &actualFormat, &nitems, &bytesLeft,
                                    (unsigned char**) &position) == Success)
            {
                if (actualType == XA_CARDINAL && actualFormat == 32 && nitems == 4)
                    monitorCoords.add (Rectangle (position[0], position[1], 
                                                  position[2], position[3]));

                XFree (workarea);
            }
        }
    }

    if (monitorCoords.size() == 0)
    {
        monitorCoords.add (Rectangle (0, 0,
                                      DisplayWidth (display, DefaultScreen (display)),
                                      DisplayHeight (display, DefaultScreen (display))));
    }
}[/code]

The XSetTransientForHint thing is tricky. I’m sure there’s a very important reason why it’s there, but I can’t remember what it is… Next time I’m messing around with a linux build I’ll play around with it and see what happens.


#5

Like I said in the comment on WM_INITMENU, the first time the sysmenu is shown the restore item is grey, but after the first time it works, the restore item isn’t grey and a click on it will restore the window. I have no idea why it dosen’t at the first time.

Without Xinerama, it’s better to do it like you suggested it, because _NET_WORKAREA is an extended feature and may not be available.

The way I understand the ICCCM is that XSetTransientForHint should be used to let the Xserver/windowmanager know about dependencies/hierarchies between toplevelwindows of one application. For example a modal dialogwindow would be transient for a documentwindow and the windowmanager would decide to decorate the dialogwindow in an other way than the documentwindow. Or perhaps with a tabbing windowmanager like Ion3 or wmii, the documentwindow will be tabbed but the dialogwindow will float over it.
I took a look on the Qt and Gtk code for Xlib wrapping and XSetTransientForHint is only used the way I described. Especially XSetTransientForHint is not set for every toplevelwindow and the rootwindow of the screen.

Edit:
I have found a solution for the grey restore item at the first popup of the sysmenu. I don’t know why it works but it does. Simply add the sysmenu-update-code to the end of Win32ComponentPeer::createWindow()

[code] // photon:
// need to update state of some sysmenu items if there is no native title bar
// because then the os is not aware that this window is maximized in a fake way
if ((styleFlags & windowHasTitleBar) == 0)
{
HMENU hmenu = GetSystemMenu (hwnd, false);

        if (isFullScreen())
        {
            EnableMenuItem (hmenu, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
            EnableMenuItem (hmenu, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
            
            if (! isMinimised())
                EnableMenuItem (hmenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
        }
    }[/code]

#6

Ah sorry, I didn’t notice that bit in your earlier post.

In fact it seems that the solution’s even simpler (and stranger). Just calling GetSystemMenu() at the end of createWindow() seems to sort it out. No need to actually do anything to the menu…


#7