#include <X11/Xmd.h>

#include "WINGsP.h"

#include <X11/Xatom.h>


typedef struct W_Window {
    W_Class widgetClass;
    W_View *view;

    struct W_Window *nextPtr;	       /* next in the window list */

    struct W_Window *owner;

    char *title;

    WMPixmap *miniImage;	       /* miniwindow */
    char *miniTitle;

    char *wname;

    WMSize resizeIncrement;
    WMSize baseSize;
    WMSize minSize;
    WMSize maxSize;
    WMPoint minAspect;
    WMPoint maxAspect;

    WMPoint upos;
    WMPoint ppos;

    WMAction *closeAction;
    void *closeData;

    int level;

    struct {
        unsigned style:4;
        unsigned configured:1;
        unsigned documentEdited:1;

        unsigned setUPos:1;
        unsigned setPPos:1;
        unsigned setAspect:1;
    } flags;
} _Window;



typedef struct {
    CARD32 flags;
    CARD32 window_style;
    CARD32 window_level;
    CARD32 reserved;
    Pixmap miniaturize_pixmap;	       /* pixmap for miniaturize button */
    Pixmap close_pixmap;	       /* pixmap for close button */
    Pixmap miniaturize_mask;	       /* miniaturize pixmap mask */
    Pixmap close_mask;		       /* close pixmap mask */
    CARD32 extra_flags;
} GNUstepWMAttributes;

#define GSWindowStyleAttr 	(1<<0)
#define GSWindowLevelAttr 	(1<<1)
#define GSMiniaturizePixmapAttr	(1<<3)
#define GSClosePixmapAttr	(1<<4)
#define GSMiniaturizeMaskAttr	(1<<5)
#define GSCloseMaskAttr		(1<<6)
#define GSExtraFlagsAttr	(1<<7)

/* extra flags */
#define GSDocumentEditedFlag	(1<<0)
#define GSNoApplicationIconFlag	(1<<5)

#define WMFHideOtherApplications	10
#define WMFHideApplication		12



static void willResizeWindow(W_ViewDelegate *, WMView *, unsigned*, unsigned*);

struct W_ViewDelegate _WindowViewDelegate = {
    NULL,
    NULL,
    NULL,
    NULL,
    willResizeWindow
};


#define DEFAULT_WIDTH	400
#define DEFAULT_HEIGHT	180
#define DEFAULT_TITLE	""


static void destroyWindow(_Window *win);

static void handleEvents();

static void realizeWindow();

static void
realizeObserver(void *self, WMNotification *not)
{
    realizeWindow(self);
}


WMWindow*
WMCreatePanelWithStyleForWindow(WMWindow *owner, char *name, int style)
{
    WMWindow *win;

    win = WMCreateWindowWithStyle(owner->view->screen, name, style);
    win->owner = owner;

    return win;
}



WMWindow*
WMCreatePanelForWindow(WMWindow *owner, char *name)
{
    return WMCreatePanelWithStyleForWindow(owner, name,
                                           WMTitledWindowMask
                                           |WMClosableWindowMask
                                           |WMResizableWindowMask);
}


void
WMChangePanelOwner(WMWindow *win, WMWindow *newOwner)
{
    win->owner = newOwner;

    if (win->view->flags.realized && newOwner) {
        XSetTransientForHint(win->view->screen->display, win->view->window,
                             newOwner->view->window);
    }
}



WMWindow*
WMCreateWindow(WMScreen *screen, char *name)
{
    return WMCreateWindowWithStyle(screen, name, WMTitledWindowMask
                                   |WMClosableWindowMask
                                   |WMMiniaturizableWindowMask
                                   |WMResizableWindowMask);
}



WMWindow*
WMCreateWindowWithStyle(WMScreen *screen, char *name, int style)
{
    _Window *win;

    win = wmalloc(sizeof(_Window));
    memset(win, 0, sizeof(_Window));

    win->widgetClass = WC_Window;

    win->view = W_CreateTopView(screen);
    if (!win->view) {
        wfree(win);
        return NULL;
    }
    win->view->self = win;

    win->view->delegate = &_WindowViewDelegate;

    win->wname = wstrdup(name);

    /* add to the window list of the screen (application) */
    win->nextPtr = screen->windowList;
    screen->windowList = win;

    WMCreateEventHandler(win->view, ExposureMask|StructureNotifyMask
                         |ClientMessageMask|FocusChangeMask,
                         handleEvents, win);

    W_ResizeView(win->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);

    WMAddNotificationObserver(realizeObserver, win,
                              WMViewRealizedNotification, win->view);

    win->flags.style = style;

    win->level = WMNormalWindowLevel;

    /* kluge. Find a better solution */
    W_SetFocusOfTopLevel(win->view, win->view);

    return win;
}



