#include <WINGs/WINGsP.h>
#include <X11/cursorfont.h>

#include "wtableview.h"


const char *WMTableViewSelectionDidChangeNotification = "WMTableViewSelectionDidChangeNotification";


struct W_TableColumn {
    WMTableView *table;
    WMWidget *titleW;
    char *title;
    int width;
    int minWidth;
    int maxWidth;

    void *id;

    WMTableColumnDelegate *delegate;

    unsigned resizable:1;
    unsigned editable:1;
};


static void handleResize(W_ViewDelegate *self, WMView *view);

static void rearrangeHeader(WMTableView *table);

static WMRange rowsInRect(WMTableView *table, WMRect rect);


WMTableColumn*
WMCreateTableColumn(char *title)
{
    WMTableColumn *col = wmalloc(sizeof(WMTableColumn));

    col->table = NULL;
    col->titleW = NULL;
    col->width = 50;
    col->minWidth = 5;
    col->maxWidth = 0;

    col->id = NULL;

    col->title = wstrdup(title);

    col->delegate = NULL;

    col->resizable = 1;
    col->editable = 0;

    return col;
}


void
WMSetTableColumnId(WMTableColumn *column, void *id)
{
    column->id = id;
}


void*
WMGetTableColumnId(WMTableColumn *column)
{
    return column->id;
}


void
WMSetTableColumnWidth(WMTableColumn *column, unsigned width)
{
    if (column->maxWidth == 0)
        column->width = WMAX(column->minWidth, width);
    else
        column->width = WMAX(column->minWidth, WMIN(column->maxWidth, width));

    if (column->table) {
        rearrangeHeader(column->table);
    }
}


void
WMSetTableColumnDelegate(WMTableColumn *column, WMTableColumnDelegate *delegate)
{
    column->delegate = delegate;
}


void
WMSetTableColumnConstraints(WMTableColumn *column, unsigned minWidth,
                            unsigned maxWidth)
{
    wassertr(maxWidth == 0 || minWidth <= maxWidth);

    column->minWidth = minWidth;
    column->maxWidth = maxWidth;

    if (column->width < column->minWidth)
        WMSetTableColumnWidth(column, column->minWidth);
    else if (column->width > column->maxWidth && column->maxWidth != 0)
        WMSetTableColumnWidth(column, column->maxWidth);
}


void
WMSetTableColumnEditable(WMTableColumn *column, Bool flag)
{
    column->editable = ((flag==0) ? 0 : 1);
}


WMTableView*
WMGetTableColumnTableView(WMTableColumn *column)
{
    return column->table;
}



struct W_TableView {
    W_Class widgetClass;
    WMView *view;

    WMFrame *header;

    WMLabel *corner;

    WMScroller *hscroll;
    WMScroller *vscroll;
    WMView *tableView;

    WMPixmap *viewBuffer;

    WMArray *columns;
    WMArray *splitters;

    WMArray *selectedRows;

    int tableWidth;

    int rows;

    WMColor *backColor;

    GC gridGC;
    WMColor *gridColor;

    Cursor splitterCursor;

    void *dataSource;

    WMTableViewDelegate *delegate;

    WMAction *action;
    void *clientData;

    void *clickedColumn;
    int clickedRow;

    int editingRow;

    unsigned headerHeight;

    unsigned rowHeight;

    unsigned dragging:1;
    unsigned drawsGrid:1;
    unsigned canSelectRow:1;
    unsigned canSelectMultiRows:1;
    unsigned canDeselectRow:1;

    unsigned int hasVScroller:1;
    unsigned int hasHScroller:1;
};

static W_Class tableClass = 0;


static W_ViewDelegate viewDelegate = {
    NULL,
    NULL,
    handleResize,
    NULL,
    NULL
};



static void reorganizeInterior(WMTableView *table);


static void handleEvents(XEvent *event, void *data);
static void handleTableEvents(XEvent *event, void *data);
static void repaintTable(WMTableView *table);

static WMSize
getTotalSize(WMTableView *table)
{
    WMSize size;
    int i;

    /* get width from columns */
    size.width = 0;
    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        WMTableColumn *column;

        column = WMGetFromArray(table->columns, i);

        size.width += column->width;
    }

    /* get height from rows */
    size.height = table->rows * table->rowHeight;

    return size;
}


