0.62.0 release preparation
[wmaker-crm.git] / WINGs / wlist.c
blobf46f49f317e867a25ccc66fc27ad00681a39a4cc
5 #include "WINGsP.h"
7 char *WMListDidScrollNotification = "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
10 typedef struct W_List {
11 W_Class widgetClass;
12 W_View *view;
14 WMBag *items; /* list of WMListItem */
16 short selectedItem;
18 short itemHeight;
20 short topItem; /* index of first visible item */
22 short fullFitLines; /* no of lines that fit entirely */
24 void *clientData;
25 WMAction *action;
26 void *doubleClientData;
27 WMAction *doubleAction;
29 WMListDrawProc *draw;
31 WMHandlerID *idleID; /* for updating the scroller after
32 * adding elements */
34 WMScroller *vScroller;
36 WMScroller *hScroller;
39 struct {
40 unsigned int allowMultipleSelection:1;
41 unsigned int userDrawn:1;
42 unsigned int userItemHeight:1;
43 /* */
44 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
45 unsigned int redrawPending:1;
46 unsigned int buttonPressed:1;
47 unsigned int buttonWasPressed:1;
48 } flags;
49 } List;
53 #define DEFAULT_WIDTH 150
54 #define DEFAULT_HEIGHT 150
57 static void destroyList(List *lPtr);
58 static void paintList(List *lPtr);
61 static void handleEvents(XEvent *event, void *data);
62 static void handleActionEvents(XEvent *event, void *data);
63 static void updateScroller(List *lPtr);
65 static void vScrollCallBack(WMWidget *scroller, void *self);
67 static void updateGeometry(WMList *lPtr);
68 static void didResizeList();
71 W_ViewDelegate _ListViewDelegate = {
72 NULL,
73 NULL,
74 didResizeList,
75 NULL,
76 NULL
81 WMList*
82 WMCreateList(WMWidget *parent)
84 List *lPtr;
85 W_Screen *scrPtr = W_VIEW(parent)->screen;
87 lPtr = wmalloc(sizeof(List));
88 memset(lPtr, 0, sizeof(List));
90 lPtr->widgetClass = WC_List;
92 lPtr->view = W_CreateView(W_VIEW(parent));
93 if (!lPtr->view) {
94 wfree(lPtr);
95 return NULL;
97 lPtr->view->self = lPtr;
99 lPtr->view->delegate = &_ListViewDelegate;
101 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
102 |ClientMessageMask, handleEvents, lPtr);
104 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
105 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
106 handleActionEvents, lPtr);
108 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
110 lPtr->items = WMCreateBag(4);
112 /* create the vertical scroller */
113 lPtr->vScroller = WMCreateScroller(lPtr);
114 WMMoveWidget(lPtr->vScroller, 1, 1);
115 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
117 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
119 /* make the scroller map itself when it's realized */
120 WMMapWidget(lPtr->vScroller);
122 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
124 lPtr->selectedItem = -1;
126 return lPtr;
130 static int
131 comparator(const void *a, const void *b)
133 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
137 void
138 WMSortListItems(WMList *lPtr)
140 WMSortBag(lPtr->items, comparator);
142 paintList(lPtr);
147 void
148 WMSortListItemsWithComparer(WMList *lPtr, int (f)(const void*, const void*))
150 WMSortBag(lPtr->items, f);
152 paintList(lPtr);
157 WMListItem*
158 WMInsertListItem(WMList *lPtr, int row, char *text)
160 WMListItem *item;
162 CHECK_CLASS(lPtr, WC_List);
164 item = wmalloc(sizeof(WMListItem));
165 memset(item, 0, sizeof(WMListItem));
166 item->text = wstrdup(text);
169 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
170 && row >= 0)
171 lPtr->selectedItem++;
173 if (row < 0)
174 WMPutInBag(lPtr->items, item);
175 else
176 WMInsertInBag(lPtr->items, row, item);
178 /* update the scroller when idle, so that we don't waste time
179 * updating it when another item is going to be added later */
180 if (!lPtr->idleID) {
181 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
184 return item;
188 void
189 WMRemoveListItem(WMList *lPtr, int row)
191 WMListItem *item;
192 int topItem = lPtr->topItem;
193 int selNotify = 0;
195 CHECK_CLASS(lPtr, WC_List);
197 if (row < 0 || row >= WMGetBagItemCount(lPtr->items))
198 return;
200 if (lPtr->selectedItem == row) {
201 lPtr->selectedItem = -1;
202 selNotify = 1;
203 } else if (lPtr->selectedItem > row) {
204 lPtr->selectedItem--;
207 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
208 lPtr->topItem--;
209 if (lPtr->topItem < 0)
210 lPtr->topItem = 0;
212 item = WMGetFromBag(lPtr->items, row);
213 if (item->text)
214 wfree(item->text);
215 wfree(item);
217 WMDeleteFromBag(lPtr->items, row);
219 if (!lPtr->idleID) {
220 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
222 if (lPtr->topItem != topItem)
223 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
224 if (selNotify)
225 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
226 (void*)((int)lPtr->selectedItem));
231 WMListItem*
232 WMGetListItem(WMList *lPtr, int row)
234 return WMGetFromBag(lPtr->items, row);
238 WMBag*
239 WMGetListItems(WMList *lPtr)
241 return lPtr->items;
246 void
247 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
249 lPtr->flags.userDrawn = 1;
250 lPtr->draw = proc;
254 void
255 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
257 assert(height > 0);
259 lPtr->flags.userItemHeight = 1;
260 lPtr->itemHeight = height;
262 updateGeometry(lPtr);
266 void
267 WMClearList(WMList *lPtr)
269 WMListItem *item;
270 int oldSelected = lPtr->selectedItem;
271 int i;
273 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
274 item = WMGetFromBag(lPtr->items, i);
275 wfree(item->text);
276 wfree(item);
278 WMEmptyBag(lPtr->items);
280 lPtr->topItem = 0;
281 lPtr->selectedItem = -1;
283 if (!lPtr->idleID) {
284 WMDeleteIdleHandler(lPtr->idleID);
285 lPtr->idleID = NULL;
287 if (lPtr->view->flags.realized) {
288 updateScroller(lPtr);
290 if (oldSelected != -1)
291 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
292 (void*)-1);
296 void
297 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
299 lPtr->action = action;
300 lPtr->clientData = clientData;
304 void
305 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
307 lPtr->doubleAction = action;
308 lPtr->doubleClientData = clientData;
312 WMListItem*
313 WMGetListSelectedItem(WMList *lPtr)
315 if (lPtr->selectedItem < 0)
316 return NULL;
318 return WMGetFromBag(lPtr->items, lPtr->selectedItem);
323 WMGetListSelectedItemRow(WMList *lPtr)
325 return lPtr->selectedItem;
330 WMGetListItemHeight(WMList *lPtr)
332 return lPtr->itemHeight;
336 void
337 WMSetListPosition(WMList *lPtr, int row)
339 lPtr->topItem = row;
340 if (lPtr->topItem + lPtr->fullFitLines > WMGetBagItemCount(lPtr->items))
341 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
343 if (lPtr->topItem < 0)
344 lPtr->topItem = 0;
346 if (lPtr->view->flags.realized)
347 updateScroller(lPtr);
351 void
352 WMSetListBottomPosition(WMList *lPtr, int row)
354 if (WMGetBagItemCount(lPtr->items) > lPtr->fullFitLines) {
355 lPtr->topItem = row - lPtr->fullFitLines;
356 if (lPtr->topItem < 0)
357 lPtr->topItem = 0;
358 if (lPtr->view->flags.realized)
359 updateScroller(lPtr);
365 WMGetListNumberOfRows(WMList *lPtr)
367 return WMGetBagItemCount(lPtr->items);
371 WMGetListPosition(WMList *lPtr)
373 return lPtr->topItem;
377 static void
378 vScrollCallBack(WMWidget *scroller, void *self)
380 WMList *lPtr = (WMList*)self;
381 WMScroller *sPtr = (WMScroller*)scroller;
382 int height;
383 int topItem = lPtr->topItem;
384 int itemCount = WMGetBagItemCount(lPtr->items);
386 height = lPtr->view->size.height - 4;
388 switch (WMGetScrollerHitPart(sPtr)) {
389 case WSDecrementLine:
390 if (lPtr->topItem > 0) {
391 lPtr->topItem--;
393 updateScroller(lPtr);
395 break;
397 case WSDecrementPage:
398 if (lPtr->topItem > 0) {
399 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
400 if (lPtr->topItem < 0)
401 lPtr->topItem = 0;
403 updateScroller(lPtr);
405 break;
408 case WSIncrementLine:
409 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
410 lPtr->topItem++;
412 updateScroller(lPtr);
414 break;
416 case WSIncrementPage:
417 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
418 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
420 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
421 lPtr->topItem = itemCount - lPtr->fullFitLines;
423 updateScroller(lPtr);
425 break;
427 case WSKnob:
429 int oldTopItem = lPtr->topItem;
431 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
432 (float)(itemCount - lPtr->fullFitLines);
434 if (oldTopItem != lPtr->topItem)
435 paintList(lPtr);
437 break;
439 case WSKnobSlot:
440 case WSNoPart:
441 /* do nothing */
442 break;
445 if (lPtr->topItem != topItem)
446 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
450 static void
451 paintItem(List *lPtr, int index)
453 WMView *view = lPtr->view;
454 W_Screen *scr = view->screen;
455 int width, height, x, y;
456 WMListItem *itemPtr;
459 itemPtr = WMGetFromBag(lPtr->items, index);
462 width = lPtr->view->size.width - 2 - 19;
463 height = lPtr->itemHeight;
464 x = 19;
465 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
467 if (lPtr->flags.userDrawn) {
468 WMRect rect;
469 int flags;
471 rect.size.width = width;
472 rect.size.height = height;
473 rect.pos.x = x;
474 rect.pos.y = y;
476 flags = itemPtr->uflags;
477 if (itemPtr->disabled)
478 flags |= WLDSDisabled;
479 if (itemPtr->selected)
480 flags |= WLDSSelected;
481 if (itemPtr->isBranch)
482 flags |= WLDSIsBranch;
484 if (lPtr->draw)
485 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
486 &rect);
487 } else {
488 if (itemPtr->selected)
489 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
490 width, height);
491 else
492 XClearArea(scr->display, view->window, x, y, width, height, False);
494 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
495 WALeft, WMColorGC(scr->black), False,
496 itemPtr->text, strlen(itemPtr->text));
502 static void
503 paintList(List *lPtr)
505 W_Screen *scrPtr = lPtr->view->screen;
506 int i, lim;
508 if (!lPtr->view->flags.mapped)
509 return;
511 if (WMGetBagItemCount(lPtr->items) > 0) {
512 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
513 > WMGetBagItemCount(lPtr->items)) {
515 lim = WMGetBagItemCount(lPtr->items) - lPtr->topItem;
516 XClearArea(scrPtr->display, lPtr->view->window, 19,
517 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
518 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
519 } else {
520 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
522 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
523 paintItem(lPtr, i);
525 } else {
526 XClearWindow(scrPtr->display, lPtr->view->window);
528 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
529 lPtr->view->size.height, WRSunken);
532 #if 0
533 static void
534 scrollTo(List *lPtr, int newTop)
538 #endif
540 static void
541 updateScroller(List *lPtr)
543 float knobProportion, floatValue, tmp;
544 int count = WMGetBagItemCount(lPtr->items);
546 if (lPtr->idleID)
547 WMDeleteIdleHandler(lPtr->idleID);
548 lPtr->idleID = NULL;
550 paintList(lPtr);
552 if (count == 0 || count <= lPtr->fullFitLines)
553 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
554 else {
555 tmp = lPtr->fullFitLines;
556 knobProportion = tmp/(float)count;
558 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
560 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
565 static void
566 handleEvents(XEvent *event, void *data)
568 List *lPtr = (List*)data;
570 CHECK_CLASS(data, WC_List);
573 switch (event->type) {
574 case Expose:
575 if (event->xexpose.count!=0)
576 break;
577 paintList(lPtr);
578 break;
580 case DestroyNotify:
581 destroyList(lPtr);
582 break;
589 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
591 WMListItem *item;
592 WMBagIterator i;
593 int ok = 0;
595 WM_ITERATE_BAG(lPtr->items, item, i) {
596 if (strcmp(item->text, title)==0) {
597 ok = 1;
598 break;
602 return ok ? WMBagIndexForIterator(lPtr->items, i) : -1;
606 void
607 WMSelectListItem(WMList *lPtr, int row)
609 WMListItem *itemPtr;
610 int notify = 0;
612 if (row >= WMGetBagItemCount(lPtr->items))
613 return;
615 /* the check below must be changed when the multiple selection is
616 * implemented. -Dan
618 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
619 notify = 0;
620 else
621 notify = 1;
623 if (!lPtr->flags.allowMultipleSelection) {
624 /* unselect previous selected item */
625 if (lPtr->selectedItem >= 0) {
626 itemPtr = WMGetFromBag(lPtr->items, lPtr->selectedItem);
628 if (itemPtr->selected) {
629 itemPtr->selected = 0;
630 if (lPtr->view->flags.mapped
631 && lPtr->selectedItem>=lPtr->topItem
632 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
633 paintItem(lPtr, lPtr->selectedItem);
639 if (row < 0) {
640 if (!lPtr->flags.allowMultipleSelection) {
641 lPtr->selectedItem = -1;
642 if (notify)
643 WMPostNotificationName(WMListSelectionDidChangeNotification,
644 lPtr, (void*)((int)lPtr->selectedItem));
646 return;
649 /* select item */
650 itemPtr = WMGetFromBag(lPtr->items, row);
652 if (lPtr->flags.allowMultipleSelection)
653 itemPtr->selected = !itemPtr->selected;
654 else
655 itemPtr->selected = 1;
657 if (lPtr->view->flags.mapped) {
658 paintItem(lPtr, row);
660 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
661 > lPtr->view->size.height-2)
662 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
663 lPtr->view->size.width, lPtr->view->size.height,
664 WRSunken);
666 lPtr->selectedItem = row;
667 if (notify)
668 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
669 (void*)((int)lPtr->selectedItem));
673 static int
674 getItemIndexAt(List *lPtr, int clickY)
676 int index;
678 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
680 if (index < 0 || index >= WMGetBagItemCount(lPtr->items))
681 return -1;
683 return index;
687 static void
688 handleActionEvents(XEvent *event, void *data)
690 List *lPtr = (List*)data;
691 int tmp;
692 int topItem = lPtr->topItem;
694 CHECK_CLASS(data, WC_List);
696 switch (event->type) {
697 case ButtonRelease:
698 lPtr->flags.buttonPressed = 0;
699 tmp = getItemIndexAt(lPtr, event->xbutton.y);
701 if (tmp == lPtr->selectedItem && tmp >= 0) {
702 if (lPtr->action)
703 (*lPtr->action)(lPtr, lPtr->clientData);
705 break;
707 case EnterNotify:
708 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
709 lPtr->flags.buttonWasPressed = 0;
710 break;
712 case LeaveNotify:
713 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
714 lPtr->flags.buttonPressed = 0;
715 break;
717 case ButtonPress:
718 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
719 tmp = getItemIndexAt(lPtr, event->xbutton.y);
720 lPtr->flags.buttonPressed = 1;
722 if (tmp >= 0) {
723 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
724 WMSelectListItem(lPtr, tmp);
725 if (lPtr->doubleAction)
726 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
727 } else {
728 WMSelectListItem(lPtr, tmp);
732 break;
734 case MotionNotify:
735 if (lPtr->flags.buttonPressed) {
736 tmp = getItemIndexAt(lPtr, event->xmotion.y);
737 if (tmp>=0 && tmp != lPtr->selectedItem) {
738 WMSelectListItem(lPtr, tmp);
741 break;
743 if (lPtr->topItem != topItem)
744 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
748 static void
749 updateGeometry(WMList *lPtr)
751 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
752 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
753 lPtr->flags.dontFitAll = 1;
754 } else {
755 lPtr->flags.dontFitAll = 0;
758 if (WMGetBagItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
759 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
760 if (lPtr->topItem < 0)
761 lPtr->topItem = 0;
764 updateScroller(lPtr);
768 static void
769 didResizeList(W_ViewDelegate *self, WMView *view)
771 WMList *lPtr = (WMList*)view->self;
773 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
775 updateGeometry(lPtr);
779 static void
780 destroyList(List *lPtr)
782 WMListItem *item;
783 WMBagIterator i;
785 if (lPtr->idleID)
786 WMDeleteIdleHandler(lPtr->idleID);
787 lPtr->idleID = NULL;
789 WM_ITERATE_BAG(lPtr->items, item, i) {
790 wfree(item->text);
791 wfree(item);
793 WMFreeBag(lPtr->items);
795 wfree(lPtr);