Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / Extras / wtableview.c
1
2 #include <WINGs/WINGsP.h>
3 #include <X11/cursorfont.h>
4 #include <stdint.h>
5
6 #include "wtableview.h"
7
8 const char *WMTableViewSelectionDidChangeNotification = "WMTableViewSelectionDidChangeNotification";
9
10 struct W_TableColumn {
11         WMTableView *table;
12         WMWidget *titleW;
13         char *title;
14         int width;
15         int minWidth;
16         int maxWidth;
17
18         void *id;
19
20         WMTableColumnDelegate *delegate;
21
22         unsigned resizable:1;
23         unsigned editable:1;
24 };
25
26 static void handleResize(W_ViewDelegate * self, WMView * view);
27
28 static void rearrangeHeader(WMTableView * table);
29
30 static WMRange rowsInRect(WMTableView * table, WMRect rect);
31
32 WMTableColumn *WMCreateTableColumn(char *title)
33 {
34         WMTableColumn *col = wmalloc(sizeof(WMTableColumn));
35
36         col->table = NULL;
37         col->titleW = NULL;
38         col->width = 50;
39         col->minWidth = 5;
40         col->maxWidth = 0;
41
42         col->id = NULL;
43
44         col->title = wstrdup(title);
45
46         col->delegate = NULL;
47
48         col->resizable = 1;
49         col->editable = 0;
50
51         return col;
52 }
53
54 void WMSetTableColumnId(WMTableColumn * column, void *id)
55 {
56         column->id = id;
57 }
58
59 void *WMGetTableColumnId(WMTableColumn * column)
60 {
61         return column->id;
62 }
63
64 void WMSetTableColumnWidth(WMTableColumn * column, unsigned width)
65 {
66         if (column->maxWidth == 0)
67                 column->width = WMAX(column->minWidth, width);
68         else
69                 column->width = WMAX(column->minWidth, WMIN(column->maxWidth, width));
70
71         if (column->table) {
72                 rearrangeHeader(column->table);
73         }
74 }
75
76 void WMSetTableColumnDelegate(WMTableColumn * column, WMTableColumnDelegate * delegate)
77 {
78         column->delegate = delegate;
79 }
80
81 void WMSetTableColumnConstraints(WMTableColumn * column, unsigned minWidth, unsigned maxWidth)
82 {
83         wassertr(maxWidth == 0 || minWidth <= maxWidth);
84
85         column->minWidth = minWidth;
86         column->maxWidth = maxWidth;
87
88         if (column->width < column->minWidth)
89                 WMSetTableColumnWidth(column, column->minWidth);
90         else if (column->width > column->maxWidth && column->maxWidth != 0)
91                 WMSetTableColumnWidth(column, column->maxWidth);
92 }
93
94 void WMSetTableColumnEditable(WMTableColumn * column, Bool flag)
95 {
96         column->editable = ((flag == 0) ? 0 : 1);
97 }
98
99 WMTableView *WMGetTableColumnTableView(WMTableColumn * column)
100 {
101         return column->table;
102 }
103
104 struct W_TableView {
105         W_Class widgetClass;
106         WMView *view;
107
108         WMFrame *header;
109
110         WMLabel *corner;
111
112         WMScroller *hscroll;
113         WMScroller *vscroll;
114         WMView *tableView;
115
116         WMPixmap *viewBuffer;
117
118         WMArray *columns;
119         WMArray *splitters;
120
121         WMArray *selectedRows;
122
123         int tableWidth;
124
125         int rows;
126
127         WMColor *backColor;
128
129         GC gridGC;
130         WMColor *gridColor;
131
132         Cursor splitterCursor;
133
134         void *dataSource;
135
136         WMTableViewDelegate *delegate;
137
138         WMAction *action;
139         void *clientData;
140
141         void *clickedColumn;
142         int clickedRow;
143
144         int editingRow;
145
146         unsigned headerHeight;
147
148         unsigned rowHeight;
149
150         unsigned dragging:1;
151         unsigned drawsGrid:1;
152         unsigned canSelectRow:1;
153         unsigned canSelectMultiRows:1;
154         unsigned canDeselectRow:1;
155
156         unsigned int hasVScroller:1;
157         unsigned int hasHScroller:1;
158 };
159
160 static W_Class tableClass = 0;
161
162 static W_ViewDelegate viewDelegate = {
163         NULL,
164         NULL,
165         handleResize,
166         NULL,
167         NULL
168 };
169
170 static void reorganizeInterior(WMTableView * table);
171
172 static void handleEvents(XEvent * event, void *data);
173 static void handleTableEvents(XEvent * event, void *data);
174 static void repaintTable(WMTableView * table);
175
176 static WMSize getTotalSize(WMTableView * table)
177 {
178         WMSize size;
179         int i;
180
181         /* get width from columns */
182         size.width = 0;
183         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
184                 WMTableColumn *column;
185
186                 column = WMGetFromArray(table->columns, i);
187
188                 size.width += column->width;
189         }
190
191         /* get height from rows */
192         size.height = table->rows * table->rowHeight;
193
194         return size;
195 }
196
197 static WMRect getVisibleRect(WMTableView * table)
198 {
199         WMSize size = getTotalSize(table);
200         WMRect rect;
201
202         if (table->vscroll) {
203                 rect.size.height = size.height * WMGetScrollerKnobProportion(table->vscroll);
204                 rect.pos.y = (size.height - rect.size.height) * WMGetScrollerValue(table->vscroll);
205         } else {
206                 rect.size.height = size.height;
207                 rect.pos.y = 0;
208         }
209
210         if (table->hscroll) {
211                 rect.size.width = size.width * WMGetScrollerKnobProportion(table->hscroll);
212                 rect.pos.x = (size.width - rect.size.width) * WMGetScrollerValue(table->hscroll);
213         } else {
214                 rect.size.width = size.width;
215                 rect.pos.x = 0;
216         }
217
218         return rect;
219 }
220
221 static void scrollToPoint(WMTableView * table, int x, int y)
222 {
223         WMSize size = getTotalSize(table);
224         int i;
225         float value, prop;
226
227         if (table->hscroll) {
228                 if (size.width > W_VIEW_WIDTH(table->tableView)) {
229                         prop = (float)W_VIEW_WIDTH(table->tableView) / (float)size.width;
230                         value = (float)x / (float)(size.width - W_VIEW_WIDTH(table->tableView));
231                 } else {
232                         prop = 1.0;
233                         value = 0.0;
234                 }
235                 WMSetScrollerParameters(table->hscroll, value, prop);
236         }
237
238         if (table->vscroll) {
239                 if (size.height > W_VIEW_HEIGHT(table->tableView)) {
240                         prop = (float)W_VIEW_HEIGHT(table->tableView) / (float)size.height;
241                         value = (float)y / (float)(size.height - W_VIEW_HEIGHT(table->tableView));
242                 } else {
243                         prop = 1.0;
244                         value = 0.0;
245                 }
246
247                 WMSetScrollerParameters(table->vscroll, value, prop);
248         }
249
250         if (table->editingRow >= 0) {
251                 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
252                         WMTableColumn *column;
253
254                         column = WMGetFromArray(table->columns, i);
255
256                         if (column->delegate && column->delegate->beginCellEdit)
257                                 (*column->delegate->beginCellEdit) (column->delegate, column, table->editingRow);
258                 }
259         }
260
261         repaintTable(table);
262 }
263
264 static void adjustScrollers(WMTableView * table)
265 {
266         WMSize size = getTotalSize(table);
267         WMSize vsize = WMGetViewSize(table->tableView);
268         float prop, value;
269         float oprop, ovalue;
270
271         if (table->hscroll) {
272                 if (size.width <= vsize.width) {
273                         value = 0.0;
274                         prop = 1.0;
275                 } else {
276                         oprop = WMGetScrollerKnobProportion(table->hscroll);
277                         if (oprop == 0.0)
278                                 oprop = 1.0;
279                         ovalue = WMGetScrollerValue(table->hscroll);
280
281                         prop = (float)vsize.width / (float)size.width;
282                         value = prop * ovalue / oprop;
283                 }
284                 WMSetScrollerParameters(table->hscroll, value, prop);
285         }
286
287         if (table->vscroll) {
288                 if (size.height <= vsize.height) {
289                         value = 0.0;
290                         prop = 1.0;
291                 } else {
292                         oprop = WMGetScrollerKnobProportion(table->vscroll);
293                         if (oprop == 0.0)
294                                 oprop = 1.0;
295                         ovalue = WMGetScrollerValue(table->vscroll);
296
297                         prop = (float)vsize.height / (float)size.height;
298                         value = prop * ovalue / oprop;
299                 }
300                 WMSetScrollerParameters(table->vscroll, value, prop);
301         }
302 }
303
304 static void doScroll(WMWidget * self, void *data)
305 {
306         WMTableView *table = (WMTableView *) data;
307         float value;
308         float vpsize;
309         float size;
310         WMSize ts = getTotalSize(table);
311
312         value = WMGetScrollerValue(self);
313
314         if (table->hscroll == (WMScroller *) self) {
315                 vpsize = W_VIEW_WIDTH(table->tableView);
316                 size = ts.width;
317         } else {
318                 vpsize = W_VIEW_HEIGHT(table->tableView);
319                 size = ts.height;
320         }
321
322         switch (WMGetScrollerHitPart(self)) {
323         case WSDecrementWheel:
324         case WSDecrementLine:
325                 value -= (float)table->rowHeight / size;
326                 if (value < 0)
327                         value = 0.0;
328                 WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
329                 repaintTable(table);
330                 break;
331
332         case WSIncrementWheel:
333         case WSIncrementLine:
334                 value += (float)table->rowHeight / size;
335                 if (value > 1.0)
336                         value = 1.0;
337                 WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
338                 repaintTable(table);
339                 break;
340
341         case WSKnob:
342                 repaintTable(table);
343                 break;
344
345         case WSDecrementPage:
346                 value -= vpsize / size;
347                 if (value < 0.0)
348                         value = 0.0;
349                 WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
350                 repaintTable(table);
351                 break;
352
353         case WSIncrementPage:
354                 value += vpsize / size;
355                 if (value > 1.0)
356                         value = 1.0;
357                 WMSetScrollerParameters(self, value, WMGetScrollerKnobProportion(self));
358                 repaintTable(table);
359                 break;
360
361         case WSNoPart:
362         case WSKnobSlot:
363                 break;
364         }
365
366         if (table->editingRow >= 0) {
367                 int i;
368                 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
369                         WMTableColumn *column;
370
371                         column = WMGetFromArray(table->columns, i);
372
373                         if (column->delegate && column->delegate->beginCellEdit)
374                                 (*column->delegate->beginCellEdit) (column->delegate, column, table->editingRow);
375                 }
376         }
377
378         if (table->hscroll == self) {
379                 int x = 0;
380                 int i;
381                 WMRect rect = getVisibleRect(table);
382
383                 for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
384                         WMTableColumn *column;
385                         WMView *splitter;
386
387                         column = WMGetFromArray(table->columns, i);
388
389                         WMMoveWidget(column->titleW, x - rect.pos.x, 0);
390
391                         x += W_VIEW_WIDTH(WMWidgetView(column->titleW)) + 1;
392
393                         splitter = WMGetFromArray(table->splitters, i);
394                         W_MoveView(splitter, x - rect.pos.x - 1, 0);
395                 }
396         }
397 }
398
399 static void splitterHandler(XEvent * event, void *data)
400 {
401         WMTableColumn *column = (WMTableColumn *) data;
402         WMTableView *table = column->table;
403         int done = 0;
404         int cx, ox, offsX;
405         WMPoint pos;
406         WMScreen *scr = WMWidgetScreen(table);
407         GC gc = scr->ixorGC;
408         Display *dpy = WMScreenDisplay(scr);
409         int h = WMWidgetHeight(table) - 22;
410         Window w = WMViewXID(table->view);
411
412         pos = WMGetViewPosition(WMWidgetView(column->titleW));
413
414         offsX = pos.x + column->width;
415
416         ox = cx = offsX;
417
418         XDrawLine(dpy, w, gc, cx + 20, 0, cx + 20, h);
419
420         while (!done) {
421                 XEvent ev;
422
423                 WMMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask, &ev);
424
425                 switch (ev.type) {
426                 case MotionNotify:
427                         ox = cx;
428
429                         if (column->width + ev.xmotion.x < column->minWidth)
430                                 cx = pos.x + column->minWidth;
431                         else if (column->maxWidth > 0 && column->width + ev.xmotion.x > column->maxWidth)
432                                 cx = pos.x + column->maxWidth;
433                         else
434                                 cx = offsX + ev.xmotion.x;
435
436                         XDrawLine(dpy, w, gc, ox + 20, 0, ox + 20, h);
437                         XDrawLine(dpy, w, gc, cx + 20, 0, cx + 20, h);
438                         break;
439
440                 case ButtonRelease:
441                         column->width = cx - pos.x;
442                         done = 1;
443                         break;
444                 }
445         }
446
447         XDrawLine(dpy, w, gc, cx + 20, 0, cx + 20, h);
448         rearrangeHeader(table);
449         repaintTable(table);
450 }
451
452 static void realizeTable(void *data, WMNotification * notif)
453 {
454         repaintTable(data);
455 }
456
457 WMTableView *WMCreateTableView(WMWidget * parent)
458 {
459         WMTableView *table = wmalloc(sizeof(WMTableView));
460         WMScreen *scr = WMWidgetScreen(parent);
461
462         memset(table, 0, sizeof(WMTableView));
463
464         if (!tableClass) {
465                 tableClass = W_RegisterUserWidget();
466         }
467         table->widgetClass = tableClass;
468
469         table->view = W_CreateView(W_VIEW(parent));
470         if (!table->view)
471                 goto error;
472         table->view->self = table;
473
474         table->view->delegate = &viewDelegate;
475
476         table->headerHeight = 20;
477
478         table->hscroll = WMCreateScroller(table);
479         WMSetScrollerAction(table->hscroll, doScroll, table);
480         WMMoveWidget(table->hscroll, 1, 2 + table->headerHeight);
481         WMMapWidget(table->hscroll);
482
483         table->hasHScroller = 1;
484
485         table->vscroll = WMCreateScroller(table);
486         WMSetScrollerArrowsPosition(table->vscroll, WSAMaxEnd);
487         WMSetScrollerAction(table->vscroll, doScroll, table);
488         WMMoveWidget(table->vscroll, 1, 2 + table->headerHeight);
489         WMMapWidget(table->vscroll);
490
491         table->hasVScroller = 1;
492
493         table->header = WMCreateFrame(table);
494         WMMoveWidget(table->header, 22, 2);
495         WMMapWidget(table->header);
496         WMSetFrameRelief(table->header, WRFlat);
497
498         table->corner = WMCreateLabel(table);
499         WMResizeWidget(table->corner, 20, table->headerHeight);
500         WMMoveWidget(table->corner, 2, 2);
501         WMMapWidget(table->corner);
502         WMSetLabelRelief(table->corner, WRRaised);
503         WMSetWidgetBackgroundColor(table->corner, scr->darkGray);
504
505         table->tableView = W_CreateView(table->view);
506         if (!table->tableView)
507                 goto error;
508         table->tableView->self = table;
509         W_MapView(table->tableView);
510
511         WMAddNotificationObserver(realizeTable, table, WMViewRealizedNotification, table->tableView);
512
513         table->tableView->flags.dontCompressExpose = 1;
514
515         table->gridColor = WMCreateNamedColor(scr, "#cccccc", False);
516         /*   table->gridColor = WMGrayColor(scr); */
517
518         {
519                 XGCValues gcv;
520
521                 table->backColor = WMWhiteColor(scr);
522
523                 gcv.foreground = WMColorPixel(table->gridColor);
524                 gcv.dashes = 1;
525                 gcv.line_style = LineOnOffDash;
526                 table->gridGC = XCreateGC(WMScreenDisplay(scr), W_DRAWABLE(scr), GCForeground, &gcv);
527         }
528
529         table->editingRow = -1;
530         table->clickedRow = -1;
531
532         table->drawsGrid = 1;
533         table->rowHeight = 16;
534
535         table->tableWidth = 1;
536
537         table->columns = WMCreateArray(4);
538         table->splitters = WMCreateArray(4);
539
540         table->selectedRows = WMCreateArray(16);
541
542         table->splitterCursor = XCreateFontCursor(WMScreenDisplay(scr), XC_sb_h_double_arrow);
543
544         table->canSelectRow = 1;
545
546         WMCreateEventHandler(table->view, ExposureMask | StructureNotifyMask, handleEvents, table);
547
548         WMCreateEventHandler(table->tableView, ExposureMask | ButtonPressMask |
549                              ButtonReleaseMask | ButtonMotionMask, handleTableEvents, table);
550
551         WMResizeWidget(table, 50, 50);
552
553         return table;
554
555  error:
556         if (table->tableView)
557                 W_DestroyView(table->tableView);
558         if (table->view)
559                 W_DestroyView(table->view);
560         wfree(table);
561         return NULL;
562 }
563
564 void WMAddTableViewColumn(WMTableView * table, WMTableColumn * column)
565 {
566         WMScreen *scr = WMWidgetScreen(table);
567
568         column->table = table;
569
570         WMAddToArray(table->columns, column);
571
572         if (!column->titleW) {
573                 column->titleW = WMCreateLabel(table);
574                 WMSetLabelRelief(column->titleW, WRRaised);
575                 WMSetLabelFont(column->titleW, scr->boldFont);
576                 WMSetLabelTextColor(column->titleW, scr->white);
577                 WMSetWidgetBackgroundColor(column->titleW, scr->darkGray);
578                 WMSetLabelText(column->titleW, column->title);
579                 W_ReparentView(WMWidgetView(column->titleW), WMWidgetView(table->header), 0, 0);
580                 if (W_VIEW_REALIZED(table->view))
581                         WMRealizeWidget(column->titleW);
582                 WMMapWidget(column->titleW);
583         }
584
585         {
586                 WMView *splitter = W_CreateView(WMWidgetView(table->header));
587
588                 W_SetViewBackgroundColor(splitter, WMWhiteColor(scr));
589
590                 if (W_VIEW_REALIZED(table->view))
591                         W_RealizeView(splitter);
592
593                 W_ResizeView(splitter, 2, table->headerHeight);
594                 W_MapView(splitter);
595
596                 W_SetViewCursor(splitter, table->splitterCursor);
597                 WMCreateEventHandler(splitter, ButtonPressMask | ButtonReleaseMask, splitterHandler, column);
598
599                 WMAddToArray(table->splitters, splitter);
600         }
601
602         rearrangeHeader(table);
603 }
604
605 void WMSetTableViewHeaderHeight(WMTableView * table, unsigned height)
606 {
607         table->headerHeight = height;
608
609         handleResize(NULL, table->view);
610 }
611
612 void WMSetTableViewDelegate(WMTableView * table, WMTableViewDelegate * delegate)
613 {
614         table->delegate = delegate;
615 }
616
617 void WMSetTableViewAction(WMTableView * table, WMAction * action, void *clientData)
618 {
619         table->action = action;
620
621         table->clientData = clientData;
622 }
623
624 void *WMGetTableViewClickedColumn(WMTableView * table)
625 {
626         return table->clickedColumn;
627 }
628
629 int WMGetTableViewClickedRow(WMTableView * table)
630 {
631         return table->clickedRow;
632 }
633
634 WMArray *WMGetTableViewSelectedRows(WMTableView * table)
635 {
636         return table->selectedRows;
637 }
638
639 WMView *WMGetTableViewDocumentView(WMTableView * table)
640 {
641         return table->tableView;
642 }
643
644 void *WMTableViewDataForCell(WMTableView * table, WMTableColumn * column, int row)
645 {
646         return (*table->delegate->valueForCell) (table->delegate, column, row);
647 }
648
649 void WMSetTableViewDataForCell(WMTableView * table, WMTableColumn * column, int row, void *data)
650 {
651         (*table->delegate->setValueForCell) (table->delegate, column, row, data);
652 }
653
654 WMRect WMTableViewRectForCell(WMTableView * table, WMTableColumn * column, int row)
655 {
656         WMRect rect;
657         int i;
658
659         rect.pos.x = 0;
660         rect.pos.y = row * table->rowHeight;
661         rect.size.height = table->rowHeight;
662
663         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
664                 WMTableColumn *col;
665                 col = WMGetFromArray(table->columns, i);
666
667                 if (col == column) {
668                         rect.size.width = col->width;
669                         break;
670                 }
671
672                 rect.pos.x += col->width;
673         }
674
675         {
676                 WMRect r = getVisibleRect(table);
677
678                 rect.pos.y -= r.pos.y;
679                 rect.pos.x -= r.pos.x;
680         }
681
682         return rect;
683 }
684
685 void WMSetTableViewDataSource(WMTableView * table, void *source)
686 {
687         table->dataSource = source;
688 }
689
690 void *WMGetTableViewDataSource(WMTableView * table)
691 {
692         return table->dataSource;
693 }
694
695 void WMSetTableViewHasHorizontalScroller(WMTableView * tPtr, Bool flag)
696 {
697         if (flag) {
698                 if (tPtr->hasHScroller)
699                         return;
700                 tPtr->hasHScroller = 1;
701
702                 tPtr->hscroll = WMCreateScroller(tPtr);
703                 WMSetScrollerAction(tPtr->hscroll, doScroll, tPtr);
704                 WMSetScrollerArrowsPosition(tPtr->hscroll, WSAMaxEnd);
705                 /* make it a horiz. scroller */
706                 WMResizeWidget(tPtr->hscroll, 1, 2);
707
708                 if (W_VIEW_REALIZED(tPtr->view)) {
709                         WMRealizeWidget(tPtr->hscroll);
710                 }
711
712                 reorganizeInterior(tPtr);
713
714                 WMMapWidget(tPtr->hscroll);
715         } else {
716                 if (!tPtr->hasHScroller)
717                         return;
718                 tPtr->hasHScroller = 0;
719
720                 WMUnmapWidget(tPtr->hscroll);
721                 WMDestroyWidget(tPtr->hscroll);
722                 tPtr->hscroll = NULL;
723
724                 reorganizeInterior(tPtr);
725         }
726 }
727
728 #if 0
729 /* not supported by now */
730 void WMSetTableViewHasVerticalScroller(WMTableView * tPtr, Bool flag)
731 {
732         if (flag) {
733                 if (tPtr->hasVScroller)
734                         return;
735                 tPtr->hasVScroller = 1;
736
737                 tPtr->vscroll = WMCreateScroller(tPtr);
738                 WMSetScrollerAction(tPtr->vscroll, doScroll, tPtr);
739                 WMSetScrollerArrowsPosition(tPtr->vscroll, WSAMaxEnd);
740                 /* make it a vert. scroller */
741                 WMResizeWidget(tPtr->vscroll, 1, 2);
742
743                 if (W_VIEW_REALIZED(tPtr->view)) {
744                         WMRealizeWidget(tPtr->vscroll);
745                 }
746
747                 reorganizeInterior(tPtr);
748
749                 WMMapWidget(tPtr->vscroll);
750         } else {
751                 if (!tPtr->hasVScroller)
752                         return;
753                 tPtr->hasVScroller = 0;
754
755                 WMUnmapWidget(tPtr->vscroll);
756                 WMDestroyWidget(tPtr->vscroll);
757                 tPtr->vscroll = NULL;
758
759                 reorganizeInterior(tPtr);
760         }
761 }
762 #endif
763
764 void WMSetTableViewBackgroundColor(WMTableView * table, WMColor * color)
765 {
766         W_SetViewBackgroundColor(table->tableView, color);
767
768         if (table->backColor)
769                 WMReleaseColor(table->backColor);
770
771         table->backColor = WMRetainColor(color);
772
773         repaintTable(table);
774 }
775
776 void WMSetTableViewGridColor(WMTableView * table, WMColor * color)
777 {
778         WMReleaseColor(table->gridColor);
779         table->gridColor = WMRetainColor(color);
780         XSetForeground(WMScreenDisplay(WMWidgetScreen(table)), table->gridGC, WMColorPixel(color));
781         repaintTable(table);
782 }
783
784 void WMSetTableViewRowHeight(WMTableView * table, int height)
785 {
786         table->rowHeight = height;
787
788         repaintTable(table);
789 }
790
791 void WMScrollTableViewRowToVisible(WMTableView * table, int row)
792 {
793         WMScroller *scroller;
794         WMRange range;
795         WMRect rect;
796         int newY, tmp;
797
798         rect = getVisibleRect(table);
799         range = rowsInRect(table, rect);
800
801         scroller = table->vscroll;
802
803         if (row < range.position) {
804                 newY = row * table->rowHeight - rect.size.height / 2;
805         } else if (row >= range.position + range.count) {
806                 newY = row * table->rowHeight - rect.size.height / 2;
807         } else {
808                 return;
809         }
810         tmp = table->rows * table->rowHeight - rect.size.height;
811         newY = WMAX(0, WMIN(newY, tmp));
812
813         scrollToPoint(table, rect.pos.x, newY);
814 }
815
816 static void drawGrid(WMTableView * table, WMRect rect)
817 {
818         WMScreen *scr = WMWidgetScreen(table);
819         Display *dpy = WMScreenDisplay(scr);
820         int i;
821         int y1, y2;
822         int x1, x2;
823         int xx;
824         Drawable d = WMGetPixmapXID(table->viewBuffer);
825         GC gc = table->gridGC;
826
827 #if 0
828         char dashl[1] = { 1 };
829
830         XSetDashes(dpy, gc, 0, dashl, 1);
831
832         y1 = (rect.pos.y / table->rowHeight - 1) * table->rowHeight;
833         y2 = y1 + (rect.size.height / table->rowHeight + 2) * table->rowHeight;
834 #endif
835         y1 = 0;
836         y2 = W_VIEW_HEIGHT(table->tableView);
837
838         xx = -rect.pos.x;
839         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
840                 WMTableColumn *column;
841
842                 XDrawLine(dpy, d, gc, xx, y1, xx, y2);
843
844                 column = WMGetFromArray(table->columns, i);
845                 xx += column->width;
846         }
847         XDrawLine(dpy, d, gc, xx, y1, xx, y2);
848
849         x1 = 0;
850         x2 = rect.size.width;
851
852         if (x2 <= x1)
853                 return;
854 #if 0
855         XSetDashes(dpy, gc, (rect.pos.x & 1), dashl, 1);
856 #endif
857
858         y1 = -rect.pos.y % table->rowHeight;
859         y2 = y1 + rect.size.height + table->rowHeight;
860
861         for (i = y1; i <= y2; i += table->rowHeight) {
862                 XDrawLine(dpy, d, gc, x1, i, x2, i);
863         }
864 }
865
866 static WMRange columnsInRect(WMTableView * table, WMRect rect)
867 {
868         WMTableColumn *column;
869         int pos;
870         int i, found;
871         int totalColumns = WMGetArrayItemCount(table->columns);
872         WMRange range;
873
874         pos = 0;
875         found = 0;
876         for (i = 0; i < totalColumns; i++) {
877                 column = WMGetFromArray(table->columns, i);
878                 if (!found) {
879                         if (rect.pos.x >= pos && rect.pos.x < pos + column->width) {
880                                 range.position = i;
881                                 range.count = 1;
882                                 found = 1;
883                         }
884                 } else {
885                         if (pos > rect.pos.x + rect.size.width) {
886                                 break;
887                         }
888                         range.count++;
889                 }
890                 pos += column->width;
891         }
892         range.count = WMAX(1, WMIN(range.count, totalColumns - range.position));
893         return range;
894 }
895
896 static WMRange rowsInRect(WMTableView * table, WMRect rect)
897 {
898         WMRange range;
899         int rh = table->rowHeight;
900         int dif;
901
902         dif = rect.pos.y % rh;
903
904         range.position = WMAX(0, (rect.pos.y - dif) / rh);
905         range.count = WMAX(1, WMIN((rect.size.height + dif) / rh, table->rows));
906
907         return range;
908 }
909
910 static void drawRow(WMTableView * table, int row, WMRect clipRect)
911 {
912         int i;
913         WMRange cols = columnsInRect(table, clipRect);
914         WMTableColumn *column;
915         Drawable d = WMGetPixmapXID(table->viewBuffer);
916
917         for (i = cols.position; i < cols.position + cols.count; i++) {
918                 column = WMGetFromArray(table->columns, i);
919
920                 if (!column->delegate || !column->delegate->drawCell)
921                         continue;
922
923                 if (WMFindInArray(table->selectedRows, NULL, (void *)(uintptr_t) row) != WANotFound)
924                         (*column->delegate->drawSelectedCell) (column->delegate, column, row, d);
925                 else
926                         (*column->delegate->drawCell) (column->delegate, column, row, d);
927         }
928 }
929
930 #if 0
931 static void drawFullRow(WMTableView * table, int row)
932 {
933         int i;
934         WMTableColumn *column;
935         Drawable d = WMGetPixmapXID(table->viewBuffer);
936
937         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
938                 column = WMGetFromArray(table->columns, i);
939
940                 if (!column->delegate || !column->delegate->drawCell)
941                         continue;
942
943                 if (WMFindInArray(table->selectedRows, NULL, (void *)row) != WANotFound)
944                         (*column->delegate->drawSelectedCell) (column->delegate, column, row, d);
945                 else
946                         (*column->delegate->drawCell) (column->delegate, column, row, d);
947         }
948 }
949 #endif
950
951 static void setRowSelected(WMTableView * table, unsigned row, Bool flag)
952 {
953         int repaint = 0;
954
955         if (WMFindInArray(table->selectedRows, NULL, (void *)(uintptr_t) row) != WANotFound) {
956                 if (!flag) {
957                         WMRemoveFromArray(table->selectedRows, (void *)(uintptr_t) row);
958                         repaint = 1;
959                 }
960         } else {
961                 if (flag) {
962                         WMAddToArray(table->selectedRows, (void *)(uintptr_t) row);
963                         repaint = 1;
964                 }
965         }
966         if (repaint && row < table->rows) {
967                 /*drawFullRow(table, row); */
968                 repaintTable(table);
969         }
970 }
971
972 static void repaintTable(WMTableView * table)
973 {
974         WMRect rect;
975         WMRange rows;
976         WMScreen *scr = WMWidgetScreen(table);
977         int i;
978
979         if (!table->delegate || !W_VIEW_REALIZED(table->view))
980                 return;
981
982         wassertr(table->delegate->numberOfRows);
983
984         if (!table->viewBuffer) {
985                 table->viewBuffer = WMCreatePixmap(scr,
986                                                    W_VIEW_WIDTH(table->tableView),
987                                                    W_VIEW_HEIGHT(table->tableView), WMScreenDepth(scr), 0);
988         }
989
990         XFillRectangle(scr->display,
991                        WMGetPixmapXID(table->viewBuffer),
992                        WMColorGC(table->backColor), 0, 0,
993                        W_VIEW_WIDTH(table->tableView), W_VIEW_HEIGHT(table->tableView));
994
995         rect = getVisibleRect(table);
996
997         if (table->drawsGrid) {
998                 drawGrid(table, rect);
999         }
1000
1001         rows = rowsInRect(table, rect);
1002         for (i = rows.position; i < WMIN(rows.position + rows.count + 1, table->rows); i++) {
1003                 drawRow(table, i, rect);
1004         }
1005
1006         XSetWindowBackgroundPixmap(scr->display, table->tableView->window, WMGetPixmapXID(table->viewBuffer));
1007         XClearWindow(scr->display, table->tableView->window);
1008 }
1009
1010 static void stopRowEdit(WMTableView * table, int row)
1011 {
1012         int i;
1013         WMTableColumn *column;
1014
1015         table->editingRow = -1;
1016         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
1017                 column = WMGetFromArray(table->columns, i);
1018
1019                 if (column->delegate && column->delegate->endCellEdit)
1020                         (*column->delegate->endCellEdit) (column->delegate, column, row);
1021         }
1022 }
1023
1024 void WMEditTableViewRow(WMTableView * table, int row)
1025 {
1026         int i;
1027         WMTableColumn *column;
1028
1029         if (table->editingRow >= 0) {
1030                 stopRowEdit(table, table->editingRow);
1031         }
1032
1033         table->editingRow = row;
1034
1035         if (row < 0)
1036                 return;
1037
1038         for (i = 0; i < WMGetArrayItemCount(table->columns); i++) {
1039                 column = WMGetFromArray(table->columns, i);
1040
1041                 if (column->delegate && column->delegate->beginCellEdit)
1042                         (*column->delegate->beginCellEdit) (column->delegate, column, row);
1043         }
1044 }
1045
1046 void WMSelectTableViewRow(WMTableView * table, int row)
1047 {
1048         if (table->clickedRow >= 0)
1049                 setRowSelected(table, table->clickedRow, False);
1050
1051         if (row >= table->rows) {
1052                 return;
1053         }
1054
1055         setRowSelected(table, row, True);
1056         table->clickedRow = row;
1057
1058         if (table->action)
1059                 (*table->action) (table, table->clientData);
1060         WMPostNotificationName(WMTableViewSelectionDidChangeNotification, table, NULL);
1061 }
1062
1063 void WMReloadTableView(WMTableView * table)
1064 {
1065         if (table->editingRow >= 0)
1066                 stopRowEdit(table, table->editingRow);
1067
1068         /* when this is called, nothing in the table can be assumed to be
1069          * like the last time we accessed it (ie, rows might have disappeared) */
1070
1071         WMEmptyArray(table->selectedRows);
1072
1073         if (table->clickedRow >= 0) {
1074                 if (table->action)
1075                         (*table->action) (table, table->clientData);
1076                 WMPostNotificationName(WMTableViewSelectionDidChangeNotification, table, NULL);
1077                 table->clickedRow = -1;
1078         }
1079
1080         if (table->delegate && table->delegate->numberOfRows) {
1081                 int rows;
1082
1083                 rows = (*table->delegate->numberOfRows) (table->delegate, table);
1084
1085                 if (rows != table->rows) {
1086                         table->rows = rows;
1087                         handleResize(table->view->delegate, table->view);
1088                 } else {
1089                         repaintTable(table);
1090                 }
1091         }
1092 }
1093
1094 void WMNoteTableViewNumberOfRowsChanged(WMTableView * table)
1095 {
1096         WMReloadTableView(table);
1097 }
1098
1099 static void handleTableEvents(XEvent * event, void *data)
1100 {
1101         WMTableView *table = (WMTableView *) data;
1102         int row;
1103
1104         switch (event->type) {
1105         case ButtonPress:
1106                 if (event->xbutton.button == Button1) {
1107                         WMRect rect = getVisibleRect(table);
1108
1109                         row = (event->xbutton.y + rect.pos.y) / table->rowHeight;
1110                         if (row != table->clickedRow) {
1111                                 setRowSelected(table, table->clickedRow, False);
1112                                 setRowSelected(table, row, True);
1113                                 table->clickedRow = row;
1114                                 table->dragging = 1;
1115                         } else {
1116                                 table->dragging = 1;
1117                         }
1118                 }
1119                 break;
1120
1121         case MotionNotify:
1122                 if (table->dragging && event->xmotion.y >= 0) {
1123                         WMRect rect = getVisibleRect(table);
1124
1125                         row = (event->xmotion.y + rect.pos.y) / table->rowHeight;
1126                         if (table->clickedRow != row && row >= 0 && row < table->rows) {
1127                                 setRowSelected(table, table->clickedRow, False);
1128                                 setRowSelected(table, row, True);
1129                                 table->clickedRow = row;
1130                         }
1131                 }
1132                 break;
1133
1134         case ButtonRelease:
1135                 if (event->xbutton.button == Button1) {
1136                         if (table->action)
1137                                 (*table->action) (table, table->clientData);
1138                         WMPostNotificationName(WMTableViewSelectionDidChangeNotification, table, NULL);
1139                         table->dragging = 0;
1140                 }
1141                 break;
1142         }
1143 }
1144
1145 static void handleEvents(XEvent * event, void *data)
1146 {
1147         WMTableView *table = (WMTableView *) data;
1148         WMScreen *scr = WMWidgetScreen(table);
1149
1150         switch (event->type) {
1151         case Expose:
1152                 W_DrawRelief(scr, W_VIEW_DRAWABLE(table->view), 0, 0,
1153                              W_VIEW_WIDTH(table->view), W_VIEW_HEIGHT(table->view), WRSunken);
1154                 break;
1155         }
1156 }
1157
1158 static void handleResize(W_ViewDelegate * self, WMView * view)
1159 {
1160         reorganizeInterior(view->self);
1161 }
1162
1163 static void reorganizeInterior(WMTableView * table)
1164 {
1165         int width;
1166         int height;
1167         WMSize size = getTotalSize(table);
1168         WMView *view = table->view;
1169         int vw, vh;
1170         int hsThickness, vsThickness;
1171
1172         if (table->vscroll)
1173                 vsThickness = WMWidgetWidth(table->vscroll);
1174         if (table->hscroll)
1175                 hsThickness = WMWidgetHeight(table->hscroll);
1176
1177         width = W_VIEW_WIDTH(view) - 2;
1178         height = W_VIEW_HEIGHT(view) - 3;
1179
1180         height -= table->headerHeight;  /* table header */
1181
1182         if (table->corner)
1183                 WMResizeWidget(table->corner, 20, table->headerHeight);
1184
1185         WMMoveWidget(table->vscroll, 1, table->headerHeight + 1);
1186         WMResizeWidget(table->vscroll, 20, height + 1);
1187
1188         if (table->hscroll) {
1189                 WMMoveWidget(table->hscroll, vsThickness, W_VIEW_HEIGHT(view) - hsThickness - 1);
1190                 WMResizeWidget(table->hscroll, width - (vsThickness + 1), hsThickness);
1191         }
1192
1193         if (table->header)
1194                 WMResizeWidget(table->header, width - (vsThickness + 1), table->headerHeight);
1195
1196         if (table->viewBuffer) {
1197                 WMReleasePixmap(table->viewBuffer);
1198                 table->viewBuffer = NULL;
1199         }
1200
1201         width -= vsThickness;
1202         height -= hsThickness;
1203
1204         vw = WMIN(size.width, width);
1205         vh = WMIN(size.height, height);
1206
1207         W_MoveView(table->tableView, vsThickness + 1, 1 + table->headerHeight + 1);
1208         W_ResizeView(table->tableView, WMAX(vw, 1), WMAX(vh, 1) + 1);
1209
1210         adjustScrollers(table);
1211
1212         repaintTable(table);
1213 }
1214
1215 static void rearrangeHeader(WMTableView * table)
1216 {
1217         int width;
1218         int count;
1219         int i;
1220         /*WMRect rect = WMGetScrollViewVisibleRect(table->scrollView); */
1221
1222         width = 0;
1223
1224         count = WMGetArrayItemCount(table->columns);
1225         for (i = 0; i < count; i++) {
1226                 WMTableColumn *column = WMGetFromArray(table->columns, i);
1227                 WMView *splitter = WMGetFromArray(table->splitters, i);
1228
1229                 WMMoveWidget(column->titleW, width, 0);
1230                 WMResizeWidget(column->titleW, column->width - 1, table->headerHeight);
1231
1232                 width += column->width;
1233                 W_MoveView(splitter, width - 1, 0);
1234         }
1235
1236         wassertr(table->delegate && table->delegate->numberOfRows);
1237
1238         table->rows = table->delegate->numberOfRows(table->delegate, table);
1239
1240         table->tableWidth = width + 1;
1241
1242         handleResize(table->view->delegate, table->view);
1243 }