tableview field editor delegates
[wmaker-crm.git] / WINGs / Extras / wtableview.c
blob7561032923683571b420a2743b309ad7e826f897
3 #include <WINGsP.h>
4 #include <X11/cursorfont.h>
6 #include "wtableview.h"
9 const char *WMTableViewRowWasSelectedNotification = "WMTableViewRowWasSelectedNotification";
10 const char *WMTableViewRowWasUnselectedNotification = "WMTableViewRowWasUnselectedNotification";
13 struct W_TableColumn {
14 WMTableView *table;
15 WMWidget *titleW;
16 char *title;
17 int width;
18 int minWidth;
19 int maxWidth;
21 void *id;
23 WMTableColumnDelegate *delegate;
25 unsigned resizable:1;
26 unsigned editable:1;
31 static void handleResize(W_ViewDelegate *self, WMView *view);
34 WMTableColumn *WMCreateTableColumn(char *title)
36 WMTableColumn *col = wmalloc(sizeof(WMTableColumn));
38 col->table = NULL;
39 col->titleW = NULL;
40 col->width = 50;
41 col->minWidth = 5;
42 col->maxWidth = 0;
44 col->id = NULL;
46 col->title = wstrdup(title);
48 col->delegate = NULL;
50 col->resizable = 1;
51 col->editable = 0;
53 return col;
57 void WMSetTableColumnId(WMTableColumn *column, void *id)
59 column->id = id;
63 void *WMGetTableColumnId(WMTableColumn *column)
65 return column->id;
69 void WMSetTableColumnWidth(WMTableColumn *column, unsigned width)
71 if (column->maxWidth == 0)
72 column->width = WMAX(column->minWidth, width);
73 else
74 column->width = WMAX(column->minWidth, WMIN(column->maxWidth, width));
76 if (column->table) {
77 handleResize(NULL, W_VIEW(column->table));
82 void WMSetTableColumnDelegate(WMTableColumn *column,
83 WMTableColumnDelegate *delegate)
85 column->delegate = delegate;
89 void WMSetTableColumnConstraints(WMTableColumn *column,
90 unsigned minWidth, unsigned maxWidth)
92 wassertr(minWidth <= maxWidth);
94 column->minWidth = minWidth;
95 column->maxWidth = maxWidth;
97 if (column->width < column->minWidth)
98 WMSetTableColumnWidth(column, column->minWidth);
99 else if (column->width > column->maxWidth || column->maxWidth == 0)
100 WMSetTableColumnWidth(column, column->maxWidth);
104 void WMSetTableColumnEditable(WMTableColumn *column, Bool flag)
106 column->editable = flag;
110 WMTableView *WMGetTableColumnTableView(WMTableColumn *column)
112 return column->table;
117 struct W_TableView {
118 W_Class widgetClass;
119 WMView *view;
121 WMFrame *header;
123 WMLabel *corner;
125 WMScrollView *scrollView;
126 WMView *tableView;
128 WMArray *columns;
129 WMArray *splitters;
131 WMArray *selectedRows;
133 int tableWidth;
135 int rows;
137 GC gridGC;
138 WMColor *gridColor;
140 Cursor splitterCursor;
142 void *dataSource;
144 WMTableViewDelegate *delegate;
146 unsigned headerHeight;
148 unsigned rowHeight;
150 unsigned drawsGrid:1;
151 unsigned canSelectRow:1;
152 unsigned canSelectMultiRows:1;
153 unsigned canDeselectRow:1;
156 static W_Class tableClass = 0;
159 static W_ViewDelegate viewDelegate = {
160 NULL,
161 NULL,
162 handleResize,
163 NULL,
164 NULL
171 static void handleEvents(XEvent *event, void *data);
172 static void handleTableEvents(XEvent *event, void *data);
175 static void scrollObserver(void *self, WMNotification *notif)
177 WMTableView *table = (WMTableView*)self;
178 WMRect rect;
179 int i, x;
181 rect = WMGetScrollViewVisibleRect(table->scrollView);
183 x = 0;
184 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
185 WMTableColumn *column;
187 column = WMGetFromArray(table->columns, i);
189 WMMoveWidget(column->titleW, x + rect.pos.x, 0);
191 if (i > 0) {
192 WMView *splitter;
194 splitter = WMGetFromArray(table->splitters, i-1);
195 W_MoveView(splitter, x + rect.pos.x - 1, 0);
198 x += W_VIEW_WIDTH(WMWidgetView(column->titleW)) + 1;
203 static void splitterHandler(XEvent *event, void *data)
205 WMTableView *table = (WMTableView*)data;
206 int done = 0;
208 while (!done) {
209 XEvent ev;
211 WMMaskEvent(event->xany.display, ButtonMotionMask|ButtonReleaseMask,
212 &ev);
214 switch (event->type) {
215 case MotionNotify:
216 printf("%i\n", event->xmotion.x);
217 break;
219 case ButtonRelease:
220 done = 1;
221 break;
228 WMTableView *WMCreateTableView(WMWidget *parent)
230 WMTableView *table = wmalloc(sizeof(WMTableView));
231 WMScreen *scr = WMWidgetScreen(parent);
233 memset(table, 0, sizeof(WMTableView));
235 if (!tableClass) {
236 tableClass = W_RegisterUserWidget();
238 table->widgetClass = tableClass;
240 table->view = W_CreateView(W_VIEW(parent));
241 if (!table->view)
242 goto error;
243 table->view->self = table;
245 table->view->delegate = &viewDelegate;
247 table->headerHeight = 20;
249 table->scrollView = WMCreateScrollView(table);
250 if (!table->scrollView)
251 goto error;
252 WMResizeWidget(table->scrollView, 10, 10);
253 WMSetScrollViewHasVerticalScroller(table->scrollView, True);
254 WMSetScrollViewHasHorizontalScroller(table->scrollView, True);
256 WMScroller *scroller;
257 scroller = WMGetScrollViewHorizontalScroller(table->scrollView);
258 WMAddNotificationObserver(scrollObserver, table,
259 WMScrollerDidScrollNotification,
260 scroller);
262 WMMoveWidget(table->scrollView, 1, 2+table->headerHeight);
263 WMMapWidget(table->scrollView);
265 table->header = WMCreateFrame(table);
266 WMMoveWidget(table->header, 22, 2);
267 WMMapWidget(table->header);
268 WMSetFrameRelief(table->header, WRFlat);
269 table->corner = WMCreateLabel(table);
270 WMResizeWidget(table->corner, 20, table->headerHeight);
271 WMMoveWidget(table->corner, 2, 2);
272 WMMapWidget(table->corner);
273 WMSetLabelRelief(table->corner, WRRaised);
274 WMSetWidgetBackgroundColor(table->corner, scr->darkGray);
277 table->tableView = W_CreateView(W_VIEW(parent));
278 if (!table->tableView)
279 goto error;
280 table->tableView->self = table;
281 W_ResizeView(table->tableView, 100, 1000);
282 W_MapView(table->tableView);
284 WMSetScrollViewContentView(table->scrollView, table->tableView);
286 table->tableView->flags.dontCompressExpose = 1;
288 table->gridColor = WMDarkGrayColor(scr);
291 XGCValues gcv;
293 gcv.foreground = WMColorPixel(table->gridColor);
294 gcv.dashes = 1;
295 gcv.line_style = LineOnOffDash;
296 table->gridGC = XCreateGC(WMScreenDisplay(scr), W_DRAWABLE(scr),
297 GCForeground, &gcv);
300 table->drawsGrid = 1;
301 table->rowHeight = 16;
303 table->tableWidth = 1;
305 table->columns = WMCreateArray(4);
306 table->splitters = WMCreateArray(4);
308 table->selectedRows = WMCreateArray(16);
310 table->splitterCursor = XCreateFontCursor(WMScreenDisplay(scr),
311 XC_sb_h_double_arrow);
313 table->canSelectRow = 1;
315 WMCreateEventHandler(table->view, ExposureMask|StructureNotifyMask,
316 handleEvents, table);
318 WMCreateEventHandler(table->tableView, ExposureMask|ButtonPressMask,
319 handleTableEvents, table);
321 return table;
323 error:
324 if (table->scrollView)
325 WMDestroyWidget(table->scrollView);
326 if (table->tableView)
327 W_DestroyView(table->tableView);
328 if (table->view)
329 W_DestroyView(table->view);
330 wfree(table);
331 return NULL;
335 void WMAddTableViewColumn(WMTableView *table, WMTableColumn *column)
337 int width;
338 int i;
339 WMScreen *scr = WMWidgetScreen(table);
340 int count;
342 column->table = table;
344 WMAddToArray(table->columns, column);
346 if (!column->titleW) {
347 column->titleW = WMCreateLabel(table);
348 WMSetLabelRelief(column->titleW, WRRaised);
349 WMSetLabelFont(column->titleW, scr->boldFont);
350 WMSetLabelTextColor(column->titleW, scr->white);
351 WMSetWidgetBackgroundColor(column->titleW, scr->darkGray);
352 WMSetLabelText(column->titleW, column->title);
353 W_ReparentView(WMWidgetView(column->titleW),
354 WMWidgetView(table->header), 0, 0);
355 if (W_VIEW_REALIZED(table->view))
356 WMRealizeWidget(column->titleW);
357 WMMapWidget(column->titleW);
360 if (WMGetArrayItemCount(table->columns) > 1) {
361 WMView *splitter = W_CreateView(WMWidgetView(table->header));
363 W_SetViewBackgroundColor(splitter, WMWhiteColor(scr));
365 if (W_VIEW_REALIZED(table->view))
366 W_RealizeView(splitter);
368 W_ResizeView(splitter, 2, table->headerHeight-1);
369 W_MapView(splitter);
371 W_SetViewCursor(splitter, table->splitterCursor);
372 WMCreateEventHandler(splitter, ButtonPressMask,
373 splitterHandler, table);
375 WMAddToArray(table->splitters, splitter);
378 count = WMGetArrayItemCount(table->columns);
379 for (i = 0, width = 0; i < count; i++) {
380 WMTableColumn *column = WMGetFromArray(table->columns, i);
382 WMMoveWidget(column->titleW, width, 0);
383 WMResizeWidget(column->titleW, column->width-1, table->headerHeight);
385 if (i > 0) {
386 WMView *splitter = WMGetFromArray(table->splitters, i-1);
388 W_MoveView(splitter, width-1, 0);
390 width += column->width;
393 wassertr(table->delegate && table->delegate->numberOfRows);
395 table->rows = table->delegate->numberOfRows(table->delegate, table);
397 W_ResizeView(table->tableView, width+1,
398 table->rows * table->rowHeight + 1);
400 table->tableWidth = width + 1;
404 void WMSetTableViewHeaderHeight(WMTableView *table, unsigned height)
406 table->headerHeight = height;
408 handleResize(NULL, table->view);
412 void WMSetTableViewDelegate(WMTableView *table, WMTableViewDelegate *delegate)
414 table->delegate = delegate;
418 WMView *WMGetTableViewDocumentView(WMTableView *table)
420 return table->tableView;
424 void *WMTableViewDataForCell(WMTableView *table, WMTableColumn *column,
425 int row)
427 return (*table->delegate->valueForCell)(table->delegate, column, row);
431 WMRect WMTableViewRectForCell(WMTableView *table, WMTableColumn *column,
432 int row)
434 WMRect rect;
435 int i;
437 rect.pos.x = 0;
438 rect.pos.y = row * table->rowHeight;
439 rect.size.height = table->rowHeight;
441 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
442 WMTableColumn *col;
443 col = WMGetFromArray(table->columns, i);
445 if (col == column) {
446 rect.size.width = col->width;
447 break;
450 rect.pos.x += col->width;
452 return rect;
456 void WMSetTableViewDataSource(WMTableView *table, void *source)
458 table->dataSource = source;
462 void *WMGetTableViewDataSource(WMTableView *table)
464 return table->dataSource;
468 void WMSetTableViewBackgroundColor(WMTableView *table, WMColor *color)
470 W_SetViewBackgroundColor(table->tableView, color);
474 void WMSetTableViewGridColor(WMTableView *table, WMColor *color)
476 WMReleaseColor(table->gridColor);
477 table->gridColor = WMRetainColor(color);
478 XSetForeground(WMScreenDisplay(WMWidgetScreen(table)), table->gridGC, WMColorPixel(color));
483 static void drawGrid(WMTableView *table, WMRect rect)
485 WMScreen *scr = WMWidgetScreen(table);
486 Display *dpy = WMScreenDisplay(scr);
487 int i;
488 int y1, y2;
489 int x1, x2;
490 int xx;
491 Drawable d = W_VIEW_DRAWABLE(table->tableView);
492 GC gc = table->gridGC;
494 #if 0
495 char dashl[1] = {1};
497 XSetDashes(dpy, gc, 0, dashl, 1);
499 y1 = (rect.pos.y/table->rowHeight - 1)*table->rowHeight;
500 y2 = y1 + (rect.size.height/table->rowHeight+2)*table->rowHeight;
501 #endif
502 y1 = 0;
503 y2 = W_VIEW_HEIGHT(table->tableView);
505 xx = 0;
506 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
507 WMTableColumn *column;
509 if (xx >= rect.pos.x && xx <= rect.pos.x+rect.size.width) {
510 XDrawLine(dpy, d, gc, xx, y1, xx, y2);
512 column = WMGetFromArray(table->columns, i);
513 xx += column->width;
515 XDrawLine(dpy, d, gc, xx, y1, xx, y2);
518 x1 = rect.pos.x;
519 x2 = WMIN(x1 + rect.size.width, xx);
521 if (x2 <= x1)
522 return;
523 #if 0
524 XSetDashes(dpy, gc, (rect.pos.x&1), dashl, 1);
525 #endif
527 y1 = rect.pos.y - rect.pos.y%table->rowHeight;
528 y2 = y1 + rect.size.height + table->rowHeight;
530 for (i = y1; i <= y2; i += table->rowHeight) {
531 XDrawLine(dpy, d, gc, x1, i, x2, i);
536 static WMRange columnsInRect(WMTableView *table, WMRect rect)
538 WMTableColumn *column;
539 int width;
540 int i , j;
541 int totalColumns = WMGetArrayItemCount(table->columns);
542 WMRange range;
544 j = 0;
545 width = 0;
546 for (i = 0; i < totalColumns; i++) {
547 column = WMGetFromArray(table->columns, i);
548 if (j == 0) {
549 if (width <= rect.pos.x && width + column->width > rect.pos.x) {
550 range.position = i;
551 j = 1;
553 } else {
554 if (width > rect.pos.x + rect.size.width) {
555 range.count = i - range.position;
556 break;
559 width += column->width;
562 range.count = WMAX(1, WMIN(range.count, totalColumns - range.position));
564 return range;
568 static WMRange rowsInRect(WMTableView *table, WMRect rect)
570 WMRange range;
571 int rh = table->rowHeight;
573 range.position = WMAX(0, rect.pos.y / rh - 1);
574 range.count = WMAX(1, WMIN(rect.size.height / rh + 3, table->rows));
576 return range;
580 static void drawRow(WMTableView *table, int row, WMRect clipRect)
582 int i;
583 WMRange cols = columnsInRect(table, clipRect);
584 WMTableColumn *column;
586 for (i = cols.position; i < cols.position+cols.count; i++) {
587 column = WMGetFromArray(table->columns, i);
589 wassertr(column->delegate && column->delegate->drawCell);
591 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
592 (*column->delegate->drawSelectedCell)(column->delegate, column, row);
593 else
594 (*column->delegate->drawCell)(column->delegate, column, row);
599 static void drawFullRow(WMTableView *table, int row)
601 int i;
602 WMTableColumn *column;
604 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
605 column = WMGetFromArray(table->columns, i);
607 wassertr(column->delegate && column->delegate->drawCell);
609 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
610 (*column->delegate->drawSelectedCell)(column->delegate, column, row);
611 else
612 (*column->delegate->drawCell)(column->delegate, column, row);
617 static void setRowSelected(WMTableView *table, unsigned row, Bool flag)
619 int repaint = 0;
622 if (WMGetArrayItemCount(table->selectedRows) > 0
623 && !table->canSelectMultiRows) {
624 int r = (int)WMGetFromArray(table->selectedRows, 0);
626 WMDeleteFromArray(table->selectedRows, 0);
628 drawFullRow(table, r);
630 WMPostNotificationName(WMTableViewRowWasUnselectedNotification,
631 table, (void*)r);
634 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound) {
635 if (!flag) {
636 WMRemoveFromArray(table->selectedRows, (void*)row);
637 WMPostNotificationName(WMTableViewRowWasUnselectedNotification,
638 table, (void*)row);
639 repaint = 1;
641 } else {
642 if (flag) {
643 WMAddToArray(table->selectedRows, (void*)row);
644 WMPostNotificationName(WMTableViewRowWasSelectedNotification,
645 table, (void*)row);
646 repaint = 1;
650 if (repaint) {
651 drawFullRow(table, row);
656 static void repaintTable(WMTableView *table, int x, int y,
657 int width, int height)
659 WMRect rect;
660 WMRange rows;
661 int i;
663 wassertr(table->delegate && table->delegate->numberOfRows);
664 i = (*table->delegate->numberOfRows)(table->delegate, table);
666 if (i != table->rows) {
667 table->rows = i;
668 W_ResizeView(table->tableView, table->tableWidth,
669 table->rows * table->rowHeight + 1);
673 rect.pos = wmkpoint(x,y);
674 rect.size = wmksize(width, height);
676 if (table->drawsGrid) {
677 drawGrid(table, rect);
680 rows = rowsInRect(table, rect);
681 for (i = rows.position;
682 i < WMIN(rows.position+rows.count, table->rows);
683 i++) {
684 drawRow(table, i, rect);
689 static void startRowEdit(WMTableView *table, int row)
691 int i;
692 WMTableColumn *column;
694 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
695 column = WMGetFromArray(table->columns, i);
697 wassertr(column->delegate && column->delegate->drawCell);
699 (*column->delegate->beginCellEdit)(column->delegate, column, row);
704 static void handleTableEvents(XEvent *event, void *data)
706 WMTableView *table = (WMTableView*)data;
708 switch (event->type) {
709 case ButtonPress:
710 setRowSelected(table, event->xbutton.y/table->rowHeight, True);
711 startRowEdit(table, event->xbutton.y/table->rowHeight);
712 break;
714 case Expose:
715 repaintTable(table, event->xexpose.x, event->xexpose.y,
716 event->xexpose.width, event->xexpose.height);
717 break;
722 static void handleEvents(XEvent *event, void *data)
724 WMTableView *table = (WMTableView*)data;
725 WMScreen *scr = WMWidgetScreen(table);
727 switch (event->type) {
728 case Expose:
729 W_DrawRelief(scr, W_VIEW_DRAWABLE(table->view), 0, 0,
730 W_VIEW_WIDTH(table->view), W_VIEW_HEIGHT(table->view),
731 WRSunken);
732 break;
737 static void handleResize(W_ViewDelegate *self, WMView *view)
739 int width;
740 int height;
741 WMTableView *table = view->self;
743 width = W_VIEW_WIDTH(view) - 2;
744 height = W_VIEW_HEIGHT(view) - 3;
746 height -= table->headerHeight; /* table header */
748 if (table->corner)
749 WMResizeWidget(table->corner, 20, table->headerHeight);
751 if (table->scrollView) {
752 WMMoveWidget(table->scrollView, 1, table->headerHeight + 2);
753 WMResizeWidget(table->scrollView, width, height);
755 if (table->header)
756 WMResizeWidget(table->header, width - 21, table->headerHeight);