Guess what? Yep, another wmtext update. Now this was all, please go to
[wmaker-crm.git] / WINGs / wlist.c
blobd92c5bc833997b74d504a81b8696ed5982d7cba2
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 row = WMIN(row, WMGetBagItemCount(lPtr->items));
175 if (row < 0)
176 WMPutInBag(lPtr->items, item);
177 else
178 WMInsertInBag(lPtr->items, row, item);
180 /* update the scroller when idle, so that we don't waste time
181 * updating it when another item is going to be added later */
182 if (!lPtr->idleID) {
183 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
186 return item;
190 void
191 WMRemoveListItem(WMList *lPtr, int row)
193 WMListItem *item;
194 int topItem = lPtr->topItem;
195 int selNotify = 0;
197 CHECK_CLASS(lPtr, WC_List);
199 if (row < 0 || row >= WMGetBagItemCount(lPtr->items))
200 return;
202 if (lPtr->selectedItem == row) {
203 lPtr->selectedItem = -1;
204 selNotify = 1;
205 } else if (lPtr->selectedItem > row) {
206 lPtr->selectedItem--;
209 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
210 lPtr->topItem--;
211 if (lPtr->topItem < 0)
212 lPtr->topItem = 0;
214 item = WMGetFromBag(lPtr->items, row);
215 if (item->text)
216 wfree(item->text);
217 wfree(item);
219 WMDeleteFromBag(lPtr->items, row);
221 if (!lPtr->idleID) {
222 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
224 if (lPtr->topItem != topItem)
225 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
226 if (selNotify)
227 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
228 (void*)((int)lPtr->selectedItem));
233 WMListItem*
234 WMGetListItem(WMList *lPtr, int row)
236 return WMGetFromBag(lPtr->items, row);
240 WMBag*
241 WMGetListItems(WMList *lPtr)
243 return lPtr->items;
248 void
249 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
251 lPtr->flags.userDrawn = 1;
252 lPtr->draw = proc;
256 void
257 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
259 assert(height > 0);
261 lPtr->flags.userItemHeight = 1;
262 lPtr->itemHeight = height;
264 updateGeometry(lPtr);
268 void
269 WMClearList(WMList *lPtr)
271 WMListItem *item;
272 int oldSelected = lPtr->selectedItem;
273 int i;
275 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
276 item = WMGetFromBag(lPtr->items, i);
277 wfree(item->text);
278 wfree(item);
280 WMEmptyBag(lPtr->items);
282 lPtr->topItem = 0;
283 lPtr->selectedItem = -1;
285 if (!lPtr->idleID) {
286 WMDeleteIdleHandler(lPtr->idleID);
287 lPtr->idleID = NULL;
289 if (lPtr->view->flags.realized) {
290 updateScroller(lPtr);
292 if (oldSelected != -1)
293 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
294 (void*)-1);
298 void
299 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
301 lPtr->action = action;
302 lPtr->clientData = clientData;
306 void
307 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
309 lPtr->doubleAction = action;
310 lPtr->doubleClientData = clientData;
314 WMListItem*
315 WMGetListSelectedItem(WMList *lPtr)
317 if (lPtr->selectedItem < 0)
318 return NULL;
320 return WMGetFromBag(lPtr->items, lPtr->selectedItem);
325 WMGetListSelectedItemRow(WMList *lPtr)
327 return lPtr->selectedItem;
332 WMGetListItemHeight(WMList *lPtr)
334 return lPtr->itemHeight;
338 void
339 WMSetListPosition(WMList *lPtr, int row)
341 lPtr->topItem = row;
342 if (lPtr->topItem + lPtr->fullFitLines > WMGetBagItemCount(lPtr->items))
343 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
345 if (lPtr->topItem < 0)
346 lPtr->topItem = 0;
348 if (lPtr->view->flags.realized)
349 updateScroller(lPtr);
353 void
354 WMSetListBottomPosition(WMList *lPtr, int row)
356 if (WMGetBagItemCount(lPtr->items) > lPtr->fullFitLines) {
357 lPtr->topItem = row - lPtr->fullFitLines;
358 if (lPtr->topItem < 0)
359 lPtr->topItem = 0;
360 if (lPtr->view->flags.realized)
361 updateScroller(lPtr);
367 WMGetListNumberOfRows(WMList *lPtr)
369 return WMGetBagItemCount(lPtr->items);
373 WMGetListPosition(WMList *lPtr)
375 return lPtr->topItem;
379 static void
380 vScrollCallBack(WMWidget *scroller, void *self)
382 WMList *lPtr = (WMList*)self;
383 WMScroller *sPtr = (WMScroller*)scroller;
384 int height;
385 int topItem = lPtr->topItem;
386 int itemCount = WMGetBagItemCount(lPtr->items);
388 height = lPtr->view->size.height - 4;
390 switch (WMGetScrollerHitPart(sPtr)) {
391 case WSDecrementLine:
392 if (lPtr->topItem > 0) {
393 lPtr->topItem--;
395 updateScroller(lPtr);
397 break;
399 case WSDecrementPage:
400 if (lPtr->topItem > 0) {
401 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
402 if (lPtr->topItem < 0)
403 lPtr->topItem = 0;
405 updateScroller(lPtr);
407 break;
410 case WSIncrementLine:
411 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
412 lPtr->topItem++;
414 updateScroller(lPtr);
416 break;
418 case WSIncrementPage:
419 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
420 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
422 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
423 lPtr->topItem = itemCount - lPtr->fullFitLines;
425 updateScroller(lPtr);
427 break;
429 case WSKnob:
431 int oldTopItem = lPtr->topItem;
433 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
434 (float)(itemCount - lPtr->fullFitLines);
436 if (oldTopItem != lPtr->topItem)
437 paintList(lPtr);
439 break;
441 case WSKnobSlot:
442 case WSNoPart:
443 /* do nothing */
444 break;
447 if (lPtr->topItem != topItem)
448 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
452 static void
453 paintItem(List *lPtr, int index)
455 WMView *view = lPtr->view;
456 W_Screen *scr = view->screen;
457 int width, height, x, y;
458 WMListItem *itemPtr;
460 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),
490 x, y, 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 assert(lPtr->selectedItem < WMGetBagItemCount(lPtr->items));
625 if (!lPtr->flags.allowMultipleSelection) {
626 /* unselect previous selected item */
627 if (lPtr->selectedItem >= 0) {
628 itemPtr = WMGetFromBag(lPtr->items, lPtr->selectedItem);
630 if (itemPtr->selected) {
631 itemPtr->selected = 0;
632 if (lPtr->view->flags.mapped
633 && lPtr->selectedItem>=lPtr->topItem
634 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
635 paintItem(lPtr, lPtr->selectedItem);
641 if (row < 0) {
642 if (!lPtr->flags.allowMultipleSelection) {
643 lPtr->selectedItem = -1;
644 if (notify)
645 WMPostNotificationName(WMListSelectionDidChangeNotification,
646 lPtr, (void*)((int)lPtr->selectedItem));
648 return;
651 /* select item */
652 itemPtr = WMGetFromBag(lPtr->items, row);
654 if (lPtr->flags.allowMultipleSelection)
655 itemPtr->selected = !itemPtr->selected;
656 else
657 itemPtr->selected = 1;
659 if (lPtr->view->flags.mapped) {
660 paintItem(lPtr, row);
662 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
663 > lPtr->view->size.height-2)
664 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
665 lPtr->view->size.width, lPtr->view->size.height,
666 WRSunken);
668 lPtr->selectedItem = row;
669 if (notify)
670 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
671 (void*)((int)lPtr->selectedItem));
675 static int
676 getItemIndexAt(List *lPtr, int clickY)
678 int index;
680 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
682 if (index < 0 || index >= WMGetBagItemCount(lPtr->items))
683 return -1;
685 return index;
689 static void
690 handleActionEvents(XEvent *event, void *data)
692 List *lPtr = (List*)data;
693 int tmp;
694 int topItem = lPtr->topItem;
696 CHECK_CLASS(data, WC_List);
698 switch (event->type) {
699 case ButtonRelease:
700 #define CHECK_WHEEL_PATCH
701 #ifdef CHECK_WHEEL_PATCH
702 /* Ignore mouse wheel events, they're not "real" button events */
703 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
704 event->xbutton.button == WINGsConfiguration.mouseWheelDown)
705 break;
706 #endif
708 lPtr->flags.buttonPressed = 0;
709 tmp = getItemIndexAt(lPtr, event->xbutton.y);
711 if (tmp == lPtr->selectedItem && tmp >= 0) {
712 if (lPtr->action)
713 (*lPtr->action)(lPtr, lPtr->clientData);
715 break;
717 case EnterNotify:
718 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
719 lPtr->flags.buttonWasPressed = 0;
720 break;
722 case LeaveNotify:
723 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
724 lPtr->flags.buttonPressed = 0;
725 break;
727 case ButtonPress:
728 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
729 #ifdef CHECK_WHEEL_PATCH
730 /* Mouse wheel events need to be properly handled here. It would
731 * be best to somehow route them to lPtr->vScroller so that the
732 * correct chain of actions is triggered. However, I found no
733 * clean way to do so, so I mostly copied the code that deals with
734 * WSIncrementPage and WSDecrementPage from vScrollCallBack.
736 * - Martynas Kunigelis <diskena@linuxfreak.com> */
738 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
739 /* Wheel down */
740 int itemCount = WMGetBagItemCount(lPtr->items);
741 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
742 int incr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
743 lPtr->topItem += incr;
745 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
746 lPtr->topItem = itemCount - lPtr->fullFitLines;
748 updateScroller(lPtr);
750 break;
753 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
754 /* Wheel up */
755 if (lPtr->topItem > 0) {
756 int decr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
757 lPtr->topItem -= decr;
759 if (lPtr->topItem < 0)
760 lPtr->topItem = 0;
762 updateScroller(lPtr);
764 break;
766 #endif
768 tmp = getItemIndexAt(lPtr, event->xbutton.y);
769 lPtr->flags.buttonPressed = 1;
771 if (tmp >= 0) {
772 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
773 WMSelectListItem(lPtr, tmp);
774 if (lPtr->doubleAction)
775 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
776 } else {
777 WMSelectListItem(lPtr, tmp);
781 break;
783 case MotionNotify:
784 if (lPtr->flags.buttonPressed) {
785 tmp = getItemIndexAt(lPtr, event->xmotion.y);
786 if (tmp>=0 && tmp != lPtr->selectedItem) {
787 WMSelectListItem(lPtr, tmp);
790 break;
792 if (lPtr->topItem != topItem)
793 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
797 static void
798 updateGeometry(WMList *lPtr)
800 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
801 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
802 lPtr->flags.dontFitAll = 1;
803 } else {
804 lPtr->flags.dontFitAll = 0;
807 if (WMGetBagItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
808 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
809 if (lPtr->topItem < 0)
810 lPtr->topItem = 0;
813 updateScroller(lPtr);
817 static void
818 didResizeList(W_ViewDelegate *self, WMView *view)
820 WMList *lPtr = (WMList*)view->self;
822 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
824 updateGeometry(lPtr);
828 static void
829 destroyList(List *lPtr)
831 WMListItem *item;
832 WMBagIterator i;
834 if (lPtr->idleID)
835 WMDeleteIdleHandler(lPtr->idleID);
836 lPtr->idleID = NULL;
838 WM_ITERATE_BAG(lPtr->items, item, i) {
839 wfree(item->text);
840 wfree(item);
842 WMFreeBag(lPtr->items);
844 wfree(lPtr);