



#include "WINGsP.h"

/*
char *WMSplitViewDidResizeSubviewsNotification
    = "WMSplitViewDidResizeSubviewsNotification";
char *WMSplitViewWillResizeSubviewsNotification
    = "WMSplitViewWillResizeSubviewsNotification";
*/

typedef struct _T_SplitViewSubview {
    WMView *view;
    int minSize;
    int maxSize;
    int size;
    int pos;
} T_SplitViewSubview;


typedef struct W_SplitView {
    W_Class widgetClass;
    W_View *view;
    
    WMBag *subviewsBag;
    
    WMSplitViewConstrainProc *constrainProc;
    
    struct {
	unsigned int vertical:1;	
	unsigned int adjustOnPaint:1;
	unsigned int subviewsWereManuallyMoved:1;
    } flags;

    /* WMSplitViewResizeSubviewsProc *resizeSubviewsProc; */

} SplitView;


#define DIVIDER_THICKNESS   8
#define MIN_SUBVIEW_SIZE    4
#define MAX_SUBVIEW_SIZE    -1


#define _GetSubviewsCount() WMGetBagItemCount(sPtr->subviewsBag)

#define _AddPSubviewStruct(P) \
(WMPutInBag(sPtr->subviewsBag,((void*)P)))

#define _GetPSubviewStructAt(i) \
((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))

#define _GetSubviewAt(i) \
(((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))->view)

#define _GetMinSizeAt(i) \
(((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))->minSize)

#define _GetMaxSizeAt(i) \
(((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))->maxSize)

#define _GetSizeAt(i) \
(((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))->size)

#define _GetPosAt(i) \
(((T_SplitViewSubview*)WMGetFromBag(sPtr->subviewsBag,(i)))->pos)

#define _GetSplitViewSize() \
((sPtr->flags.vertical) ? sPtr->view->size.width : sPtr->view->size.height)

static void destroySplitView(SplitView *sPtr);
static void paintSplitView(SplitView *sPtr);

static void handleEvents(XEvent *event, void *data);
static void handleActionEvents(XEvent *event, void *data);


static void
getConstraints(SplitView *sPtr, int index, int *minSize, int *maxSize)
{
    *minSize = MIN_SUBVIEW_SIZE;
    *maxSize = MAX_SUBVIEW_SIZE;

    if (sPtr->constrainProc)
	(*sPtr->constrainProc)(sPtr, index, minSize, maxSize);

    if (*minSize < MIN_SUBVIEW_SIZE)
	*minSize = MIN_SUBVIEW_SIZE;

    if (*maxSize < MIN_SUBVIEW_SIZE)
    	*maxSize = MAX_SUBVIEW_SIZE;
    else if (*maxSize < *minSize)
    	*maxSize = *minSize;
}


static void
updateConstraints(SplitView *sPtr)
{
    T_SplitViewSubview *p;
    int i, count;

    count = _GetSubviewsCount();
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
    	getConstraints(sPtr, i, &(p->minSize), &(p->maxSize));
    }
}


static void
resizeView(SplitView *sPtr, WMView *view, int size)
{
    int width, height;

    if (sPtr->flags.vertical) {
	width = size;
    	height = sPtr->view->size.height;
    } else {
	width = sPtr->view->size.width;
    	height = size;
    }

    if (view->self)
	WMResizeWidget(view->self, width, height);
    else
	W_ResizeView(view, width, height);
}


static void
reparentView(SplitView *sPtr, WMView *view, int pos)
{
    int x, y;

    if (sPtr->flags.vertical) {
    	x = pos;
	y = 0;
    } else {
    	x = 0;
	y = pos;
    }

    W_ReparentView(view, sPtr->view, x, y);
}


static void
moveView(SplitView *sPtr, WMView *view, int pos)
{
    int x, y;

    if (sPtr->flags.vertical) {
    	x = pos;
	y = 0;
    } else {
    	x = 0;
	y = pos;
    }

    if (view->self)
	WMMoveWidget(view->self, x, y);
    else
	W_MoveView(view, x, y);
}


static int
checkSizes(SplitView *sPtr)
{
    int i, count, offset;
    T_SplitViewSubview *p;

    count = _GetSubviewsCount();
    offset = 0;
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
	if (p->size < p->minSize) {
	    offset += p->minSize - p->size;
	    p->size = p->minSize;
	} else if (p->maxSize != MAX_SUBVIEW_SIZE && p->size > p->maxSize) {
	    offset += p->maxSize - p->size;
	    p->size = p->maxSize;
	}
    }
    
    return (offset);
}


