
#include "WINGsP.h"


typedef struct W_TabView {
    W_Class widgetClass;
    W_View *view;

    struct W_TabViewItem **items;
    int itemCount;
    int maxItems;	       /* size of items array, can be increased */

    int selectedItem;
    int firstVisible;
    
    int visibleTabs;

    WMFont *font;

    WMColor *lightGray;
    WMColor *tabColor;

    WMTabViewDelegate *delegate;

    short tabWidth;
    short tabHeight;

    struct {
	WMReliefType relief:4;
	WMTitlePosition titlePosition:4;
	WMTabViewTypes type:2;

	unsigned tabbed:1;
	unsigned dontFitAll:1;
    } flags;
} TabView;



#define DEFAULT_WIDTH	40
#define DEFAULT_HEIGHT	40

#define NORMAL_SIDE_OFFSET 8
#define BUTTONED_SIDE_OFFSET 20


static void destroyTabView(TabView *tPtr);	
static void paintTabView(TabView *tPtr);


static void W_SetTabViewItemParent(WMTabViewItem *item, WMTabView *parent);

static void W_DrawLabel(WMTabViewItem *item, Drawable d, WMRect rect);

static void W_UnmapTabViewItem(WMTabViewItem *item);

static void W_MapTabViewItem(WMTabViewItem *item);

static WMView *W_TabViewItemView(WMTabViewItem *item);

static void recalcTabWidth(TabView *tPtr);


static void
handleEvents(XEvent *event, void *data)
{
    TabView *tPtr = (TabView*)data;

    CHECK_CLASS(data, WC_TabView);

    switch (event->type) {
     case Expose:
	if (event->xexpose.count!=0)
	    break;
	paintTabView(tPtr);
	break;

     case ButtonPress:
	{
	    WMTabViewItem *item = WMTabViewItemAtPoint(tPtr,
						       event->xbutton.x,
						       event->xbutton.y);
	    if (item) {
		WMSelectTabViewItem(tPtr, item);
	    } else if (tPtr->flags.dontFitAll) {
		int redraw;
		if (event->xbutton.x < BUTTONED_SIDE_OFFSET) {
		    if (tPtr->firstVisible > 0) {
			redraw = 1;
			tPtr->firstVisible--;
		    }
		} else if (event->xbutton.x - BUTTONED_SIDE_OFFSET
			   > tPtr->visibleTabs*(tPtr->tabWidth-10)) {

		    if (tPtr->firstVisible + tPtr->visibleTabs
			< tPtr->itemCount) {
			redraw = 1;
			tPtr->firstVisible++;
		    }
		}

		if (redraw) {
		    paintTabView(tPtr);
		}
	    }
	}
	break;

     case DestroyNotify:
	destroyTabView(tPtr);
	break;
    }
}



WMTabView*
WMCreateTabView(WMWidget *parent)
{
    TabView *tPtr;
    WMScreen *scr = WMWidgetScreen(parent);

    tPtr = wmalloc(sizeof(TabView));
    memset(tPtr, 0, sizeof(TabView));

    tPtr->widgetClass = WC_TabView;

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

    tPtr->lightGray = WMCreateRGBColor(scr, 0xd9d9, 0xd9d9, 0xd9d9, False);
    tPtr->tabColor = WMCreateRGBColor(scr, 0x8420, 0x8420, 0x8420, False);

    tPtr->font = WMRetainFont(scr->normalFont);

    WMCreateEventHandler(tPtr->view, ExposureMask|StructureNotifyMask
			 |ButtonPressMask, handleEvents, tPtr);

    WMResizeWidget(tPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);

    tPtr->tabHeight = WMFontHeight(tPtr->font) + 3;

    return tPtr;
}


void
WMSetTabViewDelegate(WMTabView *tPtr, WMTabViewDelegate *delegate)
{
    tPtr->delegate = delegate;
}


