Fixed big memory leak in color panel code (patch from Pascal).
[wmaker-crm.git] / WINGs / wlist.c
blob88d34fcd7f2b5add9be0d84e2700405ff9ff9d4f
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 row = WMGetBagItemCount(lPtr->items);
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 int i;
593 int ok = 0;
595 for (i=0; i < WMGetBagItemCount(lPtr->items); i++) {
596 item = WMGetFromBag(lPtr->items, i);
597 if (strcmp(item->text, title)==0) {
598 ok = 1;
599 break;
603 return ok ? i : -1;
607 void
608 WMSelectListItem(WMList *lPtr, int row)
610 WMListItem *itemPtr;
611 int notify = 0;
613 if (row >= WMGetBagItemCount(lPtr->items))
614 return;
616 /* the check below must be changed when the multiple selection is
617 * implemented. -Dan
619 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
620 notify = 0;
621 else
622 notify = 1;
624 if (!lPtr->flags.allowMultipleSelection) {
625 /* unselect previous selected item */
626 if (lPtr->selectedItem >= 0) {
627 itemPtr = WMGetFromBag(lPtr->items, lPtr->selectedItem);
629 if (itemPtr->selected) {
630 itemPtr->selected = 0;
631 if (lPtr->view->flags.mapped
632 && lPtr->selectedItem>=lPtr->topItem
633 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
634 paintItem(lPtr, lPtr->selectedItem);
640 if (row < 0) {
641 if (!lPtr->flags.allowMultipleSelection) {
642 lPtr->selectedItem = -1;
643 if (notify)
644 WMPostNotificationName(WMListSelectionDidChangeNotification,
645 lPtr, (void*)((int)lPtr->selectedItem));
647 return;
650 /* select item */
651 itemPtr = WMGetFromBag(lPtr->items, row);
653 if (lPtr->flags.allowMultipleSelection)
654 itemPtr->selected = !itemPtr->selected;
655 else
656 itemPtr->selected = 1;
658 if (lPtr->view->flags.mapped) {
659 paintItem(lPtr, row);
661 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
662 > lPtr->view->size.height-2)
663 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
664 lPtr->view->size.width, lPtr->view->size.height,
665 WRSunken);
667 lPtr->selectedItem = row;
668 if (notify)
669 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
670 (void*)((int)lPtr->selectedItem));
674 static int
675 getItemIndexAt(List *lPtr, int clickY)
677 int index;
679 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
681 if (index < 0 || index >= WMGetBagItemCount(lPtr->items))
682 return -1;
684 return index;
688 static void
689 handleActionEvents(XEvent *event, void *data)
691 List *lPtr = (List*)data;
692 int tmp;
693 int topItem = lPtr->topItem;
695 CHECK_CLASS(data, WC_List);
697 switch (event->type) {
698 case ButtonRelease:
699 lPtr->flags.buttonPressed = 0;
700 tmp = getItemIndexAt(lPtr, event->xbutton.y);
702 if (tmp == lPtr->selectedItem && tmp >= 0) {
703 if (lPtr->action)
704 (*lPtr->action)(lPtr, lPtr->clientData);
706 break;
708 case EnterNotify:
709 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
710 lPtr->flags.buttonWasPressed = 0;
711 break;
713 case LeaveNotify:
714 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
715 lPtr->flags.buttonPressed = 0;
716 break;
718 case ButtonPress:
719 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
720 tmp = getItemIndexAt(lPtr, event->xbutton.y);
721 lPtr->flags.buttonPressed = 1;
723 if (tmp >= 0) {
724 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
725 WMSelectListItem(lPtr, tmp);
726 if (lPtr->doubleAction)
727 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
728 } else {
729 WMSelectListItem(lPtr, tmp);
733 break;
735 case MotionNotify:
736 if (lPtr->flags.buttonPressed) {
737 tmp = getItemIndexAt(lPtr, event->xmotion.y);
738 if (tmp>=0 && tmp != lPtr->selectedItem) {
739 WMSelectListItem(lPtr, tmp);
742 break;
744 if (lPtr->topItem != topItem)
745 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
749 static void
750 updateGeometry(WMList *lPtr)
752 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
753 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
754 lPtr->flags.dontFitAll = 1;
755 } else {
756 lPtr->flags.dontFitAll = 0;
759 if (WMGetBagItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
760 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
761 if (lPtr->topItem < 0)
762 lPtr->topItem = 0;
765 updateScroller(lPtr);
769 static void
770 didResizeList(W_ViewDelegate *self, WMView *view)
772 WMList *lPtr = (WMList*)view->self;
774 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
776 updateGeometry(lPtr);
780 static void
781 destroyList(List *lPtr)
783 WMListItem *item;
784 int i;
786 if (lPtr->idleID)
787 WMDeleteIdleHandler(lPtr->idleID);
788 lPtr->idleID = NULL;
790 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
791 item = WMGetFromBag(lPtr->items, i);
792 wfree(item->text);
793 wfree(item);
795 WMFreeBag(lPtr->items);
797 wfree(lPtr);