#include #include #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(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; } WMTableView *WMGetTableColumnTableView(WMTableColumn *column) { return column->table; } struct W_TableView { W_Class widgetClass; WMView *view; WMFrame *header; WMLabel *corner; WMScrollView *scrollView; WMView *tableView; WMArray *columns; WMArray *splitters; WMArray *selectedRows; int tableWidth; int rows; 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; }; static W_Class tableClass = 0; static W_ViewDelegate viewDelegate = { NULL, NULL, handleResize, NULL, NULL }; static void handleEvents(XEvent *event, void *data); static void handleTableEvents(XEvent *event, void *data); static void scrollObserver(void *self, WMNotification *notif) { WMTableView *table = (WMTableView*)self; WMRect rect; int i, x; rect = WMGetScrollViewVisibleRect(table->scrollView); x = 0; for (i = 0; i < WMGetArrayItemCount(table->columns); i++) { WMTableColumn *column; column = WMGetFromArray(table->columns, i); WMMoveWidget(column->titleW, x - rect.pos.x, 0); if (i > 0) { WMView *splitter; splitter = WMGetFromArray(table->splitters, i-1); W_MoveView(splitter, x - rect.pos.x - 1, 0); } x += W_VIEW_WIDTH(WMWidgetView(column->titleW)) + 1; } } static void splitterHandler(XEvent *event, void *data) { WMTableView *table = (WMTableView*)data; int done = 0; while (!done) { XEvent ev; WMMaskEvent(event->xany.display, ButtonMotionMask|ButtonReleaseMask, &ev); switch (event->type) { case MotionNotify: printf("%i\n", event->xmotion.x); break; case ButtonRelease: done = 1; break; } } } 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->scrollView = WMCreateScrollView(table); if (!table->scrollView) goto error; WMResizeWidget(table->scrollView, 10, 10); WMSetScrollViewHasVerticalScroller(table->scrollView, True); WMSetScrollViewHasHorizontalScroller(table->scrollView, True); { WMScroller *scroller; scroller = WMGetScrollViewHorizontalScroller(table->scrollView); WMAddNotificationObserver(scrollObserver, table, WMScrollerDidScrollNotification, scroller); } WMMoveWidget(table->scrollView, 1, 2+table->headerHeight); WMMapWidget(table->scrollView); 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(W_VIEW(parent)); if (!table->tableView) goto error; table->tableView->self = table; W_ResizeView(table->tableView, 100, 1000); W_MapView(table->tableView); WMSetScrollViewContentView(table->scrollView, table->tableView); table->tableView->flags.dontCompressExpose = 1; table->gridColor = WMCreateNamedColor(scr, "#cccccc", False); /* table->gridColor = WMGrayColor(scr);*/ { XGCValues gcv; 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; WMSetScrollViewLineScroll(table->scrollView, table->rowHeight); 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); return table; error: if (table->scrollView) WMDestroyWidget(table->scrollView); if (table->tableView) W_DestroyView(table->tableView); if (table->view) W_DestroyView(table->view); wfree(table); return NULL; } void WMAddTableViewColumn(WMTableView *table, WMTableColumn *column) { int width; int i; WMScreen *scr = WMWidgetScreen(table); int count; 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); } if (WMGetArrayItemCount(table->columns) > 1) { 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-1); W_MapView(splitter); W_SetViewCursor(splitter, table->splitterCursor); WMCreateEventHandler(splitter, ButtonPressMask, splitterHandler, table); 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; } return rect; } void WMSetTableViewDataSource(WMTableView *table, void *source) { table->dataSource = source; } void *WMGetTableViewDataSource(WMTableView *table) { return table->dataSource; } void WMSetTableViewBackgroundColor(WMTableView *table, WMColor *color) { W_SetViewBackgroundColor(table->tableView, color); } void WMSetTableViewGridColor(WMTableView *table, WMColor *color) { WMReleaseColor(table->gridColor); table->gridColor = WMRetainColor(color); XSetForeground(WMScreenDisplay(WMWidgetScreen(table)), table->gridGC, WMColorPixel(color)); } void WMSetTableViewRowHeight(WMTableView *table, int height) { table->rowHeight = height; WMRedisplayWidget(table); } void WMScrollTableViewRowToVisible(WMTableView *table, int row) { WMScroller *scroller; WMRange range; WMRect rect; int newY, tmp; rect = WMGetScrollViewVisibleRect(table->scrollView); range = rowsInRect(table, rect); scroller = WMGetScrollViewVerticalScroller(table->scrollView); 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)); WMScrollViewScrollPoint(table->scrollView, 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 = W_VIEW_DRAWABLE(table->tableView); 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 = 0; for (i = 0; i < WMGetArrayItemCount(table->columns); i++) { WMTableColumn *column; if (xx >= rect.pos.x && xx <= rect.pos.x+rect.size.width) { 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 = rect.pos.x; x2 = WMIN(x1 + rect.size.width, xx); if (x2 <= x1) return; #if 0 XSetDashes(dpy, gc, (rect.pos.x&1), dashl, 1); #endif y1 = rect.pos.y - 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 width; int i , j; int totalColumns = WMGetArrayItemCount(table->columns); WMRange range; j = 0; width = 0; for (i = 0; i < totalColumns; i++) { column = WMGetFromArray(table->columns, i); if (j == 0) { if (width <= rect.pos.x && width + column->width > rect.pos.x) { range.position = i; j = 1; } } else { range.count++; if (width > rect.pos.x + rect.size.width) { break; } } width += 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; for (i = cols.position; i < cols.position+cols.count; i++) { column = WMGetFromArray(table->columns, i); wassertr(column->delegate && column->delegate->drawCell); if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound) (*column->delegate->drawSelectedCell)(column->delegate, column, row); else (*column->delegate->drawCell)(column->delegate, column, row); } } static void drawFullRow(WMTableView *table, int row) { int i; WMTableColumn *column; for (i = 0; i < WMGetArrayItemCount(table->columns); i++) { column = WMGetFromArray(table->columns, i); wassertr(column->delegate && column->delegate->drawCell); if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound) (*column->delegate->drawSelectedCell)(column->delegate, column, row); else (*column->delegate->drawCell)(column->delegate, column, row); } } 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); } } static void repaintTable(WMTableView *table, int x, int y, int width, int height) { WMRect rect; WMRange rows; int i; wassertr(table->delegate && table->delegate->numberOfRows); i = (*table->delegate->numberOfRows)(table->delegate, table); if (i != table->rows) { table->rows = i; W_ResizeView(table->tableView, table->tableWidth, table->rows * table->rowHeight + 1); } if (x >= table->tableWidth-1) return; rect.pos = wmkpoint(x,y); rect.size = wmksize(width, height); 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); } } 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) { WMRect rect = WMGetScrollViewVisibleRect(table->scrollView); 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) { table->clickedRow = -1; if (table->action) (*table->action)(table, table->clientData); WMPostNotificationName(WMTableViewSelectionDidChangeNotification, table, NULL); } repaintTable(table, 0, 0, W_VIEW_WIDTH(table->tableView), rect.size.height); } 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) { row = event->xbutton.y/table->rowHeight; if (row != table->clickedRow) { setRowSelected(table, table->clickedRow, False); setRowSelected(table, row, True); table->clickedRow = row; table->dragging = 1; } } break; case MotionNotify: if (table->dragging && event->xmotion.y >= 0) { row = event->xmotion.y/table->rowHeight; if (table->clickedRow != row && row >= 0) { 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; case Expose: repaintTable(table, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); 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); if (event->xexpose.serial == 0) { WMRect rect = WMGetScrollViewVisibleRect(table->scrollView); repaintTable(table, rect.pos.x, rect.pos.y, rect.size.width, rect.size.height); } break; } } static void handleResize(W_ViewDelegate *self, WMView *view) { int width; int height; WMTableView *table = view->self; 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); if (table->scrollView) { WMMoveWidget(table->scrollView, 1, table->headerHeight + 2); WMResizeWidget(table->scrollView, width, height); } if (table->header) WMResizeWidget(table->header, width - 21, table->headerHeight); } static void rearrangeHeader(WMTableView *table) { int width; int count; int i; width = 0; count = WMGetArrayItemCount(table->columns); for (i = 0; i < count; i++) { WMTableColumn *column = WMGetFromArray(table->columns, i); WMMoveWidget(column->titleW, width, 0); WMResizeWidget(column->titleW, column->width-1, table->headerHeight); if (i > 0) { WMView *splitter = WMGetFromArray(table->splitters, i-1); W_MoveView(splitter, width-1, 0); } width += column->width; } wassertr(table->delegate && table->delegate->numberOfRows); table->rows = table->delegate->numberOfRows(table->delegate, table); W_ResizeView(table->tableView, width+1, table->rows * table->rowHeight + 1); table->tableWidth = width + 1; }