static WMRect
getVisibleRect(WMTableView *table)
{
    WMSize size = getTotalSize(table);
    WMRect rect;

    if (table->vscroll) {
        rect.size.height = size.height * WMGetScrollerKnobProportion(table->vscroll);
        rect.pos.y = (size.height - rect.size.height) * WMGetScrollerValue(table->vscroll);
    } else {
        rect.size.height = size.height;
        rect.pos.y = 0;
    }

    if (table->hscroll) {
        rect.size.width = size.width * WMGetScrollerKnobProportion(table->hscroll);
        rect.pos.x = (size.width - rect.size.width) * WMGetScrollerValue(table->hscroll);
    } else {
        rect.size.width = size.width;
        rect.pos.x = 0;
    }

    return rect;
}


static void
scrollToPoint(WMTableView *table, int x, int y)
{
    WMSize size = getTotalSize(table);
    int i;
    float value, prop;

    if (table->hscroll) {
        if (size.width > W_VIEW_WIDTH(table->tableView)) {
            prop = (float)W_VIEW_WIDTH(table->tableView) / (float)size.width;
            value = (float)x / (float)(size.width - W_VIEW_WIDTH(table->tableView));
        } else {
            prop = 1.0;
            value = 0.0;
        }
        WMSetScrollerParameters(table->hscroll, value, prop);
    }

    if (table->vscroll) {
        if (size.height > W_VIEW_HEIGHT(table->tableView)) {
            prop = (float)W_VIEW_HEIGHT(table->tableView) / (float)size.height;
            value = (float)y / (float)(size.height - W_VIEW_HEIGHT(table->tableView));
        } else {
            prop = 1.0;
            value = 0.0;
        }

        WMSetScrollerParameters(table->vscroll, value, prop);
    }


    if (table->editingRow >= 0) {
        for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
            WMTableColumn *column;

            column = WMGetFromArray(table->columns, i);

            if (column->delegate && column->delegate->beginCellEdit)
                (*column->delegate->beginCellEdit)(column->delegate, column,
                                                   table->editingRow);
        }
    }

    repaintTable(table);
}


static void
adjustScrollers(WMTableView *table)
{
    WMSize size = getTotalSize(table);
    WMSize vsize = WMGetViewSize(table->tableView);
    float prop, value;
    float oprop, ovalue;

    if (table->hscroll) {
        if (size.width <= vsize.width) {
            value = 0.0;
            prop = 1.0;
        } else {
            oprop = WMGetScrollerKnobProportion(table->hscroll);
            if (oprop == 0.0)
                oprop = 1.0;
            ovalue = WMGetScrollerValue(table->hscroll);

            prop = (float)vsize.width/(float)size.width;
            value = prop*ovalue / oprop;
        }
        WMSetScrollerParameters(table->hscroll, value, prop);
    }

    if (table->vscroll) {
        if (size.height <= vsize.height) {
            value = 0.0;
            prop = 1.0;
        } else {
            oprop = WMGetScrollerKnobProportion(table->vscroll);
            if (oprop == 0.0)
                oprop = 1.0;
            ovalue = WMGetScrollerValue(table->vscroll);

            prop = (float)vsize.height/(float)size.height;
            value = prop*ovalue / oprop;
        }
        WMSetScrollerParameters(table->vscroll, value, prop);
    }
}


