/*
 * Cocoa rootless implementation frame functions
 */
/*
 * Copyright (c) 2001 Greg Parker. All Rights Reserved.
 * Copyright (c) 2002-2003 Torrey T. Lyons. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the sale,
 * use or other dealings in this Software without prior written authorization.
 */
/* $XdotOrg: xc/programs/Xserver/hw/darwin/quartz/cr/crFrame.m,v 1.2 2004/04/23 19:15:51 eich Exp $ */
/* $XFree86: xc/programs/Xserver/hw/darwin/quartz/cr/crFrame.m,v 1.9 2004/03/19 02:05:29 torrey Exp $ */

#include "quartzCommon.h"
#include "cr.h"

#undef BOOL
#define BOOL xBOOL
#include "rootless.h"
#include "applewmExt.h"
#include "windowstr.h"
#undef BOOL

WindowPtr nextWindowToFrame = NULL;
unsigned int nextWindowStyle = 0;

static void CRReshapeFrame(RootlessFrameID wid, RegionPtr pShape);


/*
 * CRCreateFrame
 *  Create a new physical window.
 *  Rootless windows must not autodisplay! Autodisplay can cause a deadlock.
 *    Event thread - autodisplay: locks view hierarchy, then window
 *    X Server thread - window resize: locks window, then view hierarchy
 *  Deadlock occurs if each thread gets one lock and waits for the other.
 */
static Bool
CRCreateFrame(RootlessWindowPtr pFrame, ScreenPtr pScreen,
              int newX, int newY, RegionPtr pShape)
{
    CRWindowPtr crWinPtr;
    NSRect bounds;
    NSWindow *theWindow;
    XView *theView;
    unsigned int theStyleMask = NSBorderlessWindowMask;

    crWinPtr = (CRWindowPtr) xalloc(sizeof(CRWindowRec));

    bounds = NSMakeRect(newX,
                        NSHeight([[NSScreen mainScreen] frame]) -
                        newY - pFrame->height,
                        pFrame->width, pFrame->height);

    // Check if AppleWM has specified a style for this window
    if (pFrame->win == nextWindowToFrame) {
        theStyleMask = nextWindowStyle;
    }
    nextWindowToFrame = NULL;

    // Create an NSWindow for the new X11 window
    theWindow = [[NSWindow alloc] initWithContentRect:bounds
                                  styleMask:theStyleMask
                                  backing:NSBackingStoreBuffered
#ifdef DEFER_NSWINDOW
                                  defer:YES];
#else
                                  defer:NO];
#endif

    if (!theWindow) return FALSE;

    [theWindow setBackgroundColor:[NSColor clearColor]];  // erase transparent
    [theWindow setAlphaValue:1.0];       // draw opaque
    [theWindow setOpaque:YES];           // changed when window is shaped

    [theWindow useOptimizedDrawing:YES]; // Has no overlapping sub-views
    [theWindow setAutodisplay:NO];       // See comment above
    [theWindow disableFlushWindow];      // We do all the flushing manually
    [theWindow setHasShadow:YES];        // All windows have shadows
    [theWindow setReleasedWhenClosed:YES]; // Default, but we want to be sure

    theView = [[XView alloc] initWithFrame:bounds];
    [theWindow setContentView:theView];
    [theWindow setInitialFirstResponder:theView];

#ifdef DEFER_NSWINDOW
    // We need the NSWindow to actually be created now.
    // If we had to defer creating it, we have to order it
    // onto the screen to force it to be created.

    if (pFrame->win->prevSib) {
        CRWindowPtr crWinPtr = (CRWindowPtr) RootlessFrameForWindow(
                                                pFrame->win->prevSib, FALSE);
        int upperNum = [crWinPtr->window windowNumber];
        [theWindow orderWindow:NSWindowBelow relativeTo:upperNum];
    } else {
        [theWindow orderFront:nil];
    }
