Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / wbrowser.c
1
2 #include "WINGsP.h"
3 #include <math.h>               /* for : double rint (double) */
4
5 typedef struct W_Browser {
6         W_Class widgetClass;
7         W_View *view;
8
9         char **titles;
10         WMList **columns;
11
12         short columnCount;
13         short usedColumnCount;  /* columns actually being used */
14         short minColumnWidth;
15
16         short maxVisibleColumns;
17         short firstVisibleColumn;
18
19         short titleHeight;
20
21         short selectedColumn;
22
23         WMSize columnSize;
24
25         void *clientData;
26         WMAction *action;
27         void *doubleClientData;
28         WMAction *doubleAction;
29
30         WMBrowserDelegate *delegate;
31
32         WMScroller *scroller;
33
34         char *pathSeparator;
35
36         struct {
37                 unsigned int isTitled:1;
38                 unsigned int allowMultipleSelection:1;
39                 unsigned int allowEmptySelection:1;
40                 unsigned int hasScroller:1;
41
42                 /* */
43                 unsigned int loaded:1;
44                 unsigned int loadingColumn:1;
45         } flags;
46 } Browser;
47
48 #define COLUMN_SPACING  4
49 #define TITLE_SPACING 2
50
51 #define DEFAULT_WIDTH                 305
52 #define DEFAULT_HEIGHT                200
53 #define DEFAULT_HAS_SCROLLER          True
54 #define DEFAULT_TITLE_HEIGHT          20
55 #define DEFAULT_IS_TITLED             True
56 #define DEFAULT_MAX_VISIBLE_COLUMNS   2
57 #define DEFAULT_SEPARATOR             "/"
58
59 #define MIN_VISIBLE_COLUMNS           1
60 #define MAX_VISIBLE_COLUMNS           32
61
62 #define COLUMN_IS_VISIBLE(b, c) ((c) >= (b)->firstVisibleColumn \
63     && (c) < (b)->firstVisibleColumn + (b)->maxVisibleColumns)
64
65 static void handleEvents(XEvent * event, void *data);
66 static void destroyBrowser(WMBrowser * bPtr);
67
68 static void setupScroller(WMBrowser * bPtr);
69
70 static void scrollToColumn(WMBrowser * bPtr, int column, Bool updateScroller);
71
72 static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect);
73
74 static void loadColumn(WMBrowser * bPtr, int column);
75
76 static void removeColumn(WMBrowser * bPtr, int column);
77
78 static char *createTruncatedString(WMFont * font, char *text, int *textLen, int width);
79
80 static void willResizeBrowser(W_ViewDelegate *, WMView *, unsigned int *, unsigned int *);
81
82 W_ViewDelegate _BrowserViewDelegate = {
83         NULL,
84         NULL,
85         NULL,
86         NULL,
87         willResizeBrowser
88 };
89
90 WMBrowser *WMCreateBrowser(WMWidget * parent)
91 {
92         WMBrowser *bPtr;
93         int i;
94
95         wassertrv(parent, NULL);
96
97         bPtr = wmalloc(sizeof(WMBrowser));
98         memset(bPtr, 0, sizeof(WMBrowser));
99
100         bPtr->widgetClass = WC_Browser;
101
102         bPtr->view = W_CreateView(W_VIEW(parent));
103         if (!bPtr->view) {
104                 wfree(bPtr);
105                 return NULL;
106         }
107         bPtr->view->self = bPtr;
108
109         bPtr->view->delegate = &_BrowserViewDelegate;
110
111         WMCreateEventHandler(bPtr->view, ExposureMask | StructureNotifyMask
112                              | ClientMessageMask, handleEvents, bPtr);
113
114         /* default configuration */
115         bPtr->flags.hasScroller = DEFAULT_HAS_SCROLLER;
116
117         bPtr->titleHeight = DEFAULT_TITLE_HEIGHT;
118         bPtr->flags.isTitled = DEFAULT_IS_TITLED;
119         bPtr->maxVisibleColumns = DEFAULT_MAX_VISIBLE_COLUMNS;
120
121         WMResizeWidget(bPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
122
123         bPtr->pathSeparator = wstrdup(DEFAULT_SEPARATOR);
124
125         if (bPtr->flags.hasScroller)
126                 setupScroller(bPtr);
127
128         for (i = 0; i < bPtr->maxVisibleColumns; i++) {
129                 WMAddBrowserColumn(bPtr);
130         }
131         bPtr->usedColumnCount = 0;
132
133         bPtr->selectedColumn = -1;
134
135         return bPtr;
136 }
137
138 void WMSetBrowserAllowMultipleSelection(WMBrowser * bPtr, Bool flag)
139 {
140         int i;
141
142         bPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
143         for (i = 0; i < bPtr->columnCount; i++) {
144                 WMSetListAllowMultipleSelection(bPtr->columns[i], flag);
145         }
146 }
147
148 void WMSetBrowserAllowEmptySelection(WMBrowser * bPtr, Bool flag)
149 {
150         int i;
151
152         bPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
153         for (i = 0; i < bPtr->columnCount; i++) {
154                 WMSetListAllowEmptySelection(bPtr->columns[i], flag);
155         }
156 }
157
158 int WMGetBrowserMaxVisibleColumns(WMBrowser * bPtr)
159 {
160         return bPtr->maxVisibleColumns;
161 }
162
163 void WMSetBrowserMaxVisibleColumns(WMBrowser * bPtr, int columns)
164 {
165         int curMaxVisibleColumns;
166         int newFirstVisibleColumn = 0;
167
168         assert(bPtr != NULL);
169
170         columns = (columns < MIN_VISIBLE_COLUMNS) ? MIN_VISIBLE_COLUMNS : columns;
171         columns = (columns > MAX_VISIBLE_COLUMNS) ? MAX_VISIBLE_COLUMNS : columns;
172         if (columns == bPtr->maxVisibleColumns) {
173                 return;
174         }
175         curMaxVisibleColumns = bPtr->maxVisibleColumns;
176         bPtr->maxVisibleColumns = columns;
177         /* browser not loaded */
178         if (!bPtr->flags.loaded) {
179                 if ((columns > curMaxVisibleColumns) && (columns > bPtr->columnCount)) {
180                         int i = columns - bPtr->columnCount;
181                         bPtr->usedColumnCount = bPtr->columnCount;
182                         while (i--) {
183                                 WMAddBrowserColumn(bPtr);
184                         }
185                         bPtr->usedColumnCount = 0;
186                 }
187                 /* browser loaded and columns > curMaxVisibleColumns */
188         } else if (columns > curMaxVisibleColumns) {
189                 if (bPtr->usedColumnCount > columns) {
190                         newFirstVisibleColumn = bPtr->usedColumnCount - columns;
191                 }
192                 if (newFirstVisibleColumn > bPtr->firstVisibleColumn) {
193                         newFirstVisibleColumn = bPtr->firstVisibleColumn;
194                 }
195                 if (columns > bPtr->columnCount) {
196                         int i = columns - bPtr->columnCount;
197                         int curUsedColumnCount = bPtr->usedColumnCount;
198                         bPtr->usedColumnCount = bPtr->columnCount;
199                         while (i--) {
200                                 WMAddBrowserColumn(bPtr);
201                         }
202                         bPtr->usedColumnCount = curUsedColumnCount;
203                 }
204                 /* browser loaded and columns < curMaxVisibleColumns */
205         } else {
206                 newFirstVisibleColumn = bPtr->firstVisibleColumn;
207                 if (newFirstVisibleColumn + columns >= bPtr->usedColumnCount) {
208                         removeColumn(bPtr, newFirstVisibleColumn + columns);
209                 }
210         }
211         WMResizeWidget(bPtr, bPtr->view->size.width, bPtr->view->size.height);
212         if (bPtr->flags.loaded) {
213                 XClearArea(bPtr->view->screen->display, bPtr->view->window, 0, 0,
214                            bPtr->view->size.width, bPtr->titleHeight, False);
215                 scrollToColumn(bPtr, newFirstVisibleColumn, True);
216         }
217 }
218
219 int WMGetBrowserNumberOfColumns(WMBrowser * bPtr)
220 {
221         return bPtr->usedColumnCount;
222 }
223
224 void WMSetBrowserPathSeparator(WMBrowser * bPtr, char *separator)
225 {
226         if (bPtr->pathSeparator)
227                 wfree(bPtr->pathSeparator);
228         bPtr->pathSeparator = wstrdup(separator);
229 }
230
231 static void drawTitleOfColumn(WMBrowser * bPtr, int column)
232 {
233         WMScreen *scr = bPtr->view->screen;
234         int x;
235
236         x = (column - bPtr->firstVisibleColumn) * (bPtr->columnSize.width + COLUMN_SPACING);
237
238         XFillRectangle(scr->display, bPtr->view->window, WMColorGC(scr->darkGray), x, 0,
239                        bPtr->columnSize.width, bPtr->titleHeight);
240         W_DrawRelief(scr, bPtr->view->window, x, 0, bPtr->columnSize.width, bPtr->titleHeight, WRSunken);
241
242         if (column < bPtr->usedColumnCount && bPtr->titles[column]) {
243                 int titleLen = strlen(bPtr->titles[column]);
244                 int widthC = bPtr->columnSize.width - 8;
245
246                 if (WMWidthOfString(scr->boldFont, bPtr->titles[column], titleLen)
247                     > widthC) {
248                         char *titleBuf = createTruncatedString(scr->boldFont,
249                                                                bPtr->titles[column],
250                                                                &titleLen, widthC);
251                         W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
252                                     (bPtr->titleHeight - WMFontHeight(scr->boldFont)) / 2,
253                                     bPtr->columnSize.width, WACenter, scr->white, False, titleBuf, titleLen);
254                         wfree(titleBuf);
255                 } else {
256                         W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
257                                     (bPtr->titleHeight - WMFontHeight(scr->boldFont)) / 2,
258                                     bPtr->columnSize.width, WACenter, scr->white,
259                                     False, bPtr->titles[column], titleLen);
260                 }
261         }
262 }
263
264 WMList *WMGetBrowserListInColumn(WMBrowser * bPtr, int column)
265 {
266         if (column < 0 || column >= bPtr->usedColumnCount)
267                 return NULL;
268
269         return bPtr->columns[column];
270 }
271
272 void WMSetBrowserDelegate(WMBrowser * bPtr, WMBrowserDelegate * delegate)
273 {
274         bPtr->delegate = delegate;
275 }
276
277 int WMGetBrowserFirstVisibleColumn(WMBrowser * bPtr)
278 {
279         return bPtr->firstVisibleColumn;
280 }
281
282 static void removeColumn(WMBrowser * bPtr, int column)
283 {
284         int i, clearEnd, destroyEnd;
285         WMList **clist;
286         char **tlist;
287
288         assert(bPtr != NULL);
289
290         column = (column < 0) ? 0 : column;
291         if (column >= bPtr->columnCount) {
292                 return;
293         }
294         if (column < bPtr->maxVisibleColumns) {
295                 clearEnd = bPtr->maxVisibleColumns;
296                 destroyEnd = bPtr->columnCount;
297                 bPtr->columnCount = bPtr->maxVisibleColumns;
298         } else {
299                 clearEnd = column;
300                 destroyEnd = bPtr->columnCount;
301                 bPtr->columnCount = column;
302         }
303         if (column < bPtr->usedColumnCount) {
304                 bPtr->usedColumnCount = column;
305         }
306         for (i = column; i < clearEnd; i++) {
307                 if (bPtr->titles[i]) {
308                         wfree(bPtr->titles[i]);
309                         bPtr->titles[i] = NULL;
310                 }
311                 WMClearList(bPtr->columns[i]);
312         }
313         for (; i < destroyEnd; i++) {
314                 if (bPtr->titles[i]) {
315                         wfree(bPtr->titles[i]);
316                         bPtr->titles[i] = NULL;
317                 }
318                 WMRemoveNotificationObserverWithName(bPtr, WMListSelectionDidChangeNotification, bPtr->columns[i]);
319                 WMDestroyWidget(bPtr->columns[i]);
320                 bPtr->columns[i] = NULL;
321         }
322         clist = wmalloc(sizeof(WMList *) * (bPtr->columnCount));
323         tlist = wmalloc(sizeof(char *) * (bPtr->columnCount));
324         memcpy(clist, bPtr->columns, sizeof(WMList *) * (bPtr->columnCount));
325         memcpy(tlist, bPtr->titles, sizeof(char *) * (bPtr->columnCount));
326         wfree(bPtr->titles);
327         wfree(bPtr->columns);
328         bPtr->titles = tlist;
329         bPtr->columns = clist;
330 }
331
332 WMListItem *WMGetBrowserSelectedItemInColumn(WMBrowser * bPtr, int column)
333 {
334         if ((column < 0) || (column >= bPtr->usedColumnCount))
335                 return NULL;
336
337         return WMGetListSelectedItem(bPtr->columns[column]);
338 }
339
340 int WMGetBrowserSelectedColumn(WMBrowser * bPtr)
341 {
342         return bPtr->selectedColumn;
343 }
344
345 int WMGetBrowserSelectedRowInColumn(WMBrowser * bPtr, int column)
346 {
347         if (column >= 0 && column < bPtr->columnCount) {
348                 return WMGetListSelectedItemRow(bPtr->columns[column]);
349         } else {
350                 return -1;
351         }
352 }
353
354 void WMSetBrowserColumnTitle(WMBrowser * bPtr, int column, char *title)
355 {
356         assert(column >= 0);
357         assert(column < bPtr->usedColumnCount);
358
359         if (bPtr->titles[column])
360                 wfree(bPtr->titles[column]);
361
362         bPtr->titles[column] = wstrdup(title);
363
364         if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
365                 drawTitleOfColumn(bPtr, column);
366         }
367 }
368
369 void WMSetBrowserTitled(WMBrowser * bPtr, Bool flag)
370 {
371         int i;
372         int columnX, columnY;
373
374         flag = ((flag == 0) ? 0 : 1);
375
376         if (bPtr->flags.isTitled == flag)
377                 return;
378
379         columnX = 0;
380
381         if (!bPtr->flags.isTitled) {
382                 columnY = TITLE_SPACING + bPtr->titleHeight;
383
384                 bPtr->columnSize.height -= columnY;
385
386                 for (i = 0; i < bPtr->columnCount; i++) {
387                         WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
388
389                         columnX = WMWidgetView(bPtr->columns[i])->pos.x;
390
391                         WMMoveWidget(bPtr->columns[i], columnX, columnY);
392                 }
393         } else {
394                 bPtr->columnSize.height += TITLE_SPACING + bPtr->titleHeight;
395
396                 for (i = 0; i < bPtr->columnCount; i++) {
397                         WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
398
399                         columnX = WMWidgetView(bPtr->columns[i])->pos.x;
400
401                         WMMoveWidget(bPtr->columns[i], columnX, 0);
402                 }
403         }
404
405         bPtr->flags.isTitled = flag;
406 }
407
408 void WMSortBrowserColumn(WMBrowser * bPtr, int column)
409 {
410         WMSortListItems(bPtr->columns[column]);
411 }
412
413 void WMSortBrowserColumnWithComparer(WMBrowser * bPtr, int column, WMCompareDataProc * func)
414 {
415         WMSortListItemsWithComparer(bPtr->columns[column], func);
416 }
417
418 WMListItem *WMInsertBrowserItem(WMBrowser * bPtr, int column, int row, char *text, Bool isBranch)
419 {
420         WMListItem *item;
421
422         if (column < 0 || column >= bPtr->columnCount)
423                 return NULL;
424
425         item = WMInsertListItem(bPtr->columns[column], row, text);
426         item->isBranch = isBranch;
427
428         return item;
429 }
430
431 static void willResizeBrowser(W_ViewDelegate * self, WMView * view, unsigned int *width, unsigned int *height)
432 {
433         WMBrowser *bPtr = (WMBrowser *) view->self;
434         int cols = bPtr->maxVisibleColumns;
435         int colX, colY;
436         int i;
437
438         assert(*width > 0);
439         assert(*height > 0);
440
441         bPtr->columnSize.width = (*width - (cols - 1) * COLUMN_SPACING) / cols;
442         bPtr->columnSize.height = *height;
443
444         if (bPtr->flags.isTitled) {
445                 colY = TITLE_SPACING + bPtr->titleHeight;
446                 bPtr->columnSize.height -= colY;
447         } else {
448                 colY = 0;
449         }
450
451         if (bPtr->flags.hasScroller) {
452                 bPtr->columnSize.height -= SCROLLER_WIDTH + 4;
453
454                 if (bPtr->scroller) {
455                         WMResizeWidget(bPtr->scroller, *width - 2, 1);
456                         WMMoveWidget(bPtr->scroller, 1, *height - SCROLLER_WIDTH - 1);
457                 }
458         }
459
460         colX = 0;
461         for (i = 0; i < bPtr->columnCount; i++) {
462                 WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
463
464                 WMMoveWidget(bPtr->columns[i], colX, colY);
465
466                 if (COLUMN_IS_VISIBLE(bPtr, i)) {
467                         colX += bPtr->columnSize.width + COLUMN_SPACING;
468                 }
469         }
470 }
471
472 static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect)
473 {
474         WMView *view = W_VIEW(lPtr);
475         W_Screen *scr = view->screen;
476         Display *display = scr->display;
477         WMFont *font = ((state & WLDSIsBranch) ? scr->boldFont : scr->normalFont);
478         WMColor *backColor = ((state & WLDSSelected) ? scr->white : view->backColor);
479         int width, height, x, y, textLen;
480
481         width = rect->size.width;
482         height = rect->size.height;
483         x = rect->pos.x;
484         y = rect->pos.y;
485         textLen = strlen(text);
486
487         XFillRectangle(display, d, WMColorGC(backColor), x, y, width, height);
488
489         if (text) {
490                 /* Avoid overlaping... */
491                 int widthC = (state & WLDSIsBranch) ? width - 20 : width - 8;
492                 if (WMWidthOfString(font, text, textLen) > widthC) {
493                         char *textBuf = createTruncatedString(font, text, &textLen, widthC);
494                         W_PaintText(view, d, font, x + 4, y, widthC, WALeft, scr->black, False, textBuf, textLen);
495                         wfree(textBuf);
496                 } else {
497                         W_PaintText(view, d, font, x + 4, y, widthC, WALeft, scr->black, False, text, textLen);
498                 }
499         }
500
501         if (state & WLDSIsBranch) {
502                 WMColor *lineColor = ((state & WLDSSelected) ? scr->gray : scr->white);
503
504                 XDrawLine(display, d, WMColorGC(scr->darkGray), x + width - 11, y + 3,
505                           x + width - 6, y + height / 2);
506                 XDrawLine(display, d, WMColorGC(lineColor), x + width - 11, y + height - 5,
507                           x + width - 6, y + height / 2);
508                 XDrawLine(display, d, WMColorGC(scr->black), x + width - 12, y + 3,
509                           x + width - 12, y + height - 5);
510         }
511 }
512
513 static void scrollCallback(WMWidget * scroller, void *self)
514 {
515         WMBrowser *bPtr = (WMBrowser *) self;
516         WMScroller *sPtr = (WMScroller *) scroller;
517         int newFirst;
518 #define LAST_VISIBLE_COLUMN  bPtr->firstVisibleColumn+bPtr->maxVisibleColumns
519
520         switch (WMGetScrollerHitPart(sPtr)) {
521         case WSDecrementLine:
522                 if (bPtr->firstVisibleColumn > 0) {
523                         scrollToColumn(bPtr, bPtr->firstVisibleColumn - 1, True);
524                 }
525                 break;
526
527         case WSDecrementPage:
528         case WSDecrementWheel:
529                 if (bPtr->firstVisibleColumn > 0) {
530                         newFirst = bPtr->firstVisibleColumn - bPtr->maxVisibleColumns;
531
532                         scrollToColumn(bPtr, newFirst, True);
533                 }
534                 break;
535
536         case WSIncrementLine:
537                 if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
538                         scrollToColumn(bPtr, bPtr->firstVisibleColumn + 1, True);
539                 }
540                 break;
541
542         case WSIncrementPage:
543         case WSIncrementWheel:
544                 if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
545                         newFirst = bPtr->firstVisibleColumn + bPtr->maxVisibleColumns;
546
547                         if (newFirst + bPtr->maxVisibleColumns >= bPtr->columnCount)
548                                 newFirst = bPtr->columnCount - bPtr->maxVisibleColumns;
549
550                         scrollToColumn(bPtr, newFirst, True);
551                 }
552                 break;
553
554         case WSKnob:
555                 {
556                         double floatValue;
557                         double value = bPtr->columnCount - bPtr->maxVisibleColumns;
558
559                         floatValue = WMGetScrollerValue(bPtr->scroller);
560
561                         floatValue = (floatValue * value) / value;
562
563                         newFirst = rint(floatValue * (float)(bPtr->columnCount - bPtr->maxVisibleColumns));
564
565                         if (bPtr->firstVisibleColumn != newFirst)
566                                 scrollToColumn(bPtr, newFirst, False);
567                         /*else
568                            WMSetScrollerParameters(bPtr->scroller, floatValue,
569                            bPtr->maxVisibleColumns/(float)bPtr->columnCount);
570                          */
571
572                 }
573                 break;
574
575         case WSKnobSlot:
576         case WSNoPart:
577                 /* do nothing */
578                 break;
579         }
580 #undef LAST_VISIBLE_COLUMN
581 }
582
583 static void setupScroller(WMBrowser * bPtr)
584 {
585         WMScroller *sPtr;
586         int y;
587
588         y = bPtr->view->size.height - SCROLLER_WIDTH - 1;
589
590         sPtr = WMCreateScroller(bPtr);
591         WMSetScrollerAction(sPtr, scrollCallback, bPtr);
592         WMMoveWidget(sPtr, 1, y);
593         WMResizeWidget(sPtr, bPtr->view->size.width - 2, SCROLLER_WIDTH);
594
595         bPtr->scroller = sPtr;
596
597         WMMapWidget(sPtr);
598 }
599
600 void WMSetBrowserAction(WMBrowser * bPtr, WMAction * action, void *clientData)
601 {
602         bPtr->action = action;
603         bPtr->clientData = clientData;
604 }
605
606 void WMSetBrowserDoubleAction(WMBrowser * bPtr, WMAction * action, void *clientData)
607 {
608         bPtr->doubleAction = action;
609         bPtr->doubleClientData = clientData;
610 }
611
612 void WMSetBrowserHasScroller(WMBrowser * bPtr, int hasScroller)
613 {
614         bPtr->flags.hasScroller = hasScroller;
615 }
616
617 char *WMSetBrowserPath(WMBrowser * bPtr, char *path)
618 {
619         int i;
620         char *str;
621         char *tmp, *retPtr = NULL;
622         int item;
623         WMListItem *listItem;
624
625         /* WMLoadBrowserColumnZero must be call first */
626         if (!bPtr->flags.loaded) {
627                 return False;
628         }
629
630         removeColumn(bPtr, 1);
631
632         WMSelectListItem(bPtr->columns[0], -1);
633         WMSetListPosition(bPtr->columns[0], 0);
634
635         i = 0;
636         str = wstrdup(path);
637         tmp = strtok(str, bPtr->pathSeparator);
638         while (tmp) {
639                 /* select it in the column */
640                 item = WMFindRowOfListItemWithTitle(bPtr->columns[i], tmp);
641                 if (item < 0) {
642                         retPtr = &path[(int)(tmp - str)];
643                         break;
644                 }
645                 WMSelectListItem(bPtr->columns[i], item);
646                 WMSetListPosition(bPtr->columns[i], item);
647
648                 listItem = WMGetListItem(bPtr->columns[i], item);
649                 if (!listItem || !listItem->isBranch) {
650                         break;
651                 }
652
653                 /* load next column */
654                 WMAddBrowserColumn(bPtr);
655
656                 loadColumn(bPtr, i + 1);
657
658                 tmp = strtok(NULL, bPtr->pathSeparator);
659
660                 i++;
661         }
662
663         wfree(str);
664
665         for (i = bPtr->usedColumnCount - 1; (i > -1) && !WMGetListSelectedItem(bPtr->columns[i]); i--) ;
666
667         bPtr->selectedColumn = i;
668
669         if (bPtr->columnCount < bPtr->maxVisibleColumns) {
670                 int i = bPtr->maxVisibleColumns - bPtr->columnCount;
671                 int curUsedColumnCount = bPtr->usedColumnCount;
672                 bPtr->usedColumnCount = bPtr->columnCount;
673                 while (i--) {
674                         WMAddBrowserColumn(bPtr);
675                 }
676                 bPtr->usedColumnCount = curUsedColumnCount;
677         }
678
679         scrollToColumn(bPtr, bPtr->columnCount - bPtr->maxVisibleColumns, True);
680
681         return retPtr;
682 }
683
684 char *WMGetBrowserPath(WMBrowser * bPtr)
685 {
686         return WMGetBrowserPathToColumn(bPtr, bPtr->columnCount);
687 }
688
689 char *WMGetBrowserPathToColumn(WMBrowser * bPtr, int column)
690 {
691         int i, size;
692         char *path;
693         WMListItem *item;
694
695         if (column >= bPtr->usedColumnCount)
696                 column = bPtr->usedColumnCount - 1;
697
698         if (column < 0) {
699                 return wstrdup(bPtr->pathSeparator);
700         }
701
702         /* calculate size of buffer */
703         size = 0;
704         for (i = 0; i <= column; i++) {
705                 item = WMGetListSelectedItem(bPtr->columns[i]);
706                 if (!item)
707                         break;
708                 size += strlen(item->text);
709         }
710
711         /* get the path */
712         path = wmalloc(size + (column + 1) * strlen(bPtr->pathSeparator) + 1);
713         /* ignore first / */
714         *path = 0;
715         for (i = 0; i <= column; i++) {
716                 strcat(path, bPtr->pathSeparator);
717                 item = WMGetListSelectedItem(bPtr->columns[i]);
718                 if (!item)
719                         break;
720                 strcat(path, item->text);
721         }
722
723         return path;
724 }
725
726 WMArray *WMGetBrowserPaths(WMBrowser * bPtr)
727 {
728         int column, i, k, size, selNo;
729         char *path;
730         WMListItem *item, *lastItem;
731         WMArray *paths, *items;
732
733         column = bPtr->usedColumnCount - 1;
734
735         if (column < 0) {
736                 paths = WMCreateArrayWithDestructor(1, wfree);
737                 WMAddToArray(paths, wstrdup(bPtr->pathSeparator));
738                 return paths;
739         }
740
741         items = WMGetListSelectedItems(bPtr->columns[column]);
742         selNo = WMGetArrayItemCount(items);
743         paths = WMCreateArrayWithDestructor(selNo, wfree);
744
745         if (selNo <= 1) {
746                 WMAddToArray(paths, WMGetBrowserPath(bPtr));
747                 return paths;
748         }
749
750         /* calculate size of buffer */
751         size = 0;
752         for (i = 0; i < column; i++) {
753                 item = WMGetListSelectedItem(bPtr->columns[i]);
754                 if (!item)
755                         break;
756                 size += strlen(item->text);
757         }
758
759         size += (column + 1) * strlen(bPtr->pathSeparator) + 1;
760
761         for (k = 0; k < selNo; k++) {
762                 /* get the path */
763                 lastItem = WMGetFromArray(items, k);
764                 path = wmalloc(size + (lastItem != NULL ? strlen(lastItem->text) : 0));
765                 /* ignore first / */
766                 *path = 0;
767                 for (i = 0; i <= column; i++) {
768                         strcat(path, bPtr->pathSeparator);
769                         if (i == column) {
770                                 item = lastItem;
771                         } else {
772                                 item = WMGetListSelectedItem(bPtr->columns[i]);
773                         }
774                         if (!item)
775                                 break;
776                         strcat(path, item->text);
777                 }
778                 WMAddToArray(paths, path);
779         }
780
781         return paths;
782 }
783
784 Bool WMBrowserAllowsMultipleSelection(WMBrowser * bPtr)
785 {
786         return bPtr->flags.allowMultipleSelection;
787 }
788
789 Bool WMBrowserAllowsEmptySelection(WMBrowser * bPtr)
790 {
791         return bPtr->flags.allowEmptySelection;
792 }
793
794 static void loadColumn(WMBrowser * bPtr, int column)
795 {
796         assert(bPtr->delegate);
797         assert(bPtr->delegate->createRowsForColumn);
798
799         bPtr->flags.loadingColumn = 1;
800         (*bPtr->delegate->createRowsForColumn) (bPtr->delegate, bPtr, column, bPtr->columns[column]);
801         bPtr->flags.loadingColumn = 0;
802
803         if (bPtr->delegate->titleOfColumn) {
804                 char *title;
805
806                 title = (*bPtr->delegate->titleOfColumn) (bPtr->delegate, bPtr, column);
807
808                 if (bPtr->titles[column])
809                         wfree(bPtr->titles[column]);
810
811                 bPtr->titles[column] = wstrdup(title);
812
813                 if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
814                         drawTitleOfColumn(bPtr, column);
815                 }
816         }
817 }
818
819 static void paintBrowser(WMBrowser * bPtr)
820 {
821         int i;
822
823         if (!bPtr->view->flags.mapped)
824                 return;
825
826         W_DrawRelief(bPtr->view->screen, bPtr->view->window, 0,
827                      bPtr->view->size.height - SCROLLER_WIDTH - 2, bPtr->view->size.width, 22, WRSunken);
828
829         if (bPtr->flags.isTitled) {
830                 for (i = 0; i < bPtr->maxVisibleColumns; i++) {
831                         drawTitleOfColumn(bPtr, i + bPtr->firstVisibleColumn);
832                 }
833         }
834 }
835
836 static void handleEvents(XEvent * event, void *data)
837 {
838         WMBrowser *bPtr = (WMBrowser *) data;
839
840         CHECK_CLASS(data, WC_Browser);
841
842         switch (event->type) {
843         case Expose:
844                 paintBrowser(bPtr);
845                 break;
846
847         case DestroyNotify:
848                 destroyBrowser(bPtr);
849                 break;
850
851         }
852 }
853
854 static void scrollToColumn(WMBrowser * bPtr, int column, Bool updateScroller)
855 {
856         int i;
857         int x;
858         int notify = 0;
859
860         if (column != bPtr->firstVisibleColumn) {
861                 notify = 1;
862         }
863
864         if (column < 0)
865                 column = 0;
866
867         if (notify && bPtr->delegate && bPtr->delegate->willScroll) {
868                 (*bPtr->delegate->willScroll) (bPtr->delegate, bPtr);
869         }
870
871         x = 0;
872         bPtr->firstVisibleColumn = column;
873         for (i = 0; i < bPtr->columnCount; i++) {
874                 if (COLUMN_IS_VISIBLE(bPtr, i)) {
875                         WMMoveWidget(bPtr->columns[i], x, WMWidgetView(bPtr->columns[i])->pos.y);
876                         if (!WMWidgetView(bPtr->columns[i])->flags.realized)
877                                 WMRealizeWidget(bPtr->columns[i]);
878                         WMMapWidget(bPtr->columns[i]);
879                         x += bPtr->columnSize.width + COLUMN_SPACING;
880                 } else {
881                         WMUnmapWidget(bPtr->columns[i]);
882                 }
883         }
884
885         /* update the scroller */
886         if (updateScroller) {
887                 if (bPtr->columnCount > bPtr->maxVisibleColumns) {
888                         float value, proportion;
889
890                         value = bPtr->firstVisibleColumn / (float)(bPtr->columnCount - bPtr->maxVisibleColumns);
891                         proportion = bPtr->maxVisibleColumns / (float)bPtr->columnCount;
892                         WMSetScrollerParameters(bPtr->scroller, value, proportion);
893                 } else {
894                         WMSetScrollerParameters(bPtr->scroller, 0, 1);
895                 }
896         }
897
898         if (bPtr->view->flags.mapped)
899                 paintBrowser(bPtr);
900
901         if (notify && bPtr->delegate && bPtr->delegate->didScroll) {
902                 (*bPtr->delegate->didScroll) (bPtr->delegate, bPtr);
903         }
904 }
905
906 static void listCallback(void *self, void *clientData)
907 {
908         WMBrowser *bPtr = (WMBrowser *) clientData;
909         WMList *lPtr = (WMList *) self;
910         WMListItem *item;
911         int i, selNo;
912         static WMListItem *oldItem = NULL;
913         static int oldSelNo = 0;
914
915         item = WMGetListSelectedItem(lPtr);
916         selNo = WMGetArrayItemCount(WMGetListSelectedItems(lPtr));
917
918         if (oldItem == NULL || oldItem != item || oldSelNo != selNo) {
919                 for (i = 0; i < bPtr->columnCount; i++) {
920                         if (lPtr == bPtr->columns[i])
921                                 break;
922                 }
923                 assert(i < bPtr->columnCount);
924
925                 bPtr->selectedColumn = i;
926
927                 /* columns at right must be cleared */
928                 removeColumn(bPtr, i + 1);
929                 /* open directory */
930                 if (item && item->isBranch && selNo == 1) {
931                         WMAddBrowserColumn(bPtr);
932                 }
933                 if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
934                         i = 0;
935                 else
936                         i = bPtr->usedColumnCount - bPtr->maxVisibleColumns;
937                 scrollToColumn(bPtr, i, True);
938                 if (item && item->isBranch && selNo == 1) {
939                         loadColumn(bPtr, bPtr->usedColumnCount - 1);
940                 }
941         }
942
943         /* call callback for click */
944         if (bPtr->action)
945                 (*bPtr->action) (bPtr, bPtr->clientData);
946
947         oldItem = item;
948         oldSelNo = selNo;
949 }
950
951 static void listDoubleCallback(void *self, void *clientData)
952 {
953         WMBrowser *bPtr = (WMBrowser *) clientData;
954         WMList *lPtr = (WMList *) self;
955         WMListItem *item;
956
957         item = WMGetListSelectedItem(lPtr);
958         if (!item)
959                 return;
960
961         /* call callback for double click */
962         if (bPtr->doubleAction)
963                 (*bPtr->doubleAction) (bPtr, bPtr->doubleClientData);
964 }
965
966 void WMLoadBrowserColumnZero(WMBrowser * bPtr)
967 {
968         if (!bPtr->flags.loaded) {
969                 /* create column 0 */
970                 WMAddBrowserColumn(bPtr);
971
972                 loadColumn(bPtr, 0);
973
974                 /* make column 0 visible */
975                 scrollToColumn(bPtr, 0, True);
976
977                 bPtr->flags.loaded = 1;
978         }
979 }
980
981 void WMRemoveBrowserItem(WMBrowser * bPtr, int column, int row)
982 {
983         WMList *list;
984
985         if (column < 0 || column >= bPtr->usedColumnCount)
986                 return;
987
988         list = WMGetBrowserListInColumn(bPtr, column);
989
990         if (row < 0 || row >= WMGetListNumberOfRows(list))
991                 return;
992
993         removeColumn(bPtr, column + 1);
994         if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
995                 scrollToColumn(bPtr, 0, True);
996         else
997                 scrollToColumn(bPtr, bPtr->usedColumnCount - bPtr->maxVisibleColumns, True);
998
999         WMRemoveListItem(list, row);
1000 }
1001
1002 static void listSelectionObserver(void *observerData, WMNotification * notification)
1003 {
1004         WMBrowser *bPtr = (WMBrowser *) observerData;
1005         int column;
1006         WMList *lPtr = (WMList *) WMGetNotificationObject(notification);
1007
1008         for (column = 0; column < bPtr->usedColumnCount; column++)
1009                 if (bPtr->columns[column] == lPtr)
1010                         break;
1011
1012         /* this can happen when a list is being cleared with WMClearList
1013          * after the column was removed */
1014         if (column >= bPtr->usedColumnCount) {
1015                 return;
1016         }
1017
1018         if (WMGetArrayItemCount(WMGetListSelectedItems(lPtr)) == 0)
1019                 column--;
1020
1021         bPtr->selectedColumn = column;
1022 }
1023
1024 int WMAddBrowserColumn(WMBrowser * bPtr)
1025 {
1026         WMList *list;
1027         WMList **clist;
1028         char **tlist;
1029         int colY;
1030         int index;
1031
1032         if (bPtr->usedColumnCount < bPtr->columnCount) {
1033                 return bPtr->usedColumnCount++;
1034         }
1035
1036         bPtr->usedColumnCount++;
1037
1038         if (bPtr->flags.isTitled) {
1039                 colY = TITLE_SPACING + bPtr->titleHeight;
1040         } else {
1041                 colY = 0;
1042         }
1043
1044         index = bPtr->columnCount;
1045         bPtr->columnCount++;
1046         clist = wmalloc(sizeof(WMList *) * bPtr->columnCount);
1047         tlist = wmalloc(sizeof(char *) * bPtr->columnCount);
1048         memcpy(clist, bPtr->columns, sizeof(WMList *) * (bPtr->columnCount - 1));
1049         memcpy(tlist, bPtr->titles, sizeof(char *) * (bPtr->columnCount - 1));
1050         if (bPtr->columns)
1051                 wfree(bPtr->columns);
1052         if (bPtr->titles)
1053                 wfree(bPtr->titles);
1054         bPtr->columns = clist;
1055         bPtr->titles = tlist;
1056
1057         bPtr->titles[index] = NULL;
1058
1059         list = WMCreateList(bPtr);
1060         WMSetListAllowMultipleSelection(list, bPtr->flags.allowMultipleSelection);
1061         WMSetListAllowEmptySelection(list, bPtr->flags.allowEmptySelection);
1062         WMSetListAction(list, listCallback, bPtr);
1063         WMSetListDoubleAction(list, listDoubleCallback, bPtr);
1064         WMSetListUserDrawProc(list, paintItem);
1065         WMAddNotificationObserver(listSelectionObserver, bPtr, WMListSelectionDidChangeNotification, list);
1066
1067         bPtr->columns[index] = list;
1068
1069         WMResizeWidget(list, bPtr->columnSize.width, bPtr->columnSize.height);
1070         WMMoveWidget(list, (bPtr->columnSize.width + COLUMN_SPACING) * index, colY);
1071         if (COLUMN_IS_VISIBLE(bPtr, index))
1072                 WMMapWidget(list);
1073
1074         /* update the scroller */
1075         if (bPtr->columnCount > bPtr->maxVisibleColumns) {
1076                 float value, proportion;
1077
1078                 value = bPtr->firstVisibleColumn / (float)(bPtr->columnCount - bPtr->maxVisibleColumns);
1079                 proportion = bPtr->maxVisibleColumns / (float)bPtr->columnCount;
1080                 WMSetScrollerParameters(bPtr->scroller, value, proportion);
1081         }
1082
1083         return index;
1084 }
1085
1086 static void destroyBrowser(WMBrowser * bPtr)
1087 {
1088         int i;
1089
1090         for (i = 0; i < bPtr->columnCount; i++) {
1091                 if (bPtr->titles[i])
1092                         wfree(bPtr->titles[i]);
1093         }
1094         wfree(bPtr->titles);
1095
1096         wfree(bPtr->pathSeparator);
1097
1098         WMRemoveNotificationObserver(bPtr);
1099
1100         wfree(bPtr);
1101 }
1102
1103 static char *createTruncatedString(WMFont * font, char *text, int *textLen, int width)
1104 {
1105         int dLen = WMWidthOfString(font, ".", 1);
1106         char *textBuf = (char *)wmalloc((*textLen) + 4);
1107
1108         if (width >= 3 * dLen) {
1109                 int dddLen = 3 * dLen;
1110                 int tmpTextLen = *textLen;
1111
1112                 strcpy(textBuf, text);
1113                 while (tmpTextLen && (WMWidthOfString(font, textBuf, tmpTextLen) + dddLen > width))
1114                         tmpTextLen--;
1115                 strcpy(textBuf + tmpTextLen, "...");
1116                 *textLen = tmpTextLen + 3;
1117         } else if (width >= 2 * dLen) {
1118                 strcpy(textBuf, "..");
1119                 *textLen = 2;
1120         } else if (width >= dLen) {
1121                 strcpy(textBuf, ".");
1122                 *textLen = 1;
1123         } else {
1124                 *textBuf = '\0';
1125                 *textLen = 0;
1126         }
1127         return textBuf;
1128 }