static void
doScroll(WMWidget *self, void *data)
{
    WMTableView *table = (WMTableView*)data;
    float value;
    float vpsize;
    float size;
    WMSize ts = getTotalSize(table);

    value = WMGetScrollerValue(self);

    if (table->hscroll == (WMScroller *)self) {
        vpsize = W_VIEW_WIDTH(table->tableView);
        size = ts.width;
    } else {
        vpsize = W_VIEW_HEIGHT(table->tableView);
        size = ts.height;
    }

    switch (WMGetScrollerHitPart(self)) {
    case WSDecrementWheel:
    case WSDecrementLine:
        value -= (float)table->rowHeight / size;
        if (value < 0)
            value = 0.0;
        WMSetScrollerParameters(self, value,
                                WMGetScrollerKnobProportion(self));
        repaintTable(table);
        break;

    case WSIncrementWheel:
    case WSIncrementLine:
        value += (float)table->rowHeight / size;
        if (value > 1.0)
            value = 1.0;
        WMSetScrollerParameters(self, value,
                                WMGetScrollerKnobProportion(self));
        repaintTable(table);
        break;

    case WSKnob:
        repaintTable(table);
        break;

    case WSDecrementPage:
        value -= vpsize / size;
        if (value < 0.0)
            value = 0.0;
        WMSetScrollerParameters(self, value,
                                WMGetScrollerKnobProportion(self));
        repaintTable(table);
        break;

    case WSIncrementPage:
        value += vpsize / size;
        if (value > 1.0)
            value = 1.0;
        WMSetScrollerParameters(self, value,
                                WMGetScrollerKnobProportion(self));
        repaintTable(table);
        break;


    case WSNoPart:
    case WSKnobSlot:
        break;
    }

    if (table->editingRow >= 0) {
        int i;
        for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
            WMTableColumn *column;

            column = WMGetFromArray(table->columns, i);

            if (column->delegate && column->delegate->beginCellEdit)
                (*column->delegate->beginCellEdit)(column->delegate, column,
                                                   table->editingRow);
        }
    }


    if (table->hscroll == self) {
        int x = 0;
        int i;
        WMRect rect = getVisibleRect(table);

        for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
            WMTableColumn *column;
            WMView *splitter;

            column = WMGetFromArray(table->columns, i);

            WMMoveWidget(column->titleW, x - rect.pos.x, 0);

            x += W_VIEW_WIDTH(WMWidgetView(column->titleW)) + 1;

            splitter = WMGetFromArray(table->splitters, i);
            W_MoveView(splitter, x - rect.pos.x - 1, 0);
        }
    }
}


static void
splitterHandler(XEvent *event, void *data)
{
    WMTableColumn *column = (WMTableColumn*)data;
    WMTableView *table = column->table;
    int done = 0;
    int cx, ox, offsX;
    WMPoint pos;
    WMScreen *scr = WMWidgetScreen(table);
    GC gc = scr->ixorGC;
    Display *dpy = WMScreenDisplay(scr);
    int h = WMWidgetHeight(table) - 22;
    Window w = WMViewXID(table->view);

    pos = WMGetViewPosition(WMWidgetView(column->titleW));

    offsX = pos.x + column->width;

    ox = cx = offsX;

    XDrawLine(dpy, w, gc, cx+20, 0, cx+20, h);

    while (!done) {
        XEvent ev;

        WMMaskEvent(dpy, ButtonMotionMask|ButtonReleaseMask, &ev);

        switch (ev.type) {
        case MotionNotify:
            ox = cx;

            if (column->width + ev.xmotion.x < column->minWidth)
                cx = pos.x + column->minWidth;
            else if (column->maxWidth > 0
                     && column->width + ev.xmotion.x > column->maxWidth)
                cx = pos.x + column->maxWidth;
            else
                cx = offsX + ev.xmotion.x;

            XDrawLine(dpy, w, gc, ox+20, 0, ox+20, h);
            XDrawLine(dpy, w, gc, cx+20, 0, cx+20, h);
            break;

        case ButtonRelease:
            column->width = cx - pos.x;
            done = 1;
            break;
        }
    }

    XDrawLine(dpy, w, gc, cx+20, 0, cx+20, h);
    rearrangeHeader(table);
    repaintTable(table);
}


static void
realizeTable(void *data, WMNotification *notif)
{
    repaintTable(data);
}


