/*
    PPGNUstepGlue_WindowOrdering.m

    Copyright 2014-2018 Josh Freeman
    http://www.twilightedge.com

    This file is part of PikoPixel for GNUstep.
    PikoPixel is a graphical application for drawing & editing pixel-art images.

    PikoPixel is free software: you can redistribute it and/or modify it under
    the terms of the GNU Affero General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version approved for PikoPixel by its copyright holder (or
    an authorized proxy).

    PikoPixel is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
    details.

    You should have received a copy of the GNU Affero General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Workaround for document windows appearing in front of (instead of behind) submenus & floating
// windows (happens under Compiz, KWin, & Xfwm window managers)

#ifdef GNUSTEP

#import <Cocoa/Cocoa.h>
#import "NSObject_PPUtilities.h"
#import "PPAppBootUtilities.h"
#import "PPGNUstepGlueUtilities.h"
#import "PPApplication.h"
#import "PPPanelsController.h"
#import "PPPopupPanelsController.h"
#import "PPScreencastController.h"
#import "PPPanelController.h"
#import "PPDocument.h"
#import "PPDocumentWindowController.h"
#import "PPScreencastPopupPanelController.h"


// Install WindowOrdering glue only if the window manager is Compiz, KWin, or Xfwm
#define kTargetWindowManagerTypesMask_WindowOrdering        \
                (kPPGSWindowManagerTypeMask_Compiz          \
                | kPPGSWindowManagerTypeMask_KWin           \
                | kPPGSWindowManagerTypeMask_Xfwm)


@interface NSMenu (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetAllMenuWindowsToMainMenuLevel;

@end

@interface NSWindow (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetupToRemainInFrontOfWindow: (NSWindow *) window;

- (bool) ppGSGlue_IsSetToRemainInFrontOfWindow: (NSWindow *) window;

@end

@interface PPPanelController (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetupPanelToRemainInFrontOfWindow: (NSWindow *) window;

@end

#if PP_OPTIONAL__BUILD_WITH_SCREENCASTING

@interface PPScreencastController (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_AddAsObserverForNSWindowNotifications;
- (void) ppGSGlue_RemoveAsObserverForNSWindowNotifications;
- (void) ppGSGlue_HandleNSWindowNotification_DidBecomeMain: (NSNotification *) notification;
- (void) ppGSGlue_HandleNSWindowNotification_DidResignMain: (NSNotification *) notification;

@end

#endif  // PP_OPTIONAL__BUILD_WITH_SCREENCASTING


@implementation NSObject (PPGNUstepGlue_WindowOrdering)

+ (void) ppGSGlue_WindowOrdering_InstallPatches
{
    macroSwizzleInstanceMethod(PPApplication, runModalForWindow:,
                                ppGSPatch_WindowOrdering_RunModalForWindow:);


    macroSwizzleInstanceMethod(PPPanelsController, setPPDocument:,
                                ppGSPatch_PanelsController_SetPPDocument:);


    macroSwizzleInstanceMethod(PPPopupPanelsController, setPPDocument:,
                                ppGSPatch_PopupPanelsController_SetPPDocument:);
}

+ (void) ppGSGlue_WindowOrdering_Install
{
    if (!PPGSGlueUtils_WindowManagerMatchesTypeMask(
                                                kTargetWindowManagerTypesMask_WindowOrdering))
    {
        return;
    }

    [self ppGSGlue_WindowOrdering_InstallPatches];

    [[NSApp mainMenu] ppGSGlue_SetAllMenuWindowsToMainMenuLevel];

#if PP_OPTIONAL__BUILD_WITH_SCREENCASTING

    PPGSGlueUtils_PerformPPScreencastControllerSelectorOnEnableOrDisable(
                    @selector(ppGSGlue_WindowOrdering_HandleScreencastEnableOrDisable));

#endif  // PP_OPTIONAL__BUILD_WITH_SCREENCASTING
}

+ (void) load
{
    macroPerformNSObjectSelectorAfterAppLoads(ppGSGlue_WindowOrdering_Install);
}

@end

@implementation PPApplication (PPGNUstepGlue_WindowOrdering)

- (NSInteger) ppGSPatch_WindowOrdering_RunModalForWindow: (NSWindow *) theWindow
{
    NSInteger returnValue;
    bool didManuallyOrderModalWindowToFront = NO;

    if (![theWindow parentWindow])
    {
        [theWindow ppGSGlue_SetupToRemainInFrontOfWindow: [self mainWindow]];
        didManuallyOrderModalWindowToFront = YES;
    }

    returnValue = [self ppGSPatch_WindowOrdering_RunModalForWindow: theWindow];

    if (didManuallyOrderModalWindowToFront)
    {
        [theWindow ppGSGlue_SetupToRemainInFrontOfWindow: nil];
    }

    return returnValue;
}

@end

@implementation PPPanelsController (PPGNUstepGlue_WindowOrdering)

- (void) ppGSPatch_PanelsController_SetPPDocument: (PPDocument *) ppDocument
{
    NSWindow *documentWindow;

    [self ppGSPatch_PanelsController_SetPPDocument: ppDocument];

    documentWindow = (ppDocument) ? [[ppDocument ppDocumentWindowController] window] : nil;

    [_panelControllers makeObjectsPerformSelector:
                                        @selector(ppGSGlue_SetupPanelToRemainInFrontOfWindow:)
                        withObject: documentWindow];

    // also setup the system's color panel to remain in front of the document window

    [[NSColorPanel sharedColorPanel] ppGSGlue_SetupToRemainInFrontOfWindow: documentWindow];
}

@end

@implementation PPPopupPanelsController (PPGNUstepGlue_WindowOrdering)

// need additional patch for -[PPPopupPanelsController setPPDocument:] because its
// implementation doesn't call through to PPPanelsController's (superclass) implementation

- (void) ppGSPatch_PopupPanelsController_SetPPDocument: (PPDocument *) ppDocument
{
    NSWindow *documentWindow;

    [self ppGSPatch_PopupPanelsController_SetPPDocument: ppDocument];

    documentWindow = (ppDocument) ? [[ppDocument ppDocumentWindowController] window] : nil;

    [_popupControllers makeObjectsPerformSelector:
                                        @selector(ppGSGlue_SetupPanelToRemainInFrontOfWindow:)
                        withObject: documentWindow];
}

@end

#if PP_OPTIONAL__BUILD_WITH_SCREENCASTING

@implementation PPScreencastController (PPGNUstepGlue_WindowOrdering)

- (void) ppGSGlue_WindowOrdering_HandleScreencastEnableOrDisable
{
    if (_screencastingIsEnabled)
    {
        [self ppGSGlue_AddAsObserverForNSWindowNotifications];

        [_screencastPopupController ppGSGlue_SetupPanelToRemainInFrontOfWindow:
                                                                            [NSApp mainWindow]];
    }
    else
    {
        [self ppGSGlue_RemoveAsObserverForNSWindowNotifications];

        [_screencastPopupController ppGSGlue_SetupPanelToRemainInFrontOfWindow: nil];
    }
}

- (void) ppGSGlue_AddAsObserverForNSWindowNotifications
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    [self ppGSGlue_RemoveAsObserverForNSWindowNotifications];

    [notificationCenter addObserver: self
                        selector: @selector(ppGSGlue_HandleNSWindowNotification_DidBecomeMain:)
                        name: NSWindowDidBecomeMainNotification
                        object: nil];

    [notificationCenter addObserver: self
                        selector: @selector(ppGSGlue_HandleNSWindowNotification_DidResignMain:)
                        name: NSWindowDidResignMainNotification
                        object: nil];
}

- (void) ppGSGlue_RemoveAsObserverForNSWindowNotifications
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    [notificationCenter removeObserver: self
                        name: NSWindowDidBecomeMainNotification
                        object: nil];

    [notificationCenter removeObserver: self
                        name: NSWindowDidResignMainNotification
                        object: nil];
}

- (void) ppGSGlue_HandleNSWindowNotification_DidBecomeMain: (NSNotification *) notification
{
    NSWindow *mainWindow = [notification object];

    [_screencastPopupController ppGSGlue_SetupPanelToRemainInFrontOfWindow: mainWindow];
}

- (void) ppGSGlue_HandleNSWindowNotification_DidResignMain: (NSNotification *) notification
{
    NSWindow *resignedWindow = [notification object];

    if ([[_screencastPopupController window]
                                        ppGSGlue_IsSetToRemainInFrontOfWindow: resignedWindow])
    {
        [_screencastPopupController ppGSGlue_SetupPanelToRemainInFrontOfWindow: nil];
    }
}

@end

#endif  // PP_OPTIONAL__BUILD_WITH_SCREENCASTING

@implementation NSMenu (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetAllMenuWindowsToMainMenuLevel
{
    NSWindow *menuWindow;
    NSEnumerator *menuItemsEnumerator;
    NSMenuItem *menuItem;

    menuWindow = [self window];

    if ([menuWindow level] != NSMainMenuWindowLevel)
    {
        [menuWindow setLevel: NSMainMenuWindowLevel];
    }

    menuItemsEnumerator = [[self itemArray] objectEnumerator];

    while (menuItem = [menuItemsEnumerator nextObject])
    {
        if ([menuItem hasSubmenu])
        {
            [[menuItem submenu] ppGSGlue_SetAllMenuWindowsToMainMenuLevel];
        }
    }
}

@end

@implementation NSWindow (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetupToRemainInFrontOfWindow: (NSWindow *) window
{
    NSWindow *currentParentWindow = [self parentWindow];

    if (currentParentWindow == window)
    {
        return;
    }

    if (currentParentWindow)
    {
        [currentParentWindow removeChildWindow: self];
    }

    if (window)
    {
        [window addChildWindow: self ordered: NSWindowAbove];

        if ([self isVisible])
        {
            [self orderWindow: NSWindowAbove relativeTo: [window windowNumber]];
        }
    }
}

- (bool) ppGSGlue_IsSetToRemainInFrontOfWindow: (NSWindow *) window
{
    return (window && ([self parentWindow] == window)) ? YES : NO;
}

@end

@implementation PPPanelController (PPGNUstepGlue_WindowOrderingUtilities)

- (void) ppGSGlue_SetupPanelToRemainInFrontOfWindow: (NSWindow *) window
{
    [[self window] ppGSGlue_SetupToRemainInFrontOfWindow: window];
}

@end

#endif  // GNUSTEP

