added nana stuff
[wmaker-crm.git] / WINGs / wlist.c
blob9ca7e295596d8c7205b8fc4291d6612c3f0abfbf
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 WMListItem *item1 = *(WMListItem**)a;
134 WMListItem *item2 = *(WMListItem**)b;
136 if (strcmp(item1->text, item2->text) < 0)
137 return -1;
138 else if (strcmp(item1->text, item2->text) > 0)
139 return 1;
140 else
141 return 0;
145 void
146 WMSortListItems(WMList *lPtr)
148 WMSortBag(lPtr->items, comparator);
150 paintList(lPtr);
155 void
156 WMSortListItemsWithComparer(WMList *lPtr, int (f)(const void*, const void*))
158 WMSortBag(lPtr->items, f);
160 paintList(lPtr);
165 WMListItem*
166 WMInsertListItem(WMList *lPtr, int row, char *text)
168 WMListItem *item;
170 CHECK_CLASS(lPtr, WC_List);
172 item = wmalloc(sizeof(WMListItem));
173 memset(item, 0, sizeof(WMListItem));
174 item->text = wstrdup(text);
177 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
178 && row >= 0)
179 lPtr->selectedItem++;
181 if (row < 0)
182 row = WMGetBagItemCount(lPtr->items);
184 WMInsertInBag(lPtr->items, row, item);
186 /* update the scroller when idle, so that we don't waste time
187 * updating it when another item is going to be added later */
188 if (!lPtr->idleID) {
189 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
192 return item;
196 void
197 WMRemoveListItem(WMList *lPtr, int row)
199 WMListItem *item;
200 int topItem = lPtr->topItem;
201 int selNotify = 0;
203 CHECK_CLASS(lPtr, WC_List);
205 if (row < 0 || row >= WMGetBagItemCount(lPtr->items))
206 return;
208 if (lPtr->selectedItem == row) {
209 lPtr->selectedItem = -1;
210 selNotify = 1;
211 } else if (lPtr->selectedItem > row) {
212 lPtr->selectedItem--;
215 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
216 lPtr->topItem--;
217 if (lPtr->topItem < 0)
218 lPtr->topItem = 0;
220 item = WMGetFromBag(lPtr->items, row);
221 if (item->text)
222 wfree(item->text);
223 wfree(item);
225 WMDeleteFromBag(lPtr->items, row);
227 if (!lPtr->idleID) {
228 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
230 if (lPtr->topItem != topItem)
231 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
232 if (selNotify)
233 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
234 (void*)((int)lPtr->selectedItem));
239 WMListItem*
240 WMGetListItem(WMList *lPtr, int row)
242 return WMGetFromBag(lPtr->items, row);
246 WMBag*
247 WMGetListItems(WMList *lPtr)
249 return lPtr->items;
254 void
255 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
257 lPtr->flags.userDrawn = 1;
258 lPtr->draw = proc;
262 void
263 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
265 assert(height > 0);
267 lPtr->flags.userItemHeight = 1;
268 lPtr->itemHeight = height;
270 updateGeometry(lPtr);
274 void
275 WMClearList(WMList *lPtr)
277 WMListItem *item;
278 int oldSelected = lPtr->selectedItem;
279 int i;
281 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
282 item = WMGetFromBag(lPtr->items, i);
283 wfree(item->text);
284 wfree(item);
286 WMEmptyBag(lPtr->items);
288 lPtr->topItem = 0;
289 lPtr->selectedItem = -1;
291 if (!lPtr->idleID) {
292 WMDeleteIdleHandler(lPtr->idleID);
293 lPtr->idleID = NULL;
295 if (lPtr->view->flags.realized) {
296 updateScroller(lPtr);
298 if (oldSelected != -1)
299 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
300 (void*)-1);
304 void
305 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
307 lPtr->action = action;
308 lPtr->clientData = clientData;
312 void
313 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
315 lPtr->doubleAction = action;
316 lPtr->doubleClientData = clientData;
320 WMListItem*
321 WMGetListSelectedItem(WMList *lPtr)
323 if (lPtr->selectedItem < 0)
324 return NULL;
326 return WMGetFromBag(lPtr->items, lPtr->selectedItem);
331 WMGetListSelectedItemRow(WMList *lPtr)
333 return lPtr->selectedItem;
338 WMGetListItemHeight(WMList *lPtr)
340 return lPtr->itemHeight;
344 void
345 WMSetListPosition(WMList *lPtr, int row)
347 lPtr->topItem = row;
348 if (lPtr->topItem + lPtr->fullFitLines > WMGetBagItemCount(lPtr->items))
349 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
351 if (lPtr->topItem < 0)
352 lPtr->topItem = 0;
354 if (lPtr->view->flags.realized)
355 updateScroller(lPtr);
359 void
360 WMSetListBottomPosition(WMList *lPtr, int row)
362 if (WMGetBagItemCount(lPtr->items) > lPtr->fullFitLines) {
363 lPtr->topItem = row - lPtr->fullFitLines;
364 if (lPtr->topItem < 0)
365 lPtr->topItem = 0;
366 if (lPtr->view->flags.realized)
367 updateScroller(lPtr);
373 WMGetListNumberOfRows(WMList *lPtr)
375 return WMGetBagItemCount(lPtr->items);
379 WMGetListPosition(WMList *lPtr)
381 return lPtr->topItem;
385 static void
386 vScrollCallBack(WMWidget *scroller, void *self)
388 WMList *lPtr = (WMList*)self;
389 WMScroller *sPtr = (WMScroller*)scroller;
390 int height;
391 int topItem = lPtr->topItem;
392 int itemCount = WMGetBagItemCount(lPtr->items);
394 height = lPtr->view->size.height - 4;
396 switch (WMGetScrollerHitPart(sPtr)) {
397 case WSDecrementLine:
398 if (lPtr->topItem > 0) {
399 lPtr->topItem--;
401 updateScroller(lPtr);
403 break;
405 case WSDecrementPage:
406 if (lPtr->topItem > 0) {
407 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
408 if (lPtr->topItem < 0)
409 lPtr->topItem = 0;
411 updateScroller(lPtr);
413 break;
416 case WSIncrementLine:
417 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
418 lPtr->topItem++;
420 updateScroller(lPtr);
422 break;
424 case WSIncrementPage:
425 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
426 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
428 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
429 lPtr->topItem = itemCount - lPtr->fullFitLines;
431 updateScroller(lPtr);
433 break;
435 case WSKnob:
437 int oldTopItem = lPtr->topItem;
439 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
440 (float)(itemCount - lPtr->fullFitLines);
442 if (oldTopItem != lPtr->topItem)
443 paintList(lPtr);
445 break;
447 case WSKnobSlot:
448 case WSNoPart:
449 /* do nothing */
450 break;
453 if (lPtr->topItem != topItem)
454 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
458 static void
459 paintItem(List *lPtr, int index)
461 WMView *view = lPtr->view;
462 W_Screen *scr = view->screen;
463 int width, height, x, y;
464 WMListItem *itemPtr;
467 itemPtr = WMGetFromBag(lPtr->items, index);
470 width = lPtr->view->size.width - 2 - 19;
471 height = lPtr->itemHeight;
472 x = 19;
473 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
475 if (lPtr->flags.userDrawn) {
476 WMRect rect;
477 int flags;
479 rect.size.width = width;
480 rect.size.height = height;
481 rect.pos.x = x;
482 rect.pos.y = y;
484 flags = itemPtr->uflags;
485 if (itemPtr->disabled)
486 flags |= WLDSDisabled;
487 if (itemPtr->selected)
488 flags |= WLDSSelected;
489 if (itemPtr->isBranch)
490 flags |= WLDSIsBranch;
492 if (lPtr->draw)
493 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
494 &rect);
495 } else {
496 if (itemPtr->selected)
497 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
498 width, height);
499 else
500 XClearArea(scr->display, view->window, x, y, width, height, False);
502 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
503 WALeft, WMColorGC(scr->black), False,
504 itemPtr->text, strlen(itemPtr->text));
510 static void
511 paintList(List *lPtr)
513 W_Screen *scrPtr = lPtr->view->screen;
514 int i, lim;
516 if (!lPtr->view->flags.mapped)
517 return;
519 if (WMGetBagItemCount(lPtr->items) > 0) {
520 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
521 > WMGetBagItemCount(lPtr->items)) {
523 lim = WMGetBagItemCount(lPtr->items) - lPtr->topItem;
524 XClearArea(scrPtr->display, lPtr->view->window, 19,
525 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
526 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
527 } else {
528 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
530 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
531 paintItem(lPtr, i);
533 } else {
534 XClearWindow(scrPtr->display, lPtr->view->window);
536 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
537 lPtr->view->size.height, WRSunken);
540 #if 0
541 static void
542 scrollTo(List *lPtr, int newTop)
546 #endif
548 static void
549 updateScroller(List *lPtr)
551 float knobProportion, floatValue, tmp;
552 int count = WMGetBagItemCount(lPtr->items);
554 if (lPtr->idleID)
555 WMDeleteIdleHandler(lPtr->idleID);
556 lPtr->idleID = NULL;
558 paintList(lPtr);
560 if (count == 0 || count <= lPtr->fullFitLines)
561 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
562 else {
563 tmp = lPtr->fullFitLines;
564 knobProportion = tmp/(float)count;
566 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
568 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
573 static void
574 handleEvents(XEvent *event, void *data)
576 List *lPtr = (List*)data;
578 CHECK_CLASS(data, WC_List);
581 switch (event->type) {
582 case Expose:
583 if (event->xexpose.count!=0)
584 break;
585 paintList(lPtr);
586 break;
588 case DestroyNotify:
589 destroyList(lPtr);
590 break;
597 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
599 WMListItem *item;
600 int i;
601 int ok = 0;
603 for (i=0; i < WMGetBagItemCount(lPtr->items); i++) {
604 item = WMGetFromBag(lPtr->items, i);
605 if (strcmp(item->text, title)==0) {
606 ok = 1;
607 break;
611 return ok ? i : -1;
615 void
616 WMSelectListItem(WMList *lPtr, int row)
618 WMListItem *itemPtr;
619 int notify = 0;
621 if (row >= WMGetBagItemCount(lPtr->items))
622 return;
624 /* the check below must be changed when the multiple selection is
625 * implemented. -Dan
627 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
628 notify = 0;
629 else
630 notify = 1;
632 if (!lPtr->flags.allowMultipleSelection) {
633 /* unselect previous selected item */
634 if (lPtr->selectedItem >= 0) {
635 itemPtr = WMGetFromBag(lPtr->items, lPtr->selectedItem);
637 if (itemPtr->selected) {
638 itemPtr->selected = 0;
639 if (lPtr->view->flags.mapped
640 && lPtr->selectedItem>=lPtr->topItem
641 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
642 paintItem(lPtr, lPtr->selectedItem);
648 if (row < 0) {
649 if (!lPtr->flags.allowMultipleSelection) {
650 lPtr->selectedItem = -1;
651 if (notify)
652 WMPostNotificationName(WMListSelectionDidChangeNotification,
653 lPtr, (void*)((int)lPtr->selectedItem));
655 return;
658 /* select item */
659 itemPtr = WMGetFromBag(lPtr->items, row);
661 if (lPtr->flags.allowMultipleSelection)
662 itemPtr->selected = !itemPtr->selected;
663 else
664 itemPtr->selected = 1;
666 if (lPtr->view->flags.mapped) {
667 paintItem(lPtr, row);
669 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
670 > lPtr->view->size.height-2)
671 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
672 lPtr->view->size.width, lPtr->view->size.height,
673 WRSunken);
675 lPtr->selectedItem = row;
676 if (notify)
677 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
678 (void*)((int)lPtr->selectedItem));
682 static int
683 getItemIndexAt(List *lPtr, int clickY)
685 int index;
687 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
689 if (index < 0 || index >= WMGetBagItemCount(lPtr->items))
690 return -1;
692 return index;
696 static void
697 handleActionEvents(XEvent *event, void *data)
699 List *lPtr = (List*)data;
700 int tmp;
701 int topItem = lPtr->topItem;
703 CHECK_CLASS(data, WC_List);
705 switch (event->type) {
706 case ButtonRelease:
707 lPtr->flags.buttonPressed = 0;
708 tmp = getItemIndexAt(lPtr, event->xbutton.y);
710 if (tmp == lPtr->selectedItem && tmp >= 0) {
711 if (lPtr->action)
712 (*lPtr->action)(lPtr, lPtr->clientData);
714 break;
716 case EnterNotify:
717 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
718 lPtr->flags.buttonWasPressed = 0;
719 break;
721 case LeaveNotify:
722 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
723 lPtr->flags.buttonPressed = 0;
724 break;
726 case ButtonPress:
727 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
728 tmp = getItemIndexAt(lPtr, event->xbutton.y);
729 lPtr->flags.buttonPressed = 1;
731 if (tmp >= 0) {
732 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
733 WMSelectListItem(lPtr, tmp);
734 if (lPtr->doubleAction)
735 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
736 } else {
737 WMSelectListItem(lPtr, tmp);
741 break;
743 case MotionNotify:
744 if (lPtr->flags.buttonPressed) {
745 tmp = getItemIndexAt(lPtr, event->xmotion.y);
746 if (tmp>=0 && tmp != lPtr->selectedItem) {
747 WMSelectListItem(lPtr, tmp);
750 break;
752 if (lPtr->topItem != topItem)
753 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
757 static void
758 updateGeometry(WMList *lPtr)
760 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
761 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
762 lPtr->flags.dontFitAll = 1;
763 } else {
764 lPtr->flags.dontFitAll = 0;
767 if (WMGetBagItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
768 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
769 if (lPtr->topItem < 0)
770 lPtr->topItem = 0;
773 updateScroller(lPtr);
777 static void
778 didResizeList(W_ViewDelegate *self, WMView *view)
780 WMList *lPtr = (WMList*)view->self;
782 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
784 updateGeometry(lPtr);
788 static void
789 destroyList(List *lPtr)
791 WMListItem *item;
792 int i;
794 if (lPtr->idleID)
795 WMDeleteIdleHandler(lPtr->idleID);
796 lPtr->idleID = NULL;
798 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
799 item = WMGetFromBag(lPtr->items, i);
800 wfree(item->text);
801 wfree(item);
803 WMFreeBag(lPtr->items);
805 wfree(lPtr);