WMTableView*
WMCreateTableView(WMWidget *parent)
{
    WMTableView *table = wmalloc(sizeof(WMTableView));
    WMScreen *scr = WMWidgetScreen(parent);

    memset(table, 0, sizeof(WMTableView));

    if (!tableClass) {
        tableClass = W_RegisterUserWidget();
    }
    table->widgetClass = tableClass;

    table->view = W_CreateView(W_VIEW(parent));
    if (!table->view)
        goto error;
    table->view->self = table;

    table->view->delegate = &viewDelegate;

    table->headerHeight = 20;

    table->hscroll = WMCreateScroller(table);
    WMSetScrollerAction(table->hscroll, doScroll, table);
    WMMoveWidget(table->hscroll, 1, 2+table->headerHeight);
    WMMapWidget(table->hscroll);

    table->hasHScroller = 1;

    table->vscroll = WMCreateScroller(table);
    WMSetScrollerArrowsPosition(table->vscroll, WSAMaxEnd);
    WMSetScrollerAction(table->vscroll, doScroll, table);
    WMMoveWidget(table->vscroll, 1, 2+table->headerHeight);
    WMMapWidget(table->vscroll);

    table->hasVScroller = 1;

    table->header = WMCreateFrame(table);
    WMMoveWidget(table->header, 22, 2);
    WMMapWidget(table->header);
    WMSetFrameRelief(table->header, WRFlat);

    table->corner = WMCreateLabel(table);
    WMResizeWidget(table->corner, 20, table->headerHeight);
    WMMoveWidget(table->corner, 2, 2);
    WMMapWidget(table->corner);
    WMSetLabelRelief(table->corner, WRRaised);
    WMSetWidgetBackgroundColor(table->corner, scr->darkGray);


    table->tableView = W_CreateView(table->view);
    if (!table->tableView)
        goto error;
    table->tableView->self = table;
    W_MapView(table->tableView);

    WMAddNotificationObserver(realizeTable, table, WMViewRealizedNotification,
                              table->tableView);

    table->tableView->flags.dontCompressExpose = 1;

    table->gridColor = WMCreateNamedColor(scr, "#cccccc", False);
    /*   table->gridColor = WMGrayColor(scr);*/

    {
        XGCValues gcv;

        table->backColor = WMWhiteColor(scr);

        gcv.foreground = WMColorPixel(table->gridColor);
        gcv.dashes = 1;
        gcv.line_style = LineOnOffDash;
        table->gridGC = XCreateGC(WMScreenDisplay(scr), W_DRAWABLE(scr),
                                  GCForeground, &gcv);
    }

    table->editingRow = -1;
    table->clickedRow = -1;

    table->drawsGrid = 1;
    table->rowHeight = 16;

    table->tableWidth = 1;

    table->columns = WMCreateArray(4);
    table->splitters = WMCreateArray(4);

    table->selectedRows = WMCreateArray(16);

    table->splitterCursor = XCreateFontCursor(WMScreenDisplay(scr),
                                              XC_sb_h_double_arrow);

    table->canSelectRow = 1;

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

    WMCreateEventHandler(table->tableView, ExposureMask|ButtonPressMask|
                         ButtonReleaseMask|ButtonMotionMask,
                         handleTableEvents, table);

    WMResizeWidget(table, 50, 50);

    return table;

    error:
        if (table->tableView)
            W_DestroyView(table->tableView);
        if (table->view)
            W_DestroyView(table->view);
        wfree(table);
        return NULL;
}


void
WMAddTableViewColumn(WMTableView *table, WMTableColumn *column)
{
    WMScreen *scr = WMWidgetScreen(table);

    column->table = table;

    WMAddToArray(table->columns, column);

    if (!column->titleW) {
        column->titleW = WMCreateLabel(table);
        WMSetLabelRelief(column->titleW, WRRaised);
        WMSetLabelFont(column->titleW, scr->boldFont);
        WMSetLabelTextColor(column->titleW, scr->white);
        WMSetWidgetBackgroundColor(column->titleW, scr->darkGray);
        WMSetLabelText(column->titleW, column->title);
        W_ReparentView(WMWidgetView(column->titleW),
                       WMWidgetView(table->header), 0, 0);
        if (W_VIEW_REALIZED(table->view))
            WMRealizeWidget(column->titleW);
        WMMapWidget(column->titleW);
    }

    {
        WMView *splitter = W_CreateView(WMWidgetView(table->header));

        W_SetViewBackgroundColor(splitter, WMWhiteColor(scr));

        if (W_VIEW_REALIZED(table->view))
            W_RealizeView(splitter);

        W_ResizeView(splitter, 2, table->headerHeight);
        W_MapView(splitter);

        W_SetViewCursor(splitter, table->splitterCursor);
        WMCreateEventHandler(splitter, ButtonPressMask|ButtonReleaseMask,
                             splitterHandler, column);

        WMAddToArray(table->splitters, splitter);
    }

    rearrangeHeader(table);
}


void
WMSetTableViewHeaderHeight(WMTableView *table, unsigned height)
{
    table->headerHeight = height;

    handleResize(NULL, table->view);
}


void
WMSetTableViewDelegate(WMTableView *table, WMTableViewDelegate *delegate)
{
    table->delegate = delegate;
}


void
WMSetTableViewAction(WMTableView *table, WMAction *action, void *clientData)
{
    table->action = action;

    table->clientData = clientData;
}


void*
WMGetTableViewClickedColumn(WMTableView *table)
{
    return table->clickedColumn;
}


int
WMGetTableViewClickedRow(WMTableView *table)
{
    return table->clickedRow;
}