void
WMAddItemInTabView(WMTabView *tPtr, WMTabViewItem *item)
{
    WMInsertItemInTabView(tPtr, tPtr->itemCount, item);
}


void
WMInsertItemInTabView(WMTabView *tPtr, int index, WMTabViewItem *item)
{
    wassertr(W_TabViewItemView(item) != NULL);

    if (tPtr->maxItems == tPtr->itemCount) {
	WMTabViewItem **items;

	items = wrealloc(tPtr->items,
			 sizeof(WMTabViewItem*) * (tPtr->maxItems + 10));
	memset(&items[tPtr->maxItems], 0, sizeof(WMTabViewItem*) * 10);
	tPtr->items = items;
	tPtr->maxItems += 10;
    }

    if (index > tPtr->itemCount)
	index = tPtr->itemCount;

    if (index == 0 && tPtr->items[0]) {
	W_UnmapTabViewItem(tPtr->items[0]);
    }

    if (index < tPtr->itemCount) {
	memmove(&tPtr->items[index + 1], &tPtr->items[index],
		tPtr->itemCount - index);
    }

    tPtr->items[index] = item;

    tPtr->itemCount++;

    recalcTabWidth(tPtr);

    W_SetTabViewItemParent(item, tPtr);

    W_UnmapTabViewItem(item);

    W_ReparentView(W_TabViewItemView(item), tPtr->view, 1, 
		   tPtr->tabHeight + 1);

    W_ResizeView(W_TabViewItemView(item), tPtr->view->size.width - 3,
		 tPtr->view->size.height - tPtr->tabHeight - 3);

    if (index == 0) {
	W_MapTabViewItem(item);
    }
    if (tPtr->delegate && tPtr->delegate->didChangeNumberOfItems)
	(*tPtr->delegate->didChangeNumberOfItems)(tPtr->delegate, tPtr);
}


void
WMRemoveTabViewItem(WMTabView *tPtr, WMTabViewItem *item)
{
    int i;

    for (i = 0; i < tPtr->itemCount; i++) {
	if (tPtr->items[i] == item) {
	    if (i < tPtr->itemCount - 1)
		memmove(&tPtr->items[i], &tPtr->items[i + 1],
			tPtr->itemCount - i - 1);
	    else
		tPtr->items[i] = NULL;

	    W_SetTabViewItemParent(item, NULL);

	    tPtr->itemCount--;
	    break;
	}
    }
    if (tPtr->delegate && tPtr->delegate->didChangeNumberOfItems)
	(*tPtr->delegate->didChangeNumberOfItems)(tPtr->delegate, tPtr);
}



static Bool
isInside(int x, int y, int width, int height, int px, int py)
{
    if (py >= y + height - 3 && py <= y + height
	&& px >= x + py - (y + height - 3)
	&& px <= x + width - (py - (y + height - 3))) {

	return True;
    }
    if (py >= y + 3 && py < y + height - 3
	&& px >= x + 3 + ((y + 3) - py)*3/7
	&& px <= x + width - 3 - ((y + 3) - py)*3/7) {

	return True;
    }
    if (py >= y && py < y + 3
	&& px >= x + 7 + py - y
	&& px <= x + width - 7 - (py - y)) {

	return True;
    }
    return False;
}


WMTabViewItem*
WMTabViewItemAtPoint(WMTabView *tPtr, int x, int y)
{
    int i;
    int offset;
    int count = tPtr->visibleTabs;
    int first = tPtr->firstVisible;
    
    if (tPtr->flags.dontFitAll) {
	offset = BUTTONED_SIDE_OFFSET;

	i = tPtr->selectedItem - tPtr->firstVisible;
	if (i >= 0 && i < tPtr->visibleTabs
	    && isInside(offset + (tPtr->tabWidth-10)*i, 0, tPtr->tabWidth,
			tPtr->tabHeight, x, y)) {
	    return tPtr->items[tPtr->selectedItem];
	}
    } else {
	offset = NORMAL_SIDE_OFFSET;

	i = tPtr->selectedItem;
	if (isInside(offset + (tPtr->tabWidth-10)*i, 0, tPtr->tabWidth,
		     tPtr->tabHeight, x, y)) {
	    return tPtr->items[i];
	}
    }

    for (i = 0; i < count; i++) {
	if (isInside(offset + (tPtr->tabWidth-10)*i, 0, tPtr->tabWidth,
		     tPtr->tabHeight, x, y)) {
	    return tPtr->items[i+first];
	}
    }
    return NULL;
}