static void
checkPositions(SplitView *sPtr)
{
    int i, count, pos;
    T_SplitViewSubview *p;

    count = _GetSubviewsCount();
    pos = 0;
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
	p->pos = pos;
	pos += p->size + DIVIDER_THICKNESS;
    }
}


static void
updateSubviewsGeom(SplitView *sPtr)
{
    int i, count;
    T_SplitViewSubview *p;

    count = _GetSubviewsCount();
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
	resizeView(sPtr, p->view, p->size);
	moveView(sPtr, p->view, p->pos);
    }
}


static int
getTotalSize(SplitView *sPtr)
{
    int i, count, totSize;

    count = _GetSubviewsCount();
    if (!count)
    	return (0);

    totSize = 0;
    for (i = 0; i < count; i++)
    	totSize += _GetSizeAt(i) + DIVIDER_THICKNESS;
    
    return (totSize - DIVIDER_THICKNESS);
}


static Bool
distributeOffsetEqually(SplitView *sPtr, int offset)
{
    T_SplitViewSubview *p;
    int i, count, sizeChanged, forced;

    if ((count = _GetSubviewsCount()) < 1)
    	return (True);

    forced = False;
    while (offset != 0) {
	sizeChanged = 0;
    	for (i = 0; i < count && offset != 0; i++) {
    	    p = _GetPSubviewStructAt(i);
	    if (offset < 0) {
	    	if (p->size > p->minSize) {
		    offset++;
	    	    p->size--;
		    sizeChanged = 1;
		}
	    } else if (p->maxSize == MAX_SUBVIEW_SIZE || p->size < p->maxSize) {
		offset--;
	    	p->size++;
		sizeChanged = 1;
	    }
    	}
	if (offset != 0 && !sizeChanged) {
	    p = _GetPSubviewStructAt(count-1);
	    if (offset > 0) {
	    	p->size += offset;
	    	p->maxSize = MAX_SUBVIEW_SIZE;
	    }
	    offset = 0;
	    forced = True;
	}
    }
    
    return (forced);
}


static Bool
distributeOffsetFormEnd(SplitView *sPtr, int offset)
{
    T_SplitViewSubview *p;
    int i, count, sizeTmp;

    if ((count = _GetSubviewsCount()) < 1)
    	return (True);

    for (i = count-1; i >= 0 && offset != 0; i--) {
	p = _GetPSubviewStructAt(i);
	sizeTmp = p->size;
	if (offset > 0) {
	    if (p->maxSize == MAX_SUBVIEW_SIZE || p->size + offset < p->maxSize)
	    	p->size += offset;
	    else
		p->size = p->maxSize;
	} else {
	    if (p->size + offset >= p->minSize)
	    	p->size += offset;
	    else
		p->size = p->minSize;
	}
	offset -= p->size - sizeTmp;
    }

    return (offset == 0);
}


static void
adjustSplitViewSubviews(WMSplitView *sPtr)
{
    T_SplitViewSubview *p;
    int i, count, adjSize, adjPad;

    CHECK_CLASS(sPtr, WC_SplitView);

#if 0
    printf("---- (adjustSplitViewSubviews - 1) ----\n");
    dumpSubviews(sPtr);
#endif

    if ((count = _GetSubviewsCount()) < 1)
	return;

    adjSize = (_GetSplitViewSize() - ((count-1) * DIVIDER_THICKNESS))  / count;
    adjPad =  (_GetSplitViewSize() - ((count-1) * DIVIDER_THICKNESS)) % count;
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
	p->size = adjSize;
    }
    
    distributeOffsetEqually(sPtr, adjPad - checkSizes(sPtr));
    
    checkPositions(sPtr);
    updateSubviewsGeom(sPtr);
    
    sPtr->flags.subviewsWereManuallyMoved = 0;

#if 0    
    printf("---- (adjustSplitViewSubviews - 2) ----\n");
    dumpSubviews(sPtr);
#endif
}

#if 0
static void
handleSubviewResized(void *self, WMNotification *notif)
{
    SplitView *sPtr = (SplitView*)self;

    CHECK_CLASS(sPtr, WC_SplitView);

    if (WMGetNotificationName(notif) == WMViewSizeDidChangeNotification) {
    	T_SplitViewSubview *p;
    	int i, count, done;
    	WMView *view = WMGetNotificationObject(notif);

    	count = _GetSubviewsCount();
    	done = 0;
	for (i = 0; i < count; i++) {
    	    p = _GetPSubviewStructAt(i);
	    if (p->view == view) {
	    	done = 1;
		break;
	    }
	}
	
	if (done) {
	    /* TODO !!! */
	    resizeView(sPtr, p->view, p->size);
	    moveView(sPtr, p->view, p->pos);
	}
    }
}
#endif

