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
;
169 if (lPtr
->selectedItem
>= index
)
170 lPtr
->selectedItem
++;
172 /* update the scroller when idle, so that we don't waste time
173 * updating it when another item is going to be added later */
175 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
184 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
187 WMListItem
*tmp
= lPtr
->items
;
189 CHECK_CLASS(lPtr
, WC_List
);
191 item
= wmalloc(sizeof(WMListItem
));
192 memset(item
, 0, sizeof(WMListItem
));
193 item
->text
= wstrdup(text
);
196 if (lPtr
->selectedItem
>= row
&& lPtr
->selectedItem
>= 0
198 lPtr
->selectedItem
++;
200 if (lPtr
->items
==NULL
) {
202 } else if (row
== 0) {
203 item
->nextPtr
= lPtr
->items
;
205 } else if (row
< 0) {
210 row
= lPtr
->itemCount
;
215 item
->nextPtr
= tmp
->nextPtr
;
221 /* update the scroller when idle, so that we don't waste time
222 * updating it when another item is going to be added later */
224 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
232 WMRemoveListItem(WMList
*lPtr
, int row
)
236 int topItem
= lPtr
->topItem
;
239 CHECK_CLASS(lPtr
, WC_List
);
241 if (row
< 0 || row
>= lPtr
->itemCount
)
244 if (lPtr
->selectedItem
== row
) {
245 lPtr
->selectedItem
= -1;
247 } else if (lPtr
->selectedItem
> row
) {
248 lPtr
->selectedItem
--;
251 if (row
<= lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
)
253 if (lPtr
->topItem
< 0)
257 if (lPtr
->items
->text
)
258 free(lPtr
->items
->text
);
260 tmp
= lPtr
->items
->nextPtr
;
267 llist
= llist
->nextPtr
;
268 tmp
= llist
->nextPtr
;
269 llist
->nextPtr
= llist
->nextPtr
->nextPtr
;
280 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
282 if (lPtr
->topItem
!= topItem
)
283 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
285 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
286 (void*)((int)lPtr
->selectedItem
));
292 WMGetListItem(WMList
*lPtr
, int row
)
296 listPtr
= lPtr
->items
;
299 listPtr
= listPtr
->nextPtr
;
307 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
309 lPtr
->flags
.userDrawn
= 1;
315 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
319 lPtr
->flags
.userItemHeight
= 1;
320 lPtr
->itemHeight
= height
;
322 updateGeometry(lPtr
);
327 WMClearList(WMList
*lPtr
)
329 WMListItem
*item
, *tmp
;
330 int oldSelected
= lPtr
->selectedItem
;
342 lPtr
->selectedItem
= -1;
345 WMDeleteIdleHandler(lPtr
->idleID
);
348 if (lPtr
->view
->flags
.realized
) {
349 updateScroller(lPtr
);
351 if (oldSelected
!= -1)
352 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
358 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
360 lPtr
->action
= action
;
361 lPtr
->clientData
= clientData
;
366 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
368 lPtr
->doubleAction
= action
;
369 lPtr
->doubleClientData
= clientData
;
374 WMGetListSelectedItem(WMList
*lPtr
)
376 int i
= lPtr
->selectedItem
;
379 if (lPtr
->selectedItem
< 0)
384 item
= item
->nextPtr
;
391 WMGetListSelectedItemRow(WMList
*lPtr
)
393 return lPtr
->selectedItem
;
398 WMGetListItemHeight(WMList
*lPtr
)
400 return lPtr
->itemHeight
;
405 WMSetListPosition(WMList
*lPtr
, int row
)
408 if (lPtr
->topItem
+ lPtr
->fullFitLines
> lPtr
->itemCount
)
409 lPtr
->topItem
= lPtr
->itemCount
- lPtr
->fullFitLines
;
411 if (lPtr
->topItem
< 0)
414 if (lPtr
->view
->flags
.realized
)
415 updateScroller(lPtr
);
420 WMSetListBottomPosition(WMList
*lPtr
, int row
)
422 if (lPtr
->itemCount
> lPtr
->fullFitLines
) {
423 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
424 if (lPtr
->topItem
< 0)
426 if (lPtr
->view
->flags
.realized
)
427 updateScroller(lPtr
);
433 WMGetListNumberOfRows(WMList
*lPtr
)
435 return lPtr
->itemCount
;
439 WMGetListPosition(WMList
*lPtr
)
441 return lPtr
->topItem
;
446 vScrollCallBack(WMWidget
*scroller
, void *self
)
448 WMList
*lPtr
= (WMList
*)self
;
449 WMScroller
*sPtr
= (WMScroller
*)scroller
;
451 int topItem
= lPtr
->topItem
;
453 height
= lPtr
->view
->size
.height
- 4;
455 switch (WMGetScrollerHitPart(sPtr
)) {
456 case WSDecrementLine
:
457 if (lPtr
->topItem
> 0) {
460 updateScroller(lPtr
);
464 case WSDecrementPage
:
465 if (lPtr
->topItem
> 0) {
466 lPtr
->topItem
-= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
467 if (lPtr
->topItem
< 0)
470 updateScroller(lPtr
);
475 case WSIncrementLine
:
476 if (lPtr
->topItem
+ lPtr
->fullFitLines
< lPtr
->itemCount
) {
479 updateScroller(lPtr
);
483 case WSIncrementPage
:
484 if (lPtr
->topItem
+ lPtr
->fullFitLines
< lPtr
->itemCount
) {
485 lPtr
->topItem
+= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
487 if (lPtr
->topItem
+ lPtr
->fullFitLines
> lPtr
->itemCount
)
488 lPtr
->topItem
= lPtr
->itemCount
- lPtr
->fullFitLines
;
490 updateScroller(lPtr
);
496 int oldTopItem
= lPtr
->topItem
;
498 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
499 (float)(lPtr
->itemCount
- lPtr
->fullFitLines
);
501 if (oldTopItem
!= lPtr
->topItem
)
512 if (lPtr
->topItem
!= topItem
)
513 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
518 paintItem(List
*lPtr
, int index
)
520 WMView
*view
= lPtr
->view
;
521 W_Screen
*scr
= view
->screen
;
522 int width
, height
, x
, y
;
527 itemPtr
= lPtr
->items
;
529 itemPtr
= itemPtr
->nextPtr
;
531 width
= lPtr
->view
->size
.width
- 2 - 19;
532 height
= lPtr
->itemHeight
;
534 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
536 if (lPtr
->flags
.userDrawn
) {
540 rect
.size
.width
= width
;
541 rect
.size
.height
= height
;
545 flags
= itemPtr
->uflags
;
546 if (itemPtr
->disabled
)
547 flags
|= WLDSDisabled
;
548 if (itemPtr
->selected
)
549 flags
|= WLDSSelected
;
550 if (itemPtr
->isBranch
)
551 flags
|= WLDSIsBranch
;
554 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
557 if (itemPtr
->selected
)
558 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
), x
, y
,
561 XClearArea(scr
->display
, view
->window
, x
, y
, width
, height
, False
);
563 W_PaintText(view
, view
->window
, scr
->normalFont
, x
+4, y
, width
,
564 WALeft
, WMColorGC(scr
->black
), False
,
565 itemPtr
->text
, strlen(itemPtr
->text
));
572 paintList(List
*lPtr
)
574 W_Screen
*scrPtr
= lPtr
->view
->screen
;
577 if (!lPtr
->view
->flags
.mapped
)
580 if (lPtr
->itemCount
>0) {
581 if (lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
> lPtr
->itemCount
) {
582 lim
= lPtr
->itemCount
- lPtr
->topItem
;
583 XClearArea(scrPtr
->display
, lPtr
->view
->window
, 19,
584 2+lim
*lPtr
->itemHeight
, lPtr
->view
->size
.width
-21,
585 lPtr
->view
->size
.height
-lim
*lPtr
->itemHeight
-3, False
);
587 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
589 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
593 XClearWindow(scrPtr
->display
, lPtr
->view
->window
);
595 W_DrawRelief(scrPtr
, lPtr
->view
->window
, 0, 0, lPtr
->view
->size
.width
,
596 lPtr
->view
->size
.height
, WRSunken
);
601 scrollTo(List
*lPtr
, int newTop
)
608 updateScroller(List
*lPtr
)
610 float knobProportion
, floatValue
, tmp
;
613 WMDeleteIdleHandler(lPtr
->idleID
);
618 if (lPtr
->itemCount
== 0 || lPtr
->itemCount
<= lPtr
->fullFitLines
)
619 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
621 tmp
= lPtr
->fullFitLines
;
622 knobProportion
= tmp
/(float)lPtr
->itemCount
;
624 floatValue
= (float)lPtr
->topItem
/(float)(lPtr
->itemCount
- lPtr
->fullFitLines
);
626 WMSetScrollerParameters(lPtr
->vScroller
, floatValue
, knobProportion
);
632 handleEvents(XEvent
*event
, void *data
)
634 List
*lPtr
= (List
*)data
;
636 CHECK_CLASS(data
, WC_List
);
639 switch (event
->type
) {
641 if (event
->xexpose
.count
!=0)
655 WMFindRowOfListItemWithTitle(WMList
*lPtr
, char *title
)
661 for (i
=0, item
=lPtr
->items
; item
!=NULL
; item
=item
->nextPtr
, i
++) {
662 if (strcmp(item
->text
, title
)==0) {
673 WMSelectListItem(WMList
*lPtr
, int row
)
678 if (row
>= lPtr
->itemCount
)
681 /* the check below must be changed when the multiple selection is
684 if (!lPtr
->flags
.allowMultipleSelection
&& row
== lPtr
->selectedItem
)
689 if (!lPtr
->flags
.allowMultipleSelection
) {
690 /* unselect previous selected item */
691 if (lPtr
->selectedItem
>= 0) {
692 itemPtr
= lPtr
->items
;
693 for (i
=0; i
<lPtr
->selectedItem
; i
++)
694 itemPtr
= itemPtr
->nextPtr
;
696 if (itemPtr
->selected
) {
697 itemPtr
->selected
= 0;
698 if (lPtr
->view
->flags
.mapped
&& i
>=lPtr
->topItem
699 && i
<=lPtr
->topItem
+lPtr
->fullFitLines
)
706 if (!lPtr
->flags
.allowMultipleSelection
) {
707 lPtr
->selectedItem
= -1;
709 WMPostNotificationName(WMListSelectionDidChangeNotification
,
710 lPtr
, (void*)((int)lPtr
->selectedItem
));
716 itemPtr
= lPtr
->items
;
717 for (i
=0; i
<row
; i
++)
718 itemPtr
= itemPtr
->nextPtr
;
719 if (lPtr
->flags
.allowMultipleSelection
)
720 itemPtr
->selected
= !itemPtr
->selected
;
722 itemPtr
->selected
= 1;
724 if (lPtr
->view
->flags
.mapped
) {
725 paintItem(lPtr
, row
);
727 if ((row
-lPtr
->topItem
+lPtr
->fullFitLines
)*lPtr
->itemHeight
728 > lPtr
->view
->size
.height
-2)
729 W_DrawRelief(lPtr
->view
->screen
, lPtr
->view
->window
, 0, 0,
730 lPtr
->view
->size
.width
, lPtr
->view
->size
.height
,
733 lPtr
->selectedItem
= row
;
735 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
736 (void*)((int)lPtr
->selectedItem
));
741 getItemIndexAt(List
*lPtr
, int clickY
)
745 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
747 if (index
< 0 || index
>= lPtr
->itemCount
)
755 handleActionEvents(XEvent
*event
, void *data
)
757 List
*lPtr
= (List
*)data
;
759 int topItem
= lPtr
->topItem
;
761 CHECK_CLASS(data
, WC_List
);
763 switch (event
->type
) {
765 lPtr
->flags
.buttonPressed
= 0;
766 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
768 if (tmp
== lPtr
->selectedItem
&& tmp
>= 0) {
770 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
775 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
776 lPtr
->flags
.buttonWasPressed
= 0;
780 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
781 lPtr
->flags
.buttonPressed
= 0;
785 if (event
->xbutton
.x
> WMWidgetWidth(lPtr
->vScroller
)) {
786 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
787 lPtr
->flags
.buttonPressed
= 1;
790 if (tmp
== lPtr
->selectedItem
&& WMIsDoubleClick(event
)) {
791 WMSelectListItem(lPtr
, tmp
);
792 if (lPtr
->doubleAction
)
793 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
795 WMSelectListItem(lPtr
, tmp
);
802 if (lPtr
->flags
.buttonPressed
) {
803 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
804 if (tmp
>=0 && tmp
!= lPtr
->selectedItem
) {
805 WMSelectListItem(lPtr
, tmp
);
810 if (lPtr
->topItem
!= topItem
)
811 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
816 updateGeometry(WMList
*lPtr
)
818 lPtr
->fullFitLines
= (lPtr
->view
->size
.height
- 4) / lPtr
->itemHeight
;
819 if (lPtr
->fullFitLines
* lPtr
->itemHeight
< lPtr
->view
->size
.height
- 4) {
820 lPtr
->flags
.dontFitAll
= 1;
822 lPtr
->flags
.dontFitAll
= 0;
825 if (lPtr
->itemCount
- lPtr
->topItem
<= lPtr
->fullFitLines
) {
826 lPtr
->topItem
= lPtr
->itemCount
- lPtr
->fullFitLines
;
827 if (lPtr
->topItem
< 0)
831 updateScroller(lPtr
);
836 didResizeList(W_ViewDelegate
*self
, WMView
*view
)
838 WMList
*lPtr
= (WMList
*)view
->self
;
840 WMResizeWidget(lPtr
->vScroller
, 1, view
->size
.height
-2);
842 updateGeometry(lPtr
);
847 destroyList(List
*lPtr
)
852 WMDeleteIdleHandler(lPtr
->idleID
);
855 while (lPtr
->items
!=NULL
) {
856 itemPtr
= lPtr
->items
;
860 lPtr
->items
= itemPtr
->nextPtr
;