7 char *WMListDidScrollNotification
= "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification
= "WMListSelectionDidChangeNotification";
10 typedef struct W_List
{
14 WMBag
*items
; /* list of WMListItem */
20 short topItem
; /* index of first visible item */
22 short fullFitLines
; /* no of lines that fit entirely */
26 void *doubleClientData
;
27 WMAction
*doubleAction
;
31 WMHandlerID
*idleID
; /* for updating the scroller after
34 WMScroller
*vScroller
;
36 WMScroller *hScroller;
40 unsigned int allowMultipleSelection
:1;
41 unsigned int userDrawn
:1;
42 unsigned int userItemHeight
:1;
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;
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
= {
82 WMCreateList(WMWidget
*parent
)
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
));
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;
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)
138 else if (strcmp(item1
->text
, item2
->text
) > 0)
146 WMSortListItems(WMList
*lPtr
)
148 WMSortBag(lPtr
->items
, comparator
);
156 WMSortListItemsWithComparer(WMList
*lPtr
, int (f
)(const void*, const void*))
158 WMSortBag(lPtr
->items
, f
);
166 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
170 CHECK_CLASS(lPtr
, WC_List
);
172 item
= wmalloc(sizeof(WMListItem
));
173 memset(item
, 0, sizeof(WMListItem
));
174 item
->text
= wstrdup(text
);
177 if (lPtr
->selectedItem
>= row
&& lPtr
->selectedItem
>= 0
179 lPtr
->selectedItem
++;
182 row
= WMGetBagItemCount(lPtr
->items
);
184 WMInsertInBag(lPtr
->items
, row
, item
);
186 /* update the scroller when idle, so that we don't waste time
187 * updating it when another item is going to be added later */
189 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
197 WMRemoveListItem(WMList
*lPtr
, int row
)
200 int topItem
= lPtr
->topItem
;
203 CHECK_CLASS(lPtr
, WC_List
);
205 if (row
< 0 || row
>= WMGetBagItemCount(lPtr
->items
))
208 if (lPtr
->selectedItem
== row
) {
209 lPtr
->selectedItem
= -1;
211 } else if (lPtr
->selectedItem
> row
) {
212 lPtr
->selectedItem
--;
215 if (row
<= lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
)
217 if (lPtr
->topItem
< 0)
220 item
= WMGetFromBag(lPtr
->items
, row
);
225 WMDeleteFromBag(lPtr
->items
, row
);
228 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
230 if (lPtr
->topItem
!= topItem
)
231 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
233 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
234 (void*)((int)lPtr
->selectedItem
));
240 WMGetListItem(WMList
*lPtr
, int row
)
242 return WMGetFromBag(lPtr
->items
, row
);
247 WMGetListItems(WMList
*lPtr
)
255 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
257 lPtr
->flags
.userDrawn
= 1;
263 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
267 lPtr
->flags
.userItemHeight
= 1;
268 lPtr
->itemHeight
= height
;
270 updateGeometry(lPtr
);
275 WMClearList(WMList
*lPtr
)
278 int oldSelected
= lPtr
->selectedItem
;
281 for (i
= 0; i
< WMGetBagItemCount(lPtr
->items
); i
++) {
282 item
= WMGetFromBag(lPtr
->items
, i
);
286 WMEmptyBag(lPtr
->items
);
289 lPtr
->selectedItem
= -1;
292 WMDeleteIdleHandler(lPtr
->idleID
);
295 if (lPtr
->view
->flags
.realized
) {
296 updateScroller(lPtr
);
298 if (oldSelected
!= -1)
299 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
305 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
307 lPtr
->action
= action
;
308 lPtr
->clientData
= clientData
;
313 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
315 lPtr
->doubleAction
= action
;
316 lPtr
->doubleClientData
= clientData
;
321 WMGetListSelectedItem(WMList
*lPtr
)
323 if (lPtr
->selectedItem
< 0)
326 return WMGetFromBag(lPtr
->items
, lPtr
->selectedItem
);
331 WMGetListSelectedItemRow(WMList
*lPtr
)
333 return lPtr
->selectedItem
;
338 WMGetListItemHeight(WMList
*lPtr
)
340 return lPtr
->itemHeight
;
345 WMSetListPosition(WMList
*lPtr
, int row
)
348 if (lPtr
->topItem
+ lPtr
->fullFitLines
> WMGetBagItemCount(lPtr
->items
))
349 lPtr
->topItem
= WMGetBagItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
351 if (lPtr
->topItem
< 0)
354 if (lPtr
->view
->flags
.realized
)
355 updateScroller(lPtr
);
360 WMSetListBottomPosition(WMList
*lPtr
, int row
)
362 if (WMGetBagItemCount(lPtr
->items
) > lPtr
->fullFitLines
) {
363 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
364 if (lPtr
->topItem
< 0)
366 if (lPtr
->view
->flags
.realized
)
367 updateScroller(lPtr
);
373 WMGetListNumberOfRows(WMList
*lPtr
)
375 return WMGetBagItemCount(lPtr
->items
);
379 WMGetListPosition(WMList
*lPtr
)
381 return lPtr
->topItem
;
386 vScrollCallBack(WMWidget
*scroller
, void *self
)
388 WMList
*lPtr
= (WMList
*)self
;
389 WMScroller
*sPtr
= (WMScroller
*)scroller
;
391 int topItem
= lPtr
->topItem
;
392 int itemCount
= WMGetBagItemCount(lPtr
->items
);
394 height
= lPtr
->view
->size
.height
- 4;
396 switch (WMGetScrollerHitPart(sPtr
)) {
397 case WSDecrementLine
:
398 if (lPtr
->topItem
> 0) {
401 updateScroller(lPtr
);
405 case WSDecrementPage
:
406 if (lPtr
->topItem
> 0) {
407 lPtr
->topItem
-= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
408 if (lPtr
->topItem
< 0)
411 updateScroller(lPtr
);
416 case WSIncrementLine
:
417 if (lPtr
->topItem
+ lPtr
->fullFitLines
< itemCount
) {
420 updateScroller(lPtr
);
424 case WSIncrementPage
:
425 if (lPtr
->topItem
+ lPtr
->fullFitLines
< itemCount
) {
426 lPtr
->topItem
+= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
428 if (lPtr
->topItem
+ lPtr
->fullFitLines
> itemCount
)
429 lPtr
->topItem
= itemCount
- lPtr
->fullFitLines
;
431 updateScroller(lPtr
);
437 int oldTopItem
= lPtr
->topItem
;
439 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
440 (float)(itemCount
- lPtr
->fullFitLines
);
442 if (oldTopItem
!= lPtr
->topItem
)
453 if (lPtr
->topItem
!= topItem
)
454 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
459 paintItem(List
*lPtr
, int index
)
461 WMView
*view
= lPtr
->view
;
462 W_Screen
*scr
= view
->screen
;
463 int width
, height
, x
, y
;
467 itemPtr
= WMGetFromBag(lPtr
->items
, index
);
470 width
= lPtr
->view
->size
.width
- 2 - 19;
471 height
= lPtr
->itemHeight
;
473 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
475 if (lPtr
->flags
.userDrawn
) {
479 rect
.size
.width
= width
;
480 rect
.size
.height
= height
;
484 flags
= itemPtr
->uflags
;
485 if (itemPtr
->disabled
)
486 flags
|= WLDSDisabled
;
487 if (itemPtr
->selected
)
488 flags
|= WLDSSelected
;
489 if (itemPtr
->isBranch
)
490 flags
|= WLDSIsBranch
;
493 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
496 if (itemPtr
->selected
)
497 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
), x
, y
,
500 XClearArea(scr
->display
, view
->window
, x
, y
, width
, height
, False
);
502 W_PaintText(view
, view
->window
, scr
->normalFont
, x
+4, y
, width
,
503 WALeft
, WMColorGC(scr
->black
), False
,
504 itemPtr
->text
, strlen(itemPtr
->text
));
511 paintList(List
*lPtr
)
513 W_Screen
*scrPtr
= lPtr
->view
->screen
;
516 if (!lPtr
->view
->flags
.mapped
)
519 if (WMGetBagItemCount(lPtr
->items
) > 0) {
520 if (lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
521 > WMGetBagItemCount(lPtr
->items
)) {
523 lim
= WMGetBagItemCount(lPtr
->items
) - lPtr
->topItem
;
524 XClearArea(scrPtr
->display
, lPtr
->view
->window
, 19,
525 2+lim
*lPtr
->itemHeight
, lPtr
->view
->size
.width
-21,
526 lPtr
->view
->size
.height
-lim
*lPtr
->itemHeight
-3, False
);
528 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
530 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
534 XClearWindow(scrPtr
->display
, lPtr
->view
->window
);
536 W_DrawRelief(scrPtr
, lPtr
->view
->window
, 0, 0, lPtr
->view
->size
.width
,
537 lPtr
->view
->size
.height
, WRSunken
);
542 scrollTo(List
*lPtr
, int newTop
)
549 updateScroller(List
*lPtr
)
551 float knobProportion
, floatValue
, tmp
;
552 int count
= WMGetBagItemCount(lPtr
->items
);
555 WMDeleteIdleHandler(lPtr
->idleID
);
560 if (count
== 0 || count
<= lPtr
->fullFitLines
)
561 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
563 tmp
= lPtr
->fullFitLines
;
564 knobProportion
= tmp
/(float)count
;
566 floatValue
= (float)lPtr
->topItem
/(float)(count
- lPtr
->fullFitLines
);
568 WMSetScrollerParameters(lPtr
->vScroller
, floatValue
, knobProportion
);
574 handleEvents(XEvent
*event
, void *data
)
576 List
*lPtr
= (List
*)data
;
578 CHECK_CLASS(data
, WC_List
);
581 switch (event
->type
) {
583 if (event
->xexpose
.count
!=0)
597 WMFindRowOfListItemWithTitle(WMList
*lPtr
, char *title
)
603 for (i
=0; i
< WMGetBagItemCount(lPtr
->items
); i
++) {
604 item
= WMGetFromBag(lPtr
->items
, i
);
605 if (strcmp(item
->text
, title
)==0) {
616 WMSelectListItem(WMList
*lPtr
, int row
)
621 if (row
>= WMGetBagItemCount(lPtr
->items
))
624 /* the check below must be changed when the multiple selection is
627 if (!lPtr
->flags
.allowMultipleSelection
&& row
== lPtr
->selectedItem
)
632 if (!lPtr
->flags
.allowMultipleSelection
) {
633 /* unselect previous selected item */
634 if (lPtr
->selectedItem
>= 0) {
635 itemPtr
= WMGetFromBag(lPtr
->items
, lPtr
->selectedItem
);
637 if (itemPtr
->selected
) {
638 itemPtr
->selected
= 0;
639 if (lPtr
->view
->flags
.mapped
640 && lPtr
->selectedItem
>=lPtr
->topItem
641 && lPtr
->selectedItem
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
642 paintItem(lPtr
, lPtr
->selectedItem
);
649 if (!lPtr
->flags
.allowMultipleSelection
) {
650 lPtr
->selectedItem
= -1;
652 WMPostNotificationName(WMListSelectionDidChangeNotification
,
653 lPtr
, (void*)((int)lPtr
->selectedItem
));
659 itemPtr
= WMGetFromBag(lPtr
->items
, row
);
661 if (lPtr
->flags
.allowMultipleSelection
)
662 itemPtr
->selected
= !itemPtr
->selected
;
664 itemPtr
->selected
= 1;
666 if (lPtr
->view
->flags
.mapped
) {
667 paintItem(lPtr
, row
);
669 if ((row
-lPtr
->topItem
+lPtr
->fullFitLines
)*lPtr
->itemHeight
670 > lPtr
->view
->size
.height
-2)
671 W_DrawRelief(lPtr
->view
->screen
, lPtr
->view
->window
, 0, 0,
672 lPtr
->view
->size
.width
, lPtr
->view
->size
.height
,
675 lPtr
->selectedItem
= row
;
677 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
678 (void*)((int)lPtr
->selectedItem
));
683 getItemIndexAt(List
*lPtr
, int clickY
)
687 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
689 if (index
< 0 || index
>= WMGetBagItemCount(lPtr
->items
))
697 handleActionEvents(XEvent
*event
, void *data
)
699 List
*lPtr
= (List
*)data
;
701 int topItem
= lPtr
->topItem
;
703 CHECK_CLASS(data
, WC_List
);
705 switch (event
->type
) {
707 lPtr
->flags
.buttonPressed
= 0;
708 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
710 if (tmp
== lPtr
->selectedItem
&& tmp
>= 0) {
712 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
717 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
718 lPtr
->flags
.buttonWasPressed
= 0;
722 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
723 lPtr
->flags
.buttonPressed
= 0;
727 if (event
->xbutton
.x
> WMWidgetWidth(lPtr
->vScroller
)) {
728 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
729 lPtr
->flags
.buttonPressed
= 1;
732 if (tmp
== lPtr
->selectedItem
&& WMIsDoubleClick(event
)) {
733 WMSelectListItem(lPtr
, tmp
);
734 if (lPtr
->doubleAction
)
735 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
737 WMSelectListItem(lPtr
, tmp
);
744 if (lPtr
->flags
.buttonPressed
) {
745 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
746 if (tmp
>=0 && tmp
!= lPtr
->selectedItem
) {
747 WMSelectListItem(lPtr
, tmp
);
752 if (lPtr
->topItem
!= topItem
)
753 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
758 updateGeometry(WMList
*lPtr
)
760 lPtr
->fullFitLines
= (lPtr
->view
->size
.height
- 4) / lPtr
->itemHeight
;
761 if (lPtr
->fullFitLines
* lPtr
->itemHeight
< lPtr
->view
->size
.height
- 4) {
762 lPtr
->flags
.dontFitAll
= 1;
764 lPtr
->flags
.dontFitAll
= 0;
767 if (WMGetBagItemCount(lPtr
->items
) - lPtr
->topItem
<= lPtr
->fullFitLines
) {
768 lPtr
->topItem
= WMGetBagItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
769 if (lPtr
->topItem
< 0)
773 updateScroller(lPtr
);
778 didResizeList(W_ViewDelegate
*self
, WMView
*view
)
780 WMList
*lPtr
= (WMList
*)view
->self
;
782 WMResizeWidget(lPtr
->vScroller
, 1, view
->size
.height
-2);
784 updateGeometry(lPtr
);
789 destroyList(List
*lPtr
)
795 WMDeleteIdleHandler(lPtr
->idleID
);
798 for (i
= 0; i
< WMGetBagItemCount(lPtr
->items
); i
++) {
799 item
= WMGetFromBag(lPtr
->items
, i
);
803 WMFreeBag(lPtr
->items
);