WMArray*
WMGetTableViewSelectedRows(WMTableView *table)
{
    return table->selectedRows;
}


WMView*
WMGetTableViewDocumentView(WMTableView *table)
{
    return table->tableView;
}


void*
WMTableViewDataForCell(WMTableView *table, WMTableColumn *column, int row)
{
    return (*table->delegate->valueForCell)(table->delegate, column, row);
}


void
WMSetTableViewDataForCell(WMTableView *table, WMTableColumn *column, int row,
                          void *data)
{
    (*table->delegate->setValueForCell)(table->delegate, column, row, data);
}


WMRect
WMTableViewRectForCell(WMTableView *table, WMTableColumn *column, int row)
{
    WMRect rect;
    int i;

    rect.pos.x = 0;
    rect.pos.y = row * table->rowHeight;
    rect.size.height = table->rowHeight;

    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        WMTableColumn *col;
        col = WMGetFromArray(table->columns, i);

        if (col == column) {
            rect.size.width = col->width;
            break;
        }

        rect.pos.x += col->width;
    }

    {
        WMRect r = getVisibleRect(table);

        rect.pos.y -= r.pos.y;
        rect.pos.x -= r.pos.x;
    }

    return rect;
}


void
WMSetTableViewDataSource(WMTableView *table, void *source)
{
    table->dataSource = source;
}


void*
WMGetTableViewDataSource(WMTableView *table)
{
    return table->dataSource;
}



void
WMSetTableViewHasHorizontalScroller(WMTableView *tPtr, Bool flag)
{
    if (flag) {
        if (tPtr->hasHScroller)
            return;
        tPtr->hasHScroller = 1;

        tPtr->hscroll = WMCreateScroller(tPtr);
        WMSetScrollerAction(tPtr->hscroll, doScroll, tPtr);
        WMSetScrollerArrowsPosition(tPtr->hscroll, WSAMaxEnd);
        /* make it a horiz. scroller */
        WMResizeWidget(tPtr->hscroll, 1, 2);

        if (W_VIEW_REALIZED(tPtr->view)) {
            WMRealizeWidget(tPtr->hscroll);
        }

        reorganizeInterior(tPtr);

        WMMapWidget(tPtr->hscroll);
    } else {
        if (!tPtr->hasHScroller)
            return;
        tPtr->hasHScroller = 0;

        WMUnmapWidget(tPtr->hscroll);
        WMDestroyWidget(tPtr->hscroll);
        tPtr->hscroll = NULL;

        reorganizeInterior(tPtr);
    }
}

#if 0
/* not supported by now */
void
WMSetTableViewHasVerticalScroller(WMTableView *tPtr, Bool flag)
{
    if (flag) {
        if (tPtr->hasVScroller)
            return;
        tPtr->hasVScroller = 1;

        tPtr->vscroll = WMCreateScroller(tPtr);
        WMSetScrollerAction(tPtr->vscroll, doScroll, tPtr);
        WMSetScrollerArrowsPosition(tPtr->vscroll, WSAMaxEnd);
        /* make it a vert. scroller */
        WMResizeWidget(tPtr->vscroll, 1, 2);

        if (W_VIEW_REALIZED(tPtr->view)) {
            WMRealizeWidget(tPtr->vscroll);
        }

        reorganizeInterior(tPtr);

        WMMapWidget(tPtr->vscroll);
    } else {
        if (!tPtr->hasVScroller)
            return;
        tPtr->hasVScroller = 0;

        WMUnmapWidget(tPtr->vscroll);
        WMDestroyWidget(tPtr->vscroll);
        tPtr->vscroll = NULL;

        reorganizeInterior(tPtr);
    }
}
#endif

void
WMSetTableViewBackgroundColor(WMTableView *table, WMColor *color)
{
    W_SetViewBackgroundColor(table->tableView, color);

    if (table->backColor)
        WMReleaseColor(table->backColor);

    table->backColor = WMRetainColor(color);

    repaintTable(table);
}


void
WMSetTableViewGridColor(WMTableView *table, WMColor *color)
{
    WMReleaseColor(table->gridColor);
    table->gridColor = WMRetainColor(color);
    XSetForeground(WMScreenDisplay(WMWidgetScreen(table)), table->gridGC,
                   WMColorPixel(color));
    repaintTable(table);
}