static void
setWindowTitle(WMWindow *win, const char *title)
{
    WMScreen *scr= win->view->screen;
    XTextProperty property;
    int result;

    result = XmbTextListToTextProperty(scr->display,
                                       (char**)&title, 1, XStdICCTextStyle,
                                       &property);
    if (result == XNoMemory || result == XLocaleNotSupported) {
        wwarning("window title conversion error... using STRING encoding");
        XStoreName(scr->display, win->view->window, title);
    } else {
        XSetWMName(scr->display, win->view->window, &property);
        if (property.value)
          XFree(property.value);
    }

    XChangeProperty(scr->display, win->view->window,
                    scr->netwmName, scr->utf8String, 8,
                    PropModeReplace, (unsigned char *)title, strlen(title));
}


static void
setMiniwindowTitle(WMWindow *win, const char *title)
{
    WMScreen *scr= win->view->screen;
    XTextProperty property;
    int result;

    result = XmbTextListToTextProperty(scr->display,
                                       (char**)&title, 1, XStdICCTextStyle,
                                       &property);
    if (result == XNoMemory || result == XLocaleNotSupported) {
        wwarning("icon title conversion error..using STRING encoding");
        XSetIconName(scr->display, win->view->window, title);
    } else {
        XSetWMIconName(scr->display, win->view->window, &property);
        if (property.value)
          XFree(property.value);
    }
    
    XChangeProperty(scr->display, win->view->window,
                    scr->netwmIconName, scr->utf8String, 8,
                    PropModeReplace, (unsigned char *)title, strlen(title));
}


static void
setMiniwindow(WMWindow *win, RImage *image)
{
    WMScreen *scr= win->view->screen;
    CARD32 *data;
    int x, y;
    int o;

    if (!image)
      return;

    data = wmalloc((image->width * image->height + 2) * sizeof(CARD32));

    o= 0;
    data[o++] = image->width;
    data[o++] = image->height;

    for (y= 0; y < image->height; y++) {
        for (x= 0; x < image->width; x++) {
            CARD32 pixel;
            int offs= (x+y*image->width);
            
            if (image->format == RRGBFormat)
              pixel= image->data[offs*3]<<16 | image->data[offs*3+1]<<8 | image->data[offs*3+2];
            else
              pixel= image->data[offs*4]<<16 | image->data[offs*4+1]<<8 | image->data[offs*4+2] | image->data[offs*4+3] << 24;

            data[o++]= pixel;
        }
    }

    XChangeProperty(scr->display, win->view->window, scr->netwmIcon,
                    XA_CARDINAL, 32, PropModeReplace,
                    (unsigned char *)data,
                    (image->width * image->height + 2));

    wfree(data);
}


void
WMSetWindowTitle(WMWindow *win, char *title)
{
    if (win->title!=NULL)
        wfree(win->title);
    if (title!=NULL)
        win->title = wstrdup(title);
    else
        win->title = NULL;

    if (win->view->flags.realized) {
        setWindowTitle(win, title);
    }
}




void
WMSetWindowCloseAction(WMWindow *win, WMAction *action, void *clientData)
{
    Atom *atoms = NULL;
    Atom *newAtoms;
    int count;
    WMScreen *scr = win->view->screen;

    if (win->view->flags.realized) {
        if (action && !win->closeAction) {
            if (!XGetWMProtocols(scr->display, win->view->window, &atoms,
                                 &count)) {
                count = 0;
            }
            newAtoms = wmalloc((count+1)*sizeof(Atom));
            if (count > 0)
                memcpy(newAtoms, atoms, count*sizeof(Atom));
            newAtoms[count++] = scr->deleteWindowAtom;
            XSetWMProtocols(scr->display, win->view->window, newAtoms, count);
            if (atoms)
                XFree(atoms);
            wfree(newAtoms);
        } else if (!action && win->closeAction) {
            int i, ncount;

            if (XGetWMProtocols(scr->display, win->view->window, &atoms,
                                &count) && count>0) {
                newAtoms = wmalloc((count-1)*sizeof(Atom));
                ncount = 0;
                for (i=0; i < count; i++) {
                    if (atoms[i]!=scr->deleteWindowAtom) {
                        newAtoms[i] = atoms[i];
                        ncount++;
                    }
                }
                XSetWMProtocols(scr->display, win->view->window, newAtoms,
                                ncount);
                if (atoms)
                    XFree(atoms);
                wfree(newAtoms);
            }
        }
    }
    win->closeAction = action;
    win->closeData = clientData;
}