#endif

    [theWindow setAcceptsMouseMovedEvents:YES];

    crWinPtr->window = theWindow;
    crWinPtr->view = theView;

    [theView lockFocus];
    // Fill the window with white to make sure alpha channel is set
    NSEraseRect(bounds);
    crWinPtr->port = [theView qdPort];
    crWinPtr->context = [[NSGraphicsContext currentContext] graphicsPort];
    // CreateCGContextForPort(crWinPtr->port, &crWinPtr->context);
    [theView unlockFocus];

    // Store the implementation private frame ID
    pFrame->wid = (RootlessFrameID) crWinPtr;

    // Reshape the frame if it was created shaped.
    if (pShape != NULL)
        CRReshapeFrame(pFrame->wid, pShape);

    return TRUE;
}


/*
 * CRDestroyFrame
 *  Destroy a frame.
 */
static void
CRDestroyFrame(RootlessFrameID wid)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;

    [crWinPtr->window orderOut:nil];
    [crWinPtr->window close];
    [crWinPtr->view release];
    free(crWinPtr);
}


/*
 * CRMoveFrame
 *  Move a frame on screen.
 */
static void
CRMoveFrame(RootlessFrameID wid, ScreenPtr pScreen, int newX, int newY)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    NSPoint topLeft;

    topLeft = NSMakePoint(newX,
                          NSHeight([[NSScreen mainScreen] frame]) - newY);

    [crWinPtr->window setFrameTopLeftPoint:topLeft];
}


/*
 * CRResizeFrame
 *  Move and resize a frame.
 */
static void
CRResizeFrame(RootlessFrameID wid, ScreenPtr pScreen,
              int newX, int newY, unsigned int newW, unsigned int newH,
              unsigned int gravity)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    NSRect bounds = NSMakeRect(newX, NSHeight([[NSScreen mainScreen] frame]) -
                               newY - newH, newW, newH);

    [crWinPtr->window setFrame:bounds display:NO];
}


/*
 * CRRestackFrame
 *  Change the frame order. Put the frame behind nextWid or on top if
 *  it is NULL. Unmapped frames are mapped by restacking them.
 */
static void
CRRestackFrame(RootlessFrameID wid, RootlessFrameID nextWid)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    CRWindowPtr crNextWinPtr = (CRWindowPtr) nextWid;

    if (crNextWinPtr) {
        int upperNum = [crNextWinPtr->window windowNumber];

        [crWinPtr->window orderWindow:NSWindowBelow relativeTo:upperNum];
    } else {
        [crWinPtr->window makeKeyAndOrderFront:nil];
    }
}


/*
 * CRReshapeFrame
 *  Set the shape of a frame.
 */
static void
CRReshapeFrame(RootlessFrameID wid, RegionPtr pShape)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    NSRect bounds = [crWinPtr->view frame];
    int winHeight = NSHeight(bounds);
    BoxRec localBox = {0, 0, NSWidth(bounds), winHeight};

    [crWinPtr->view lockFocus];

    if (pShape != NULL) {
        // Calculate the region outside the new shape.
        miInverse(pShape, pShape, &localBox);
    }

    // If window is currently shaped we need to undo the previous shape.
    if (![crWinPtr->window isOpaque]) {
        [[NSColor whiteColor] set];
        NSRectFillUsingOperation(bounds, NSCompositeDestinationAtop);
    }

    if (pShape != NULL) {
        int count = REGION_NUM_RECTS(pShape);
        BoxRec *extRects = REGION_RECTS(pShape);
        BoxRec *rects, *end;

        // Make transparent if window is now shaped.
        [crWinPtr->window setOpaque:NO];

        // Clear the areas outside the window shape
        [[NSColor clearColor] set];
        for (rects = extRects, end = extRects+count; rects < end; rects++) {
            int rectHeight = rects->y2 - rects->y1;
            NSRectFill( NSMakeRect(rects->x1,
                                   winHeight - rects->y1 - rectHeight,
                                   rects->x2 - rects->x1, rectHeight) );
        }
        [[NSGraphicsContext currentContext] flushGraphics];

        // force update of window shadow
        [crWinPtr->window setHasShadow:NO];
        [crWinPtr->window setHasShadow:YES];

    } else {
        [crWinPtr->window setOpaque:YES];
        [[NSGraphicsContext currentContext] flushGraphics];
    }

    [crWinPtr->view unlockFocus];
}


/*
 * CRUnmapFrame
 *  Unmap a frame.
 */
static void
CRUnmapFrame(RootlessFrameID wid)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;

    [crWinPtr->window orderOut:nil];
}