void
WMSetTableViewRowHeight(WMTableView *table, int height)
{
    table->rowHeight = height;

    repaintTable(table);
}


void
WMScrollTableViewRowToVisible(WMTableView *table, int row)
{
    WMScroller *scroller;
    WMRange range;
    WMRect rect;
    int newY, tmp;

    rect = getVisibleRect(table);
    range = rowsInRect(table, rect);

    scroller = table->vscroll;

    if (row < range.position) {
        newY = row * table->rowHeight - rect.size.height / 2;
    } else if (row >= range.position + range.count) {
        newY = row * table->rowHeight - rect.size.height / 2;
    } else {
        return;
    }
    tmp = table->rows*table->rowHeight - rect.size.height;
    newY = WMAX(0, WMIN(newY, tmp));

    scrollToPoint(table, rect.pos.x, newY);
}



static void
drawGrid(WMTableView *table, WMRect rect)
{
    WMScreen *scr = WMWidgetScreen(table);
    Display *dpy = WMScreenDisplay(scr);
    int i;
    int y1, y2;
    int x1, x2;
    int xx;
    Drawable d = WMGetPixmapXID(table->viewBuffer);
    GC gc = table->gridGC;

#if 0
    char dashl[1] = {1};

    XSetDashes(dpy, gc, 0, dashl, 1);

    y1 = (rect.pos.y/table->rowHeight - 1)*table->rowHeight;
    y2 = y1 + (rect.size.height/table->rowHeight+2)*table->rowHeight;
#endif
    y1 = 0;
    y2 = W_VIEW_HEIGHT(table->tableView);

    xx = -rect.pos.x;
    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        WMTableColumn *column;

        XDrawLine(dpy, d, gc, xx, y1, xx, y2);

        column = WMGetFromArray(table->columns, i);
        xx += column->width;
    }
    XDrawLine(dpy, d, gc, xx, y1, xx, y2);

    x1 = 0;
    x2 = rect.size.width;

    if (x2 <= x1)
        return;
#if 0
    XSetDashes(dpy, gc, (rect.pos.x&1), dashl, 1);
#endif

    y1 = -rect.pos.y%table->rowHeight;
    y2 = y1 + rect.size.height + table->rowHeight;

    for (i = y1; i <= y2; i += table->rowHeight) {
        XDrawLine(dpy, d, gc, x1, i, x2, i);
    }
}


static WMRange
columnsInRect(WMTableView *table, WMRect rect)
{
    WMTableColumn *column;
    int pos;
    int i, found;
    int totalColumns = WMGetArrayItemCount(table->columns);
    WMRange range;

    pos = 0;
    found = 0;
    for (i = 0; i < totalColumns; i++) {
        column = WMGetFromArray(table->columns, i);
        if (!found) {
            if (rect.pos.x >= pos && rect.pos.x < pos + column->width) {
                range.position = i;
                range.count = 1;
                found = 1;
            }
        } else {
            if (pos > rect.pos.x + rect.size.width) {
                break;
            }
            range.count++;
        }
        pos += column->width;
    }
    range.count = WMAX(1, WMIN(range.count, totalColumns - range.position));
    return range;
}


static WMRange
rowsInRect(WMTableView *table, WMRect rect)
{
    WMRange range;
    int rh = table->rowHeight;
    int dif;

    dif = rect.pos.y % rh;

    range.position = WMAX(0, (rect.pos.y - dif) / rh);
    range.count = WMAX(1, WMIN((rect.size.height + dif) / rh, table->rows));

    return range;
}


static void
drawRow(WMTableView *table, int row, WMRect clipRect)
{
    int i;
    WMRange cols = columnsInRect(table, clipRect);
    WMTableColumn *column;
    Drawable d = WMGetPixmapXID(table->viewBuffer);

    for (i = cols.position; i < cols.position+cols.count; i++) {
        column = WMGetFromArray(table->columns, i);

        if (!column->delegate || !column->delegate->drawCell)
            continue;

        if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
            (*column->delegate->drawSelectedCell)(column->delegate, column, row, d);
        else
            (*column->delegate->drawCell)(column->delegate, column, row, d);
    }
}


#if 0
static void
drawFullRow(WMTableView *table, int row)
{
    int i;
    WMTableColumn *column;
    Drawable d = WMGetPixmapXID(table->viewBuffer);

    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        column = WMGetFromArray(table->columns, i);

        if (!column->delegate || !column->delegate->drawCell)
            continue;

        if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
            (*column->delegate->drawSelectedCell)(column->delegate, column, row, d);
        else
            (*column->delegate->drawCell)(column->delegate, column, row, d);
    }
}
#endif


