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 }