#include "WINGsP.h"

#include <X11/Xresource.h>



/* the notifications about views */

char *WMViewSizeDidChangeNotification = "WMViewSizeDidChangeNotification";
char *WMViewFocusDidChangeNotification = "WMViewFocusDidChangeNotification";
char *WMViewRealizedNotification = "WMViewRealizedNotification";


#define EVENT_MASK  \
    KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask| \
    EnterWindowMask|LeaveWindowMask|PointerMotionMask|ExposureMask| \
    VisibilityChangeMask|FocusChangeMask|PropertyChangeMask|\
    SubstructureNotifyMask|SubstructureRedirectMask


static XSetWindowAttributes defAtts= {
    None,                       /* background_pixmap */
    0,                          /* background_pixel */
    CopyFromParent,             /* border_pixmap */
    0,                          /* border_pixel */
    ForgetGravity,              /* bit_gravity */
    ForgetGravity,              /* win_gravity */
    NotUseful,                  /* backing_store */
    (unsigned) ~0,              /* backing_planes */
    0,                          /* backing_pixel */
    False,                      /* save_under */
    EVENT_MASK,                 /* event_mask */
    0,                          /* do_not_propagate_mask */
    False,                      /* override_redirect */
    None,                       /* colormap */
    None                        /* cursor */
};



static XContext ViewContext=0;  /* context for views */




W_View*
W_GetViewForXWindow(Display *display, Window window)
{
    W_View *view;

    if (XFindContext(display, window, ViewContext, (XPointer*)&view)==0) {
        return view;
    }
    return NULL;
}



static void
unparentView(W_View *view)
{
    /* remove from parent's children list */
    if (view->parent!=NULL) {
        W_View *ptr;

        ptr = view->parent->childrenList;
        if (ptr == view) {
            view->parent->childrenList = view->nextSister;
        } else {
            while (ptr!=NULL) {
                if (ptr->nextSister == view) {
                    ptr->nextSister = view->nextSister;
                    break;
                }
                ptr = ptr->nextSister;
            }
        }
    }
    view->parent = NULL;
}


static void
adoptChildView(W_View *view, W_View *child)
{
    child->nextSister = NULL;

    /* add to end of children list of parent */
    if (view->childrenList == NULL) {
        view->childrenList = child;
    } else {
        W_View *ptr;

        ptr = view->childrenList;
        while (ptr->nextSister!=NULL)
            ptr = ptr->nextSister;
        ptr->nextSister = child;
    }
    child->parent = view;
}


static W_View*
createView(W_Screen *screen, W_View *parent)
{
    W_View *view;

    if (ViewContext==0)
        ViewContext = XUniqueContext();

    view = wmalloc(sizeof(W_View));
    memset(view, 0, sizeof(W_View));

    view->screen = screen;

    if (parent!=NULL) {
        /* attributes are not valid for root window */
        view->attribFlags = CWEventMask|CWBitGravity;
        view->attribs = defAtts;

        view->attribFlags |= CWBackPixel|CWColormap|CWBorderPixel;
        view->attribs.background_pixel = W_PIXEL(screen->gray);
        view->attribs.border_pixel = W_PIXEL(screen->black);
        view->attribs.colormap = screen->colormap;

        view->backColor = WMRetainColor(screen->gray);

        adoptChildView(parent, view);
    }

    view->xic = 0;

    view->refCount = 1;

    view->eventHandlers = WMCreateArrayWithDestructor(4, wfree);

    return view;
}



W_View*
W_CreateView(W_View *parent)
{
    return createView(parent->screen, parent);
}


W_View*
W_CreateRootView(W_Screen *screen)
{
    W_View *view;

    view = createView(screen, NULL);

    view->window = screen->rootWin;

    view->flags.realized = 1;
    view->flags.mapped = 1;
    view->flags.root = 1;

    view->size.width =
        WidthOfScreen(ScreenOfDisplay(screen->display, screen->screen));
    view->size.height =
        HeightOfScreen(ScreenOfDisplay(screen->display, screen->screen));

    return view;
}


W_View*
W_CreateTopView(W_Screen *screen)
{
    W_View *view;

    view = createView(screen, screen->rootView);
    if (!view)
        return NULL;

    view->flags.topLevel = 1;
    view->attribs.event_mask |= StructureNotifyMask;

    return view;
}