void
WMSelectFirstTabViewItem(WMTabView *tPtr)
{
    WMSelectTabViewItemAtIndex(tPtr, 0);
}


void
WMSelectLastTabViewItem(WMTabView *tPtr)
{
    WMSelectTabViewItemAtIndex(tPtr, tPtr->itemCount);
}


void
WMSelectNextTabViewItem(WMTabView *tPtr)
{
    WMSelectTabViewItemAtIndex(tPtr, tPtr->selectedItem + 1);
}


void
WMSelectPreviousTabViewItem(WMTabView *tPtr)
{
    WMSelectTabViewItemAtIndex(tPtr, tPtr->selectedItem - 1);
}


WMTabViewItem*
WMGetSelectedTabViewItem(WMTabView *tPtr)
{
    return tPtr->items[tPtr->selectedItem];
}


void
WMSelectTabViewItem(WMTabView *tPtr, WMTabViewItem *item)
{
    int i;

    for (i = 0; i < tPtr->itemCount; i++) {
	if (tPtr->items[i] == item) {
	    WMSelectTabViewItemAtIndex(tPtr, i);
	    break;
	}
    }
}


void
WMSelectTabViewItemAtIndex(WMTabView *tPtr, int index)
{
    WMTabViewItem *item;

    if (index == tPtr->selectedItem)
	return;

    if (index < 0)
	index = 0;
    else if (index >= tPtr->itemCount)
	index = tPtr->itemCount - 1;

    item = tPtr->items[tPtr->selectedItem];

    if (tPtr->delegate && tPtr->delegate->shouldSelectItem)
	if (!(*tPtr->delegate->shouldSelectItem)(tPtr->delegate, tPtr, 
						 tPtr->items[index]))
	    return;

    if (tPtr->delegate && tPtr->delegate->willSelectItem)
	(*tPtr->delegate->willSelectItem)(tPtr->delegate, tPtr, 
					  tPtr->items[index]);

    W_UnmapTabViewItem(item);


    item = tPtr->items[index];

    W_MapTabViewItem(item);

    tPtr->selectedItem = index;

    if (tPtr->delegate && tPtr->delegate->didSelectItem)
	(*tPtr->delegate->didSelectItem)(tPtr->delegate, tPtr, 
					 tPtr->items[index]);
}


static void
recalcTabWidth(TabView *tPtr)
{
    int i;
    int twidth = W_VIEW(tPtr)->size.width;
    int width;

    tPtr->tabWidth = 0;
    for (i = 0; i < tPtr->itemCount; i++) {
	char *str = WMGetTabViewItemLabel(tPtr->items[i]);

	if (str) {
	    width = WMWidthOfString(tPtr->font, str, strlen(str));
	    if (width > tPtr->tabWidth)
		tPtr->tabWidth = width;
	}
    }
    tPtr->tabWidth += 30;
    if ((tPtr->tabWidth + 2) * tPtr->itemCount > twidth - 2*NORMAL_SIDE_OFFSET) {
	tPtr->flags.dontFitAll = 1;
	tPtr->firstVisible = 0;
	tPtr->visibleTabs = (twidth - 2*BUTTONED_SIDE_OFFSET) / (tPtr->tabWidth-10);
    } else {
	tPtr->flags.dontFitAll = 0;
	tPtr->firstVisible = 0;
	tPtr->visibleTabs = tPtr->itemCount;
    }
}