static void
setRowSelected(WMTableView *table, unsigned row, Bool flag)
{
    int repaint = 0;

    if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound) {
        if (!flag) {
            WMRemoveFromArray(table->selectedRows, (void*)row);
            repaint = 1;
        }
    } else {
        if (flag) {
            WMAddToArray(table->selectedRows, (void*)row);
            repaint = 1;
        }
    }
    if (repaint && row < table->rows) {
        /*drawFullRow(table, row);*/
        repaintTable(table);
    }
}


static void
repaintTable(WMTableView *table)
{
    WMRect rect;
    WMRange rows;
    WMScreen *scr = WMWidgetScreen(table);
    int i;

    if (!table->delegate || !W_VIEW_REALIZED(table->view))
        return;

    wassertr(table->delegate->numberOfRows);

    if (!table->viewBuffer) {
        table->viewBuffer = WMCreatePixmap(scr,
                                           W_VIEW_WIDTH(table->tableView),
                                           W_VIEW_HEIGHT(table->tableView),
                                           WMScreenDepth(scr), 0);
    }

    XFillRectangle(scr->display,
                   WMGetPixmapXID(table->viewBuffer),
                   WMColorGC(table->backColor), 0, 0,
                   W_VIEW_WIDTH(table->tableView),
                   W_VIEW_HEIGHT(table->tableView));

    rect = getVisibleRect(table);

    if (table->drawsGrid) {
        drawGrid(table, rect);
    }

    rows = rowsInRect(table, rect);
    for (i = rows.position;
         i < WMIN(rows.position+rows.count + 1, table->rows);
         i++) {
        drawRow(table, i, rect);
    }

    XSetWindowBackgroundPixmap(scr->display, table->tableView->window,
                               WMGetPixmapXID(table->viewBuffer));
    XClearWindow(scr->display, table->tableView->window);
}


static void
stopRowEdit(WMTableView *table, int row)
{
    int i;
    WMTableColumn *column;

    table->editingRow = -1;
    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        column = WMGetFromArray(table->columns, i);

        if (column->delegate && column->delegate->endCellEdit)
            (*column->delegate->endCellEdit)(column->delegate, column, row);
    }
}



void
WMEditTableViewRow(WMTableView *table, int row)
{
    int i;
    WMTableColumn *column;

    if (table->editingRow >= 0) {
        stopRowEdit(table, table->editingRow);
    }

    table->editingRow = row;

    if (row < 0)
        return;

    for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
        column = WMGetFromArray(table->columns, i);

        if (column->delegate && column->delegate->beginCellEdit)
            (*column->delegate->beginCellEdit)(column->delegate, column, row);
    }
}


void
WMSelectTableViewRow(WMTableView *table, int row)
{
    if (table->clickedRow >= 0)
        setRowSelected(table, table->clickedRow, False);

    if (row >= table->rows) {
        return;
    }

    setRowSelected(table, row, True);
    table->clickedRow = row;

    if (table->action)
        (*table->action)(table, table->clientData);
    WMPostNotificationName(WMTableViewSelectionDidChangeNotification,
                           table, NULL);
}


void
WMReloadTableView(WMTableView *table)
{
    if (table->editingRow >= 0)
        stopRowEdit(table, table->editingRow);

    /* when this is called, nothing in the table can be assumed to be
     * like the last time we accessed it (ie, rows might have disappeared) */

    WMEmptyArray(table->selectedRows);

    if (table->clickedRow >= 0) {
        if (table->action)
            (*table->action)(table, table->clientData);
        WMPostNotificationName(WMTableViewSelectionDidChangeNotification,
                               table, NULL);
        table->clickedRow = -1;
    }

    if (table->delegate && table->delegate->numberOfRows) {
        int rows;

        rows = (*table->delegate->numberOfRows)(table->delegate, table);

        if (rows != table->rows) {
            table->rows = rows;
            handleResize(table->view->delegate, table->view);
        } else {
            repaintTable(table);
        }
    }
}


void
WMNoteTableViewNumberOfRowsChanged(WMTableView *table)
{
    WMReloadTableView(table);
}