W_View*
W_CreateUnmanagedTopView(W_Screen *screen)
{
    W_View *view;

    view = createView(screen, screen->rootView);
    if (!view)
        return NULL;

    view->flags.topLevel = 1;
    view->attribs.event_mask |= StructureNotifyMask;

    view->attribFlags |= CWOverrideRedirect;
    view->attribs.override_redirect = True;

    return view;
}


void
W_RealizeView(W_View *view)
{
    Window parentWID;
    Display *dpy = view->screen->display;
    W_View *ptr;

    assert(view->size.width > 0);
    assert(view->size.height > 0);

    if (view->parent && !view->parent->flags.realized) {
        wwarning("trying to realize widget of unrealized parent");
        return;
    }

    if (!view->flags.realized) {
        parentWID = view->parent->window;
        view->window = XCreateWindow(dpy, parentWID, view->pos.x, view->pos.y,
                                     view->size.width, view->size.height, 0,
                                     view->screen->depth, InputOutput,
                                     view->screen->visual, view->attribFlags,
                                     &view->attribs);

        XSaveContext(dpy, view->window, ViewContext, (XPointer)view);

        view->flags.realized = 1;

        if (view->flags.mapWhenRealized) {
            W_MapView(view);
            view->flags.mapWhenRealized = 0;
        }

        WMPostNotificationName(WMViewRealizedNotification, view, NULL);
    }

    /* realize children */
    ptr = view->childrenList;
    while (ptr!=NULL) {
        W_RealizeView(ptr);

        ptr = ptr->nextSister;
    }
}


void
W_ReparentView(W_View *view, W_View *newParent, int x, int y)
{
    Display *dpy = view->screen->display;

    assert(!view->flags.topLevel);

    unparentView(view);
    adoptChildView(newParent, view);

    if (view->flags.realized) {
        if (newParent->flags.realized) {
            XReparentWindow(dpy, view->window, newParent->window, x, y);
        } else {
            wwarning("trying to reparent realized view to unrealized parent");
            return;
        }
    }

    view->pos.x = x;
    view->pos.y = y;
}


void
W_RaiseView(W_View *view)
{
    if (W_VIEW_REALIZED(view))
        XRaiseWindow(W_VIEW_DISPLAY(view), W_VIEW_DRAWABLE(view));
}



void
W_LowerView(W_View *view)
{
    if (W_VIEW_REALIZED(view))
        XLowerWindow(W_VIEW_DISPLAY(view), W_VIEW_DRAWABLE(view));
}



void
W_MapView(W_View *view)
{
    if (!view->flags.mapped) {
        if (view->flags.realized) {
            XMapRaised(view->screen->display, view->window);
            XFlush(view->screen->display);
            view->flags.mapped = 1;
        } else {
            view->flags.mapWhenRealized = 1;
        }
    }
}


/*
 * W_MapSubviews-
 *     maps all children of the current view that where already realized.
 */
void
W_MapSubviews(W_View *view)
{
    XMapSubwindows(view->screen->display, view->window);
    XFlush(view->screen->display);

    view = view->childrenList;
    while (view) {
        view->flags.mapped = 1;
        view->flags.mapWhenRealized = 0;
        view = view->nextSister;
    }
}



void
W_UnmapSubviews(W_View *view)
{
    XUnmapSubwindows(view->screen->display, view->window);
    XFlush(view->screen->display);

    view = view->childrenList;
    while (view) {
        view->flags.mapped = 0;
        view->flags.mapWhenRealized = 0;
        view = view->nextSister;
    }
}



void
W_UnmapView(W_View *view)
{
    view->flags.mapWhenRealized = 0;
    if (!view->flags.mapped)
        return;

    XUnmapWindow(view->screen->display, view->window);
    XFlush(view->screen->display);

    view->flags.mapped = 0;
}


W_View*
W_TopLevelOfView(W_View *view)
{
    W_View *toplevel;

    for (toplevel=view;
         toplevel && !toplevel->flags.topLevel;
         toplevel=toplevel->parent);

    return toplevel;
}


