fixed some compilation warnings.
[wmaker-crm.git] / WINGs / wlist.c
blobd86de02f1927fd6f10f2c6c2eab8311d83dd39a6
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 if (index < lPtr->fullFitLines+lPtr->flags.dontFitAll-lPtr->topItem) {
168 paintList(lPtr);
171 lPtr->itemCount++;
173 if (lPtr->selectedItem >= index)
174 lPtr->selectedItem++;
176 /* update the scroller when idle, so that we don't waste time
177 * updating it when another item is going to be added later */
178 if (!lPtr->idleID) {
179 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
182 return item;
187 WMListItem*
188 WMInsertListItem(WMList *lPtr, int row, char *text)
190 WMListItem *item;
191 WMListItem *tmp = lPtr->items;
193 CHECK_CLASS(lPtr, WC_List);
195 item = wmalloc(sizeof(WMListItem));
196 memset(item, 0, sizeof(WMListItem));
197 item->text = wstrdup(text);
200 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
201 && row >= 0)
202 lPtr->selectedItem++;
204 if (lPtr->items==NULL) {
205 lPtr->items = item;
206 } else if (row == 0) {
207 item->nextPtr = lPtr->items;
208 lPtr->items = item;
209 } else if (row < 0) {
210 while (tmp->nextPtr)
211 tmp = tmp->nextPtr;
213 tmp->nextPtr = item;
214 row = lPtr->itemCount;
215 } else {
216 while (--row > 0)
217 tmp = tmp->nextPtr;
219 item->nextPtr = tmp->nextPtr;
220 tmp->nextPtr = item;
223 if (row < lPtr->fullFitLines+lPtr->flags.dontFitAll-lPtr->topItem) {
224 paintList(lPtr);
227 lPtr->itemCount++;
229 /* update the scroller when idle, so that we don't waste time
230 * updating it when another item is going to be added later */
231 if (!lPtr->idleID) {
232 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
235 return item;
239 void
240 WMRemoveListItem(WMList *lPtr, int row)
242 WMListItem *llist;
243 WMListItem *tmp;
244 int topItem = lPtr->topItem;
245 int selNotify = 0;
247 CHECK_CLASS(lPtr, WC_List);
249 if (row < 0 || row >= lPtr->itemCount)
250 return;
252 if (lPtr->selectedItem == row) {
253 lPtr->selectedItem = -1;
254 selNotify = 1;
255 } else if (lPtr->selectedItem > row) {
256 lPtr->selectedItem--;
259 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
260 lPtr->topItem--;
261 if (lPtr->topItem < 0)
262 lPtr->topItem = 0;
264 if (row == 0) {
265 if (lPtr->items->text)
266 free(lPtr->items->text);
268 tmp = lPtr->items->nextPtr;
269 free(lPtr->items);
271 lPtr->items = tmp;
272 } else {
273 llist = lPtr->items;
274 while (--row > 0)
275 llist = llist->nextPtr;
276 tmp = llist->nextPtr;
277 llist->nextPtr = llist->nextPtr->nextPtr;
279 if (tmp->text)
280 free(tmp->text);
282 free(tmp);
285 lPtr->itemCount--;
287 if (!lPtr->idleID) {
288 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
290 if (lPtr->topItem != topItem)
291 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
292 if (selNotify)
293 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
294 (void*)((int)lPtr->selectedItem));
299 WMListItem*
300 WMGetListItem(WMList *lPtr, int row)
302 WMListItem *listPtr;
304 listPtr = lPtr->items;
306 while (row-- > 0)
307 listPtr = listPtr->nextPtr;
309 return listPtr;
314 void
315 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
317 lPtr->flags.userDrawn = 1;
318 lPtr->draw = proc;
322 void
323 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
325 assert(height > 0);
327 lPtr->flags.userItemHeight = 1;
328 lPtr->itemHeight = height;
330 updateGeometry(lPtr);
334 void
335 WMClearList(WMList *lPtr)
337 WMListItem *item, *tmp;
338 int oldSelected = lPtr->selectedItem;
340 item = lPtr->items;
341 while (item) {
342 free(item->text);
343 tmp = item->nextPtr;
344 free(item);
345 item = tmp;
347 lPtr->items = NULL;
348 lPtr->itemCount = 0;
349 lPtr->topItem = 0;
350 lPtr->selectedItem = -1;
352 if (!lPtr->idleID) {
353 WMDeleteIdleHandler(lPtr->idleID);
354 lPtr->idleID = NULL;
356 if (lPtr->view->flags.realized) {
357 updateScroller(lPtr);
359 if (oldSelected != -1)
360 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
361 (void*)-1);
365 void
366 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
368 lPtr->action = action;
369 lPtr->clientData = clientData;
373 void
374 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
376 lPtr->doubleAction = action;
377 lPtr->doubleClientData = clientData;
381 WMListItem*
382 WMGetListSelectedItem(WMList *lPtr)
384 int i = lPtr->selectedItem;
385 WMListItem *item;
387 if (lPtr->selectedItem < 0)
388 return NULL;
390 item = lPtr->items;
391 while (i-- > 0) {
392 item = item->nextPtr;
394 return item;
399 WMGetListSelectedItemRow(WMList *lPtr)
401 return lPtr->selectedItem;
406 WMGetListItemHeight(WMList *lPtr)
408 return lPtr->itemHeight;
412 void
413 WMSetListPosition(WMList *lPtr, int row)
415 lPtr->topItem = row;
416 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
417 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
419 if (lPtr->topItem < 0)
420 lPtr->topItem = 0;
422 if (lPtr->view->flags.realized)
423 updateScroller(lPtr);
427 void
428 WMSetListBottomPosition(WMList *lPtr, int row)
430 if (lPtr->itemCount > lPtr->fullFitLines) {
431 lPtr->topItem = row - lPtr->fullFitLines;
432 if (lPtr->topItem < 0)
433 lPtr->topItem = 0;
434 if (lPtr->view->flags.realized)
435 updateScroller(lPtr);
441 WMGetListNumberOfRows(WMList *lPtr)
443 return lPtr->itemCount;
447 WMGetListPosition(WMList *lPtr)
449 return lPtr->topItem;
453 static void
454 vScrollCallBack(WMWidget *scroller, void *self)
456 WMList *lPtr = (WMList*)self;
457 WMScroller *sPtr = (WMScroller*)scroller;
458 int height;
459 int topItem = lPtr->topItem;
461 height = lPtr->view->size.height - 4;
463 switch (WMGetScrollerHitPart(sPtr)) {
464 case WSDecrementLine:
465 if (lPtr->topItem > 0) {
466 lPtr->topItem--;
468 updateScroller(lPtr);
470 break;
472 case WSDecrementPage:
473 if (lPtr->topItem > 0) {
474 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
475 if (lPtr->topItem < 0)
476 lPtr->topItem = 0;
478 updateScroller(lPtr);
480 break;
483 case WSIncrementLine:
484 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
485 lPtr->topItem++;
487 updateScroller(lPtr);
489 break;
491 case WSIncrementPage:
492 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
493 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
495 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
496 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
498 updateScroller(lPtr);
500 break;
502 case WSKnob:
504 int oldTopItem = lPtr->topItem;
506 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
507 (float)(lPtr->itemCount - lPtr->fullFitLines);
509 if (oldTopItem != lPtr->topItem)
510 paintList(lPtr);
512 break;
514 case WSKnobSlot:
515 case WSNoPart:
516 /* do nothing */
517 break;
520 if (lPtr->topItem != topItem)
521 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
525 static void
526 paintItem(List *lPtr, int index)
528 WMView *view = lPtr->view;
529 W_Screen *scr = view->screen;
530 int width, height, x, y;
531 WMListItem *itemPtr;
532 int i;
534 i = index;
535 itemPtr = lPtr->items;
536 while (i-- > 0)
537 itemPtr = itemPtr->nextPtr;
539 width = lPtr->view->size.width - 2 - 19;
540 height = lPtr->itemHeight;
541 x = 19;
542 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
544 if (lPtr->flags.userDrawn) {
545 WMRect rect;
546 int flags;
548 rect.size.width = width;
549 rect.size.height = height;
550 rect.pos.x = x;
551 rect.pos.y = y;
553 flags = itemPtr->uflags;
554 if (itemPtr->disabled)
555 flags |= WLDSDisabled;
556 if (itemPtr->selected)
557 flags |= WLDSSelected;
558 if (itemPtr->isBranch)
559 flags |= WLDSIsBranch;
561 if (lPtr->draw)
562 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
563 &rect);
564 } else {
565 if (itemPtr->selected)
566 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
567 width, height);
568 else
569 XClearArea(scr->display, view->window, x, y, width, height, False);
571 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
572 WALeft, WMColorGC(scr->black), False,
573 itemPtr->text, strlen(itemPtr->text));
579 static void
580 paintList(List *lPtr)
582 W_Screen *scrPtr = lPtr->view->screen;
583 int i, lim;
585 if (!lPtr->view->flags.mapped)
586 return;
588 if (lPtr->itemCount>0) {
589 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
590 lim = lPtr->itemCount - lPtr->topItem;
591 XClearArea(scrPtr->display, lPtr->view->window, 19,
592 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
593 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
594 } else {
595 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
597 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
598 paintItem(lPtr, i);
600 } else {
601 XClearWindow(scrPtr->display, lPtr->view->window);
603 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
604 lPtr->view->size.height, WRSunken);
607 #if 0
608 static void
609 scrollTo(List *lPtr, int newTop)
613 #endif
615 static void
616 updateScroller(List *lPtr)
618 float knobProportion, floatValue, tmp;
620 if (lPtr->idleID)
621 WMDeleteIdleHandler(lPtr->idleID);
622 lPtr->idleID = NULL;
624 paintList(lPtr);
626 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
627 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
628 else {
629 tmp = lPtr->fullFitLines;
630 knobProportion = tmp/(float)lPtr->itemCount;
632 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
634 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
639 static void
640 handleEvents(XEvent *event, void *data)
642 List *lPtr = (List*)data;
644 CHECK_CLASS(data, WC_List);
647 switch (event->type) {
648 case Expose:
649 if (event->xexpose.count!=0)
650 break;
651 paintList(lPtr);
652 break;
654 case DestroyNotify:
655 destroyList(lPtr);
656 break;
663 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
665 WMListItem *item;
666 int i;
667 int ok = 0;
669 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
670 if (strcmp(item->text, title)==0) {
671 ok = 1;
672 break;
676 return ok ? i : -1;
680 void
681 WMSelectListItem(WMList *lPtr, int row)
683 WMListItem *itemPtr;
684 int i, notify = 0;
686 if (row >= lPtr->itemCount)
687 return;
689 /* the check below must be changed when the multiple selection is
690 * implemented. -Dan
692 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
693 notify = 0;
694 else
695 notify = 1;
697 if (!lPtr->flags.allowMultipleSelection) {
698 /* unselect previous selected item */
699 if (lPtr->selectedItem >= 0) {
700 itemPtr = lPtr->items;
701 for (i=0; i<lPtr->selectedItem; i++)
702 itemPtr = itemPtr->nextPtr;
704 if (itemPtr->selected) {
705 itemPtr->selected = 0;
706 if (lPtr->view->flags.mapped && i>=lPtr->topItem
707 && i<=lPtr->topItem+lPtr->fullFitLines)
708 paintItem(lPtr, i);
713 if (row < 0) {
714 if (!lPtr->flags.allowMultipleSelection) {
715 lPtr->selectedItem = -1;
716 if (notify)
717 WMPostNotificationName(WMListSelectionDidChangeNotification,
718 lPtr, (void*)((int)lPtr->selectedItem));
720 return;
723 /* select item */
724 itemPtr = lPtr->items;
725 for (i=0; i<row; i++)
726 itemPtr = itemPtr->nextPtr;
727 if (lPtr->flags.allowMultipleSelection)
728 itemPtr->selected = !itemPtr->selected;
729 else
730 itemPtr->selected = 1;
732 if (lPtr->view->flags.mapped) {
733 paintItem(lPtr, row);
735 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
736 > lPtr->view->size.height-2)
737 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
738 lPtr->view->size.width, lPtr->view->size.height,
739 WRSunken);
741 lPtr->selectedItem = row;
742 if (notify)
743 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
744 (void*)((int)lPtr->selectedItem));
748 static int
749 getItemIndexAt(List *lPtr, int clickY)
751 int index;
753 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
755 if (index < 0 || index >= lPtr->itemCount)
756 return -1;
758 return index;
762 static void
763 handleActionEvents(XEvent *event, void *data)
765 List *lPtr = (List*)data;
766 int tmp;
767 int topItem = lPtr->topItem;
769 CHECK_CLASS(data, WC_List);
771 switch (event->type) {
772 case ButtonRelease:
773 lPtr->flags.buttonPressed = 0;
774 tmp = getItemIndexAt(lPtr, event->xbutton.y);
776 if (tmp == lPtr->selectedItem && tmp >= 0) {
777 if (lPtr->action)
778 (*lPtr->action)(lPtr, lPtr->clientData);
780 break;
782 case EnterNotify:
783 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
784 lPtr->flags.buttonWasPressed = 0;
785 break;
787 case LeaveNotify:
788 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
789 lPtr->flags.buttonPressed = 0;
790 break;
792 case ButtonPress:
793 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
794 tmp = getItemIndexAt(lPtr, event->xbutton.y);
795 lPtr->flags.buttonPressed = 1;
797 if (tmp >= 0) {
798 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
799 WMSelectListItem(lPtr, tmp);
800 if (lPtr->doubleAction)
801 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
802 } else {
803 WMSelectListItem(lPtr, tmp);
807 break;
809 case MotionNotify:
810 if (lPtr->flags.buttonPressed) {
811 tmp = getItemIndexAt(lPtr, event->xmotion.y);
812 if (tmp>=0 && tmp != lPtr->selectedItem) {
813 WMSelectListItem(lPtr, tmp);
816 break;
818 if (lPtr->topItem != topItem)
819 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
823 static void
824 updateGeometry(WMList *lPtr)
826 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
827 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
828 lPtr->flags.dontFitAll = 1;
829 } else {
830 lPtr->flags.dontFitAll = 0;
833 if (lPtr->itemCount - lPtr->topItem <= lPtr->fullFitLines) {
834 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
835 if (lPtr->topItem < 0)
836 lPtr->topItem = 0;
839 updateScroller(lPtr);
843 static void
844 didResizeList(W_ViewDelegate *self, WMView *view)
846 WMList *lPtr = (WMList*)view->self;
848 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
850 updateGeometry(lPtr);
854 static void
855 destroyList(List *lPtr)
857 WMListItem *itemPtr;
859 if (lPtr->idleID)
860 WMDeleteIdleHandler(lPtr->idleID);
861 lPtr->idleID = NULL;
863 while (lPtr->items!=NULL) {
864 itemPtr = lPtr->items;
865 if (itemPtr->text)
866 free(itemPtr->text);
868 lPtr->items = itemPtr->nextPtr;
869 free(itemPtr);
872 free(lPtr);