static void
handleViewResized(void *self, WMNotification *notification)
{
    SplitView *sPtr = (SplitView*)self;

#if 0
    printf("---- (handleViewResized - 1) ----\n");
    dumpSubviews(sPtr);
#endif

    updateConstraints(sPtr);
    checkSizes(sPtr);
    
    if (sPtr->constrainProc || sPtr->flags.subviewsWereManuallyMoved) {
    	distributeOffsetFormEnd(sPtr, _GetSplitViewSize() - getTotalSize(sPtr));
    	checkPositions(sPtr);
    	updateSubviewsGeom(sPtr);
    } else
    	adjustSplitViewSubviews(sPtr);

    assert(checkSizes(sPtr) == 0);

#if 0
    printf("---- (handleViewResized - 2) ----\n");
    dumpSubviews(sPtr);
#endif
}


static void
paintSplitView(SplitView *sPtr)
{
    T_SplitViewSubview *p;
    W_Screen *scr = sPtr->view->screen;
    int x, y, i, count;
    WMPixmap *dimple = scr->scrollerDimple;

#if 0
    printf("---- (paintSplitView - 1) ----\n");
    dumpSubviews(sPtr);
#endif
    
    if (!sPtr->view->flags.mapped || !sPtr->view->flags.realized)
    	return;

    XClearWindow(scr->display, sPtr->view->window);

    count = _GetSubviewsCount();
    if (count == 0)
    	return;

    if (sPtr->flags.adjustOnPaint) {
	handleViewResized(sPtr, NULL);
	sPtr->flags.adjustOnPaint = 0;
    }

    XSetClipMask(scr->display, scr->clipGC, dimple->mask);

    if (sPtr->flags.vertical) {
	x = ((DIVIDER_THICKNESS - dimple->width) / 2);
	y = (sPtr->view->size.height - dimple->height)/2;
    } else {
	x = (sPtr->view->size.width - dimple->width)/2;
	y = ((DIVIDER_THICKNESS - dimple->height) / 2);
    }

    for (i = 0; i < count-1; i++) {
    	p = _GetPSubviewStructAt(i);

	if (sPtr->flags.vertical)
    	    x += p->size;
	else
    	    y += p->size;

	XSetClipOrigin(scr->display, scr->clipGC, x, y);
	XCopyArea(scr->display, dimple->pixmap, sPtr->view->window,
		  scr->clipGC, 0, 0, dimple->width, dimple->height, x, y);
	
	if (sPtr->flags.vertical)
    	    x += DIVIDER_THICKNESS;
	else
	    y += DIVIDER_THICKNESS;
    }

#if 0
    printf("---- (paintSplitView - 2) ----\n");
    dumpSubviews(sPtr);
#endif
}


static void
drawDragingRectangle(SplitView *sPtr, int pos)
{
    int x, y, w, h;
    
    if (sPtr->flags.vertical) {
    	x = pos;
	y = 0;
	w = DIVIDER_THICKNESS;
	h = sPtr->view->size.height;
    } else {
    	x = 0;
	y = pos;
	w = sPtr->view->size.width;
	h = DIVIDER_THICKNESS;
    }
    
    XFillRectangle(sPtr->view->screen->display, sPtr->view->window,
		   sPtr->view->screen->ixorGC, x, y, w, h);
}


static void
getMinMaxDividerCoord(SplitView *sPtr, int divider, int *minC, int *maxC)
{
    int relMinC, relMaxC;
    int totSize = _GetSizeAt(divider) + _GetSizeAt(divider+1);

    relMinC = _GetMinSizeAt(divider);
    if (_GetMaxSizeAt(divider+1) != MAX_SUBVIEW_SIZE
     && relMinC < totSize - _GetMaxSizeAt(divider+1))
    	relMinC = totSize - _GetMaxSizeAt(divider+1);

    relMaxC = totSize - _GetMinSizeAt(divider+1);
    if (_GetMaxSizeAt(divider) != MAX_SUBVIEW_SIZE
     && relMaxC > _GetMaxSizeAt(divider))
    	relMaxC = _GetMaxSizeAt(divider);
    
    *minC = _GetPosAt(divider) + relMinC;
    *maxC = _GetPosAt(divider) + relMaxC;
}