static void
handleTableEvents(XEvent *event, void *data)
{
    WMTableView *table = (WMTableView*)data;
    int row;

    switch (event->type) {
    case ButtonPress:
        if (event->xbutton.button == Button1) {
            WMRect rect = getVisibleRect(table);

            row = (event->xbutton.y + rect.pos.y)/table->rowHeight;
            if (row != table->clickedRow) {
                setRowSelected(table, table->clickedRow, False);
                setRowSelected(table, row, True);
                table->clickedRow = row;
                table->dragging = 1;
            } else {
                table->dragging = 1;
            }
        }
        break;

    case MotionNotify:
        if (table->dragging && event->xmotion.y >= 0) {
            WMRect rect = getVisibleRect(table);

            row = (event->xmotion.y + rect.pos.y)/table->rowHeight;
            if (table->clickedRow != row && row >= 0 && row < table->rows) {
                setRowSelected(table, table->clickedRow, False);
                setRowSelected(table, row, True);
                table->clickedRow = row;
            }
        }
        break;

    case ButtonRelease:
        if (event->xbutton.button == Button1) {
            if (table->action)
                (*table->action)(table, table->clientData);
            WMPostNotificationName(WMTableViewSelectionDidChangeNotification,
                                   table, NULL);
            table->dragging = 0;
        }
        break;
    }
}


static void
handleEvents(XEvent *event, void *data)
{
    WMTableView *table = (WMTableView*)data;
    WMScreen *scr = WMWidgetScreen(table);

    switch (event->type) {
    case Expose:
        W_DrawRelief(scr, W_VIEW_DRAWABLE(table->view), 0, 0,
                     W_VIEW_WIDTH(table->view), W_VIEW_HEIGHT(table->view),
                     WRSunken);
        break;
    }
}


static void
handleResize(W_ViewDelegate *self, WMView *view)
{
    reorganizeInterior(view->self);
}


static void
reorganizeInterior(WMTableView *table)
{
    int width;
    int height;
    WMSize size = getTotalSize(table);
    WMView *view = table->view;
    int vw, vh;
    int hsThickness, vsThickness;

    if (table->vscroll)
        vsThickness = WMWidgetWidth(table->vscroll);
    if (table->hscroll)
        hsThickness = WMWidgetHeight(table->hscroll);

    width = W_VIEW_WIDTH(view) - 2;
    height = W_VIEW_HEIGHT(view) - 3;

    height -= table->headerHeight; /* table header */

    if (table->corner)
        WMResizeWidget(table->corner, 20, table->headerHeight);

    WMMoveWidget(table->vscroll, 1, table->headerHeight + 1);
    WMResizeWidget(table->vscroll, 20, height + 1);

    if (table->hscroll) {
        WMMoveWidget(table->hscroll, vsThickness, W_VIEW_HEIGHT(view) - hsThickness - 1);
        WMResizeWidget(table->hscroll, width-(vsThickness+1), hsThickness);
    }

    if (table->header)
        WMResizeWidget(table->header, width-(vsThickness+1), table->headerHeight);

    if (table->viewBuffer) {
        WMReleasePixmap(table->viewBuffer);
        table->viewBuffer = NULL;
    }

    width -= vsThickness;
    height -= hsThickness;


    vw = WMIN(size.width, width);
    vh = WMIN(size.height, height);

    W_MoveView(table->tableView, vsThickness+1, 1+table->headerHeight+1);
    W_ResizeView(table->tableView, WMAX(vw, 1), WMAX(vh, 1)+1);

    adjustScrollers(table);

    repaintTable(table);
}


static void
rearrangeHeader(WMTableView *table)
{
    int width;
    int count;
    int i;
    /*WMRect rect = WMGetScrollViewVisibleRect(table->scrollView);*/

    width = 0;

    count = WMGetArrayItemCount(table->columns);
    for (i = 0; i < count; i++) {
        WMTableColumn *column = WMGetFromArray(table->columns, i);
        WMView *splitter = WMGetFromArray(table->splitters, i);

        WMMoveWidget(column->titleW, width, 0);
        WMResizeWidget(column->titleW, column->width-1, table->headerHeight);

        width += column->width;
        W_MoveView(splitter, width-1, 0);
    }

    wassertr(table->delegate && table->delegate->numberOfRows);

    table->rows = table->delegate->numberOfRows(table->delegate, table);

    table->tableWidth = width + 1;

    handleResize(table->view->delegate, table->view);
}



syntax highlighted by Code2HTML, v. 0.9.1