static void
destroyView(W_View *view)
{
    W_View *ptr;

    if (view->flags.alreadyDead)
        return;
    view->flags.alreadyDead = 1;

    /* delete the balloon text for the view, if there's any */
    WMSetBalloonTextForView(NULL, view);

    if (view->nextFocusChain)
        view->nextFocusChain->prevFocusChain = view->prevFocusChain;
    if (view->prevFocusChain)
        view->prevFocusChain->nextFocusChain = view->nextFocusChain;

    /* Do not leave focus in a inexisting control */
    if (W_FocusedViewOfToplevel(W_TopLevelOfView(view))==view)
        W_SetFocusOfTopLevel(W_TopLevelOfView(view), NULL);

    if (view->flags.topLevel) {
        W_FocusInfo *info = view->screen->focusInfo;
        /* remove focus information associated to this toplevel */

        if (info) {
            if (info->toplevel==view) {
                view->screen->focusInfo = info->next;
                wfree(info);
            } else {
                while (info->next) {
                    if (info->next->toplevel == view)
                        break;
                    info = info->next;
                }
                if (info->next) {
                    W_FocusInfo *next = info->next->next;
                    wfree(info->next);
                    info->next = next;
                }
                /* else the toplevel did not have any focused subview */
            }
        }
    }

    /* destroy children recursively */
    while (view->childrenList!=NULL) {
        ptr = view->childrenList;
        ptr->flags.parentDying = 1;

        W_DestroyView(ptr);

        if (ptr == view->childrenList) {
            view->childrenList = ptr->nextSister;
            ptr->parent = NULL;
        }
    }

    W_CallDestroyHandlers(view);

    if (view->flags.realized) {
        XDeleteContext(view->screen->display,
                       view->window, ViewContext);

        /* if parent is being destroyed, it will die naturaly */
        if (!view->flags.parentDying || view->flags.topLevel)
            XDestroyWindow(view->screen->display, view->window);
    }

    /* remove self from parent's children list */
    unparentView(view);

    /* the array has a wfree() destructor that will be called automatically */
    WMFreeArray(view->eventHandlers);
    view->eventHandlers = NULL;

    WMRemoveNotificationObserver(view);

    W_FreeViewXdndPart(view);

    wfree(view);
}



void
W_DestroyView(W_View *view)
{
    view->refCount--;

    if (view->refCount < 1) {
        destroyView(view);
    }
}



void
W_MoveView(W_View *view, int x, int y)
{
    assert(view->flags.root==0);

    if (view->delegate && view->delegate->willMove) {
        (*view->delegate->willMove)(view->delegate, view, &x, &y);
    }

    if (view->pos.x == x && view->pos.y == y)
        return;

    if (view->flags.realized) {
        XMoveWindow(view->screen->display, view->window, x, y);
    }
    view->pos.x = x;
    view->pos.y = y;

    if (view->delegate && view->delegate->didMove) {
        (*view->delegate->didMove)(view->delegate, view);
    }
}


void
W_ResizeView(W_View *view, unsigned int width, unsigned int height)
{
    /*int shrinked;*/

    if (view->delegate && view->delegate->willResize) {
        (*view->delegate->willResize)(view->delegate, view, &width, &height);
    }

    assert(width > 0);
    assert(height > 0);

    if (view->size.width == width && view->size.height == height)
        return;

    /*shrinked = width < view->size.width || height < view->size.height;*/

    if (view->flags.realized) {
        XResizeWindow(view->screen->display, view->window, width, height);
    }
    view->size.width = width;
    view->size.height = height;

    if (view->delegate && view->delegate->didResize) {
        (*view->delegate->didResize)(view->delegate, view);
    }

    /* // TODO. replace in WINGs code, with the didResize delegate */
    if (view->flags.notifySizeChanged)
        WMPostNotificationName(WMViewSizeDidChangeNotification, view, NULL);
}


void
W_RedisplayView(W_View *view)
{
    XEvent ev;

    if (!view->flags.mapped)
        return;

    ev.xexpose.type = Expose;
    ev.xexpose.display = view->screen->display;
    ev.xexpose.window = view->window;
    ev.xexpose.count = 0;

    ev.xexpose.serial = 0;

    WMHandleEvent(&ev);
}


void
W_SetViewBackgroundColor(W_View *view, WMColor *color)
{
    if (view->backColor)
        WMReleaseColor(view->backColor);
    view->backColor = WMRetainColor(color);

    view->attribFlags |= CWBackPixel;
    view->attribs.background_pixel = W_PIXEL(color);
    if (view->flags.realized) {
        XSetWindowBackground(view->screen->display, view->window,
                             W_PIXEL(color));
        XClearWindow(view->screen->display, view->window);
    }
}


void
W_SetViewCursor(W_View *view, Cursor cursor)
{
    view->cursor = cursor;
    if (W_VIEW_REALIZED(view)) {
        XDefineCursor(W_VIEW_DISPLAY(view), W_VIEW_DRAWABLE(view), cursor);
    } else {
        view->attribFlags |= CWCursor;
        view->attribs.cursor = cursor;
    }
}