static void
drawRelief(W_Screen *scr, Drawable d, int x, int y, unsigned int width,
	   unsigned int height)
{
    Display *dpy = scr->display;
    GC bgc = WMColorGC(scr->black);
    GC wgc = WMColorGC(scr->white);
    GC dgc = WMColorGC(scr->darkGray);

    XDrawLine(dpy, d, wgc, x, y, x, y+height-1);

    XDrawLine(dpy, d, bgc, x, y+height-1, x+width-1, y+height-1);
    XDrawLine(dpy, d, dgc, x+1, y+height-2, x+width-2, y+height-2);

    XDrawLine(dpy, d, bgc, x+width-1, y, x+width-1, y+height-1);
    XDrawLine(dpy, d, dgc, x+width-2, y+1, x+width-2, y+height-2);
}


static void
drawTab(TabView *tPtr, Drawable d, int x, int y, 
	unsigned width, unsigned height, Bool selected)
{
    WMScreen *scr = W_VIEW(tPtr)->screen;
    Display *dpy = scr->display;
    GC white = WMColorGC(selected ? scr->white : tPtr->lightGray);
    GC black = WMColorGC(scr->black);
    GC dark = WMColorGC(scr->darkGray);
    GC light = WMColorGC(scr->gray);
    XPoint trap[8];

    trap[0].x = x + (selected ? 0 : 1);
    trap[0].y = y + height - (selected ? 0 : 1);

    trap[1].x = x + 3;
    trap[1].y = y + height - 3;

    trap[2].x = x + 10 - 3;
    trap[2].y = y + 3;

    trap[3].x = x + 10;
    trap[3].y = y;

    trap[4].x = x + width - 10;
    trap[4].y = y;

    trap[5].x = x + width - 10 + 3;
    trap[5].y = y + 3;

    trap[6].x = x + width - 3;
    trap[6].y = y + height - 3;

    trap[7].x = x + width - (selected ? 0 : 1);
    trap[7].y = y + height - (selected ? 0 : 1);

    XFillPolygon(dpy, d, selected ? light : WMColorGC(tPtr->tabColor), trap, 8,
		 Convex, CoordModeOrigin);

    XDrawLine(dpy, d, white, trap[0].x, trap[0].y, trap[1].x, trap[1].y);
    XDrawLine(dpy, d, white, trap[1].x, trap[1].y, trap[2].x, trap[2].y);
    XDrawLine(dpy, d, white, trap[2].x, trap[2].y, trap[3].x, trap[3].y);
    XDrawLine(dpy, d, white, trap[3].x, trap[3].y, trap[4].x, trap[4].y);
    XDrawLine(dpy, d, dark, trap[4].x, trap[4].y, trap[5].x, trap[5].y);
    XDrawLine(dpy, d, black, trap[5].x, trap[5].y, trap[6].x, trap[6].y);
    XDrawLine(dpy, d, black, trap[6].x, trap[6].y, trap[7].x, trap[7].y);

    XDrawLine(dpy, d, selected ? light : WMColorGC(scr->white), 
	      trap[0].x, trap[0].y, trap[7].x, trap[7].y);
}


static void
paintDot(TabView *tPtr, Drawable d, int x, int y)
{
    WMScreen *scr = W_VIEW(tPtr)->screen;
    Display *dpy = scr->display;
    GC white = WMColorGC(scr->white);
    GC black = WMColorGC(scr->black);

    XFillRectangle(dpy, d, black, x, y, 2, 2);
    XDrawPoint(dpy, d, white, x, y);
}