/*
 * CRStartDrawing
 *  When a window's buffer is not being drawn to, the CoreGraphics
 *  window server may compress or move it. Call this routine
 *  to lock down the buffer during direct drawing. It returns
 *  a pointer to the backing buffer.
 */
static void
CRStartDrawing(RootlessFrameID wid, char **pixelData, int *bytesPerRow)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    PixMapHandle pix;

    [crWinPtr->view lockFocus];
    crWinPtr->port = [crWinPtr->view qdPort];
    LockPortBits(crWinPtr->port);
    [crWinPtr->view unlockFocus];
    pix = GetPortPixMap(crWinPtr->port);

    *pixelData = GetPixBaseAddr(pix);
    *bytesPerRow = GetPixRowBytes(pix) & 0x3fff; // fixme is mask needed?
}


/*
 * CRStopDrawing
 *  When direct access to a window's buffer is no longer needed, this
 *  routine should be called to allow CoreGraphics to compress or
 *  move it.
 */
static void
CRStopDrawing(RootlessFrameID wid, Bool flush)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;

    UnlockPortBits(crWinPtr->port);

    if (flush) {
        QDFlushPortBuffer(crWinPtr->port, NULL);
    }
}


/*
 * CRUpdateRegion
 *  Flush a region from a window's backing buffer to the screen.
 */
static void
CRUpdateRegion(RootlessFrameID wid, RegionPtr pDamage)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;

#ifdef ROOTLESS_TRACK_DAMAGE
    int count = REGION_NUM_RECTS(pDamage);
    BoxRec *rects = REGION_RECTS(pDamage);
    BoxRec *end;

    static RgnHandle rgn = NULL;
    static RgnHandle box = NULL;

    if (!rgn) rgn = NewRgn();
    if (!box) box = NewRgn();

    for (end = rects+count; rects < end; rects++) {
        Rect qdRect;
        qdRect.left = rects->x1;
        qdRect.top = rects->y1;
        qdRect.right = rects->x2;
        qdRect.bottom = rects->y2;

        RectRgn(box, &qdRect);
        UnionRgn(rgn, box, rgn);
    }

    QDFlushPortBuffer(crWinPtr->port, rgn);

    SetEmptyRgn(rgn);
    SetEmptyRgn(box);

#else	/* !ROOTLESS_TRACK_DAMAGE */
    QDFlushPortBuffer(crWinPtr->port, NULL);
#endif
}


/*
 * CRDamageRects
 *  Mark damaged rectangles as requiring redisplay to screen.
 */
static void
CRDamageRects(RootlessFrameID wid, int count, const BoxRec *rects,
               int shift_x, int shift_y)
{
    CRWindowPtr crWinPtr = (CRWindowPtr) wid;
    const BoxRec *end;

    for (end = rects + count; rects < end; rects++) {
        Rect qdRect;
        qdRect.left = rects->x1 + shift_x;
        qdRect.top = rects->y1 + shift_y;
        qdRect.right = rects->x2 + shift_x;
        qdRect.bottom = rects->y2 + shift_y;

        QDAddRectToDirtyRegion(crWinPtr->port, &qdRect);
    }
}


/*
 * Called to check if the frame should be reordered when it is restacked.
 */
Bool CRDoReorderWindow(RootlessWindowPtr pFrame)
{
    WindowPtr pWin = pFrame->win;

    return AppleWMDoReorderWindow(pWin);
}


static RootlessFrameProcsRec CRRootlessProcs = {
    CRCreateFrame,
    CRDestroyFrame,
    CRMoveFrame,
    CRResizeFrame,
    CRRestackFrame,
    CRReshapeFrame,
    CRUnmapFrame,
    CRStartDrawing,
    CRStopDrawing,
    CRUpdateRegion,
    CRDamageRects,
    NULL,
    CRDoReorderWindow,
    NULL,
    NULL,
    NULL,
    NULL
};


/*
 * Initialize CR implementation
 */
Bool
CRInit(ScreenPtr pScreen)
{
    RootlessInit(pScreen, &CRRootlessProcs);

    rootless_CopyBytes_threshold = 0;
    rootless_FillBytes_threshold = 0;
    rootless_CompositePixels_threshold = 0;
    rootless_CopyWindow_threshold = 0;

    return TRUE;
}
