WINGs: Added 'const' attribute to function 'WMCreateHashTable'
[wmaker-crm.git] / WINGs / wbrowser.c
blobd3904070f99e135ec9d36a6dd2164ac2a9de3c7a
2 #include "WINGsP.h"
3 #include <math.h> /* for : double rint (double) */
5 typedef struct W_Browser {
6 W_Class widgetClass;
7 W_View *view;
9 char **titles;
10 WMList **columns;
12 short columnCount;
13 short usedColumnCount; /* columns actually being used */
14 short minColumnWidth;
16 short maxVisibleColumns;
17 short firstVisibleColumn;
19 short titleHeight;
21 short selectedColumn;
23 WMSize columnSize;
25 void *clientData;
26 WMAction *action;
27 void *doubleClientData;
28 WMAction *doubleAction;
30 WMBrowserDelegate *delegate;
32 WMScroller *scroller;
34 char *pathSeparator;
36 struct {
37 unsigned int isTitled:1;
38 unsigned int allowMultipleSelection:1;
39 unsigned int allowEmptySelection:1;
40 unsigned int hasScroller:1;
42 /* */
43 unsigned int loaded:1;
44 unsigned int loadingColumn:1;
45 } flags;
46 } Browser;
48 #define COLUMN_SPACING 4
49 #define TITLE_SPACING 2
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 "/"
59 #define MIN_VISIBLE_COLUMNS 1
60 #define MAX_VISIBLE_COLUMNS 32
62 #define COLUMN_IS_VISIBLE(b, c) ((c) >= (b)->firstVisibleColumn \
63 && (c) < (b)->firstVisibleColumn + (b)->maxVisibleColumns)
65 static void handleEvents(XEvent * event, void *data);
66 static void destroyBrowser(WMBrowser * bPtr);
68 static void setupScroller(WMBrowser * bPtr);
70 static void scrollToColumn(WMBrowser * bPtr, int column, Bool updateScroller);
72 static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect);
74 static void loadColumn(WMBrowser * bPtr, int column);
76 static void removeColumn(WMBrowser * bPtr, int column);
78 static char *createTruncatedString(WMFont * font, const char *text, int *textLen, int width);
80 static void willResizeBrowser(W_ViewDelegate *, WMView *, unsigned int *, unsigned int *);
82 W_ViewDelegate _BrowserViewDelegate = {
83 NULL,
84 NULL,
85 NULL,
86 NULL,
87 willResizeBrowser
90 WMBrowser *WMCreateBrowser(WMWidget * parent)
92 WMBrowser *bPtr;
93 int i;
95 wassertrv(parent, NULL);
97 bPtr = wmalloc(sizeof(WMBrowser));
99 bPtr->widgetClass = WC_Browser;
101 bPtr->view = W_CreateView(W_VIEW(parent));
102 if (!bPtr->view) {
103 wfree(bPtr);
104 return NULL;
106 bPtr->view->self = bPtr;
108 bPtr->view->delegate = &_BrowserViewDelegate;
110 WMCreateEventHandler(bPtr->view, ExposureMask | StructureNotifyMask
111 | ClientMessageMask, handleEvents, bPtr);
113 /* default configuration */
114 bPtr->flags.hasScroller = DEFAULT_HAS_SCROLLER;
116 bPtr->titleHeight = DEFAULT_TITLE_HEIGHT;
117 bPtr->flags.isTitled = DEFAULT_IS_TITLED;
118 bPtr->maxVisibleColumns = DEFAULT_MAX_VISIBLE_COLUMNS;
120 WMResizeWidget(bPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
122 bPtr->pathSeparator = wstrdup(DEFAULT_SEPARATOR);
124 if (bPtr->flags.hasScroller)
125 setupScroller(bPtr);
127 for (i = 0; i < bPtr->maxVisibleColumns; i++) {
128 WMAddBrowserColumn(bPtr);
130 bPtr->usedColumnCount = 0;
132 bPtr->selectedColumn = -1;
134 return bPtr;
137 void WMSetBrowserAllowMultipleSelection(WMBrowser * bPtr, Bool flag)
139 int i;
141 bPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
142 for (i = 0; i < bPtr->columnCount; i++) {
143 WMSetListAllowMultipleSelection(bPtr->columns[i], flag);
147 void WMSetBrowserAllowEmptySelection(WMBrowser * bPtr, Bool flag)
149 int i;
151 bPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
152 for (i = 0; i < bPtr->columnCount; i++) {
153 WMSetListAllowEmptySelection(bPtr->columns[i], flag);
157 int WMGetBrowserMaxVisibleColumns(WMBrowser * bPtr)
159 return bPtr->maxVisibleColumns;
162 void WMSetBrowserMaxVisibleColumns(WMBrowser * bPtr, int columns)
164 int curMaxVisibleColumns;
165 int newFirstVisibleColumn = 0;
167 assert(bPtr != NULL);
169 columns = (columns < MIN_VISIBLE_COLUMNS) ? MIN_VISIBLE_COLUMNS : columns;
170 columns = (columns > MAX_VISIBLE_COLUMNS) ? MAX_VISIBLE_COLUMNS : columns;
171 if (columns == bPtr->maxVisibleColumns) {
172 return;
174 curMaxVisibleColumns = bPtr->maxVisibleColumns;
175 bPtr->maxVisibleColumns = columns;
176 /* browser not loaded */
177 if (!bPtr->flags.loaded) {
178 if ((columns > curMaxVisibleColumns) && (columns > bPtr->columnCount)) {
179 int i = columns - bPtr->columnCount;
180 bPtr->usedColumnCount = bPtr->columnCount;
181 while (i--) {
182 WMAddBrowserColumn(bPtr);
184 bPtr->usedColumnCount = 0;
186 /* browser loaded and columns > curMaxVisibleColumns */
187 } else if (columns > curMaxVisibleColumns) {
188 if (bPtr->usedColumnCount > columns) {
189 newFirstVisibleColumn = bPtr->usedColumnCount - columns;
191 if (newFirstVisibleColumn > bPtr->firstVisibleColumn) {
192 newFirstVisibleColumn = bPtr->firstVisibleColumn;
194 if (columns > bPtr->columnCount) {
195 int i = columns - bPtr->columnCount;
196 int curUsedColumnCount = bPtr->usedColumnCount;
197 bPtr->usedColumnCount = bPtr->columnCount;
198 while (i--) {
199 WMAddBrowserColumn(bPtr);
201 bPtr->usedColumnCount = curUsedColumnCount;
203 /* browser loaded and columns < curMaxVisibleColumns */
204 } else {
205 newFirstVisibleColumn = bPtr->firstVisibleColumn;
206 if (newFirstVisibleColumn + columns >= bPtr->usedColumnCount) {
207 removeColumn(bPtr, newFirstVisibleColumn + columns);
210 WMResizeWidget(bPtr, bPtr->view->size.width, bPtr->view->size.height);
211 if (bPtr->flags.loaded) {
212 XClearArea(bPtr->view->screen->display, bPtr->view->window, 0, 0,
213 bPtr->view->size.width, bPtr->titleHeight, False);
214 scrollToColumn(bPtr, newFirstVisibleColumn, True);
218 int WMGetBrowserNumberOfColumns(WMBrowser * bPtr)
220 return bPtr->usedColumnCount;
223 void WMSetBrowserPathSeparator(WMBrowser * bPtr, const char *separator)
225 if (bPtr->pathSeparator)
226 wfree(bPtr->pathSeparator);
227 bPtr->pathSeparator = wstrdup(separator);
230 static void drawTitleOfColumn(WMBrowser * bPtr, int column)
232 WMScreen *scr = bPtr->view->screen;
233 int x;
235 x = (column - bPtr->firstVisibleColumn) * (bPtr->columnSize.width + COLUMN_SPACING);
237 XFillRectangle(scr->display, bPtr->view->window, WMColorGC(scr->darkGray), x, 0,
238 bPtr->columnSize.width, bPtr->titleHeight);
239 W_DrawRelief(scr, bPtr->view->window, x, 0, bPtr->columnSize.width, bPtr->titleHeight, WRSunken);
241 if (column < bPtr->usedColumnCount && bPtr->titles[column]) {
242 int titleLen = strlen(bPtr->titles[column]);
243 int widthC = bPtr->columnSize.width - 8;
245 if (WMWidthOfString(scr->boldFont, bPtr->titles[column], titleLen)
246 > widthC) {
247 char *titleBuf = createTruncatedString(scr->boldFont,
248 bPtr->titles[column],
249 &titleLen, widthC);
250 W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
251 (bPtr->titleHeight - WMFontHeight(scr->boldFont)) / 2,
252 bPtr->columnSize.width, WACenter, scr->white, False, titleBuf, titleLen);
253 wfree(titleBuf);
254 } else {
255 W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
256 (bPtr->titleHeight - WMFontHeight(scr->boldFont)) / 2,
257 bPtr->columnSize.width, WACenter, scr->white,
258 False, bPtr->titles[column], titleLen);
263 WMList *WMGetBrowserListInColumn(WMBrowser * bPtr, int column)
265 if (column < 0 || column >= bPtr->usedColumnCount)
266 return NULL;
268 return bPtr->columns[column];
271 void WMSetBrowserDelegate(WMBrowser * bPtr, WMBrowserDelegate * delegate)
273 bPtr->delegate = delegate;
276 int WMGetBrowserFirstVisibleColumn(WMBrowser * bPtr)
278 return bPtr->firstVisibleColumn;
281 static void removeColumn(WMBrowser * bPtr, int column)
283 int i, clearEnd, destroyEnd;
284 WMList **clist;
285 char **tlist;
287 assert(bPtr != NULL);
289 column = (column < 0) ? 0 : column;
290 if (column >= bPtr->columnCount) {
291 return;
293 if (column < bPtr->maxVisibleColumns) {
294 clearEnd = bPtr->maxVisibleColumns;
295 destroyEnd = bPtr->columnCount;
296 bPtr->columnCount = bPtr->maxVisibleColumns;
297 } else {
298 clearEnd = column;
299 destroyEnd = bPtr->columnCount;
300 bPtr->columnCount = column;
302 if (column < bPtr->usedColumnCount) {
303 bPtr->usedColumnCount = column;
305 for (i = column; i < clearEnd; i++) {
306 if (bPtr->titles[i]) {
307 wfree(bPtr->titles[i]);
308 bPtr->titles[i] = NULL;
310 WMClearList(bPtr->columns[i]);
312 for (; i < destroyEnd; i++) {
313 if (bPtr->titles[i]) {
314 wfree(bPtr->titles[i]);
315 bPtr->titles[i] = NULL;
317 WMRemoveNotificationObserverWithName(bPtr, WMListSelectionDidChangeNotification, bPtr->columns[i]);
318 WMDestroyWidget(bPtr->columns[i]);
319 bPtr->columns[i] = NULL;
321 clist = wmalloc(sizeof(WMList *) * (bPtr->columnCount));
322 tlist = wmalloc(sizeof(char *) * (bPtr->columnCount));
323 memcpy(clist, bPtr->columns, sizeof(WMList *) * (bPtr->columnCount));
324 memcpy(tlist, bPtr->titles, sizeof(char *) * (bPtr->columnCount));
325 wfree(bPtr->titles);
326 wfree(bPtr->columns);
327 bPtr->titles = tlist;
328 bPtr->columns = clist;
331 WMListItem *WMGetBrowserSelectedItemInColumn(WMBrowser * bPtr, int column)
333 if ((column < 0) || (column >= bPtr->usedColumnCount))
334 return NULL;
336 return WMGetListSelectedItem(bPtr->columns[column]);
339 int WMGetBrowserSelectedColumn(WMBrowser * bPtr)
341 return bPtr->selectedColumn;
344 int WMGetBrowserSelectedRowInColumn(WMBrowser * bPtr, int column)
346 if (column >= 0 && column < bPtr->columnCount) {
347 return WMGetListSelectedItemRow(bPtr->columns[column]);
348 } else {
349 return -1;
353 void WMSetBrowserColumnTitle(WMBrowser * bPtr, int column, const char *title)
355 assert(column >= 0);
356 assert(column < bPtr->usedColumnCount);
358 if (bPtr->titles[column])
359 wfree(bPtr->titles[column]);
361 bPtr->titles[column] = wstrdup(title);
363 if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
364 drawTitleOfColumn(bPtr, column);
368 void WMSetBrowserTitled(WMBrowser * bPtr, Bool flag)
370 int i;
371 int columnX, columnY;
373 flag = ((flag == 0) ? 0 : 1);
375 if (bPtr->flags.isTitled == flag)
376 return;
378 columnX = 0;
380 if (!bPtr->flags.isTitled) {
381 columnY = TITLE_SPACING + bPtr->titleHeight;
383 bPtr->columnSize.height -= columnY;
385 for (i = 0; i < bPtr->columnCount; i++) {
386 WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
388 columnX = WMWidgetView(bPtr->columns[i])->pos.x;
390 WMMoveWidget(bPtr->columns[i], columnX, columnY);
392 } else {
393 bPtr->columnSize.height += TITLE_SPACING + bPtr->titleHeight;
395 for (i = 0; i < bPtr->columnCount; i++) {
396 WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
398 columnX = WMWidgetView(bPtr->columns[i])->pos.x;
400 WMMoveWidget(bPtr->columns[i], columnX, 0);
404 bPtr->flags.isTitled = flag;
407 void WMSortBrowserColumn(WMBrowser * bPtr, int column)
409 WMSortListItems(bPtr->columns[column]);
412 void WMSortBrowserColumnWithComparer(WMBrowser * bPtr, int column, WMCompareDataProc * func)
414 WMSortListItemsWithComparer(bPtr->columns[column], func);
417 WMListItem *WMInsertBrowserItem(WMBrowser * bPtr, int column, int row, const char *text, Bool isBranch)
419 WMListItem *item;
421 if (column < 0 || column >= bPtr->columnCount)
422 return NULL;
424 item = WMInsertListItem(bPtr->columns[column], row, text);
425 item->isBranch = isBranch;
427 return item;
430 static void willResizeBrowser(W_ViewDelegate * self, WMView * view, unsigned int *width, unsigned int *height)
432 WMBrowser *bPtr = (WMBrowser *) view->self;
433 int cols = bPtr->maxVisibleColumns;
434 int colX, colY;
435 int i;
437 /* Parameter not used, but tell the compiler that it is ok */
438 (void) self;
440 assert(*width > 0);
441 assert(*height > 0);
443 bPtr->columnSize.width = (*width - (cols - 1) * COLUMN_SPACING) / cols;
444 bPtr->columnSize.height = *height;
446 if (bPtr->flags.isTitled) {
447 colY = TITLE_SPACING + bPtr->titleHeight;
448 bPtr->columnSize.height -= colY;
449 } else {
450 colY = 0;
453 if (bPtr->flags.hasScroller) {
454 bPtr->columnSize.height -= SCROLLER_WIDTH + 4;
456 if (bPtr->scroller) {
457 WMResizeWidget(bPtr->scroller, *width - 2, 1);
458 WMMoveWidget(bPtr->scroller, 1, *height - SCROLLER_WIDTH - 1);
462 colX = 0;
463 for (i = 0; i < bPtr->columnCount; i++) {
464 WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width, bPtr->columnSize.height);
466 WMMoveWidget(bPtr->columns[i], colX, colY);
468 if (COLUMN_IS_VISIBLE(bPtr, i)) {
469 colX += bPtr->columnSize.width + COLUMN_SPACING;
474 static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect)
476 WMView *view = W_VIEW(lPtr);
477 W_Screen *scr = view->screen;
478 Display *display = scr->display;
479 WMFont *font = ((state & WLDSIsBranch) ? scr->boldFont : scr->normalFont);
480 WMColor *backColor = ((state & WLDSSelected) ? scr->white : view->backColor);
481 int width, height, x, y, textLen;
483 /* Parameter not used, but tell the compiler that it is ok */
484 (void) index;
486 width = rect->size.width;
487 height = rect->size.height;
488 x = rect->pos.x;
489 y = rect->pos.y;
490 textLen = strlen(text);
492 XFillRectangle(display, d, WMColorGC(backColor), x, y, width, height);
494 if (text) {
495 /* Avoid overlaping... */
496 int widthC = (state & WLDSIsBranch) ? width - 20 : width - 8;
497 if (WMWidthOfString(font, text, textLen) > widthC) {
498 char *textBuf = createTruncatedString(font, text, &textLen, widthC);
499 W_PaintText(view, d, font, x + 4, y, widthC, WALeft, scr->black, False, textBuf, textLen);
500 wfree(textBuf);
501 } else {
502 W_PaintText(view, d, font, x + 4, y, widthC, WALeft, scr->black, False, text, textLen);
506 if (state & WLDSIsBranch) {
507 WMColor *lineColor = ((state & WLDSSelected) ? scr->gray : scr->white);
509 XDrawLine(display, d, WMColorGC(scr->darkGray), x + width - 11, y + 3,
510 x + width - 6, y + height / 2);
511 XDrawLine(display, d, WMColorGC(lineColor), x + width - 11, y + height - 5,
512 x + width - 6, y + height / 2);
513 XDrawLine(display, d, WMColorGC(scr->black), x + width - 12, y + 3,
514 x + width - 12, y + height - 5);
518 static void scrollCallback(WMWidget * scroller, void *self)
520 WMBrowser *bPtr = (WMBrowser *) self;
521 WMScroller *sPtr = (WMScroller *) scroller;
522 int newFirst;
523 #define LAST_VISIBLE_COLUMN bPtr->firstVisibleColumn+bPtr->maxVisibleColumns
525 switch (WMGetScrollerHitPart(sPtr)) {
526 case WSDecrementLine:
527 if (bPtr->firstVisibleColumn > 0) {
528 scrollToColumn(bPtr, bPtr->firstVisibleColumn - 1, True);
530 break;
532 case WSDecrementPage:
533 case WSDecrementWheel:
534 if (bPtr->firstVisibleColumn > 0) {
535 newFirst = bPtr->firstVisibleColumn - bPtr->maxVisibleColumns;
537 scrollToColumn(bPtr, newFirst, True);
539 break;
541 case WSIncrementLine:
542 if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
543 scrollToColumn(bPtr, bPtr->firstVisibleColumn + 1, True);
545 break;
547 case WSIncrementPage:
548 case WSIncrementWheel:
549 if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
550 newFirst = bPtr->firstVisibleColumn + bPtr->maxVisibleColumns;
552 if (newFirst + bPtr->maxVisibleColumns >= bPtr->columnCount)
553 newFirst = bPtr->columnCount - bPtr->maxVisibleColumns;
555 scrollToColumn(bPtr, newFirst, True);
557 break;
559 case WSKnob:
561 double floatValue;
562 double value = bPtr->columnCount - bPtr->maxVisibleColumns;
564 floatValue = WMGetScrollerValue(bPtr->scroller);
566 floatValue = (floatValue * value) / value;
568 newFirst = rint(floatValue * (float)(bPtr->columnCount - bPtr->maxVisibleColumns));
570 if (bPtr->firstVisibleColumn != newFirst)
571 scrollToColumn(bPtr, newFirst, False);
572 /*else
573 WMSetScrollerParameters(bPtr->scroller, floatValue,
574 bPtr->maxVisibleColumns/(float)bPtr->columnCount);
578 break;
580 case WSKnobSlot:
581 case WSNoPart:
582 /* do nothing */
583 break;
585 #undef LAST_VISIBLE_COLUMN
588 static void setupScroller(WMBrowser * bPtr)
590 WMScroller *sPtr;
591 int y;
593 y = bPtr->view->size.height - SCROLLER_WIDTH - 1;
595 sPtr = WMCreateScroller(bPtr);
596 WMSetScrollerAction(sPtr, scrollCallback, bPtr);
597 WMMoveWidget(sPtr, 1, y);
598 WMResizeWidget(sPtr, bPtr->view->size.width - 2, SCROLLER_WIDTH);
600 bPtr->scroller = sPtr;
602 WMMapWidget(sPtr);
605 void WMSetBrowserAction(WMBrowser * bPtr, WMAction * action, void *clientData)
607 bPtr->action = action;
608 bPtr->clientData = clientData;
611 void WMSetBrowserDoubleAction(WMBrowser * bPtr, WMAction * action, void *clientData)
613 bPtr->doubleAction = action;
614 bPtr->doubleClientData = clientData;
617 void WMSetBrowserHasScroller(WMBrowser * bPtr, int hasScroller)
619 bPtr->flags.hasScroller = hasScroller;
622 char *WMSetBrowserPath(WMBrowser * bPtr, char *path)
624 int i;
625 char *str;
626 char *tmp, *retPtr = NULL;
627 int item;
628 WMListItem *listItem;
630 /* WMLoadBrowserColumnZero must be call first */
631 if (!bPtr->flags.loaded) {
632 return False;
635 removeColumn(bPtr, 1);
637 WMSelectListItem(bPtr->columns[0], -1);
638 WMSetListPosition(bPtr->columns[0], 0);
640 i = 0;
641 str = wstrdup(path);
642 tmp = strtok(str, bPtr->pathSeparator);
643 while (tmp) {
644 /* select it in the column */
645 item = WMFindRowOfListItemWithTitle(bPtr->columns[i], tmp);
646 if (item < 0) {
647 retPtr = &path[(int)(tmp - str)];
648 break;
650 WMSelectListItem(bPtr->columns[i], item);
651 WMSetListPosition(bPtr->columns[i], item);
653 listItem = WMGetListItem(bPtr->columns[i], item);
654 if (!listItem || !listItem->isBranch) {
655 break;
658 /* load next column */
659 WMAddBrowserColumn(bPtr);
661 loadColumn(bPtr, i + 1);
663 tmp = strtok(NULL, bPtr->pathSeparator);
665 i++;
668 wfree(str);
670 for (i = bPtr->usedColumnCount - 1; (i > -1) && !WMGetListSelectedItem(bPtr->columns[i]); i--) ;
672 bPtr->selectedColumn = i;
674 if (bPtr->columnCount < bPtr->maxVisibleColumns) {
675 int i = bPtr->maxVisibleColumns - bPtr->columnCount;
676 int curUsedColumnCount = bPtr->usedColumnCount;
677 bPtr->usedColumnCount = bPtr->columnCount;
678 while (i--) {
679 WMAddBrowserColumn(bPtr);
681 bPtr->usedColumnCount = curUsedColumnCount;
684 scrollToColumn(bPtr, bPtr->columnCount - bPtr->maxVisibleColumns, True);
686 return retPtr;
689 char *WMGetBrowserPath(WMBrowser * bPtr)
691 return WMGetBrowserPathToColumn(bPtr, bPtr->columnCount);
694 char *WMGetBrowserPathToColumn(WMBrowser * bPtr, int column)
696 int i, size;
697 char *path;
698 size_t slen;
699 WMListItem *item;
701 if (column >= bPtr->usedColumnCount)
702 column = bPtr->usedColumnCount - 1;
704 if (column < 0) {
705 return wstrdup(bPtr->pathSeparator);
708 /* calculate size of buffer */
709 size = 0;
710 for (i = 0; i <= column; i++) {
711 item = WMGetListSelectedItem(bPtr->columns[i]);
712 if (!item)
713 break;
714 size += strlen(item->text);
717 /* get the path */
718 slen = size + (column + 1) * strlen(bPtr->pathSeparator) + 1;
719 path = wmalloc(slen);
720 /* ignore first `/' */
721 for (i = 0; i <= column; i++) {
722 if (wstrlcat(path, bPtr->pathSeparator, slen) >= slen)
723 goto error;
725 item = WMGetListSelectedItem(bPtr->columns[i]);
726 if (!item)
727 break;
729 if (wstrlcat(path, item->text, slen) >= slen)
730 goto error;
734 return path;
736 error:
737 wfree(path);
738 return NULL;
741 WMArray *WMGetBrowserPaths(WMBrowser * bPtr)
743 int column, i, k, size, selNo;
744 char *path;
745 size_t slen;
746 WMListItem *item, *lastItem;
747 WMArray *paths, *items;
749 column = bPtr->usedColumnCount - 1;
751 if (column < 0) {
752 paths = WMCreateArrayWithDestructor(1, wfree);
753 WMAddToArray(paths, wstrdup(bPtr->pathSeparator));
754 return paths;
757 items = WMGetListSelectedItems(bPtr->columns[column]);
758 selNo = WMGetArrayItemCount(items);
759 paths = WMCreateArrayWithDestructor(selNo, wfree);
761 if (selNo <= 1) {
762 WMAddToArray(paths, WMGetBrowserPath(bPtr));
763 return paths;
766 /* calculate size of buffer */
767 size = 0;
768 for (i = 0; i < column; i++) {
769 item = WMGetListSelectedItem(bPtr->columns[i]);
770 if (!item)
771 break;
772 size += strlen(item->text);
775 size += (column + 1) * strlen(bPtr->pathSeparator) + 1;
777 for (k = 0; k < selNo; k++) {
778 /* get the path */
779 lastItem = WMGetFromArray(items, k);
780 slen = size + (lastItem != NULL ? strlen(lastItem->text) : 0);
781 path = wmalloc(slen);
782 /* ignore first `/' */
783 for (i = 0; i <= column; i++) {
784 if (wstrlcat(path, bPtr->pathSeparator, slen) >= slen) {
785 wfree(path);
786 return NULL;
788 if (i == column) {
789 item = lastItem;
790 } else {
791 item = WMGetListSelectedItem(bPtr->columns[i]);
793 if (!item)
794 break;
795 if (wstrlcat(path, item->text, slen) >= slen) {
796 wfree(path);
797 return NULL;
800 WMAddToArray(paths, path);
803 return paths;
806 Bool WMBrowserAllowsMultipleSelection(WMBrowser * bPtr)
808 return bPtr->flags.allowMultipleSelection;
811 Bool WMBrowserAllowsEmptySelection(WMBrowser * bPtr)
813 return bPtr->flags.allowEmptySelection;
816 static void loadColumn(WMBrowser * bPtr, int column)
818 assert(bPtr->delegate);
819 assert(bPtr->delegate->createRowsForColumn);
821 bPtr->flags.loadingColumn = 1;
822 (*bPtr->delegate->createRowsForColumn) (bPtr->delegate, bPtr, column, bPtr->columns[column]);
823 bPtr->flags.loadingColumn = 0;
825 if (bPtr->delegate->titleOfColumn) {
826 char *title;
828 title = (*bPtr->delegate->titleOfColumn) (bPtr->delegate, bPtr, column);
830 if (bPtr->titles[column])
831 wfree(bPtr->titles[column]);
833 bPtr->titles[column] = wstrdup(title);
835 if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
836 drawTitleOfColumn(bPtr, column);
841 static void paintBrowser(WMBrowser * bPtr)
843 int i;
845 if (!bPtr->view->flags.mapped)
846 return;
848 W_DrawRelief(bPtr->view->screen, bPtr->view->window, 0,
849 bPtr->view->size.height - SCROLLER_WIDTH - 2, bPtr->view->size.width, 22, WRSunken);
851 if (bPtr->flags.isTitled) {
852 for (i = 0; i < bPtr->maxVisibleColumns; i++) {
853 drawTitleOfColumn(bPtr, i + bPtr->firstVisibleColumn);
858 static void handleEvents(XEvent * event, void *data)
860 WMBrowser *bPtr = (WMBrowser *) data;
862 CHECK_CLASS(data, WC_Browser);
864 switch (event->type) {
865 case Expose:
866 paintBrowser(bPtr);
867 break;
869 case DestroyNotify:
870 destroyBrowser(bPtr);
871 break;
876 static void scrollToColumn(WMBrowser * bPtr, int column, Bool updateScroller)
878 int i;
879 int x;
880 int notify = 0;
882 if (column != bPtr->firstVisibleColumn) {
883 notify = 1;
886 if (column < 0)
887 column = 0;
889 if (notify && bPtr->delegate && bPtr->delegate->willScroll) {
890 (*bPtr->delegate->willScroll) (bPtr->delegate, bPtr);
893 x = 0;
894 bPtr->firstVisibleColumn = column;
895 for (i = 0; i < bPtr->columnCount; i++) {
896 if (COLUMN_IS_VISIBLE(bPtr, i)) {
897 WMMoveWidget(bPtr->columns[i], x, WMWidgetView(bPtr->columns[i])->pos.y);
898 if (!WMWidgetView(bPtr->columns[i])->flags.realized)
899 WMRealizeWidget(bPtr->columns[i]);
900 WMMapWidget(bPtr->columns[i]);
901 x += bPtr->columnSize.width + COLUMN_SPACING;
902 } else {
903 WMUnmapWidget(bPtr->columns[i]);
907 /* update the scroller */
908 if (updateScroller) {
909 if (bPtr->columnCount > bPtr->maxVisibleColumns) {
910 float value, proportion;
912 value = bPtr->firstVisibleColumn / (float)(bPtr->columnCount - bPtr->maxVisibleColumns);
913 proportion = bPtr->maxVisibleColumns / (float)bPtr->columnCount;
914 WMSetScrollerParameters(bPtr->scroller, value, proportion);
915 } else {
916 WMSetScrollerParameters(bPtr->scroller, 0, 1);
920 if (bPtr->view->flags.mapped)
921 paintBrowser(bPtr);
923 if (notify && bPtr->delegate && bPtr->delegate->didScroll) {
924 (*bPtr->delegate->didScroll) (bPtr->delegate, bPtr);
928 static void listCallback(void *self, void *clientData)
930 WMBrowser *bPtr = (WMBrowser *) clientData;
931 WMList *lPtr = (WMList *) self;
932 WMListItem *item;
933 int i, selNo;
934 static WMListItem *oldItem = NULL;
935 static int oldSelNo = 0;
937 item = WMGetListSelectedItem(lPtr);
938 selNo = WMGetArrayItemCount(WMGetListSelectedItems(lPtr));
940 if (oldItem == NULL || oldItem != item || oldSelNo != selNo) {
941 for (i = 0; i < bPtr->columnCount; i++) {
942 if (lPtr == bPtr->columns[i])
943 break;
945 assert(i < bPtr->columnCount);
947 bPtr->selectedColumn = i;
949 /* columns at right must be cleared */
950 removeColumn(bPtr, i + 1);
951 /* open directory */
952 if (item && item->isBranch && selNo == 1) {
953 WMAddBrowserColumn(bPtr);
955 if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
956 i = 0;
957 else
958 i = bPtr->usedColumnCount - bPtr->maxVisibleColumns;
959 scrollToColumn(bPtr, i, True);
960 if (item && item->isBranch && selNo == 1) {
961 loadColumn(bPtr, bPtr->usedColumnCount - 1);
965 /* call callback for click */
966 if (bPtr->action)
967 (*bPtr->action) (bPtr, bPtr->clientData);
969 oldItem = item;
970 oldSelNo = selNo;
973 static void listDoubleCallback(void *self, void *clientData)
975 WMBrowser *bPtr = (WMBrowser *) clientData;
976 WMList *lPtr = (WMList *) self;
977 WMListItem *item;
979 item = WMGetListSelectedItem(lPtr);
980 if (!item)
981 return;
983 /* call callback for double click */
984 if (bPtr->doubleAction)
985 (*bPtr->doubleAction) (bPtr, bPtr->doubleClientData);
988 void WMLoadBrowserColumnZero(WMBrowser * bPtr)
990 if (!bPtr->flags.loaded) {
991 /* create column 0 */
992 WMAddBrowserColumn(bPtr);
994 loadColumn(bPtr, 0);
996 /* make column 0 visible */
997 scrollToColumn(bPtr, 0, True);
999 bPtr->flags.loaded = 1;
1003 void WMRemoveBrowserItem(WMBrowser * bPtr, int column, int row)
1005 WMList *list;
1007 if (column < 0 || column >= bPtr->usedColumnCount)
1008 return;
1010 list = WMGetBrowserListInColumn(bPtr, column);
1012 if (row < 0 || row >= WMGetListNumberOfRows(list))
1013 return;
1015 removeColumn(bPtr, column + 1);
1016 if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
1017 scrollToColumn(bPtr, 0, True);
1018 else
1019 scrollToColumn(bPtr, bPtr->usedColumnCount - bPtr->maxVisibleColumns, True);
1021 WMRemoveListItem(list, row);
1024 static void listSelectionObserver(void *observerData, WMNotification * notification)
1026 WMBrowser *bPtr = (WMBrowser *) observerData;
1027 int column;
1028 WMList *lPtr = (WMList *) WMGetNotificationObject(notification);
1030 for (column = 0; column < bPtr->usedColumnCount; column++)
1031 if (bPtr->columns[column] == lPtr)
1032 break;
1034 /* this can happen when a list is being cleared with WMClearList
1035 * after the column was removed */
1036 if (column >= bPtr->usedColumnCount) {
1037 return;
1040 if (WMGetArrayItemCount(WMGetListSelectedItems(lPtr)) == 0)
1041 column--;
1043 bPtr->selectedColumn = column;
1046 int WMAddBrowserColumn(WMBrowser * bPtr)
1048 WMList *list;
1049 WMList **clist;
1050 char **tlist;
1051 int colY;
1052 int index;
1054 if (bPtr->usedColumnCount < bPtr->columnCount) {
1055 return bPtr->usedColumnCount++;
1058 bPtr->usedColumnCount++;
1060 if (bPtr->flags.isTitled) {
1061 colY = TITLE_SPACING + bPtr->titleHeight;
1062 } else {
1063 colY = 0;
1066 index = bPtr->columnCount;
1067 bPtr->columnCount++;
1068 clist = wmalloc(sizeof(WMList *) * bPtr->columnCount);
1069 tlist = wmalloc(sizeof(char *) * bPtr->columnCount);
1070 memcpy(clist, bPtr->columns, sizeof(WMList *) * (bPtr->columnCount - 1));
1071 memcpy(tlist, bPtr->titles, sizeof(char *) * (bPtr->columnCount - 1));
1072 if (bPtr->columns)
1073 wfree(bPtr->columns);
1074 if (bPtr->titles)
1075 wfree(bPtr->titles);
1076 bPtr->columns = clist;
1077 bPtr->titles = tlist;
1079 bPtr->titles[index] = NULL;
1081 list = WMCreateList(bPtr);
1082 WMSetListAllowMultipleSelection(list, bPtr->flags.allowMultipleSelection);
1083 WMSetListAllowEmptySelection(list, bPtr->flags.allowEmptySelection);
1084 WMSetListAction(list, listCallback, bPtr);
1085 WMSetListDoubleAction(list, listDoubleCallback, bPtr);
1086 WMSetListUserDrawProc(list, paintItem);
1087 WMAddNotificationObserver(listSelectionObserver, bPtr, WMListSelectionDidChangeNotification, list);
1089 bPtr->columns[index] = list;
1091 WMResizeWidget(list, bPtr->columnSize.width, bPtr->columnSize.height);
1092 WMMoveWidget(list, (bPtr->columnSize.width + COLUMN_SPACING) * index, colY);
1093 if (COLUMN_IS_VISIBLE(bPtr, index))
1094 WMMapWidget(list);
1096 /* update the scroller */
1097 if (bPtr->columnCount > bPtr->maxVisibleColumns) {
1098 float value, proportion;
1100 value = bPtr->firstVisibleColumn / (float)(bPtr->columnCount - bPtr->maxVisibleColumns);
1101 proportion = bPtr->maxVisibleColumns / (float)bPtr->columnCount;
1102 WMSetScrollerParameters(bPtr->scroller, value, proportion);
1105 return index;
1108 static void destroyBrowser(WMBrowser * bPtr)
1110 int i;
1112 for (i = 0; i < bPtr->columnCount; i++) {
1113 if (bPtr->titles[i])
1114 wfree(bPtr->titles[i]);
1116 wfree(bPtr->titles);
1118 wfree(bPtr->pathSeparator);
1120 WMRemoveNotificationObserver(bPtr);
1122 wfree(bPtr);
1125 static char *createTruncatedString(WMFont * font, const char *text, int *textLen, int width)
1127 size_t slen;
1128 int dLen;
1129 char *textBuf;
1131 dLen = WMWidthOfString(font, ".", 1);
1132 slen = *textLen + 4;
1133 textBuf = wmalloc(slen);
1135 if (width >= 3 * dLen) {
1136 int tmpTextLen = *textLen;
1138 if (wstrlcpy(textBuf, text, slen) >= slen)
1139 goto error;
1141 while (tmpTextLen && (WMWidthOfString(font, textBuf, tmpTextLen) + 3 * dLen > width))
1142 tmpTextLen--;
1144 if (wstrlcpy(textBuf + tmpTextLen, "...", slen) >= slen)
1145 goto error;
1147 *textLen = tmpTextLen + 3;
1149 } else if (width >= 2 * dLen) {
1150 if (wstrlcpy(textBuf, "..", slen) >= slen)
1151 goto error;
1153 *textLen = 2;
1155 } else if (width >= dLen) {
1156 if (wstrlcpy(textBuf, ".", slen) >= slen)
1157 goto error;
1159 *textLen = 1;
1161 } else {
1162 *textBuf = '\0';
1163 *textLen = 0;
1166 return textBuf;
1168 error:
1169 wfree(textBuf);
1170 return NULL;