- Fixed WMArray.
[wmaker-crm.git] / WINGs / wlist.c
blob68ce3665741d813d13dd4d3811abb631b788834c
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 WMArray *items; /* list of WMListItem */
15 WMArray *selectedItems; /* list of selected WMListItems */
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 adding elements */
34 WMScroller *vScroller;
36 struct {
37 unsigned int allowMultipleSelection:1;
38 unsigned int allowEmptySelection:1;
39 unsigned int userDrawn:1;
40 unsigned int userItemHeight:1;
41 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
42 unsigned int redrawPending:1;
43 unsigned int buttonPressed:1;
44 unsigned int buttonWasPressed:1;
45 } flags;
46 } List;
50 #define DEFAULT_WIDTH 150
51 #define DEFAULT_HEIGHT 150
54 static void destroyList(List *lPtr);
55 static void paintList(List *lPtr);
58 static void handleEvents(XEvent *event, void *data);
59 static void handleActionEvents(XEvent *event, void *data);
60 static void updateScroller(List *lPtr);
62 static void vScrollCallBack(WMWidget *scroller, void *self);
64 static void updateGeometry(WMList *lPtr);
65 static void didResizeList();
68 W_ViewDelegate _ListViewDelegate = {
69 NULL,
70 NULL,
71 didResizeList,
72 NULL,
73 NULL
77 static void
78 releaseItem(void *data)
80 WMListItem *item = (WMListItem*)data;
82 if (item->text)
83 wfree(item->text);
84 wfree(item);
88 WMList*
89 WMCreateList(WMWidget *parent)
91 List *lPtr;
92 W_Screen *scrPtr = W_VIEW(parent)->screen;
94 lPtr = wmalloc(sizeof(List));
95 memset(lPtr, 0, sizeof(List));
97 lPtr->widgetClass = WC_List;
99 lPtr->view = W_CreateView(W_VIEW(parent));
100 if (!lPtr->view) {
101 wfree(lPtr);
102 return NULL;
104 lPtr->view->self = lPtr;
106 lPtr->view->delegate = &_ListViewDelegate;
108 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
109 |ClientMessageMask, handleEvents, lPtr);
111 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
112 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
113 handleActionEvents, lPtr);
115 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
117 lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
119 /* create the vertical scroller */
120 lPtr->vScroller = WMCreateScroller(lPtr);
121 WMMoveWidget(lPtr->vScroller, 1, 1);
122 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
124 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
126 /* make the scroller map itself when it's realized */
127 WMMapWidget(lPtr->vScroller);
129 W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
131 lPtr->selectedItem = -1;
133 return lPtr;
137 static int
138 comparator(const void *a, const void *b)
140 return (strcmp((*(WMListItem**)a)->text, (*(WMListItem**)b)->text));
144 void
145 WMSortListItems(WMList *lPtr)
147 WMSortArray(lPtr->items, comparator);
149 paintList(lPtr);
154 void
155 WMSortListItemsWithComparer(WMList *lPtr, int (f)(const void*, const void*))
157 WMSortArray(lPtr->items, f);
159 paintList(lPtr);
164 WMListItem*
165 WMInsertListItem(WMList *lPtr, int row, char *text)
167 WMListItem *item;
169 CHECK_CLASS(lPtr, WC_List);
171 item = wmalloc(sizeof(WMListItem));
172 memset(item, 0, sizeof(WMListItem));
173 item->text = wstrdup(text);
176 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
177 && row >= 0)
178 lPtr->selectedItem++;
180 row = WMIN(row, WMGetArrayItemCount(lPtr->items));
182 if (row < 0)
183 WMAddToArray(lPtr->items, item);
184 else
185 WMInsertInArray(lPtr->items, row, item);
187 /* update the scroller when idle, so that we don't waste time
188 * updating it when another item is going to be added later */
189 if (!lPtr->idleID) {
190 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
193 return item;
197 void
198 WMRemoveListItem(WMList *lPtr, int row)
200 int topItem = lPtr->topItem;
201 int selNotify = 0;
203 CHECK_CLASS(lPtr, WC_List);
205 if (row < 0 || row >= WMGetArrayItemCount(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 WMDeleteFromArray(lPtr->items, row);
222 if (!lPtr->idleID) {
223 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
225 if (lPtr->topItem != topItem)
226 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
227 if (selNotify)
228 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
229 (void*)((int)lPtr->selectedItem));
234 WMListItem*
235 WMGetListItem(WMList *lPtr, int row)
237 return WMGetFromArray(lPtr->items, row);
241 WMArray*
242 WMGetListItems(WMList *lPtr)
244 return lPtr->items;
249 void
250 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
252 lPtr->flags.userDrawn = 1;
253 lPtr->draw = proc;
257 void
258 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
260 assert(height > 0);
262 lPtr->flags.userItemHeight = 1;
263 lPtr->itemHeight = height;
265 updateGeometry(lPtr);
269 void
270 WMClearList(WMList *lPtr)
272 int oldSelected = lPtr->selectedItem;
273 int i;
275 WMEmptyArray(lPtr->items);
277 lPtr->topItem = 0;
278 lPtr->selectedItem = -1;
280 if (!lPtr->idleID) {
281 WMDeleteIdleHandler(lPtr->idleID);
282 lPtr->idleID = NULL;
284 if (lPtr->view->flags.realized) {
285 updateScroller(lPtr);
287 if (oldSelected != -1)
288 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
289 (void*)-1);
293 void
294 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
296 lPtr->action = action;
297 lPtr->clientData = clientData;
301 void
302 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
304 lPtr->doubleAction = action;
305 lPtr->doubleClientData = clientData;
309 WMListItem*
310 WMGetListSelectedItem(WMList *lPtr)
312 if (lPtr->selectedItem < 0)
313 return NULL;
315 return WMGetFromArray(lPtr->items, lPtr->selectedItem);
320 WMGetListSelectedItemRow(WMList *lPtr)
322 return lPtr->selectedItem;
327 WMGetListItemHeight(WMList *lPtr)
329 return lPtr->itemHeight;
333 void
334 WMSetListPosition(WMList *lPtr, int row)
336 lPtr->topItem = row;
337 if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
338 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
340 if (lPtr->topItem < 0)
341 lPtr->topItem = 0;
343 if (lPtr->view->flags.realized)
344 updateScroller(lPtr);
348 void
349 WMSetListBottomPosition(WMList *lPtr, int row)
351 if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
352 lPtr->topItem = row - lPtr->fullFitLines;
353 if (lPtr->topItem < 0)
354 lPtr->topItem = 0;
355 if (lPtr->view->flags.realized)
356 updateScroller(lPtr);
362 WMGetListNumberOfRows(WMList *lPtr)
364 return WMGetArrayItemCount(lPtr->items);
368 WMGetListPosition(WMList *lPtr)
370 return lPtr->topItem;
374 static void
375 vScrollCallBack(WMWidget *scroller, void *self)
377 WMList *lPtr = (WMList*)self;
378 WMScroller *sPtr = (WMScroller*)scroller;
379 int height;
380 int topItem = lPtr->topItem;
381 int itemCount = WMGetArrayItemCount(lPtr->items);
383 height = lPtr->view->size.height - 4;
385 switch (WMGetScrollerHitPart(sPtr)) {
386 case WSDecrementLine:
387 if (lPtr->topItem > 0) {
388 lPtr->topItem--;
390 updateScroller(lPtr);
392 break;
394 case WSDecrementPage:
395 if (lPtr->topItem > 0) {
396 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
397 if (lPtr->topItem < 0)
398 lPtr->topItem = 0;
400 updateScroller(lPtr);
402 break;
405 case WSIncrementLine:
406 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
407 lPtr->topItem++;
409 updateScroller(lPtr);
411 break;
413 case WSIncrementPage:
414 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
415 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
417 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
418 lPtr->topItem = itemCount - lPtr->fullFitLines;
420 updateScroller(lPtr);
422 break;
424 case WSKnob:
426 int oldTopItem = lPtr->topItem;
428 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
429 (float)(itemCount - lPtr->fullFitLines);
431 if (oldTopItem != lPtr->topItem)
432 paintList(lPtr);
434 break;
436 case WSKnobSlot:
437 case WSNoPart:
438 /* do nothing */
439 break;
442 if (lPtr->topItem != topItem)
443 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
447 static void
448 paintItem(List *lPtr, int index)
450 WMView *view = lPtr->view;
451 W_Screen *scr = view->screen;
452 int width, height, x, y;
453 WMListItem *itemPtr;
455 itemPtr = WMGetFromArray(lPtr->items, index);
457 width = lPtr->view->size.width - 2 - 19;
458 height = lPtr->itemHeight;
459 x = 19;
460 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
462 if (lPtr->flags.userDrawn) {
463 WMRect rect;
464 int flags;
466 rect.size.width = width;
467 rect.size.height = height;
468 rect.pos.x = x;
469 rect.pos.y = y;
471 flags = itemPtr->uflags;
472 if (itemPtr->disabled)
473 flags |= WLDSDisabled;
474 if (itemPtr->selected)
475 flags |= WLDSSelected;
476 if (itemPtr->isBranch)
477 flags |= WLDSIsBranch;
479 if (lPtr->draw)
480 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
481 &rect);
482 } else {
483 if (itemPtr->selected)
484 XFillRectangle(scr->display, view->window, WMColorGC(scr->white),
485 x, y, width, height);
486 else
487 XClearArea(scr->display, view->window, x, y, width, height, False);
489 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
490 WALeft, WMColorGC(scr->black), False,
491 itemPtr->text, strlen(itemPtr->text));
497 static void
498 paintList(List *lPtr)
500 W_Screen *scrPtr = lPtr->view->screen;
501 int i, lim;
503 if (!lPtr->view->flags.mapped)
504 return;
506 if (WMGetArrayItemCount(lPtr->items) > 0) {
507 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll
508 > WMGetArrayItemCount(lPtr->items)) {
510 lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
511 XClearArea(scrPtr->display, lPtr->view->window, 19,
512 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
513 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
514 } else {
515 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
517 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
518 paintItem(lPtr, i);
520 } else {
521 XClearWindow(scrPtr->display, lPtr->view->window);
523 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
524 lPtr->view->size.height, WRSunken);
527 #if 0
528 static void
529 scrollTo(List *lPtr, int newTop)
533 #endif
535 static void
536 updateScroller(List *lPtr)
538 float knobProportion, floatValue, tmp;
539 int count = WMGetArrayItemCount(lPtr->items);
541 if (lPtr->idleID)
542 WMDeleteIdleHandler(lPtr->idleID);
543 lPtr->idleID = NULL;
545 paintList(lPtr);
547 if (count == 0 || count <= lPtr->fullFitLines)
548 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
549 else {
550 tmp = lPtr->fullFitLines;
551 knobProportion = tmp/(float)count;
553 floatValue = (float)lPtr->topItem/(float)(count - lPtr->fullFitLines);
555 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
560 static void
561 handleEvents(XEvent *event, void *data)
563 List *lPtr = (List*)data;
565 CHECK_CLASS(data, WC_List);
568 switch (event->type) {
569 case Expose:
570 if (event->xexpose.count!=0)
571 break;
572 paintList(lPtr);
573 break;
575 case DestroyNotify:
576 destroyList(lPtr);
577 break;
583 static int
584 matchTitle(void *item, void *title)
586 return (strcmp(((WMListItem*)item)->text, (char*)title)==0 ? 1 : 0);
591 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
593 return WMFindInArray(lPtr->items, matchTitle, title);
597 void
598 WMSelectListItem(WMList *lPtr, int row)
600 WMListItem *itemPtr;
601 int notify = 0;
603 if (row >= WMGetArrayItemCount(lPtr->items))
604 return;
606 /* the check below must be changed when the multiple selection is
607 * implemented. -Dan
609 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
610 notify = 0;
611 else
612 notify = 1;
614 assert(lPtr->selectedItem < WMGetArrayItemCount(lPtr->items));
616 if (!lPtr->flags.allowMultipleSelection) {
617 /* unselect previous selected item */
618 if (lPtr->selectedItem >= 0) {
619 itemPtr = WMGetFromArray(lPtr->items, lPtr->selectedItem);
621 if (itemPtr->selected) {
622 itemPtr->selected = 0;
623 if (lPtr->view->flags.mapped
624 && lPtr->selectedItem>=lPtr->topItem
625 && lPtr->selectedItem<=lPtr->topItem+lPtr->fullFitLines) {
626 paintItem(lPtr, lPtr->selectedItem);
632 if (row < 0) {
633 if (!lPtr->flags.allowMultipleSelection) {
634 lPtr->selectedItem = -1;
635 if (notify)
636 WMPostNotificationName(WMListSelectionDidChangeNotification,
637 lPtr, (void*)((int)lPtr->selectedItem));
639 return;
642 /* select item */
643 itemPtr = WMGetFromArray(lPtr->items, row);
645 if (lPtr->flags.allowMultipleSelection)
646 itemPtr->selected = !itemPtr->selected;
647 else
648 itemPtr->selected = 1;
650 if (lPtr->view->flags.mapped) {
651 paintItem(lPtr, row);
653 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
654 > lPtr->view->size.height-2)
655 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
656 lPtr->view->size.width, lPtr->view->size.height,
657 WRSunken);
659 lPtr->selectedItem = row;
660 if (notify)
661 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
662 (void*)((int)lPtr->selectedItem));
666 static int
667 getItemIndexAt(List *lPtr, int clickY)
669 int index;
671 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
673 if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
674 return -1;
676 return index;
680 static void
681 handleActionEvents(XEvent *event, void *data)
683 List *lPtr = (List*)data;
684 int tmp;
685 int topItem = lPtr->topItem;
687 CHECK_CLASS(data, WC_List);
689 switch (event->type) {
690 case ButtonRelease:
691 #define CHECK_WHEEL_PATCH
692 #ifdef CHECK_WHEEL_PATCH
693 /* Ignore mouse wheel events, they're not "real" button events */
694 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
695 event->xbutton.button == WINGsConfiguration.mouseWheelDown)
696 break;
697 #endif
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 #ifdef CHECK_WHEEL_PATCH
721 /* Mouse wheel events need to be properly handled here. It would
722 * be best to somehow route them to lPtr->vScroller so that the
723 * correct chain of actions is triggered. However, I found no
724 * clean way to do so, so I mostly copied the code that deals with
725 * WSIncrementPage and WSDecrementPage from vScrollCallBack.
727 * - Martynas Kunigelis <diskena@linuxfreak.com> */
729 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
730 /* Wheel down */
731 int itemCount = WMGetArrayItemCount(lPtr->items);
732 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
733 int incr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
734 lPtr->topItem += incr;
736 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
737 lPtr->topItem = itemCount - lPtr->fullFitLines;
739 updateScroller(lPtr);
741 break;
744 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
745 /* Wheel up */
746 if (lPtr->topItem > 0) {
747 int decr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
748 lPtr->topItem -= decr;
750 if (lPtr->topItem < 0)
751 lPtr->topItem = 0;
753 updateScroller(lPtr);
755 break;
757 #endif
759 tmp = getItemIndexAt(lPtr, event->xbutton.y);
760 lPtr->flags.buttonPressed = 1;
762 if (tmp >= 0) {
763 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
764 WMSelectListItem(lPtr, tmp);
765 if (lPtr->doubleAction)
766 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
767 } else {
768 WMSelectListItem(lPtr, tmp);
772 break;
774 case MotionNotify:
775 if (lPtr->flags.buttonPressed) {
776 tmp = getItemIndexAt(lPtr, event->xmotion.y);
777 if (tmp>=0 && tmp != lPtr->selectedItem) {
778 WMSelectListItem(lPtr, tmp);
781 break;
783 if (lPtr->topItem != topItem)
784 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
788 static void
789 updateGeometry(WMList *lPtr)
791 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
792 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
793 lPtr->flags.dontFitAll = 1;
794 } else {
795 lPtr->flags.dontFitAll = 0;
798 if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
799 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
800 if (lPtr->topItem < 0)
801 lPtr->topItem = 0;
804 updateScroller(lPtr);
808 static void
809 didResizeList(W_ViewDelegate *self, WMView *view)
811 WMList *lPtr = (WMList*)view->self;
813 WMResizeWidget(lPtr->vScroller, 1, view->size.height-2);
815 updateGeometry(lPtr);
819 static void
820 destroyList(List *lPtr)
822 if (lPtr->idleID)
823 WMDeleteIdleHandler(lPtr->idleID);
824 lPtr->idleID = NULL;
826 WMFreeArray(lPtr->items);
828 wfree(lPtr);