static void
paintTabView(TabView *tPtr)
{
    Pixmap buffer;
    WMScreen *scr = W_VIEW(tPtr)->screen;
    Display *dpy = scr->display;
    GC white = WMColorGC(scr->white);
    int i;
    WMRect rect;

    if (tPtr->flags.type == WTTopTabsBevelBorder) {
	int count = tPtr->visibleTabs;
	int first = tPtr->firstVisible;
	int offs;
	int moreAtLeft;
	int moreAtRight;
	int selectedIsVisible;
	
	buffer = XCreatePixmap(dpy, W_VIEW(tPtr)->window,
			       W_VIEW(tPtr)->size.width, tPtr->tabHeight,
			       W_VIEW(tPtr)->screen->depth);

	XFillRectangle(dpy, buffer, WMColorGC(W_VIEW(tPtr)->backColor),
		       0, 0, W_VIEW(tPtr)->size.width, tPtr->tabHeight);

	rect.pos.y = 2;
	if (tPtr->flags.dontFitAll) {
	    rect.pos.x = 15 + BUTTONED_SIDE_OFFSET;
	    offs = BUTTONED_SIDE_OFFSET;
	    moreAtLeft = first > 0;
	    moreAtRight = (first + count) < tPtr->itemCount;
	    if (tPtr->selectedItem >= first
		&& tPtr->selectedItem < first + count)
		selectedIsVisible = 1;
	    else
		selectedIsVisible = 0;
	} else {
	    rect.pos.x = 15 + NORMAL_SIDE_OFFSET;
	    offs = NORMAL_SIDE_OFFSET;
	    moreAtLeft = 0;
	    moreAtRight = 0;
	    selectedIsVisible = 1;
	}
	rect.size.width = tPtr->tabWidth;
	rect.size.height = tPtr->tabHeight;

	for (i = count - (moreAtRight ? 0 : 1);
	     i >= (moreAtLeft ? -1 : 0); i--) {
	    if (!selectedIsVisible || i != (tPtr->selectedItem-first)) {
		drawTab(tPtr, buffer, offs + (rect.size.width-10)*i, 0,
			rect.size.width, rect.size.height, False);
	    }
	}
	
	if (selectedIsVisible) {
	    drawTab(tPtr, buffer, 
		    offs + (rect.size.width-10) * (tPtr->selectedItem - first),
		    0, rect.size.width, rect.size.height, True);

	    XDrawLine(dpy, buffer, white, 0, tPtr->tabHeight - 1,
		      offs, tPtr->tabHeight - 1);

	    XDrawLine(dpy, buffer, white, 
		      offs + 10 + (rect.size.width-10) * count,
		      tPtr->tabHeight - 1, W_VIEW(tPtr)->size.width - 1,
		      tPtr->tabHeight - 1);
	} else {
	    XDrawLine(dpy, buffer, white, 0, tPtr->tabHeight - 1,
		      W_VIEW(tPtr)->size.width, tPtr->tabHeight - 1);	    
	}

	for (i = 0; i < count; i++) {
	    W_DrawLabel(tPtr->items[first+i], buffer, rect);

	    rect.pos.x += rect.size.width - 10;
	}

	if (moreAtLeft) {
	    paintDot(tPtr, buffer, 4, 10);
	    paintDot(tPtr, buffer, 7, 10);
	    paintDot(tPtr, buffer, 10, 10);
	}
	if (moreAtRight) {
	    int x;
	    
	    x = BUTTONED_SIDE_OFFSET - 5 + tPtr->visibleTabs * (tPtr->tabWidth - 10);
	    
	    x = x + (W_VIEW(tPtr)->size.width - x)/2;
	    
	    paintDot(tPtr, buffer, x + 5, 10);
	    paintDot(tPtr, buffer, x + 8, 10);
	    paintDot(tPtr, buffer, x + 11, 10);	    
	}

	XCopyArea(dpy, buffer, W_VIEW(tPtr)->window, scr->copyGC, 0, 0,
		  W_VIEW(tPtr)->size.width, tPtr->tabHeight, 0, 0);

	XFreePixmap(dpy, buffer);
    }
    switch (tPtr->flags.type) {
     case WTTopTabsBevelBorder:
	drawRelief(scr, W_VIEW(tPtr)->window, 0, tPtr->tabHeight - 1,
		   W_VIEW(tPtr)->size.width,
		   W_VIEW(tPtr)->size.height - tPtr->tabHeight + 1);
	break;

     case WTNoTabsBevelBorder:
	W_DrawRelief(scr, W_VIEW(tPtr)->window, 0, 0, W_VIEW(tPtr)->size.width,
		     W_VIEW(tPtr)->size.height, WRRaised);
	break;

     case WTNoTabsLineBorder:
	W_DrawRelief(scr, W_VIEW(tPtr)->window, 0, 0, W_VIEW(tPtr)->size.width,
		     W_VIEW(tPtr)->size.height, WRSimple);
	break;

     case WTNoTabsNoBorder:
	break;
    }
}