static void
willResizeWindow(W_ViewDelegate *self, WMView *view,
                 unsigned *width, unsigned *height)
{
    WMWindow *win = (WMWindow*)view->self;

    if (win->minSize.width > 0 && win->minSize.height > 0) {
        if (*width < win->minSize.width)
            *width = win->minSize.width;
        if (*height < win->minSize.height)
            *height = win->minSize.height;
    }

    if (win->maxSize.width > 0 && win->maxSize.height > 0) {
        if (*width > win->maxSize.width)
            *width = win->maxSize.width;
        if (*height > win->maxSize.height)
            *height = win->maxSize.height;
    }
}


static void
setSizeHints(WMWindow *win)
{
    XSizeHints *hints;

    hints = XAllocSizeHints();
    if (!hints) {
        wwarning("could not allocate memory for window size hints");
        return;
    }

    hints->flags = 0;

    if (win->flags.setPPos) {
        hints->flags |= PPosition;
        hints->x = win->ppos.x;
        hints->y = win->ppos.y;
    }
    if (win->flags.setUPos) {
        hints->flags |= USPosition;
        hints->x = win->upos.x;
        hints->y = win->upos.y;
    }
    if (win->minSize.width>0 && win->minSize.height>0) {
        hints->flags |= PMinSize;
        hints->min_width = win->minSize.width;
        hints->min_height = win->minSize.height;
    }
    if (win->maxSize.width>0 && win->maxSize.height>0) {
        hints->flags |= PMaxSize;
        hints->max_width = win->maxSize.width;
        hints->max_height = win->maxSize.height;
    }
    if (win->baseSize.width>0 && win->baseSize.height>0) {
        hints->flags |= PBaseSize;
        hints->base_width = win->baseSize.width;
        hints->base_height = win->baseSize.height;
    }
    if (win->resizeIncrement.width>0 && win->resizeIncrement.height>0) {
        hints->flags |= PResizeInc;
        hints->width_inc = win->resizeIncrement.width;
        hints->height_inc = win->resizeIncrement.height;
    }
    if (win->flags.setAspect) {
        hints->flags |= PAspect;
        hints->min_aspect.x = win->minAspect.x;
        hints->min_aspect.y = win->minAspect.y;
        hints->max_aspect.x = win->maxAspect.x;
        hints->max_aspect.y = win->maxAspect.y;
    }


    if (hints->flags) {
        XSetWMNormalHints(win->view->screen->display, win->view->window, hints);
    }
    XFree(hints);
}



static void
writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
{
    unsigned long data[9];

    /* handle idiot compilers where array of CARD32 != struct of CARD32 */
    data[0] = attr->flags;
    data[1] = attr->window_style;
    data[2] = attr->window_level;
    data[3] = 0;		       /* reserved */
    /* The X protocol says XIDs are 32bit */
    data[4] = attr->miniaturize_pixmap;
    data[5] = attr->close_pixmap;
    data[6] = attr->miniaturize_mask;
    data[7] = attr->close_mask;
    data[8] = attr->extra_flags;
    XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
                    32, PropModeReplace,  (unsigned char *)data, 9);
}


static void
setWindowMakerHints(WMWindow *win)
{
    GNUstepWMAttributes attribs;
    WMScreen *scr = WMWidgetScreen(win);

    memset(&attribs, 0, sizeof(GNUstepWMAttributes));
    attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr|GSExtraFlagsAttr;
    attribs.window_style = win->flags.style;
    attribs.window_level = win->level;
    if (win->flags.documentEdited)
        attribs.extra_flags = GSDocumentEditedFlag;
    else
        attribs.extra_flags = 0;

    writeGNUstepWMAttr(scr, win->view->window, &attribs);
}


