7 char *WMListDidScrollNotification
= "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification
= "WMListSelectionDidChangeNotification";
10 typedef struct W_List
{
14 WMArray
*items
; /* list of WMListItem */
15 WMArray
*selectedItems
; /* list of selected WMListItems */
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 adding elements */
34 WMScroller
*vScroller
;
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;
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
= {
78 releaseItem(void *data
)
80 WMListItem
*item
= (WMListItem
*)data
;
89 WMCreateList(WMWidget
*parent
)
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
));
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;
138 comparator(const void *a
, const void *b
)
140 return (strcmp((*(WMListItem
**)a
)->text
, (*(WMListItem
**)b
)->text
));
145 WMSortListItems(WMList
*lPtr
)
147 WMSortArray(lPtr
->items
, comparator
);
155 WMSortListItemsWithComparer(WMList
*lPtr
, int (f
)(const void*, const void*))
157 WMSortArray(lPtr
->items
, f
);
165 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
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
178 lPtr
->selectedItem
++;
180 row
= WMIN(row
, WMGetArrayItemCount(lPtr
->items
));
183 WMAddToArray(lPtr
->items
, item
);
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 */
190 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
198 WMRemoveListItem(WMList
*lPtr
, int row
)
200 int topItem
= lPtr
->topItem
;
203 CHECK_CLASS(lPtr
, WC_List
);
205 if (row
< 0 || row
>= WMGetArrayItemCount(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 WMDeleteFromArray(lPtr
->items
, row
);
223 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
225 if (lPtr
->topItem
!= topItem
)
226 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
228 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
229 (void*)((int)lPtr
->selectedItem
));
235 WMGetListItem(WMList
*lPtr
, int row
)
237 return WMGetFromArray(lPtr
->items
, row
);
242 WMGetListItems(WMList
*lPtr
)
250 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
252 lPtr
->flags
.userDrawn
= 1;
258 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
262 lPtr
->flags
.userItemHeight
= 1;
263 lPtr
->itemHeight
= height
;
265 updateGeometry(lPtr
);
270 WMClearList(WMList
*lPtr
)
272 int oldSelected
= lPtr
->selectedItem
;
275 WMEmptyArray(lPtr
->items
);
278 lPtr
->selectedItem
= -1;
281 WMDeleteIdleHandler(lPtr
->idleID
);
284 if (lPtr
->view
->flags
.realized
) {
285 updateScroller(lPtr
);
287 if (oldSelected
!= -1)
288 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
294 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
296 lPtr
->action
= action
;
297 lPtr
->clientData
= clientData
;
302 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
304 lPtr
->doubleAction
= action
;
305 lPtr
->doubleClientData
= clientData
;
310 WMGetListSelectedItem(WMList
*lPtr
)
312 if (lPtr
->selectedItem
< 0)
315 return WMGetFromArray(lPtr
->items
, lPtr
->selectedItem
);
320 WMGetListSelectedItemRow(WMList
*lPtr
)
322 return lPtr
->selectedItem
;
327 WMGetListItemHeight(WMList
*lPtr
)
329 return lPtr
->itemHeight
;
334 WMSetListPosition(WMList
*lPtr
, int row
)
337 if (lPtr
->topItem
+ lPtr
->fullFitLines
> WMGetArrayItemCount(lPtr
->items
))
338 lPtr
->topItem
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
340 if (lPtr
->topItem
< 0)
343 if (lPtr
->view
->flags
.realized
)
344 updateScroller(lPtr
);
349 WMSetListBottomPosition(WMList
*lPtr
, int row
)
351 if (WMGetArrayItemCount(lPtr
->items
) > lPtr
->fullFitLines
) {
352 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
353 if (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
;
375 vScrollCallBack(WMWidget
*scroller
, void *self
)
377 WMList
*lPtr
= (WMList
*)self
;
378 WMScroller
*sPtr
= (WMScroller
*)scroller
;
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) {
390 updateScroller(lPtr
);
394 case WSDecrementPage
:
395 if (lPtr
->topItem
> 0) {
396 lPtr
->topItem
-= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
397 if (lPtr
->topItem
< 0)
400 updateScroller(lPtr
);
405 case WSIncrementLine
:
406 if (lPtr
->topItem
+ lPtr
->fullFitLines
< itemCount
) {
409 updateScroller(lPtr
);
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
);
426 int oldTopItem
= lPtr
->topItem
;
428 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
429 (float)(itemCount
- lPtr
->fullFitLines
);
431 if (oldTopItem
!= lPtr
->topItem
)
442 if (lPtr
->topItem
!= topItem
)
443 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
448 paintItem(List
*lPtr
, int index
)
450 WMView
*view
= lPtr
->view
;
451 W_Screen
*scr
= view
->screen
;
452 int width
, height
, x
, y
;
455 itemPtr
= WMGetFromArray(lPtr
->items
, index
);
457 width
= lPtr
->view
->size
.width
- 2 - 19;
458 height
= lPtr
->itemHeight
;
460 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
462 if (lPtr
->flags
.userDrawn
) {
466 rect
.size
.width
= width
;
467 rect
.size
.height
= height
;
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
;
480 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
483 if (itemPtr
->selected
)
484 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
),
485 x
, y
, width
, height
);
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
));
498 paintList(List
*lPtr
)
500 W_Screen
*scrPtr
= lPtr
->view
->screen
;
503 if (!lPtr
->view
->flags
.mapped
)
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
);
515 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
517 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
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
);
529 scrollTo(List
*lPtr
, int newTop
)
536 updateScroller(List
*lPtr
)
538 float knobProportion
, floatValue
, tmp
;
539 int count
= WMGetArrayItemCount(lPtr
->items
);
542 WMDeleteIdleHandler(lPtr
->idleID
);
547 if (count
== 0 || count
<= lPtr
->fullFitLines
)
548 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
550 tmp
= lPtr
->fullFitLines
;
551 knobProportion
= tmp
/(float)count
;
553 floatValue
= (float)lPtr
->topItem
/(float)(count
- lPtr
->fullFitLines
);
555 WMSetScrollerParameters(lPtr
->vScroller
, floatValue
, knobProportion
);
561 handleEvents(XEvent
*event
, void *data
)
563 List
*lPtr
= (List
*)data
;
565 CHECK_CLASS(data
, WC_List
);
568 switch (event
->type
) {
570 if (event
->xexpose
.count
!=0)
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
);
598 WMSelectListItem(WMList
*lPtr
, int row
)
603 if (row
>= WMGetArrayItemCount(lPtr
->items
))
606 /* the check below must be changed when the multiple selection is
609 if (!lPtr
->flags
.allowMultipleSelection
&& row
== lPtr
->selectedItem
)
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
);
633 if (!lPtr
->flags
.allowMultipleSelection
) {
634 lPtr
->selectedItem
= -1;
636 WMPostNotificationName(WMListSelectionDidChangeNotification
,
637 lPtr
, (void*)((int)lPtr
->selectedItem
));
643 itemPtr
= WMGetFromArray(lPtr
->items
, row
);
645 if (lPtr
->flags
.allowMultipleSelection
)
646 itemPtr
->selected
= !itemPtr
->selected
;
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
,
659 lPtr
->selectedItem
= row
;
661 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
,
662 (void*)((int)lPtr
->selectedItem
));
667 getItemIndexAt(List
*lPtr
, int clickY
)
671 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
673 if (index
< 0 || index
>= WMGetArrayItemCount(lPtr
->items
))
681 handleActionEvents(XEvent
*event
, void *data
)
683 List
*lPtr
= (List
*)data
;
685 int topItem
= lPtr
->topItem
;
687 CHECK_CLASS(data
, WC_List
);
689 switch (event
->type
) {
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
)
699 lPtr
->flags
.buttonPressed
= 0;
700 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
702 if (tmp
== lPtr
->selectedItem
&& tmp
>= 0) {
704 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
709 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
710 lPtr
->flags
.buttonWasPressed
= 0;
714 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
715 lPtr
->flags
.buttonPressed
= 0;
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
) {
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
);
744 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
746 if (lPtr
->topItem
> 0) {
747 int decr
= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
748 lPtr
->topItem
-= decr
;
750 if (lPtr
->topItem
< 0)
753 updateScroller(lPtr
);
759 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
760 lPtr
->flags
.buttonPressed
= 1;
763 if (tmp
== lPtr
->selectedItem
&& WMIsDoubleClick(event
)) {
764 WMSelectListItem(lPtr
, tmp
);
765 if (lPtr
->doubleAction
)
766 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
768 WMSelectListItem(lPtr
, tmp
);
775 if (lPtr
->flags
.buttonPressed
) {
776 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
777 if (tmp
>=0 && tmp
!= lPtr
->selectedItem
) {
778 WMSelectListItem(lPtr
, tmp
);
783 if (lPtr
->topItem
!= topItem
)
784 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
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;
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)
804 updateScroller(lPtr
);
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
);
820 destroyList(List
*lPtr
)
823 WMDeleteIdleHandler(lPtr
->idleID
);
826 WMFreeArray(lPtr
->items
);