static void
dragDivider(SplitView *sPtr, int clickX, int clickY)
{
    int divider, pos, ofs, done, dragging;
    int i, count;
    XEvent ev;
    WMScreen *scr;
    int minCoord, maxCoord, coord;

    if (sPtr->constrainProc) {
	updateConstraints(sPtr);
	checkSizes(sPtr);
	distributeOffsetFormEnd(sPtr, _GetSplitViewSize() - getTotalSize(sPtr));
	checkPositions(sPtr);
	updateSubviewsGeom(sPtr);
    }

    scr = sPtr->view->screen;
    divider = ofs = pos = done = 0;
    coord = (sPtr->flags.vertical) ? clickX : clickY;
    count = _GetSubviewsCount();
    if (count < 2)
    	return;

    for (i = 0; i < count-1; i++) {
	pos += _GetSizeAt(i) + DIVIDER_THICKNESS;
	if (coord < pos) {
	    ofs = coord - pos + DIVIDER_THICKNESS;
	    done = 1;
	    break;
	}
	divider++;
    }

    if (!done)
    	return;

    getMinMaxDividerCoord(sPtr, divider, &minCoord, &maxCoord);

    done = 0;
    dragging = 0;
    while (!done) {
	WMMaskEvent(scr->display, ButtonMotionMask|ButtonReleaseMask
		    |ExposureMask, &ev);

    	coord = (sPtr->flags.vertical) ? ev.xmotion.x : ev.xmotion.y;

	switch (ev.type) {
	 case ButtonRelease:
	    done = 1;
	    if (dragging)
		drawDragingRectangle(sPtr, pos);
	    break;
	    
	 case MotionNotify:
	    if (dragging)
		drawDragingRectangle(sPtr, pos);
	    if (coord - ofs < minCoord)
		pos = minCoord;
	    else if (coord - ofs > maxCoord)
		pos = maxCoord;
	    else
		pos = coord - ofs;
	    drawDragingRectangle(sPtr, pos);
	    dragging = 1;
	    break;
	    
	 default:
	    WMHandleEvent(&ev);
	    break;
	}
    }
    
    if (dragging) {
    	T_SplitViewSubview *p1, *p2;
	int totSize;
	
	p1 = _GetPSubviewStructAt(divider);
	p2 = _GetPSubviewStructAt(divider+1);
	
	totSize = p1->size + DIVIDER_THICKNESS + p2->size;
	
	p1->size = pos - p1->pos;
	p2->size = totSize - p1->size - DIVIDER_THICKNESS;
	p2->pos = p1->pos + p1->size  + DIVIDER_THICKNESS;
	
	resizeView(sPtr, p1->view, p1->size);
	moveView(sPtr, p2->view, p2->pos);
	resizeView(sPtr, p2->view, p2->size);
	sPtr->flags.subviewsWereManuallyMoved = 1;
    }
}


static void
handleEvents(XEvent *event, void *data)
{
    SplitView *sPtr = (SplitView*)data;

    CHECK_CLASS(data, WC_SplitView);


    switch (event->type) {	
     case Expose:
	if (event->xexpose.count!=0)
	    break;
	paintSplitView(sPtr);
	break;
	
     case DestroyNotify:
	destroySplitView(sPtr);
	break;
    }
}


static void
handleActionEvents(XEvent *event, void *data)
{
    
    CHECK_CLASS(data, WC_SplitView);

    switch (event->type) {
     case ButtonPress:
	if (event->xbutton.button == Button1)
	    dragDivider(data, event->xbutton.x, event->xbutton.y);
	break;
    }
}



static void
destroySplitView(SplitView *sPtr)
{
    int i, count;

    count = _GetSubviewsCount();
    for (i = 0; i < count; i++)
    	wfree(WMGetFromBag(sPtr->subviewsBag, i));
    WMFreeBag(sPtr->subviewsBag);
   
    WMRemoveNotificationObserver(sPtr);
    
    wfree(sPtr);
}

WMSplitView*
WMCreateSplitView(WMWidget *parent)
{
    SplitView *sPtr;

    sPtr = wmalloc(sizeof(SplitView));
    memset(sPtr, 0, sizeof(SplitView));

    sPtr->widgetClass = WC_SplitView;
    
    sPtr->view = W_CreateView(W_VIEW(parent));
    if (!sPtr->view) {
	wfree(sPtr);
	return NULL;
    }
    sPtr->view->self = sPtr;

    WMSetViewNotifySizeChanges(sPtr->view, True);

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


    WMCreateEventHandler(sPtr->view, ButtonPressMask|ButtonReleaseMask
			 |EnterWindowMask|LeaveWindowMask, 
			 handleActionEvents, sPtr);

    
    WMAddNotificationObserver(handleViewResized, sPtr, 
			      WMViewSizeDidChangeNotification, sPtr->view);
    
    sPtr->subviewsBag = WMCreateBag(8);

    return sPtr;
}