static void
destroyTabView(TabView *tPtr)
{
    int i;

    for (i = 0; i < tPtr->itemCount; i++) {
	WMSetTabViewItemView(tPtr->items[i], NULL);
	WMDestroyTabViewItem(tPtr->items[i]);
    }
    wfree(tPtr->items);

    WMReleaseColor(tPtr->lightGray);
    WMReleaseColor(tPtr->tabColor);
    WMReleaseFont(tPtr->font);

    wfree(tPtr);
}

/******************************************************************/


typedef struct W_TabViewItem {
    WMTabView *tabView;

    W_View *view;

    char *label;

    int identifier;

    struct {
	unsigned visible:1;
    } flags;
} TabViewItem;


static void
W_SetTabViewItemParent(WMTabViewItem *item, WMTabView *parent)
{
    item->tabView = parent;
}


static void
W_DrawLabel(WMTabViewItem *item, Drawable d, WMRect rect)
{
    WMScreen *scr = W_VIEW(item->tabView)->screen;

    if (!item->label)
	return;

    WMDrawString(scr, d, WMColorGC(scr->black), item->tabView->font,
		 rect.pos.x, rect.pos.y, item->label, strlen(item->label));
}


static void
W_UnmapTabViewItem(WMTabViewItem *item)
{
    wassertr(item->view);

    W_UnmapView(item->view);

    item->flags.visible = 0;
}


static void
W_MapTabViewItem(WMTabViewItem *item)
{
    wassertr(item->view);

    W_MapView(item->view);

    item->flags.visible = 1;
}


static WMView*
W_TabViewItemView(WMTabViewItem *item)
{
    return item->view;
}


WMTabViewItem*
WMCreateTabViewItemWithIdentifier(int identifier)
{
    WMTabViewItem *item;

    item = wmalloc(sizeof(WMTabViewItem));
    memset(item, 0, sizeof(WMTabViewItem));

    item->identifier = identifier;

    return item;
}


int
WMGetTabViewItemIdentifier(WMTabViewItem *item)
{
    return item->identifier;
}


void
WMSetTabViewFont(WMTabView *tPtr, WMFont *font)
{
    if (tPtr->font)
	WMReleaseFont(tPtr->font);

    tPtr->font = WMRetainFont(font);
    tPtr->tabHeight = WMFontHeight(tPtr->font) + 3;
    recalcTabWidth(tPtr);
}


void
WMSetTabViewItemLabel(WMTabViewItem *item, char *label)
{
    if (item->label)
	wfree(item->label);

    item->label = wstrdup(label);

    if (item->tabView)
	recalcTabWidth(item->tabView);
}


char*
WMGetTabViewItemLabel(WMTabViewItem *item)
{
    return item->label;
}


void
WMSetTabViewItemView(WMTabViewItem *item, WMView *view)
{
    item->view = view;
}


WMView*
WMGetTabViewItemView(WMTabViewItem *item)
{
    return item->view;
}


void
WMDestroyTabViewItem(WMTabViewItem *item)
{
    if (item->label)
	wfree(item->label);

    if (item->view)
	W_DestroyView(item->view);

    wfree(item);
}