static void
realizeWindow(WMWindow *win)
{
    XWMHints *hints;
    XClassHint *classHint;
    WMScreen *scr = win->view->screen;
    Atom atoms[4];
    int count;

    classHint = XAllocClassHint();
    classHint->res_name = win->wname;
    classHint->res_class = WMGetApplicationName();
    XSetClassHint(scr->display, win->view->window, classHint);
    XFree(classHint);

    hints = XAllocWMHints();
    hints->flags = 0;
    if (!scr->aflags.simpleApplication) {
        hints->flags |= WindowGroupHint;
        hints->window_group = scr->groupLeader;
    }
    if (win->miniImage) {
        hints->flags |= IconPixmapHint;
        hints->icon_pixmap = WMGetPixmapXID(win->miniImage);
        hints->icon_mask = WMGetPixmapMaskXID(win->miniImage);
        if (hints->icon_mask != None) {
            hints->flags |= IconMaskHint;
        }
    }
    if (hints->flags != 0)
        XSetWMHints(scr->display, win->view->window, hints);
    XFree(hints);

    count = 0;
    if (win->closeAction) {
        atoms[count++] = scr->deleteWindowAtom;
    }

    if (count>0)
        XSetWMProtocols(scr->display, win->view->window, atoms, count);

    if (win->title || win->miniTitle)
        XmbSetWMProperties(scr->display, win->view->window, win->title,
                           win->miniTitle, NULL, 0, NULL, NULL, NULL);

    setWindowMakerHints(win);

    setSizeHints(win);

    if (win->owner) {
        XSetTransientForHint(scr->display, win->view->window,
                             win->owner->view->window);
    }
    
    if (win->title)
      setWindowTitle(win, win->title);
}



void
WMSetWindowAspectRatio(WMWindow *win, int minX, int minY,
                       int maxX, int maxY)
{
    win->flags.setAspect = 1;
    win->minAspect.x = minX;
    win->minAspect.y = minY;
    win->maxAspect.x = maxX;
    win->maxAspect.y = maxY;
    if (win->view->flags.realized)
        setSizeHints(win);
}



void
WMSetWindowInitialPosition(WMWindow *win, int x, int y)
{
    win->flags.setPPos = 1;
    win->ppos.x = x;
    win->ppos.y = y;
    if (win->view->flags.realized)
        setSizeHints(win);
    WMMoveWidget(win, x, y);
}



void
WMSetWindowUserPosition(WMWindow *win, int x, int y)
{
    win->flags.setUPos = 1;
    win->upos.x = x;
    win->upos.y = y;
    if (win->view->flags.realized)
        setSizeHints(win);
    WMMoveWidget(win, x, y);
}




void
WMSetWindowMinSize(WMWindow *win, unsigned width, unsigned height)
{
    win->minSize.width = width;
    win->minSize.height = height;
    if (win->view->flags.realized)
        setSizeHints(win);
}



void
WMSetWindowMaxSize(WMWindow *win, unsigned width, unsigned height)
{
    win->maxSize.width = width;
    win->maxSize.height = height;
    if (win->view->flags.realized)
        setSizeHints(win);
}


void
WMSetWindowBaseSize(WMWindow *win, unsigned width, unsigned height)
{
    /* TODO: validate sizes */
    win->baseSize.width = width;
    win->baseSize.height = height;
    if (win->view->flags.realized)
        setSizeHints(win);
}


void
WMSetWindowResizeIncrements(WMWindow *win, unsigned wIncr, unsigned hIncr)
{
    win->resizeIncrement.width = wIncr;
    win->resizeIncrement.height = hIncr;
    if (win->view->flags.realized)
        setSizeHints(win);
}


void
WMSetWindowLevel(WMWindow *win, int level)
{
    win->level = level;
    if (win->view->flags.realized)
        setWindowMakerHints(win);
}


void
WMSetWindowDocumentEdited(WMWindow *win, Bool flag)
{
    flag = ((flag==0) ? 0 : 1);
    if (win->flags.documentEdited != flag) {
        win->flags.documentEdited = flag;
        if (win->view->flags.realized)
            setWindowMakerHints(win);
    }
}


void
WMSetWindowMiniwindowImage(WMWindow *win, RImage *image)
{
    if (win->view->flags.realized)
        setMiniwindow(win, image);
}


