Fixed bug with sorting list items in WINGs
[wmaker-crm.git] / WINGs / wlist.c
blobb318e3abe92080a711b64a4fa63382d915e19bf6
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 WMListItem*
156 WMInsertListItem(WMList *lPtr, int row, char *text)
158 WMListItem *item;
160 CHECK_CLASS(lPtr, WC_List);
162 item = wmalloc(sizeof(WMListItem));
163 memset(item, 0, sizeof(WMListItem));
164 item->text = wstrdup(text);
167 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
168 && row >= 0)
169 lPtr->selectedItem++;
171 if (row < 0)
172 row = WMGetBagItemCount(lPtr->items);
174 WMInsertInBag(lPtr->items, row, item);
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;
186 void
187 WMRemoveListItem(WMList *lPtr, int row)
189 WMListItem *item;
190 int topItem = lPtr->topItem;
191 int selNotify = 0;
193 CHECK_CLASS(lPtr, WC_List);
195 if (row < 0 || row >= WMGetBagItemCount(lPtr->items))
196 return;
198 if (lPtr->selectedItem == row) {
199 lPtr->selectedItem = -1;
200 selNotify = 1;
201 } else if (lPtr->selectedItem > row) {
202 lPtr->selectedItem--;
205 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
206 lPtr->topItem--;
207 if (lPtr->topItem < 0)
208 lPtr->topItem = 0;
210 item = WMGetFromBag(lPtr->items, row);
211 if (item->text)
212 wfree(item->text);
213 wfree(item);
215 WMDeleteFromBag(lPtr->items, row);
217 if (!lPtr->idleID) {
218 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
220 if (lPtr->topItem != topItem)
221 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
222 if (selNotify)
223 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
224 (void*)((int)lPtr->selectedItem));
229 WMListItem*
230 WMGetListItem(WMList *lPtr, int row)
232 return WMGetFromBag(lPtr->items, row);
236 WMBag*
237 WMGetListItems(WMList *lPtr)
239 return lPtr->items;
244 void
245 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
247 lPtr->flags.userDrawn = 1;
248 lPtr->draw = proc;
252 void
253 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
255 assert(height > 0);
257 lPtr->flags.userItemHeight = 1;
258 lPtr->itemHeight = height;
260 updateGeometry(lPtr);
264 void
265 WMClearList(WMList *lPtr)
267 WMListItem *item;
268 int oldSelected = lPtr->selectedItem;
269 int i;
271 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
272 item = WMGetFromBag(lPtr->items, i);
273 wfree(item->text);
274 wfree(item);
276 WMEmptyBag(lPtr->items);
278 lPtr->topItem = 0;
279 lPtr->selectedItem = -1;
281 if (!lPtr->idleID) {
282 WMDeleteIdleHandler(lPtr->idleID);
283 lPtr->idleID = NULL;
285 if (lPtr->view->flags.realized) {
286 updateScroller(lPtr);
288 if (oldSelected != -1)
289 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
290 (void*)-1);
294 void
295 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
297 lPtr->action = action;
298 lPtr->clientData = clientData;
302 void
303 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
305 lPtr->doubleAction = action;
306 lPtr->doubleClientData = clientData;
310 WMListItem*
311 WMGetListSelectedItem(WMList *lPtr)
313 if (lPtr->selectedItem < 0)
314 return NULL;
316 return WMGetFromBag(lPtr->items, lPtr->selectedItem);
321 WMGetListSelectedItemRow(WMList *lPtr)
323 return lPtr->selectedItem;
328 WMGetListItemHeight(WMList *lPtr)
330 return lPtr->itemHeight;
334 void
335 WMSetListPosition(WMList *lPtr, int row)
337 lPtr->topItem = row;
338 if (lPtr->topItem + lPtr->fullFitLines > WMGetBagItemCount(lPtr->items))
339 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
341 if (lPtr->topItem < 0)
342 lPtr->topItem = 0;
344 if (lPtr->view->flags.realized)
345 updateScroller(lPtr);
349 void
350 WMSetListBottomPosition(WMList *lPtr, int row)
352 if (WMGetBagItemCount(lPtr->items) > lPtr->fullFitLines) {
353 lPtr->topItem = row - lPtr->fullFitLines;
354 if (lPtr->topItem < 0)
355 lPtr->topItem = 0;
356 if (lPtr->view->flags.realized)
357 updateScroller(lPtr);
363 WMGetListNumberOfRows(WMList *lPtr)
365 return WMGetBagItemCount(lPtr->items);
369 WMGetListPosition(WMList *lPtr)
371 return lPtr->topItem;
375 static void
376 vScrollCallBack(WMWidget *scroller, void *self)
378 WMList *lPtr = (WMList*)self;
379 WMScroller *sPtr = (WMScroller*)scroller;
380 int height;
381 int topItem = lPtr->topItem;
382 int itemCount = WMGetBagItemCount(lPtr->items);
384 height = lPtr->view->size.height - 4;
386 switch (WMGetScrollerHitPart(sPtr)) {
387 case WSDecrementLine:
388 if (lPtr->topItem > 0) {
389 lPtr->topItem--;
391 updateScroller(lPtr);
393 break;
395 case WSDecrementPage:
396 if (lPtr->topItem > 0) {
397 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
398 if (lPtr->topItem < 0)
399 lPtr->topItem = 0;
401 updateScroller(lPtr);
403 break;
406 case WSIncrementLine:
407 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
408 lPtr->topItem++;
410 updateScroller(lPtr);
412 break;
414 case WSIncrementPage:
415 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
416 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
418 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
419 lPtr->topItem = itemCount - lPtr->fullFitLines;
421 updateScroller(lPtr);
423 break;
425 case WSKnob:
427 int oldTopItem = lPtr->topItem;
429 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
430 (float)(itemCount - lPtr->fullFitLines);
432 if (oldTopItem != lPtr->topItem)
433 paintList(lPtr);
435 break;
437 case WSKnobSlot:
438 case WSNoPart:
439 /* do nothing */
440 break;
443 if (lPtr->topItem != topItem)
444 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
448 static void
449 paintItem(List *lPtr, int index)
451 WMView *view = lPtr->view;
452 W_Screen *scr = view->screen;
453 int width, height, x, y;
454 WMListItem *itemPtr;
457 itemPtr = WMGetFromBag(lPtr->items, index);
460 width = lPtr->view->size.width - 2 - 19;
461 height = lPtr->itemHeight;
462 x = 19;
463 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
465 if (lPtr->flags.userDrawn) {
466 WMRect rect;
467 int flags;
469 rect.size.width = width;
470 rect.size.height = height;
471 rect.pos.x = x;
472 rect.pos.y = y;
474 flags = itemPtr->uflags;
475 if (itemPtr->disabled)
476 flags |= WLDSDisabled;
477 if (itemPtr->selected)
478 flags |= WLDSSelected;
479 if (itemPtr->isBranch)
480 flags |= WLDSIsBranch;
482 if (lPtr->draw)
483 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
484 &rect);
485 } else {
486 if (itemPtr->selected)
487 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
488 width, height);
489 else
490 XClearArea(scr->display, view->window, x, y, width, height, False);
492 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
493 WALeft, WMColorGC(scr->black), False,
494 itemPtr->text, strlen(itemPtr->text));
500 static void
501 paintList(List *lPtr)
503 W_Screen *scrPtr = lPtr->view->screen;
504 int i, lim;
506 if (!lPtr->view->flags.mapped)
507 return;
509 if (WMGetBagItemCount(lPtr->items) > 0) {
510 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
511 > WMGetBagItemCount(lPtr->items)) {
513 lim = WMGetBagItemCount(lPtr->items) - lPtr->topItem;
514 XClearArea(scrPtr->display, lPtr->view->window, 19,
515 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
516 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
517 } else {
518 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
520 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
521 paintItem(lPtr, i);
523 } else {
524 XClearWindow(scrPtr->display, lPtr->view->window);
526 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
527 lPtr->view->size.height, WRSunken);
530 #if 0
531 static void
532 scrollTo(List *lPtr, int newTop)
536 #endif
538 static void
539 updateScroller(List *lPtr)
541 float knobProportion, floatValue, tmp;
542 int count = WMGetBagItemCount(lPtr->items);
544 if (lPtr->idleID)
545 WMDeleteIdleHandler(lPtr->idleID);
546 lPtr->idleID = NULL;
548 paintList(lPtr);
550 if (count == 0 || count <= lPtr->fullFitLines)
551 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
552 else {
553 tmp = lPtr->fullFitLines;
554 knobProportion = tmp/(float)count;
556 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
558 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
563 static void
564 handleEvents(XEvent *event, void *data)
566 List *lPtr = (List*)data;
568 CHECK_CLASS(data, WC_List);
571 switch (event->type) {
572 case Expose:
573 if (event->xexpose.count!=0)
574 break;
575 paintList(lPtr);
576 break;
578 case DestroyNotify:
579 destroyList(lPtr);
580 break;
587 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
589 WMListItem *item;
590 int i;
591 int ok = 0;
593 for (i=0; i < WMGetBagItemCount(lPtr->items); i++) {
594 item = WMGetFromBag(lPtr->items, i);
595 if (strcmp(item->text, title)==0) {
596 ok = 1;
597 break;
601 return ok ? i : -1;
605 void
606 WMSelectListItem(WMList *lPtr, int row)
608 WMListItem *itemPtr;
609 int notify = 0;
611 if (row >= WMGetBagItemCount(lPtr->items))
612 return;
614 /* the check below must be changed when the multiple selection is
615 * implemented. -Dan
617 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
618 notify = 0;
619 else
620 notify = 1;
622 if (!lPtr->flags.allowMultipleSelection) {
623 /* unselect previous selected item */
624 if (lPtr->selectedItem >= 0) {
625 itemPtr = WMGetFromBag(lPtr->items, lPtr->selectedItem);
627 if (itemPtr->selected) {
628 itemPtr->selected = 0;
629 if (lPtr->view->flags.mapped
630 && lPtr->selectedItem>=lPtr->topItem
631 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
632 paintItem(lPtr, lPtr->selectedItem);
638 if (row < 0) {
639 if (!lPtr->flags.allowMultipleSelection) {
640 lPtr->selectedItem = -1;
641 if (notify)
642 WMPostNotificationName(WMListSelectionDidChangeNotification,
643 lPtr, (void*)((int)lPtr->selectedItem));
645 return;
648 /* select item */
649 itemPtr = WMGetFromBag(lPtr->items, row);
651 if (lPtr->flags.allowMultipleSelection)
652 itemPtr->selected = !itemPtr->selected;
653 else
654 itemPtr->selected = 1;
656 if (lPtr->view->flags.mapped) {
657 paintItem(lPtr, row);
659 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
660 > lPtr->view->size.height-2)
661 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
662 lPtr->view->size.width, lPtr->view->size.height,
663 WRSunken);
665 lPtr->selectedItem = row;
666 if (notify)
667 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
668 (void*)((int)lPtr->selectedItem));
672 static int
673 getItemIndexAt(List *lPtr, int clickY)
675 int index;
677 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
679 if (index < 0 || index >= WMGetBagItemCount(lPtr->items))
680 return -1;
682 return index;
686 static void
687 handleActionEvents(XEvent *event, void *data)
689 List *lPtr = (List*)data;
690 int tmp;
691 int topItem = lPtr->topItem;
693 CHECK_CLASS(data, WC_List);
695 switch (event->type) {
696 case ButtonRelease:
697 lPtr->flags.buttonPressed = 0;
698 tmp = getItemIndexAt(lPtr, event->xbutton.y);
700 if (tmp == lPtr->selectedItem && tmp >= 0) {
701 if (lPtr->action)
702 (*lPtr->action)(lPtr, lPtr->clientData);
704 break;
706 case EnterNotify:
707 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
708 lPtr->flags.buttonWasPressed = 0;
709 break;
711 case LeaveNotify:
712 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
713 lPtr->flags.buttonPressed = 0;
714 break;
716 case ButtonPress:
717 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
718 tmp = getItemIndexAt(lPtr, event->xbutton.y);
719 lPtr->flags.buttonPressed = 1;
721 if (tmp >= 0) {
722 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
723 WMSelectListItem(lPtr, tmp);
724 if (lPtr->doubleAction)
725 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
726 } else {
727 WMSelectListItem(lPtr, tmp);
731 break;
733 case MotionNotify:
734 if (lPtr->flags.buttonPressed) {
735 tmp = getItemIndexAt(lPtr, event->xmotion.y);
736 if (tmp>=0 && tmp != lPtr->selectedItem) {
737 WMSelectListItem(lPtr, tmp);
740 break;
742 if (lPtr->topItem != topItem)
743 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
747 static void
748 updateGeometry(WMList *lPtr)
750 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
751 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
752 lPtr->flags.dontFitAll = 1;
753 } else {
754 lPtr->flags.dontFitAll = 0;
757 if (WMGetBagItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
758 lPtr->topItem = WMGetBagItemCount(lPtr->items) - lPtr->fullFitLines;
759 if (lPtr->topItem < 0)
760 lPtr->topItem = 0;
763 updateScroller(lPtr);
767 static void
768 didResizeList(W_ViewDelegate *self, WMView *view)
770 WMList *lPtr = (WMList*)view->self;
772 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
774 updateGeometry(lPtr);
778 static void
779 destroyList(List *lPtr)
781 WMListItem *item;
782 int i;
784 if (lPtr->idleID)
785 WMDeleteIdleHandler(lPtr->idleID);
786 lPtr->idleID = NULL;
788 for (i = 0; i < WMGetBagItemCount(lPtr->items); i++) {
789 item = WMGetFromBag(lPtr->items, i);
790 wfree(item->text);
791 wfree(item);
793 WMFreeBag(lPtr->items);
795 wfree(lPtr);