7 char *WMListDidScrollNotification
= "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification
= "WMListSelectionDidChangeNotification";
10 typedef struct W_List
{
14 WMListItem
*items
; /* array of items */
21 short topItem
; /* index of first visible item */
23 short fullFitLines
; /* no of lines that fit entirely */
27 void *doubleClientData
;
28 WMAction
*doubleAction
;
32 WMHandlerID
*idleID
; /* for updating the scroller after
35 WMScroller
*vScroller
;
37 WMScroller *hScroller;
41 unsigned int allowMultipleSelection
:1;
42 unsigned int userDrawn
:1;
43 unsigned int userItemHeight
:1;
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;
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
= {
83 WMCreateList(WMWidget
*parent
)
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
));
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;
131 WMAddSortedListItem(WMList
*lPtr
, char *text
)
137 item
= wmalloc(sizeof(WMListItem
));
138 memset(item
, 0, sizeof(WMListItem
));
139 item
->text
= wstrdup(text
);
143 } else if (strcmp(lPtr
->items
->text
, text
) > 0) {
144 item
->nextPtr
= lPtr
->items
;
152 while (tmp
->nextPtr
) {
153 if (strcmp(tmp
->nextPtr
->text
, text
) >= 0) {
154 item
->nextPtr
= tmp
->nextPtr
;
167 if (index
< lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
-lPtr
->topItem
) {
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 */
179 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
188 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
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
202 lPtr
->selectedItem
++;
204 if (lPtr
->items
==NULL
) {
206 } else if (row
== 0) {
207 item
->nextPtr
= lPtr
->items
;
209 } else if (row
< 0) {
214 row
= lPtr
->itemCount
;
219 item
->nextPtr
= tmp
->nextPtr
;
223 if (row
< lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
-lPtr
->topItem
) {
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 */
232 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
240 WMRemoveListItem(WMList
*lPtr
, int row
)
244 int topItem
= lPtr
->topItem
;
247 CHECK_CLASS(lPtr
, WC_List
);
249 if (row
< 0 || row
>= lPtr
->itemCount
)
252 if (lPtr
->selectedItem
== row
) {
253 lPtr
->selectedItem
= -1;
255 } else if (lPtr
->selectedItem
> row
) {
256 lPtr
->selectedItem
--;
259 if (row
<= lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
)
261 if (lPtr
->topItem
< 0)
265 if (lPtr
->items
->text
)
266 free(lPtr
->items
->text
);
268 tmp
= lPtr
->items
->nextPtr
;
275 llist
= llist
->nextPtr
;
276 tmp
= llist
->nextPtr
;
277 llist
->nextPtr
= llist
->nextPtr
->nextPtr
;
288 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
290 if (lPtr
->topItem
!= topItem
)
291 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
293 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
294 (void*)((int)lPtr
->selectedItem
));
300 WMGetListItem(WMList
*lPtr
, int row
)
304 listPtr
= lPtr
->items
;
307 listPtr
= listPtr
->nextPtr
;
315 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
317 lPtr
->flags
.userDrawn
= 1;
323 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
327 lPtr
->flags
.userItemHeight
= 1;
328 lPtr
->itemHeight
= height
;
330 updateGeometry(lPtr
);
335 WMClearList(WMList
*lPtr
)
337 WMListItem
*item
, *tmp
;
338 int oldSelected
= lPtr
->selectedItem
;
350 lPtr
->selectedItem
= -1;
353 WMDeleteIdleHandler(lPtr
->idleID
);
356 if (lPtr
->view
->flags
.realized
) {
357 updateScroller(lPtr
);
359 if (oldSelected
!= -1)
360 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
366 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
368 lPtr
->action
= action
;
369 lPtr
->clientData
= clientData
;
374 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
376 lPtr
->doubleAction
= action
;
377 lPtr
->doubleClientData
= clientData
;
382 WMGetListSelectedItem(WMList
*lPtr
)
384 int i
= lPtr
->selectedItem
;
387 if (lPtr
->selectedItem
< 0)
392 item
= item
->nextPtr
;
399 WMGetListSelectedItemRow(WMList
*lPtr
)
401 return lPtr
->selectedItem
;
406 WMGetListItemHeight(WMList
*lPtr
)
408 return lPtr
->itemHeight
;
413 WMSetListPosition(WMList
*lPtr
, int row
)
416 if (lPtr
->topItem
+ lPtr
->fullFitLines
> lPtr
->itemCount
)
417 lPtr
->topItem
= lPtr
->itemCount
- lPtr
->fullFitLines
;
419 if (lPtr
->topItem
< 0)
422 if (lPtr
->view
->flags
.realized
)
423 updateScroller(lPtr
);
428 WMSetListBottomPosition(WMList
*lPtr
, int row
)
430 if (lPtr
->itemCount
> lPtr
->fullFitLines
) {
431 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
432 if (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
;
454 vScrollCallBack(WMWidget
*scroller
, void *self
)
456 WMList
*lPtr
= (WMList
*)self
;
457 WMScroller
*sPtr
= (WMScroller
*)scroller
;
459 int topItem
= lPtr
->topItem
;
461 height
= lPtr
->view
->size
.height
- 4;
463 switch (WMGetScrollerHitPart(sPtr
)) {
464 case WSDecrementLine
:
465 if (lPtr
->topItem
> 0) {
468 updateScroller(lPtr
);
472 case WSDecrementPage
:
473 if (lPtr
->topItem
> 0) {
474 lPtr
->topItem
-= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
475 if (lPtr
->topItem
< 0)
478 updateScroller(lPtr
);
483 case WSIncrementLine
:
484 if (lPtr
->topItem
+ lPtr
->fullFitLines
< lPtr
->itemCount
) {
487 updateScroller(lPtr
);
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
);
504 int oldTopItem
= lPtr
->topItem
;
506 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
507 (float)(lPtr
->itemCount
- lPtr
->fullFitLines
);
509 if (oldTopItem
!= lPtr
->topItem
)
520 if (lPtr
->topItem
!= topItem
)
521 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
526 paintItem(List
*lPtr
, int index
)
528 WMView
*view
= lPtr
->view
;
529 W_Screen
*scr
= view
->screen
;
530 int width
, height
, x
, y
;
535 itemPtr
= lPtr
->items
;
537 itemPtr
= itemPtr
->nextPtr
;
539 width
= lPtr
->view
->size
.width
- 2 - 19;
540 height
= lPtr
->itemHeight
;
542 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
544 if (lPtr
->flags
.userDrawn
) {
548 rect
.size
.width
= width
;
549 rect
.size
.height
= height
;
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
;
562 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
565 if (itemPtr
->selected
)
566 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
), x
, y
,
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
));
580 paintList(List
*lPtr
)
582 W_Screen
*scrPtr
= lPtr
->view
->screen
;
585 if (!lPtr
->view
->flags
.mapped
)
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
);
595 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
597 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
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
);
609 scrollTo(List
*lPtr
, int newTop
)
616 updateScroller(List
*lPtr
)
618 float knobProportion
, floatValue
, tmp
;
621 WMDeleteIdleHandler(lPtr
->idleID
);
626 if (lPtr
->itemCount
== 0 || lPtr
->itemCount
<= lPtr
->fullFitLines
)
627 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
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
);
640 handleEvents(XEvent
*event
, void *data
)
642 List
*lPtr
= (List
*)data
;
644 CHECK_CLASS(data
, WC_List
);
647 switch (event
->type
) {
649 if (event
->xexpose
.count
!=0)
663 WMFindRowOfListItemWithTitle(WMList
*lPtr
, char *title
)
669 for (i
=0, item
=lPtr
->items
; item
!=NULL
; item
=item
->nextPtr
, i
++) {
670 if (strcmp(item
->text
, title
)==0) {
681 WMSelectListItem(WMList
*lPtr
, int row
)
686 if (row
>= lPtr
->itemCount
)
689 /* the check below must be changed when the multiple selection is
692 if (!lPtr
->flags
.allowMultipleSelection
&& row
== lPtr
->selectedItem
)
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
)
714 if (!lPtr
->flags
.allowMultipleSelection
) {
715 lPtr
->selectedItem
= -1;
717 WMPostNotificationName(WMListSelectionDidChangeNotification
,
718 lPtr
, (void*)((int)lPtr
->selectedItem
));
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
;
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
,
741 lPtr
->selectedItem
= row
;
743 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
744 (void*)((int)lPtr
->selectedItem
));
749 getItemIndexAt(List
*lPtr
, int clickY
)
753 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
755 if (index
< 0 || index
>= lPtr
->itemCount
)
763 handleActionEvents(XEvent
*event
, void *data
)
765 List
*lPtr
= (List
*)data
;
767 int topItem
= lPtr
->topItem
;
769 CHECK_CLASS(data
, WC_List
);
771 switch (event
->type
) {
773 lPtr
->flags
.buttonPressed
= 0;
774 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
776 if (tmp
== lPtr
->selectedItem
&& tmp
>= 0) {
778 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
783 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
784 lPtr
->flags
.buttonWasPressed
= 0;
788 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
789 lPtr
->flags
.buttonPressed
= 0;
793 if (event
->xbutton
.x
> WMWidgetWidth(lPtr
->vScroller
)) {
794 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
795 lPtr
->flags
.buttonPressed
= 1;
798 if (tmp
== lPtr
->selectedItem
&& WMIsDoubleClick(event
)) {
799 WMSelectListItem(lPtr
, tmp
);
800 if (lPtr
->doubleAction
)
801 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
803 WMSelectListItem(lPtr
, tmp
);
810 if (lPtr
->flags
.buttonPressed
) {
811 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
812 if (tmp
>=0 && tmp
!= lPtr
->selectedItem
) {
813 WMSelectListItem(lPtr
, tmp
);
818 if (lPtr
->topItem
!= topItem
)
819 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
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;
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)
839 updateScroller(lPtr
);
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
);
855 destroyList(List
*lPtr
)
860 WMDeleteIdleHandler(lPtr
->idleID
);
863 while (lPtr
->items
!=NULL
) {
864 itemPtr
= lPtr
->items
;
868 lPtr
->items
= itemPtr
->nextPtr
;