void
WMSetWindowMiniwindowPixmap(WMWindow *win, WMPixmap *pixmap)
{
    if ((win->miniImage && !pixmap) || (!win->miniImage && pixmap)) {
        if (win->miniImage)
            WMReleasePixmap(win->miniImage);

        if (pixmap)
            win->miniImage = WMRetainPixmap(pixmap);
        else
            win->miniImage = NULL;

        if (win->view->flags.realized) {
            XWMHints *hints;

            hints = XGetWMHints(win->view->screen->display, win->view->window);
            if (!hints) {
                hints = XAllocWMHints();
                if (!hints) {
                    wwarning("could not allocate memory for WM hints");
                    return;
                }
                hints->flags = 0;
            }
            if (pixmap) {
                hints->flags |= IconPixmapHint;
                hints->icon_pixmap = WMGetPixmapXID(pixmap);
                hints->icon_mask = WMGetPixmapMaskXID(pixmap);
                if (hints->icon_mask != None) {
                    hints->flags |= IconMaskHint;
                }
            }
            XSetWMHints(win->view->screen->display, win->view->window, hints);
            XFree(hints);
        }
    }
}


void
WMSetWindowMiniwindowTitle(WMWindow *win, char *title)
{
    if ((win->miniTitle && !title) || (!win->miniTitle && title)
        || (title && win->miniTitle && strcoll(title, win->miniTitle)!=0)) {
        if (win->miniTitle)
            wfree(win->miniTitle);

        if (title)
            win->miniTitle = wstrdup(title);
        else
            win->miniTitle = NULL;

        if (win->view->flags.realized) {
            setMiniwindowTitle(win, title);
        }
    }
}


void
WMCloseWindow(WMWindow *win)
{
    WMUnmapWidget(win);
    /* withdraw the window */
    if (win->view->flags.realized)
        XWithdrawWindow(win->view->screen->display, win->view->window,
                        win->view->screen->screen);
}


static void
handleEvents(XEvent *event, void *clientData)
{
    _Window *win = (_Window*)clientData;
    W_View *view = win->view;


    switch (event->type) {
    case ClientMessage:
        if (event->xclient.message_type == win->view->screen->protocolsAtom
            && event->xclient.format == 32
            && event->xclient.data.l[0]==win->view->screen->deleteWindowAtom) {

            if (win->closeAction) {
                (*win->closeAction)(win, win->closeData);
            }
        }
        break;
        /*
         * was causing windows to ignore commands like closeWindow
         * after the windows is iconized/restored or a workspace change
         * if this is really needed, put the MapNotify portion too and
         * fix the restack bug in wmaker
         case UnmapNotify:
         WMUnmapWidget(win);
         break;
         *
         case MapNotify:
         WMMapWidget(win);
         break;

         */
    case DestroyNotify:
        destroyWindow(win);
        break;

    case ConfigureNotify:
        if (event->xconfigure.width != view->size.width
            || event->xconfigure.height != view->size.height) {

            view->size.width = event->xconfigure.width;
            view->size.height = event->xconfigure.height;

            if (view->flags.notifySizeChanged) {
                WMPostNotificationName(WMViewSizeDidChangeNotification,
                                       view, NULL);
            }
        }
        if (event->xconfigure.x != view->pos.x
            || event->xconfigure.y != view->pos.y) {

            if (event->xconfigure.send_event) {
                view->pos.x = event->xconfigure.x;
                view->pos.y = event->xconfigure.y;
            } else {
                Window foo;

                XTranslateCoordinates(view->screen->display,
                                      view->window, view->screen->rootWin,
                                      event->xconfigure.x, event->xconfigure.y,
                                      &view->pos.x, &view->pos.y, &foo);
            }
        }
        break;
    }
}




static void
destroyWindow(_Window *win)
{
    WMScreen *scr = win->view->screen;

    WMRemoveNotificationObserver(win);

    if (scr->windowList == win) {
        scr->windowList = scr->windowList->nextPtr;
    } else {
        WMWindow *ptr;
        ptr = scr->windowList;

        if (ptr) {
            while (ptr->nextPtr) {
                if (ptr->nextPtr==win) {
                    ptr->nextPtr = ptr->nextPtr->nextPtr;
                    break;
                }
                ptr = ptr->nextPtr;
            }
        }
    }

    if (win->title) {
        wfree(win->title);
    }

    if (win->miniTitle) {
        wfree(win->miniTitle);
    }

    if (win->miniImage) {
        WMReleasePixmap(win->miniImage);
    }

    if (win->wname)
        wfree(win->wname);

    wfree(win);
}




syntax highlighted by Code2HTML, v. 0.9.1