many bug fixes, finished some delegate code, updated menu file bug from EXEC
[wmaker-crm.git] / WINGs / wlist.c
blobd7a95fcde109e84d45391e6694c76a01c09fd2dc
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 WMListItem *items; /* array of items */
15 short itemCount;
17 short selectedItem;
19 short itemHeight;
21 short topItem; /* index of first visible item */
23 short fullFitLines; /* no of lines that fit entirely */
25 void *clientData;
26 WMAction *action;
27 void *doubleClientData;
28 WMAction *doubleAction;
30 WMListDrawProc *draw;
32 WMHandlerID *idleID; /* for updating the scroller after
33 * adding elements */
35 WMScroller *vScroller;
37 WMScroller *hScroller;
40 struct {
41 unsigned int allowMultipleSelection:1;
42 unsigned int userDrawn:1;
43 unsigned int userItemHeight:1;
44 /* */
45 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
46 unsigned int redrawPending:1;
47 unsigned int buttonPressed:1;
48 unsigned int buttonWasPressed:1;
49 } flags;
50 } List;
54 #define DEFAULT_WIDTH 150
55 #define DEFAULT_HEIGHT 150
58 static void destroyList(List *lPtr);
59 static void paintList(List *lPtr);
62 static void handleEvents(XEvent *event, void *data);
63 static void handleActionEvents(XEvent *event, void *data);
64 static void updateScroller(List *lPtr);
66 static void vScrollCallBack(WMWidget *scroller, void *self);
68 static void updateGeometry(WMList *lPtr);
69 static void didResizeList();
72 W_ViewDelegate _ListViewDelegate = {
73 NULL,
74 NULL,
75 didResizeList,
76 NULL,
77 NULL
82 WMList*
83 WMCreateList(WMWidget *parent)
85 List *lPtr;
86 W_Screen *scrPtr = W_VIEW(parent)->screen;
88 lPtr = wmalloc(sizeof(List));
89 memset(lPtr, 0, sizeof(List));
91 lPtr->widgetClass = WC_List;
93 lPtr->view = W_CreateView(W_VIEW(parent));
94 if (!lPtr->view) {
95 free(lPtr);
96 return NULL;
98 lPtr->view->self = lPtr;
100 lPtr->view->delegate = &_ListViewDelegate;
102 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
103 |ClientMessageMask, handleEvents, lPtr);
105 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
106 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
107 handleActionEvents, lPtr);
109 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
111 /* create the vertical scroller */
112 lPtr->vScroller = WMCreateScroller(lPtr);
113 WMMoveWidget(lPtr->vScroller, 1, 1);
114 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
116 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
118 /* make the scroller map itself when it's realized */
119 WMMapWidget(lPtr->vScroller);
121 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
123 lPtr->selectedItem = -1;
125 return lPtr;
130 WMListItem*
131 WMAddSortedListItem(WMList *lPtr, char *text)
133 WMListItem *item;
134 WMListItem *tmp;
135 int index;
137 item = wmalloc(sizeof(WMListItem));
138 memset(item, 0, sizeof(WMListItem));
139 item->text = wstrdup(text);
140 if (!lPtr->items) {
141 lPtr->items = item;
142 index = 0;
143 } else if (strcmp(lPtr->items->text, text) > 0) {
144 item->nextPtr = lPtr->items;
145 lPtr->items = item;
146 index = 1;
147 } else {
148 int added = 0;
150 index = 0;
151 tmp = lPtr->items;
152 while (tmp->nextPtr) {
153 if (strcmp(tmp->nextPtr->text, text) >= 0) {
154 item->nextPtr = tmp->nextPtr;
155 tmp->nextPtr = item;
156 added = 1;
157 break;
159 index++;
160 tmp = tmp->nextPtr;
162 if (!added) {
163 tmp->nextPtr = item;
167 lPtr->itemCount++;
169 if (lPtr->selectedItem >= index)
170 lPtr->selectedItem++;
172 /* update the scroller when idle, so that we don't waste time
173 * updating it when another item is going to be added later */
174 if (!lPtr->idleID) {
175 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
178 return item;
183 WMListItem*
184 WMInsertListItem(WMList *lPtr, int row, char *text)
186 WMListItem *item;
187 WMListItem *tmp = lPtr->items;
189 CHECK_CLASS(lPtr, WC_List);
191 item = wmalloc(sizeof(WMListItem));
192 memset(item, 0, sizeof(WMListItem));
193 item->text = wstrdup(text);
196 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
197 && row >= 0)
198 lPtr->selectedItem++;
200 if (lPtr->items==NULL) {
201 lPtr->items = item;
202 } else if (row == 0) {
203 item->nextPtr = lPtr->items;
204 lPtr->items = item;
205 } else if (row < 0) {
206 while (tmp->nextPtr)
207 tmp = tmp->nextPtr;
209 tmp->nextPtr = item;
210 row = lPtr->itemCount;
211 } else {
212 while (--row > 0)
213 tmp = tmp->nextPtr;
215 item->nextPtr = tmp->nextPtr;
216 tmp->nextPtr = item;
219 lPtr->itemCount++;
221 /* update the scroller when idle, so that we don't waste time
222 * updating it when another item is going to be added later */
223 if (!lPtr->idleID) {
224 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
227 return item;
231 void
232 WMRemoveListItem(WMList *lPtr, int row)
234 WMListItem *llist;
235 WMListItem *tmp;
236 int topItem = lPtr->topItem;
237 int selNotify = 0;
239 CHECK_CLASS(lPtr, WC_List);
241 if (row < 0 || row >= lPtr->itemCount)
242 return;
244 if (lPtr->selectedItem == row) {
245 lPtr->selectedItem = -1;
246 selNotify = 1;
247 } else if (lPtr->selectedItem > row) {
248 lPtr->selectedItem--;
251 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
252 lPtr->topItem--;
253 if (lPtr->topItem < 0)
254 lPtr->topItem = 0;
256 if (row == 0) {
257 if (lPtr->items->text)
258 free(lPtr->items->text);
260 tmp = lPtr->items->nextPtr;
261 free(lPtr->items);
263 lPtr->items = tmp;
264 } else {
265 llist = lPtr->items;
266 while (--row > 0)
267 llist = llist->nextPtr;
268 tmp = llist->nextPtr;
269 llist->nextPtr = llist->nextPtr->nextPtr;
271 if (tmp->text)
272 free(tmp->text);
274 free(tmp);
277 lPtr->itemCount--;
279 if (!lPtr->idleID) {
280 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
282 if (lPtr->topItem != topItem)
283 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
284 if (selNotify)
285 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
286 (void*)((int)lPtr->selectedItem));
291 WMListItem*
292 WMGetListItem(WMList *lPtr, int row)
294 WMListItem *listPtr;
296 listPtr = lPtr->items;
298 while (row-- > 0)
299 listPtr = listPtr->nextPtr;
301 return listPtr;
306 void
307 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
309 lPtr->flags.userDrawn = 1;
310 lPtr->draw = proc;
314 void
315 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
317 assert(height > 0);
319 lPtr->flags.userItemHeight = 1;
320 lPtr->itemHeight = height;
322 updateGeometry(lPtr);
326 void
327 WMClearList(WMList *lPtr)
329 WMListItem *item, *tmp;
330 int oldSelected = lPtr->selectedItem;
332 item = lPtr->items;
333 while (item) {
334 free(item->text);
335 tmp = item->nextPtr;
336 free(item);
337 item = tmp;
339 lPtr->items = NULL;
340 lPtr->itemCount = 0;
341 lPtr->topItem = 0;
342 lPtr->selectedItem = -1;
344 if (!lPtr->idleID) {
345 WMDeleteIdleHandler(lPtr->idleID);
346 lPtr->idleID = NULL;
348 if (lPtr->view->flags.realized) {
349 updateScroller(lPtr);
351 if (oldSelected != -1)
352 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
353 (void*)-1);
357 void
358 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
360 lPtr->action = action;
361 lPtr->clientData = clientData;
365 void
366 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
368 lPtr->doubleAction = action;
369 lPtr->doubleClientData = clientData;
373 WMListItem*
374 WMGetListSelectedItem(WMList *lPtr)
376 int i = lPtr->selectedItem;
377 WMListItem *item;
379 if (lPtr->selectedItem < 0)
380 return NULL;
382 item = lPtr->items;
383 while (i-- > 0) {
384 item = item->nextPtr;
386 return item;
391 WMGetListSelectedItemRow(WMList *lPtr)
393 return lPtr->selectedItem;
398 WMGetListItemHeight(WMList *lPtr)
400 return lPtr->itemHeight;
404 void
405 WMSetListPosition(WMList *lPtr, int row)
407 lPtr->topItem = row;
408 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
409 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
411 if (lPtr->topItem < 0)
412 lPtr->topItem = 0;
414 if (lPtr->view->flags.realized)
415 updateScroller(lPtr);
419 void
420 WMSetListBottomPosition(WMList *lPtr, int row)
422 if (lPtr->itemCount > lPtr->fullFitLines) {
423 lPtr->topItem = row - lPtr->fullFitLines;
424 if (lPtr->topItem < 0)
425 lPtr->topItem = 0;
426 if (lPtr->view->flags.realized)
427 updateScroller(lPtr);
433 WMGetListNumberOfRows(WMList *lPtr)
435 return lPtr->itemCount;
439 WMGetListPosition(WMList *lPtr)
441 return lPtr->topItem;
445 static void
446 vScrollCallBack(WMWidget *scroller, void *self)
448 WMList *lPtr = (WMList*)self;
449 WMScroller *sPtr = (WMScroller*)scroller;
450 int height;
451 int topItem = lPtr->topItem;
453 height = lPtr->view->size.height - 4;
455 switch (WMGetScrollerHitPart(sPtr)) {
456 case WSDecrementLine:
457 if (lPtr->topItem > 0) {
458 lPtr->topItem--;
460 updateScroller(lPtr);
462 break;
464 case WSDecrementPage:
465 if (lPtr->topItem > 0) {
466 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
467 if (lPtr->topItem < 0)
468 lPtr->topItem = 0;
470 updateScroller(lPtr);
472 break;
475 case WSIncrementLine:
476 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
477 lPtr->topItem++;
479 updateScroller(lPtr);
481 break;
483 case WSIncrementPage:
484 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
485 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
487 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
488 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
490 updateScroller(lPtr);
492 break;
494 case WSKnob:
496 int oldTopItem = lPtr->topItem;
498 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
499 (float)(lPtr->itemCount - lPtr->fullFitLines);
501 if (oldTopItem != lPtr->topItem)
502 paintList(lPtr);
504 break;
506 case WSKnobSlot:
507 case WSNoPart:
508 /* do nothing */
509 break;
512 if (lPtr->topItem != topItem)
513 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
517 static void
518 paintItem(List *lPtr, int index)
520 WMView *view = lPtr->view;
521 W_Screen *scr = view->screen;
522 int width, height, x, y;
523 WMListItem *itemPtr;
524 int i;
526 i = index;
527 itemPtr = lPtr->items;
528 while (i-- > 0)
529 itemPtr = itemPtr->nextPtr;
531 width = lPtr->view->size.width - 2 - 19;
532 height = lPtr->itemHeight;
533 x = 19;
534 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
536 if (lPtr->flags.userDrawn) {
537 WMRect rect;
538 int flags;
540 rect.size.width = width;
541 rect.size.height = height;
542 rect.pos.x = x;
543 rect.pos.y = y;
545 flags = itemPtr->uflags;
546 if (itemPtr->disabled)
547 flags |= WLDSDisabled;
548 if (itemPtr->selected)
549 flags |= WLDSSelected;
550 if (itemPtr->isBranch)
551 flags |= WLDSIsBranch;
553 if (lPtr->draw)
554 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
555 &rect);
556 } else {
557 if (itemPtr->selected)
558 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
559 width, height);
560 else
561 XClearArea(scr->display, view->window, x, y, width, height, False);
563 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
564 WALeft, WMColorGC(scr->black), False,
565 itemPtr->text, strlen(itemPtr->text));
571 static void
572 paintList(List *lPtr)
574 W_Screen *scrPtr = lPtr->view->screen;
575 int i, lim;
577 if (!lPtr->view->flags.mapped)
578 return;
580 if (lPtr->itemCount>0) {
581 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
582 lim = lPtr->itemCount - lPtr->topItem;
583 XClearArea(scrPtr->display, lPtr->view->window, 19,
584 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
585 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
586 } else {
587 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
589 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
590 paintItem(lPtr, i);
592 } else {
593 XClearWindow(scrPtr->display, lPtr->view->window);
595 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
596 lPtr->view->size.height, WRSunken);
599 #if 0
600 static void
601 scrollTo(List *lPtr, int newTop)
605 #endif
607 static void
608 updateScroller(List *lPtr)
610 float knobProportion, floatValue, tmp;
612 if (lPtr->idleID)
613 WMDeleteIdleHandler(lPtr->idleID);
614 lPtr->idleID = NULL;
616 paintList(lPtr);
618 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
619 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
620 else {
621 tmp = lPtr->fullFitLines;
622 knobProportion = tmp/(float)lPtr->itemCount;
624 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
626 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
631 static void
632 handleEvents(XEvent *event, void *data)
634 List *lPtr = (List*)data;
636 CHECK_CLASS(data, WC_List);
639 switch (event->type) {
640 case Expose:
641 if (event->xexpose.count!=0)
642 break;
643 paintList(lPtr);
644 break;
646 case DestroyNotify:
647 destroyList(lPtr);
648 break;
655 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
657 WMListItem *item;
658 int i;
659 int ok = 0;
661 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
662 if (strcmp(item->text, title)==0) {
663 ok = 1;
664 break;
668 return ok ? i : -1;
672 void
673 WMSelectListItem(WMList *lPtr, int row)
675 WMListItem *itemPtr;
676 int i, notify = 0;
678 if (row >= lPtr->itemCount)
679 return;
681 /* the check below must be changed when the multiple selection is
682 * implemented. -Dan
684 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
685 notify = 0;
686 else
687 notify = 1;
689 if (!lPtr->flags.allowMultipleSelection) {
690 /* unselect previous selected item */
691 if (lPtr->selectedItem >= 0) {
692 itemPtr = lPtr->items;
693 for (i=0; i<lPtr->selectedItem; i++)
694 itemPtr = itemPtr->nextPtr;
696 if (itemPtr->selected) {
697 itemPtr->selected = 0;
698 if (lPtr->view->flags.mapped && i>=lPtr->topItem
699 && i<=lPtr->topItem+lPtr->fullFitLines)
700 paintItem(lPtr, i);
705 if (row < 0) {
706 if (!lPtr->flags.allowMultipleSelection) {
707 lPtr->selectedItem = -1;
708 if (notify)
709 WMPostNotificationName(WMListSelectionDidChangeNotification,
710 lPtr, (void*)((int)lPtr->selectedItem));
712 return;
715 /* select item */
716 itemPtr = lPtr->items;
717 for (i=0; i<row; i++)
718 itemPtr = itemPtr->nextPtr;
719 if (lPtr->flags.allowMultipleSelection)
720 itemPtr->selected = !itemPtr->selected;
721 else
722 itemPtr->selected = 1;
724 if (lPtr->view->flags.mapped) {
725 paintItem(lPtr, row);
727 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
728 > lPtr->view->size.height-2)
729 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
730 lPtr->view->size.width, lPtr->view->size.height,
731 WRSunken);
733 lPtr->selectedItem = row;
734 if (notify)
735 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
736 (void*)((int)lPtr->selectedItem));
740 static int
741 getItemIndexAt(List *lPtr, int clickY)
743 int index;
745 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
747 if (index < 0 || index >= lPtr->itemCount)
748 return -1;
750 return index;
754 static void
755 handleActionEvents(XEvent *event, void *data)
757 List *lPtr = (List*)data;
758 int tmp;
759 int topItem = lPtr->topItem;
761 CHECK_CLASS(data, WC_List);
763 switch (event->type) {
764 case ButtonRelease:
765 lPtr->flags.buttonPressed = 0;
766 tmp = getItemIndexAt(lPtr, event->xbutton.y);
768 if (tmp == lPtr->selectedItem && tmp >= 0) {
769 if (lPtr->action)
770 (*lPtr->action)(lPtr, lPtr->clientData);
772 break;
774 case EnterNotify:
775 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
776 lPtr->flags.buttonWasPressed = 0;
777 break;
779 case LeaveNotify:
780 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
781 lPtr->flags.buttonPressed = 0;
782 break;
784 case ButtonPress:
785 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
786 tmp = getItemIndexAt(lPtr, event->xbutton.y);
787 lPtr->flags.buttonPressed = 1;
789 if (tmp >= 0) {
790 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
791 WMSelectListItem(lPtr, tmp);
792 if (lPtr->doubleAction)
793 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
794 } else {
795 WMSelectListItem(lPtr, tmp);
799 break;
801 case MotionNotify:
802 if (lPtr->flags.buttonPressed) {
803 tmp = getItemIndexAt(lPtr, event->xmotion.y);
804 if (tmp>=0 && tmp != lPtr->selectedItem) {
805 WMSelectListItem(lPtr, tmp);
808 break;
810 if (lPtr->topItem != topItem)
811 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
815 static void
816 updateGeometry(WMList *lPtr)
818 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
819 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
820 lPtr->flags.dontFitAll = 1;
821 } else {
822 lPtr->flags.dontFitAll = 0;
825 if (lPtr->itemCount - lPtr->topItem <= lPtr->fullFitLines) {
826 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
827 if (lPtr->topItem < 0)
828 lPtr->topItem = 0;
831 updateScroller(lPtr);
835 static void
836 didResizeList(W_ViewDelegate *self, WMView *view)
838 WMList *lPtr = (WMList*)view->self;
840 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
842 updateGeometry(lPtr);
846 static void
847 destroyList(List *lPtr)
849 WMListItem *itemPtr;
851 if (lPtr->idleID)
852 WMDeleteIdleHandler(lPtr->idleID);
853 lPtr->idleID = NULL;
855 while (lPtr->items!=NULL) {
856 itemPtr = lPtr->items;
857 if (itemPtr->text)
858 free(itemPtr->text);
860 lPtr->items = itemPtr->nextPtr;
861 free(itemPtr);
864 free(lPtr);