void
WMAdjustSplitViewSubviews(WMSplitView *sPtr)
{
    CHECK_CLASS(sPtr, WC_SplitView);
    
    checkSizes(sPtr);

    adjustSplitViewSubviews(sPtr);

    assert(checkSizes(sPtr) == 0);
}


void
WMAddSplitViewSubview(WMSplitView *sPtr, WMView *subview)
{
    int wasMapped, count;
    T_SplitViewSubview *p;

    CHECK_CLASS(sPtr, WC_SplitView);

    if (!(p = (T_SplitViewSubview*)wmalloc(sizeof(T_SplitViewSubview))))
    	return;

    wasMapped = subview->flags.mapped;
    if (wasMapped)
	W_UnmapView(subview);

    count = _GetSubviewsCount();
    p->view = subview;
    getConstraints(sPtr, count, &(p->minSize), &(p->maxSize));
    if (sPtr->flags.vertical)
    	p->size = subview->size.width;
    else
    	p->size = subview->size.height;

    WMPutInBag(sPtr->subviewsBag,(void*)p);
    reparentView(sPtr, subview, 0);
    
/*
    We should have something like that...
    
    WMSetViewNotifySizeChanges(subview, True);
    WMAddNotificationObserver(handleSubviewResized, sPtr, 
			      WMViewSizeDidChangeNotification,
			      subview);
    WMSetViewNotifyMoveChanges(subview, True);
    WMAddNotificationObserver(handleSubviewResized, sPtr, 
			      WMViewMoveDidChangeNotification,
			      subview);
*/
    if (wasMapped)
	W_MapView(subview);
    
    sPtr->flags.adjustOnPaint = 1;
    paintSplitView(sPtr);
}


WMView*
WMGetSplitViewSubviewAt(WMSplitView *sPtr, int index)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    if (index > 0 && index < _GetSubviewsCount())
    	return (_GetSubviewAt(index));
    else
    	return (NULL);
}


void
WMRemoveSplitViewSubview(WMSplitView *sPtr, WMView *view)
{
    T_SplitViewSubview *p;
    int i, count;

    CHECK_CLASS(sPtr, WC_SplitView);

    count = _GetSubviewsCount();
    for (i = 0; i < count; i++) {
    	p = _GetPSubviewStructAt(i);
	if (p->view == view) {
    	    wfree(p);
    	    WMDeleteFromBag(sPtr->subviewsBag, i);
    	    sPtr->flags.adjustOnPaint = 1;
    	    paintSplitView(sPtr);
	    break;
	}
    }
}


void
WMRemoveSplitViewSubviewAt(WMSplitView *sPtr, int index)
{
    T_SplitViewSubview *p;

    CHECK_CLASS(sPtr, WC_SplitView);

    if (index > 0 && index < _GetSubviewsCount()) {
    	p = _GetPSubviewStructAt(index);
    	wfree(p);
    	WMDeleteFromBag(sPtr->subviewsBag, index);
    	sPtr->flags.adjustOnPaint = 1;
	paintSplitView(sPtr);
    }
}


void 
WMSetSplitViewConstrainProc(WMSplitView *sPtr, WMSplitViewConstrainProc *proc)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    sPtr->constrainProc = proc;
}


int
WMGetSplitViewSubviewsCount(WMSplitView *sPtr)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    return (_GetSubviewsCount());
}


Bool
WMGetSplitViewVertical(WMSplitView *sPtr)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    return (sPtr->flags.vertical == 1);
}

void
WMSetSplitViewVertical(WMSplitView *sPtr, Bool flag)
{
    int vertical;
    
    CHECK_CLASS(sPtr, WC_SplitView);

    vertical = (flag) ? 1 : 0;
    if (sPtr->flags.vertical == vertical)
    	return;
    
    sPtr->flags.vertical = vertical;
    
    if (sPtr->view->flags.mapped && sPtr->view->flags.realized)
    	handleViewResized(sPtr, NULL);
    else
    	sPtr->flags.adjustOnPaint = 1;
}


int
WMGetSplitViewDividerThickness(WMSplitView *sPtr)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    return (DIVIDER_THICKNESS);
}

#if 0
void
WMSetSplitViewResizeSubviewsProc(WMSplitView *sPtr, 
				 WMSplitViewResizeSubviewsProc *proc)
{
    CHECK_CLASS(sPtr, WC_SplitView);

    sPtr->resizeSubviewsProc = proc;
}
#endif