W_View*
W_FocusedViewOfToplevel(W_View *view)
{
    WMScreen *scr = view->screen;
    W_FocusInfo *info;

    for (info = scr->focusInfo; info!=NULL; info = info->next)
        if (view == info->toplevel)
            break;

    if (!info)
        return NULL;

    return info->focused;
}


void
W_SetFocusOfTopLevel(W_View *toplevel, W_View *view)
{
    WMScreen *scr = toplevel->screen;
    XEvent event;
    W_FocusInfo *info;

    for (info = scr->focusInfo; info!=NULL; info = info->next)
        if (toplevel == info->toplevel)
            break;

    if (!info) {
        info = wmalloc(sizeof(W_FocusInfo));
        info->toplevel = toplevel;
        info->focused = view;
        info->next = scr->focusInfo;
        scr->focusInfo = info;
    } else {
        event.xfocus.mode = NotifyNormal;
        event.xfocus.detail = NotifyDetailNone;
        if (info->focused) {
            /* simulate FocusOut event */
            event.xfocus.type = FocusOut;
            W_DispatchMessage(info->focused, &event);
        }
        info->focused = view;
    }
    if (view) {
        /* simulate FocusIn event */
        event.xfocus.type = FocusIn;
        W_DispatchMessage(view, &event);
    }
}


void
W_BroadcastMessage(W_View *targetParent, XEvent *event)
{
    W_View *target;

    target = targetParent->childrenList;
    while (target!=NULL) {
        W_DispatchMessage(target, event);

        target = target->nextSister;
    }
}


void
W_DispatchMessage(W_View *target, XEvent *event)
{
    if (target->window==None)
        return;
    event->xclient.window = target->window;
    event->xclient.display = target->screen->display;

    WMHandleEvent(event);
    /*
     XSendEvent(target->screen->display, target->window, False,
     SubstructureNotifyMask, event);
     */
}



WMView*
W_RetainView(WMView *view)
{
    view->refCount++;

    return view;
}


void
W_ReleaseView(WMView *view)
{
    view->refCount--;

    if (view->refCount < 1) {
        destroyView(view);
    }
}


WMWidget*
WMWidgetOfView(WMView *view)
{
    return view->self;
}


WMSize
WMGetViewSize(WMView *view)
{
    return view->size;
}

WMPoint
WMGetViewPosition(WMView *view)
{
    return view->pos;
}


void
WMSetViewNotifySizeChanges(WMView *view, Bool flag)
{
    view->flags.notifySizeChanged = ((flag==0) ? 0 : 1);
}


Window
WMViewXID(WMView *view)
{
    return view->window;
}


WMPoint
WMGetViewScreenPosition(WMView *view)
{
    WMScreen *scr = W_VIEW_SCREEN(view);
    Window foo;
    int x, y, topX, topY, bar;
    WMView *topView;

    topView = view;
    while (topView->parent && topView->parent!=scr->rootView)
        topView = topView->parent;

    if (!XGetGeometry(scr->display, W_VIEW_DRAWABLE(topView), &foo, &topX,
                      &topY, &bar, &bar, &bar, &bar)) {
        topX = topY = 0;
    }

    XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(view),
                          scr->rootWin, 0, 0, &x, &y, &foo);

    return wmkpoint(x-topX, y-topY);
}


static void
resizedParent(void *self, WMNotification *notif)
{
    WMSize size = WMGetViewSize((WMView*)WMGetNotificationObject(notif));
    WMView *view = (WMView*)self;

    W_MoveView(view, view->leftOffs, view->topOffs);
    W_ResizeView(view, size.width - (view->leftOffs + view->rightOffs),
                 size.height - (view->topOffs + view->bottomOffs));
}


void
WMSetViewExpandsToParent(WMView *view, int leftOffs, int topOffs,
                         int rightOffs, int bottomOffs)
{
    WMSize size = view->parent->size;

    view->topOffs = topOffs;
    view->bottomOffs = bottomOffs;
    view->leftOffs = leftOffs;
    view->rightOffs = rightOffs;

    WMAddNotificationObserver(resizedParent, view,
                              WMViewSizeDidChangeNotification,
                              view->parent);
    WMSetViewNotifySizeChanges(view->parent, True);

    W_MoveView(view, leftOffs, topOffs);
    W_ResizeView(view, size.width - (leftOffs + rightOffs),
                 size.height - (topOffs + bottomOffs));
}




syntax highlighted by Code2HTML, v. 0.9.1