changed panel to use boxes
[wmaker-crm.git] / WINGs / Extras / wtableview.c
blob76623591e1a7e93dc5f840d7a25f242ce03bcdc0
3 #include <WINGsP.h>
4 #include <X11/cursorfont.h>
6 #include "wtableview.h"
9 const char *WMTableViewSelectionDidChangeNotification = "WMTableViewSelectionDidChangeNotification";
12 struct W_TableColumn {
13 WMTableView *table;
14 WMWidget *titleW;
15 char *title;
16 int width;
17 int minWidth;
18 int maxWidth;
20 void *id;
22 WMTableColumnDelegate *delegate;
24 unsigned resizable:1;
25 unsigned editable:1;
30 static void handleResize(W_ViewDelegate *self, WMView *view);
32 static void rearrangeHeader(WMTableView *table);
34 static WMRange rowsInRect(WMTableView *table, WMRect rect);
37 WMTableColumn *WMCreateTableColumn(char *title)
39 WMTableColumn *col = wmalloc(sizeof(WMTableColumn));
41 col->table = NULL;
42 col->titleW = NULL;
43 col->width = 50;
44 col->minWidth = 5;
45 col->maxWidth = 0;
47 col->id = NULL;
49 col->title = wstrdup(title);
51 col->delegate = NULL;
53 col->resizable = 1;
54 col->editable = 0;
56 return col;
60 void WMSetTableColumnId(WMTableColumn *column, void *id)
62 column->id = id;
66 void *WMGetTableColumnId(WMTableColumn *column)
68 return column->id;
72 void WMSetTableColumnWidth(WMTableColumn *column, unsigned width)
74 if (column->maxWidth == 0)
75 column->width = WMAX(column->minWidth, width);
76 else
77 column->width = WMAX(column->minWidth, WMIN(column->maxWidth, width));
79 if (column->table) {
80 rearrangeHeader(column->table);
85 void WMSetTableColumnDelegate(WMTableColumn *column,
86 WMTableColumnDelegate *delegate)
88 column->delegate = delegate;
92 void WMSetTableColumnConstraints(WMTableColumn *column,
93 unsigned minWidth, unsigned maxWidth)
95 wassertr(minWidth <= maxWidth);
97 column->minWidth = minWidth;
98 column->maxWidth = maxWidth;
100 if (column->width < column->minWidth)
101 WMSetTableColumnWidth(column, column->minWidth);
102 else if (column->width > column->maxWidth || column->maxWidth == 0)
103 WMSetTableColumnWidth(column, column->maxWidth);
107 void WMSetTableColumnEditable(WMTableColumn *column, Bool flag)
109 column->editable = flag;
113 WMTableView *WMGetTableColumnTableView(WMTableColumn *column)
115 return column->table;
120 struct W_TableView {
121 W_Class widgetClass;
122 WMView *view;
124 WMFrame *header;
126 WMLabel *corner;
128 WMScrollView *scrollView;
129 WMView *tableView;
131 WMArray *columns;
132 WMArray *splitters;
134 WMArray *selectedRows;
136 int tableWidth;
138 int rows;
140 GC gridGC;
141 WMColor *gridColor;
143 Cursor splitterCursor;
145 void *dataSource;
147 WMTableViewDelegate *delegate;
149 WMAction *action;
150 void *clientData;
152 void *clickedColumn;
153 int clickedRow;
155 int editingRow;
157 unsigned headerHeight;
159 unsigned rowHeight;
161 unsigned dragging:1;
162 unsigned drawsGrid:1;
163 unsigned canSelectRow:1;
164 unsigned canSelectMultiRows:1;
165 unsigned canDeselectRow:1;
168 static W_Class tableClass = 0;
171 static W_ViewDelegate viewDelegate = {
172 NULL,
173 NULL,
174 handleResize,
175 NULL,
176 NULL
183 static void handleEvents(XEvent *event, void *data);
184 static void handleTableEvents(XEvent *event, void *data);
187 static void scrollObserver(void *self, WMNotification *notif)
189 WMTableView *table = (WMTableView*)self;
190 WMRect rect;
191 int i, x;
193 rect = WMGetScrollViewVisibleRect(table->scrollView);
195 x = 0;
196 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
197 WMTableColumn *column;
199 column = WMGetFromArray(table->columns, i);
201 WMMoveWidget(column->titleW, x - rect.pos.x, 0);
203 if (i > 0) {
204 WMView *splitter;
206 splitter = WMGetFromArray(table->splitters, i-1);
207 W_MoveView(splitter, x - rect.pos.x - 1, 0);
210 x += W_VIEW_WIDTH(WMWidgetView(column->titleW)) + 1;
215 static void splitterHandler(XEvent *event, void *data)
217 WMTableView *table = (WMTableView*)data;
218 int done = 0;
220 while (!done) {
221 XEvent ev;
223 WMMaskEvent(event->xany.display, ButtonMotionMask|ButtonReleaseMask,
224 &ev);
226 switch (event->type) {
227 case MotionNotify:
228 printf("%i\n", event->xmotion.x);
229 break;
231 case ButtonRelease:
232 done = 1;
233 break;
240 WMTableView *WMCreateTableView(WMWidget *parent)
242 WMTableView *table = wmalloc(sizeof(WMTableView));
243 WMScreen *scr = WMWidgetScreen(parent);
245 memset(table, 0, sizeof(WMTableView));
247 if (!tableClass) {
248 tableClass = W_RegisterUserWidget();
250 table->widgetClass = tableClass;
252 table->view = W_CreateView(W_VIEW(parent));
253 if (!table->view)
254 goto error;
255 table->view->self = table;
257 table->view->delegate = &viewDelegate;
259 table->headerHeight = 20;
261 table->scrollView = WMCreateScrollView(table);
262 if (!table->scrollView)
263 goto error;
264 WMResizeWidget(table->scrollView, 10, 10);
265 WMSetScrollViewHasVerticalScroller(table->scrollView, True);
266 WMSetScrollViewHasHorizontalScroller(table->scrollView, True);
268 WMScroller *scroller;
269 scroller = WMGetScrollViewHorizontalScroller(table->scrollView);
270 WMAddNotificationObserver(scrollObserver, table,
271 WMScrollerDidScrollNotification,
272 scroller);
274 WMMoveWidget(table->scrollView, 1, 2+table->headerHeight);
275 WMMapWidget(table->scrollView);
277 table->header = WMCreateFrame(table);
278 WMMoveWidget(table->header, 22, 2);
279 WMMapWidget(table->header);
280 WMSetFrameRelief(table->header, WRFlat);
282 table->corner = WMCreateLabel(table);
283 WMResizeWidget(table->corner, 20, table->headerHeight);
284 WMMoveWidget(table->corner, 2, 2);
285 WMMapWidget(table->corner);
286 WMSetLabelRelief(table->corner, WRRaised);
287 WMSetWidgetBackgroundColor(table->corner, scr->darkGray);
290 table->tableView = W_CreateView(W_VIEW(parent));
291 if (!table->tableView)
292 goto error;
293 table->tableView->self = table;
294 W_ResizeView(table->tableView, 100, 1000);
295 W_MapView(table->tableView);
297 WMSetScrollViewContentView(table->scrollView, table->tableView);
299 table->tableView->flags.dontCompressExpose = 1;
301 table->gridColor = WMCreateNamedColor(scr, "#cccccc", False);
302 /* table->gridColor = WMGrayColor(scr);*/
305 XGCValues gcv;
307 gcv.foreground = WMColorPixel(table->gridColor);
308 gcv.dashes = 1;
309 gcv.line_style = LineOnOffDash;
310 table->gridGC = XCreateGC(WMScreenDisplay(scr), W_DRAWABLE(scr),
311 GCForeground, &gcv);
314 table->editingRow = -1;
315 table->clickedRow = -1;
317 table->drawsGrid = 1;
318 table->rowHeight = 16;
320 WMSetScrollViewLineScroll(table->scrollView, table->rowHeight);
322 table->tableWidth = 1;
324 table->columns = WMCreateArray(4);
325 table->splitters = WMCreateArray(4);
327 table->selectedRows = WMCreateArray(16);
329 table->splitterCursor = XCreateFontCursor(WMScreenDisplay(scr),
330 XC_sb_h_double_arrow);
332 table->canSelectRow = 1;
334 WMCreateEventHandler(table->view, ExposureMask|StructureNotifyMask,
335 handleEvents, table);
337 WMCreateEventHandler(table->tableView, ExposureMask|ButtonPressMask|
338 ButtonReleaseMask|ButtonMotionMask,
339 handleTableEvents, table);
341 return table;
343 error:
344 if (table->scrollView)
345 WMDestroyWidget(table->scrollView);
346 if (table->tableView)
347 W_DestroyView(table->tableView);
348 if (table->view)
349 W_DestroyView(table->view);
350 wfree(table);
351 return NULL;
355 void WMAddTableViewColumn(WMTableView *table, WMTableColumn *column)
357 int width;
358 int i;
359 WMScreen *scr = WMWidgetScreen(table);
360 int count;
362 column->table = table;
364 WMAddToArray(table->columns, column);
366 if (!column->titleW) {
367 column->titleW = WMCreateLabel(table);
368 WMSetLabelRelief(column->titleW, WRRaised);
369 WMSetLabelFont(column->titleW, scr->boldFont);
370 WMSetLabelTextColor(column->titleW, scr->white);
371 WMSetWidgetBackgroundColor(column->titleW, scr->darkGray);
372 WMSetLabelText(column->titleW, column->title);
373 W_ReparentView(WMWidgetView(column->titleW),
374 WMWidgetView(table->header), 0, 0);
375 if (W_VIEW_REALIZED(table->view))
376 WMRealizeWidget(column->titleW);
377 WMMapWidget(column->titleW);
380 if (WMGetArrayItemCount(table->columns) > 1) {
381 WMView *splitter = W_CreateView(WMWidgetView(table->header));
383 W_SetViewBackgroundColor(splitter, WMWhiteColor(scr));
385 if (W_VIEW_REALIZED(table->view))
386 W_RealizeView(splitter);
388 W_ResizeView(splitter, 2, table->headerHeight-1);
389 W_MapView(splitter);
391 W_SetViewCursor(splitter, table->splitterCursor);
392 WMCreateEventHandler(splitter, ButtonPressMask,
393 splitterHandler, table);
395 WMAddToArray(table->splitters, splitter);
398 rearrangeHeader(table);
402 void WMSetTableViewHeaderHeight(WMTableView *table, unsigned height)
404 table->headerHeight = height;
406 handleResize(NULL, table->view);
410 void WMSetTableViewDelegate(WMTableView *table, WMTableViewDelegate *delegate)
412 table->delegate = delegate;
416 void WMSetTableViewAction(WMTableView *table, WMAction *action, void *clientData)
418 table->action = action;
420 table->clientData = clientData;
424 void *WMGetTableViewClickedColumn(WMTableView *table)
426 return table->clickedColumn;
430 int WMGetTableViewClickedRow(WMTableView *table)
432 return table->clickedRow;
436 WMView *WMGetTableViewDocumentView(WMTableView *table)
438 return table->tableView;
442 void *WMTableViewDataForCell(WMTableView *table, WMTableColumn *column,
443 int row)
445 return (*table->delegate->valueForCell)(table->delegate, column, row);
449 void WMSetTableViewDataForCell(WMTableView *table, WMTableColumn *column,
450 int row, void *data)
452 (*table->delegate->setValueForCell)(table->delegate, column, row, data);
456 WMRect WMTableViewRectForCell(WMTableView *table, WMTableColumn *column,
457 int row)
459 WMRect rect;
460 int i;
462 rect.pos.x = 0;
463 rect.pos.y = row * table->rowHeight;
464 rect.size.height = table->rowHeight;
466 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
467 WMTableColumn *col;
468 col = WMGetFromArray(table->columns, i);
470 if (col == column) {
471 rect.size.width = col->width;
472 break;
475 rect.pos.x += col->width;
477 return rect;
481 void WMSetTableViewDataSource(WMTableView *table, void *source)
483 table->dataSource = source;
487 void *WMGetTableViewDataSource(WMTableView *table)
489 return table->dataSource;
493 void WMSetTableViewBackgroundColor(WMTableView *table, WMColor *color)
495 W_SetViewBackgroundColor(table->tableView, color);
499 void WMSetTableViewGridColor(WMTableView *table, WMColor *color)
501 WMReleaseColor(table->gridColor);
502 table->gridColor = WMRetainColor(color);
503 XSetForeground(WMScreenDisplay(WMWidgetScreen(table)), table->gridGC,
504 WMColorPixel(color));
509 void WMSetTableViewRowHeight(WMTableView *table, int height)
511 table->rowHeight = height;
513 WMRedisplayWidget(table);
517 void WMScrollTableViewRowToVisible(WMTableView *table, int row)
519 WMScroller *scroller;
520 WMRange range;
521 WMRect rect;
522 int newY, tmp;
524 rect = WMGetScrollViewVisibleRect(table->scrollView);
525 range = rowsInRect(table, rect);
527 scroller = WMGetScrollViewVerticalScroller(table->scrollView);
529 if (row < range.position) {
530 newY = row * table->rowHeight - rect.size.height / 2;
531 } else if (row >= range.position + range.count) {
532 newY = row * table->rowHeight - rect.size.height / 2;
533 } else {
534 return;
536 tmp = table->rows*table->rowHeight - rect.size.height;
537 newY = WMAX(0, WMIN(newY, tmp));
538 WMScrollViewScrollPoint(table->scrollView, rect.pos.x, newY);
543 static void drawGrid(WMTableView *table, WMRect rect)
545 WMScreen *scr = WMWidgetScreen(table);
546 Display *dpy = WMScreenDisplay(scr);
547 int i;
548 int y1, y2;
549 int x1, x2;
550 int xx;
551 Drawable d = W_VIEW_DRAWABLE(table->tableView);
552 GC gc = table->gridGC;
554 #if 0
555 char dashl[1] = {1};
557 XSetDashes(dpy, gc, 0, dashl, 1);
559 y1 = (rect.pos.y/table->rowHeight - 1)*table->rowHeight;
560 y2 = y1 + (rect.size.height/table->rowHeight+2)*table->rowHeight;
561 #endif
562 y1 = 0;
563 y2 = W_VIEW_HEIGHT(table->tableView);
565 xx = 0;
566 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
567 WMTableColumn *column;
569 if (xx >= rect.pos.x && xx <= rect.pos.x+rect.size.width) {
570 XDrawLine(dpy, d, gc, xx, y1, xx, y2);
572 column = WMGetFromArray(table->columns, i);
573 xx += column->width;
575 XDrawLine(dpy, d, gc, xx, y1, xx, y2);
578 x1 = rect.pos.x;
579 x2 = WMIN(x1 + rect.size.width, xx);
581 if (x2 <= x1)
582 return;
583 #if 0
584 XSetDashes(dpy, gc, (rect.pos.x&1), dashl, 1);
585 #endif
587 y1 = rect.pos.y - rect.pos.y%table->rowHeight;
588 y2 = y1 + rect.size.height + table->rowHeight;
590 for (i = y1; i <= y2; i += table->rowHeight) {
591 XDrawLine(dpy, d, gc, x1, i, x2, i);
596 static WMRange columnsInRect(WMTableView *table, WMRect rect)
598 WMTableColumn *column;
599 int width;
600 int i , j;
601 int totalColumns = WMGetArrayItemCount(table->columns);
602 WMRange range;
604 j = 0;
605 width = 0;
606 for (i = 0; i < totalColumns; i++) {
607 column = WMGetFromArray(table->columns, i);
608 if (j == 0) {
609 if (width <= rect.pos.x && width + column->width > rect.pos.x) {
610 range.position = i;
611 j = 1;
613 } else {
614 if (width > rect.pos.x + rect.size.width) {
615 range.count = i - range.position;
616 break;
619 width += column->width;
622 range.count = WMAX(1, WMIN(range.count, totalColumns - range.position));
624 return range;
628 static WMRange rowsInRect(WMTableView *table, WMRect rect)
630 WMRange range;
631 int rh = table->rowHeight;
632 int dif;
634 dif = rect.pos.y % rh;
636 range.position = WMAX(0, (rect.pos.y - dif) / rh);
637 range.count = WMAX(1, WMIN((rect.size.height + dif) / rh, table->rows));
639 return range;
643 static void drawRow(WMTableView *table, int row, WMRect clipRect)
645 int i;
646 WMRange cols = columnsInRect(table, clipRect);
647 WMTableColumn *column;
649 for (i = cols.position; i < cols.position+cols.count; i++) {
650 column = WMGetFromArray(table->columns, i);
652 wassertr(column->delegate && column->delegate->drawCell);
654 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
655 (*column->delegate->drawSelectedCell)(column->delegate, column, row);
656 else
657 (*column->delegate->drawCell)(column->delegate, column, row);
662 static void drawFullRow(WMTableView *table, int row)
664 int i;
665 WMTableColumn *column;
667 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
668 column = WMGetFromArray(table->columns, i);
670 wassertr(column->delegate && column->delegate->drawCell);
672 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound)
673 (*column->delegate->drawSelectedCell)(column->delegate, column, row);
674 else
675 (*column->delegate->drawCell)(column->delegate, column, row);
680 static void setRowSelected(WMTableView *table, unsigned row, Bool flag)
682 int repaint = 0;
685 if (WMFindInArray(table->selectedRows, NULL, (void*)row) != WANotFound) {
686 if (!flag) {
687 WMRemoveFromArray(table->selectedRows, (void*)row);
688 repaint = 1;
690 } else {
691 if (flag) {
692 WMAddToArray(table->selectedRows, (void*)row);
693 repaint = 1;
697 if (repaint) {
698 drawFullRow(table, row);
703 static void repaintTable(WMTableView *table, int x, int y,
704 int width, int height)
706 WMRect rect;
707 WMRange rows;
708 int i;
710 wassertr(table->delegate && table->delegate->numberOfRows);
711 i = (*table->delegate->numberOfRows)(table->delegate, table);
713 if (i != table->rows) {
714 table->rows = i;
715 W_ResizeView(table->tableView, table->tableWidth,
716 table->rows * table->rowHeight + 1);
720 rect.pos = wmkpoint(x,y);
721 rect.size = wmksize(width, height);
723 if (table->drawsGrid) {
724 drawGrid(table, rect);
727 rows = rowsInRect(table, rect);
728 for (i = rows.position;
729 i < WMIN(rows.position+rows.count + 1, table->rows);
730 i++) {
731 drawRow(table, i, rect);
736 static void stopRowEdit(WMTableView *table, int row)
738 int i;
739 WMTableColumn *column;
741 table->editingRow = -1;
742 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
743 column = WMGetFromArray(table->columns, i);
745 if (column->delegate && column->delegate->endCellEdit)
746 (*column->delegate->endCellEdit)(column->delegate, column, row);
752 void WMEditTableViewRow(WMTableView *table, int row)
754 int i;
755 WMTableColumn *column;
757 if (table->editingRow >= 0) {
758 stopRowEdit(table, table->editingRow);
761 table->editingRow = row;
762 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
763 column = WMGetFromArray(table->columns, i);
765 if (column->delegate && column->delegate->beginCellEdit)
766 (*column->delegate->beginCellEdit)(column->delegate, column, row);
771 void WMSelectTableViewRow(WMTableView *table, int row)
773 if (table->clickedRow >= 0)
774 setRowSelected(table, table->clickedRow, False);
776 if (row <= table->rows)
777 return;
779 setRowSelected(table, row, True);
780 table->clickedRow = row;
782 if (table->action)
783 (*table->action)(table, table->clientData);
784 WMPostNotificationName(WMTableViewSelectionDidChangeNotification,
785 table, NULL);
789 void WMReloadTableView(WMTableView *table)
791 WMRect rect = WMGetScrollViewVisibleRect(table->scrollView);
793 repaintTable(table, 0, 0,
794 W_VIEW_WIDTH(table->tableView), rect.size.height);
796 table->clickedRow = -1;
800 void WMNoteTableViewNumberOfRowsChanged(WMTableView *table)
802 WMReloadTableView(table);
806 static void handleTableEvents(XEvent *event, void *data)
808 WMTableView *table = (WMTableView*)data;
809 int row;
811 switch (event->type) {
812 case ButtonPress:
813 if (event->xbutton.button == Button1) {
814 row = event->xbutton.y/table->rowHeight;
815 if (row != table->clickedRow) {
816 setRowSelected(table, table->clickedRow, False);
817 setRowSelected(table, row, True);
818 table->clickedRow = row;
819 table->dragging = 1;
822 break;
824 case MotionNotify:
825 if (table->dragging && event->xmotion.y >= 0) {
826 row = event->xmotion.y/table->rowHeight;
827 if (table->clickedRow != row && row >= 0) {
828 setRowSelected(table, table->clickedRow, False);
829 setRowSelected(table, row, True);
830 table->clickedRow = row;
833 break;
835 case ButtonRelease:
836 if (event->xbutton.button == Button1) {
837 if (table->action)
838 (*table->action)(table, table->clientData);
839 WMPostNotificationName(WMTableViewSelectionDidChangeNotification,
840 table, NULL);
841 table->dragging = 0;
843 break;
845 case Expose:
846 repaintTable(table, event->xexpose.x, event->xexpose.y,
847 event->xexpose.width, event->xexpose.height);
848 break;
853 static void handleEvents(XEvent *event, void *data)
855 WMTableView *table = (WMTableView*)data;
856 WMScreen *scr = WMWidgetScreen(table);
858 switch (event->type) {
859 case Expose:
860 W_DrawRelief(scr, W_VIEW_DRAWABLE(table->view), 0, 0,
861 W_VIEW_WIDTH(table->view), W_VIEW_HEIGHT(table->view),
862 WRSunken);
864 if (event->xexpose.serial == 0) {
865 WMRect rect = WMGetScrollViewVisibleRect(table->scrollView);
867 repaintTable(table, rect.pos.x, rect.pos.y,
868 rect.size.width, rect.size.height);
870 break;
875 static void handleResize(W_ViewDelegate *self, WMView *view)
877 int width;
878 int height;
879 WMTableView *table = view->self;
881 width = W_VIEW_WIDTH(view) - 2;
882 height = W_VIEW_HEIGHT(view) - 3;
884 height -= table->headerHeight; /* table header */
886 if (table->corner)
887 WMResizeWidget(table->corner, 20, table->headerHeight);
889 if (table->scrollView) {
890 WMMoveWidget(table->scrollView, 1, table->headerHeight + 2);
891 WMResizeWidget(table->scrollView, width, height);
893 if (table->header)
894 WMResizeWidget(table->header, width - 21, table->headerHeight);
898 static void rearrangeHeader(WMTableView *table)
900 int width;
901 int count;
902 int i;
903 width = 0;
905 count = WMGetArrayItemCount(table->columns);
906 for (i = 0; i < count; i++) {
907 WMTableColumn *column = WMGetFromArray(table->columns, i);
909 WMMoveWidget(column->titleW, width, 0);
910 WMResizeWidget(column->titleW, column->width-1, table->headerHeight);
912 if (i > 0) {
913 WMView *splitter = WMGetFromArray(table->splitters, i-1);
915 W_MoveView(splitter, width-1, 0);
917 width += column->width;
920 wassertr(table->delegate && table->delegate->numberOfRows);
922 table->rows = table->delegate->numberOfRows(table->delegate, table);
924 W_ResizeView(table->tableView, width+1,
925 table->rows * table->rowHeight + 1);
927 table->tableWidth = width + 1;