0.51.1 pre snapshot. Be careful, it may be buggy. It fixes some bugs though.
[wmaker-crm.git] / WINGs / wlist.c
blobc9a02573519d9f1c812908215cd611f674b90f21
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 resizeList();
71 W_ViewProcedureTable _ListViewProcedures = {
72 NULL,
73 resizeList,
74 NULL
79 WMList*
80 WMCreateList(WMWidget *parent)
82 List *lPtr;
83 W_Screen *scrPtr = W_VIEW(parent)->screen;
85 lPtr = wmalloc(sizeof(List));
86 memset(lPtr, 0, sizeof(List));
88 lPtr->widgetClass = WC_List;
90 lPtr->view = W_CreateView(W_VIEW(parent));
91 if (!lPtr->view) {
92 free(lPtr);
93 return NULL;
95 lPtr->view->self = lPtr;
97 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
98 |ClientMessageMask, handleEvents, lPtr);
100 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
101 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
102 handleActionEvents, lPtr);
104 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
106 /* create the vertical scroller */
107 lPtr->vScroller = WMCreateScroller(lPtr);
108 WMMoveWidget(lPtr->vScroller, 1, 1);
109 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
111 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
113 /* make the scroller map itself when it's realized */
114 WMMapWidget(lPtr->vScroller);
116 resizeList(lPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
118 lPtr->selectedItem = -1;
120 return lPtr;
125 WMListItem*
126 WMAddSortedListItem(WMList *lPtr, char *text)
128 WMListItem *item;
129 WMListItem *tmp;
130 int index;
132 item = wmalloc(sizeof(WMListItem));
133 memset(item, 0, sizeof(WMListItem));
134 item->text = wstrdup(text);
135 if (!lPtr->items) {
136 lPtr->items = item;
137 index = 0;
138 } else if (strcmp(lPtr->items->text, text) > 0) {
139 item->nextPtr = lPtr->items;
140 lPtr->items = item;
141 index = 1;
142 } else {
143 int added = 0;
145 index = 0;
146 tmp = lPtr->items;
147 while (tmp->nextPtr) {
148 if (strcmp(tmp->nextPtr->text, text) >= 0) {
149 item->nextPtr = tmp->nextPtr;
150 tmp->nextPtr = item;
151 added = 1;
152 break;
154 index++;
155 tmp = tmp->nextPtr;
157 if (!added) {
158 tmp->nextPtr = item;
162 lPtr->itemCount++;
164 if (lPtr->selectedItem >= index)
165 lPtr->selectedItem++;
167 /* update the scroller when idle, so that we don't waste time
168 * updating it when another item is going to be added later */
169 if (!lPtr->idleID) {
170 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
173 return item;
178 WMListItem*
179 WMInsertListItem(WMList *lPtr, int row, char *text)
181 WMListItem *item;
182 WMListItem *tmp = lPtr->items;
184 CHECK_CLASS(lPtr, WC_List);
186 item = wmalloc(sizeof(WMListItem));
187 memset(item, 0, sizeof(WMListItem));
188 item->text = wstrdup(text);
190 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0)
191 lPtr->selectedItem++;
193 if (lPtr->items==NULL) {
194 lPtr->items = item;
195 } else if (row == 0) {
196 item->nextPtr = lPtr->items;
197 lPtr->items = item;
198 } else if (row < 0) {
199 while (tmp->nextPtr)
200 tmp = tmp->nextPtr;
202 tmp->nextPtr = item;
203 row = lPtr->itemCount;
204 } else {
205 while (--row > 0)
206 tmp = tmp->nextPtr;
208 item->nextPtr = tmp->nextPtr;
209 tmp->nextPtr = item;
212 lPtr->itemCount++;
214 /* update the scroller when idle, so that we don't waste time
215 * updating it when another item is going to be added later */
216 if (!lPtr->idleID) {
217 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
220 return item;
224 void
225 WMRemoveListItem(WMList *lPtr, int row)
227 WMListItem *llist;
228 WMListItem *tmp;
229 int topItem = lPtr->topItem;
230 int selNotify = 0;
232 CHECK_CLASS(lPtr, WC_List);
234 if (row < 0 || row >= lPtr->itemCount)
235 return;
237 if (lPtr->selectedItem == row) {
238 lPtr->selectedItem = -1;
239 selNotify = 1;
240 } else if (lPtr->selectedItem > row) {
241 lPtr->selectedItem--;
244 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
245 lPtr->topItem--;
246 if (lPtr->topItem < 0)
247 lPtr->topItem = 0;
249 if (row == 0) {
250 if (lPtr->items->text)
251 free(lPtr->items->text);
253 tmp = lPtr->items->nextPtr;
254 free(lPtr->items);
256 lPtr->items = tmp;
257 } else {
258 llist = lPtr->items;
259 while (--row > 0)
260 llist = llist->nextPtr;
261 tmp = llist->nextPtr;
262 llist->nextPtr = llist->nextPtr->nextPtr;
264 if (tmp->text)
265 free(tmp->text);
267 free(tmp);
270 lPtr->itemCount--;
272 if (!lPtr->idleID) {
273 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
275 if (lPtr->topItem != topItem)
276 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
277 if (selNotify)
278 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
279 (void*)((int)lPtr->selectedItem));
284 WMListItem*
285 WMGetListItem(WMList *lPtr, int row)
287 WMListItem *listPtr;
289 listPtr = lPtr->items;
291 while (row-- > 0)
292 listPtr = listPtr->nextPtr;
294 return listPtr;
299 void
300 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
302 lPtr->flags.userDrawn = 1;
303 lPtr->draw = proc;
307 void
308 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
310 assert(height > 0);
312 lPtr->flags.userItemHeight = 1;
313 lPtr->itemHeight = height;
317 void
318 WMClearList(WMList *lPtr)
320 WMListItem *item, *tmp;
321 int oldSelected = lPtr->selectedItem;
323 item = lPtr->items;
324 while (item) {
325 free(item->text);
326 tmp = item->nextPtr;
327 free(item);
328 item = tmp;
330 lPtr->items = NULL;
331 lPtr->itemCount = 0;
332 lPtr->topItem = 0;
333 lPtr->selectedItem = -1;
335 if (!lPtr->idleID) {
336 WMDeleteIdleHandler(lPtr->idleID);
337 lPtr->idleID = NULL;
339 if (lPtr->view->flags.realized) {
340 updateScroller(lPtr);
342 if (oldSelected != -1)
343 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
344 (void*)-1);
348 void
349 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
351 lPtr->action = action;
352 lPtr->clientData = clientData;
356 void
357 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
359 lPtr->doubleAction = action;
360 lPtr->doubleClientData = clientData;
364 WMListItem*
365 WMGetListSelectedItem(WMList *lPtr)
367 int i = lPtr->selectedItem;
368 WMListItem *item;
370 if (lPtr->selectedItem < 0)
371 return NULL;
373 item = lPtr->items;
374 while (i-- > 0) {
375 item = item->nextPtr;
377 return item;
382 WMGetListSelectedItemRow(WMList *lPtr)
384 return lPtr->selectedItem;
389 WMGetListItemHeight(WMList *lPtr)
391 return lPtr->itemHeight;
395 void
396 WMSetListPosition(WMList *lPtr, int row)
398 lPtr->topItem = row;
399 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
400 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
402 if (lPtr->topItem < 0)
403 lPtr->topItem = 0;
405 if (lPtr->view->flags.realized)
406 updateScroller(lPtr);
410 void
411 WMSetListBottomPosition(WMList *lPtr, int row)
413 if (lPtr->itemCount > lPtr->fullFitLines) {
414 lPtr->topItem = row - lPtr->fullFitLines;
415 if (lPtr->topItem < 0)
416 lPtr->topItem = 0;
417 if (lPtr->view->flags.realized)
418 updateScroller(lPtr);
424 WMGetListNumberOfRows(WMList *lPtr)
426 return lPtr->itemCount;
430 WMGetListPosition(WMList *lPtr)
432 return lPtr->topItem;
436 static void
437 vScrollCallBack(WMWidget *scroller, void *self)
439 WMList *lPtr = (WMList*)self;
440 WMScroller *sPtr = (WMScroller*)scroller;
441 int height;
442 int topItem = lPtr->topItem;
444 height = lPtr->view->size.height - 4;
446 switch (WMGetScrollerHitPart(sPtr)) {
447 case WSDecrementLine:
448 if (lPtr->topItem > 0) {
449 lPtr->topItem--;
451 updateScroller(lPtr);
453 break;
455 case WSDecrementPage:
456 if (lPtr->topItem > 0) {
457 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
458 if (lPtr->topItem < 0)
459 lPtr->topItem = 0;
461 updateScroller(lPtr);
463 break;
466 case WSIncrementLine:
467 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
468 lPtr->topItem++;
470 updateScroller(lPtr);
472 break;
474 case WSIncrementPage:
475 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
476 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
478 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
479 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
481 updateScroller(lPtr);
483 break;
485 case WSKnob:
487 int oldTopItem = lPtr->topItem;
489 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
490 (float)(lPtr->itemCount - lPtr->fullFitLines);
492 if (oldTopItem != lPtr->topItem)
493 paintList(lPtr);
495 break;
497 case WSKnobSlot:
498 case WSNoPart:
499 /* do nothing */
500 break;
503 if (lPtr->topItem != topItem)
504 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
508 static void
509 paintItem(List *lPtr, int index)
511 WMView *view = lPtr->view;
512 W_Screen *scr = view->screen;
513 int width, height, x, y;
514 WMListItem *itemPtr;
515 int i;
517 i = index;
518 itemPtr = lPtr->items;
519 while (i-- > 0)
520 itemPtr = itemPtr->nextPtr;
522 width = lPtr->view->size.width - 2 - 19;
523 height = lPtr->itemHeight;
524 x = 19;
525 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
527 if (lPtr->flags.userDrawn) {
528 WMRect rect;
529 int flags;
531 rect.size.width = width;
532 rect.size.height = height;
533 rect.pos.x = x;
534 rect.pos.y = y;
536 flags = itemPtr->uflags;
537 if (itemPtr->disabled)
538 flags |= WLDSDisabled;
539 if (itemPtr->selected)
540 flags |= WLDSSelected;
541 if (itemPtr->isBranch)
542 flags |= WLDSIsBranch;
544 if (lPtr->draw)
545 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
546 &rect);
547 } else {
548 if (itemPtr->selected)
549 XFillRectangle(scr->display, view->window, W_GC(scr->white), x, y,
550 width, height);
551 else
552 XClearArea(scr->display, view->window, x, y, width, height, False);
554 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
555 WALeft, W_GC(scr->black), False,
556 itemPtr->text, strlen(itemPtr->text));
562 static void
563 paintList(List *lPtr)
565 W_Screen *scrPtr = lPtr->view->screen;
566 int i, lim;
568 if (!lPtr->view->flags.mapped)
569 return;
571 if (lPtr->itemCount>0) {
572 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
573 lim = lPtr->itemCount - lPtr->topItem;
574 XClearArea(scrPtr->display, lPtr->view->window, 19,
575 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
576 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
577 } else {
578 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
580 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
581 paintItem(lPtr, i);
583 } else {
584 XClearWindow(scrPtr->display, lPtr->view->window);
586 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
587 lPtr->view->size.height, WRSunken);
590 #if 0
591 static void
592 scrollTo(List *lPtr, int newTop)
596 #endif
598 static void
599 updateScroller(List *lPtr)
601 float knobProportion, floatValue, tmp;
603 if (lPtr->idleID)
604 WMDeleteIdleHandler(lPtr->idleID);
605 lPtr->idleID = NULL;
607 paintList(lPtr);
609 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
610 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
611 else {
612 tmp = lPtr->fullFitLines;
613 knobProportion = tmp/(float)lPtr->itemCount;
615 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
617 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
622 static void
623 handleEvents(XEvent *event, void *data)
625 List *lPtr = (List*)data;
627 CHECK_CLASS(data, WC_List);
630 switch (event->type) {
631 case Expose:
632 if (event->xexpose.count!=0)
633 break;
634 paintList(lPtr);
635 break;
637 case DestroyNotify:
638 destroyList(lPtr);
639 break;
646 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
648 WMListItem *item;
649 int i;
650 int ok = 0;
652 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
653 if (strcmp(item->text, title)==0) {
654 ok = 1;
655 break;
659 return ok ? i : -1;
663 void
664 WMSelectListItem(WMList *lPtr, int row)
666 WMListItem *itemPtr;
667 int i, notify = 0;
669 if (row >= lPtr->itemCount)
670 return;
672 /* the check below must be changed when the multiple selection is
673 * implemented. -Dan
675 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
676 notify = 0;
677 else
678 notify = 1;
680 if (!lPtr->flags.allowMultipleSelection) {
681 /* unselect previous selected item */
682 if (lPtr->selectedItem >= 0) {
683 itemPtr = lPtr->items;
684 for (i=0; i<lPtr->selectedItem; i++)
685 itemPtr = itemPtr->nextPtr;
687 if (itemPtr->selected) {
688 itemPtr->selected = 0;
689 if (lPtr->view->flags.mapped && i>=lPtr->topItem
690 && i<=lPtr->topItem+lPtr->fullFitLines)
691 paintItem(lPtr, i);
696 if (row < 0) {
697 if (!lPtr->flags.allowMultipleSelection) {
698 lPtr->selectedItem = -1;
699 if (notify)
700 WMPostNotificationName(WMListSelectionDidChangeNotification,
701 lPtr, (void*)((int)lPtr->selectedItem));
703 return;
706 /* select item */
707 itemPtr = lPtr->items;
708 for (i=0; i<row; i++)
709 itemPtr = itemPtr->nextPtr;
710 if (lPtr->flags.allowMultipleSelection)
711 itemPtr->selected = !itemPtr->selected;
712 else
713 itemPtr->selected = 1;
715 if (lPtr->view->flags.mapped) {
716 paintItem(lPtr, row);
718 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
719 > lPtr->view->size.height-2)
720 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
721 lPtr->view->size.width, lPtr->view->size.height,
722 WRSunken);
724 lPtr->selectedItem = row;
725 if (notify)
726 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
727 (void*)((int)lPtr->selectedItem));
731 static int
732 getItemIndexAt(List *lPtr, int clickY)
734 int index;
736 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
738 if (index < 0 || index >= lPtr->itemCount)
739 return -1;
741 return index;
745 static void
746 handleActionEvents(XEvent *event, void *data)
748 List *lPtr = (List*)data;
749 int tmp;
750 int topItem = lPtr->topItem;
752 CHECK_CLASS(data, WC_List);
754 switch (event->type) {
755 case ButtonRelease:
756 lPtr->flags.buttonPressed = 0;
757 tmp = getItemIndexAt(lPtr, event->xbutton.y);
759 if (tmp == lPtr->selectedItem && tmp >= 0) {
760 if (lPtr->action)
761 (*lPtr->action)(lPtr, lPtr->clientData);
763 break;
765 case EnterNotify:
766 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
767 lPtr->flags.buttonWasPressed = 0;
768 break;
770 case LeaveNotify:
771 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
772 lPtr->flags.buttonPressed = 0;
773 break;
775 case ButtonPress:
776 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
777 tmp = getItemIndexAt(lPtr, event->xbutton.y);
778 lPtr->flags.buttonPressed = 1;
780 if (tmp >= 0) {
781 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
782 WMSelectListItem(lPtr, tmp);
783 if (lPtr->doubleAction)
784 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
785 } else {
786 WMSelectListItem(lPtr, tmp);
790 break;
792 case MotionNotify:
793 if (lPtr->flags.buttonPressed) {
794 tmp = getItemIndexAt(lPtr, event->xmotion.y);
795 if (tmp>=0 && tmp != lPtr->selectedItem) {
796 WMSelectListItem(lPtr, tmp);
799 break;
801 if (lPtr->topItem != topItem)
802 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
806 static void
807 resizeList(WMList *lPtr, unsigned int width, unsigned int height)
809 W_ResizeView(lPtr->view, width, height);
811 WMResizeWidget(lPtr->vScroller, 1, height-2);
813 lPtr->fullFitLines = (height - 4) / lPtr->itemHeight;
814 if (lPtr->fullFitLines * lPtr->itemHeight < height-4) {
815 lPtr->flags.dontFitAll = 1;
816 } else {
817 lPtr->flags.dontFitAll = 0;
820 if (lPtr->itemCount - lPtr->topItem <= lPtr->fullFitLines) {
821 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
822 if (lPtr->topItem < 0)
823 lPtr->topItem = 0;
826 updateScroller(lPtr);
830 static void
831 destroyList(List *lPtr)
833 WMListItem *itemPtr;
835 if (lPtr->idleID)
836 WMDeleteIdleHandler(lPtr->idleID);
837 lPtr->idleID = NULL;
839 while (lPtr->items!=NULL) {
840 itemPtr = lPtr->items;
841 if (itemPtr->text)
842 free(itemPtr->text);
844 lPtr->items = itemPtr->nextPtr;
845 free(itemPtr);
848 free(lPtr);