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
);
118 lPtr
->selectedItems
= WMCreateArray(4);
120 /* create the vertical scroller */
121 lPtr
->vScroller
= WMCreateScroller(lPtr
);
122 WMMoveWidget(lPtr
->vScroller
, 1, 1);
123 WMSetScrollerArrowsPosition(lPtr
->vScroller
, WSAMaxEnd
);
125 WMSetScrollerAction(lPtr
->vScroller
, vScrollCallBack
, lPtr
);
127 /* make the scroller map itself when it's realized */
128 WMMapWidget(lPtr
->vScroller
);
130 W_ResizeView(lPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
132 //lPtr->selectedItem = -1;
139 WMSetListAllowMultipleSelection(WMList
*lPtr
, Bool flag
)
141 lPtr
->flags
.allowMultipleSelection
= flag
? 1 : 0;
147 WMSetListAllowEmptySelection(WMList
*lPtr
, Bool flag
)
149 lPtr
->flags
.allowEmptySelection
= flag
? 1 : 0;
154 comparator(const void *a
, const void *b
)
156 return (strcmp((*(WMListItem
**)a
)->text
, (*(WMListItem
**)b
)->text
));
161 WMSortListItems(WMList
*lPtr
)
163 WMSortArray(lPtr
->items
, comparator
);
171 WMSortListItemsWithComparer(WMList
*lPtr
, WMCompareDataProc
*func
)
173 WMSortArray(lPtr
->items
, func
);
181 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
185 CHECK_CLASS(lPtr
, WC_List
);
187 item
= wmalloc(sizeof(WMListItem
));
188 memset(item
, 0, sizeof(WMListItem
));
189 item
->text
= wstrdup(text
);
192 //if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
194 // lPtr->selectedItem++;
196 row
= WMIN(row
, WMGetArrayItemCount(lPtr
->items
));
199 WMAddToArray(lPtr
->items
, item
);
201 WMInsertInArray(lPtr
->items
, row
, item
);
203 /* update the scroller when idle, so that we don't waste time
204 * updating it when another item is going to be added later */
206 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
214 WMRemoveListItem(WMList
*lPtr
, int row
)
217 int topItem
= lPtr
->topItem
;
220 CHECK_CLASS(lPtr
, WC_List
);
222 if (row
< 0 || row
>= WMGetArrayItemCount(lPtr
->items
))
225 item
= WMGetFromArray(lPtr
->items
, row
);
226 if (item
->selected
) {
227 WMRemoveFromArray(lPtr
->selectedItems
, item
);
228 //WMUnselectListItem(lPtr, row);
232 //if (lPtr->selectedItem == row) {
233 // lPtr->selectedItem = -1;
235 //} else if (lPtr->selectedItem > row) {
236 // lPtr->selectedItem--;
239 if (row
<= lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
)
241 if (lPtr
->topItem
< 0)
244 WMDeleteFromArray(lPtr
->items
, row
);
247 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
249 if (lPtr
->topItem
!= topItem
) {
250 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
253 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
261 WMGetListItem(WMList
*lPtr
, int row
)
263 return WMGetFromArray(lPtr
->items
, row
);
268 WMGetListItems(WMList
*lPtr
)
275 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
277 lPtr
->flags
.userDrawn
= 1;
283 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
287 lPtr
->flags
.userItemHeight
= 1;
288 lPtr
->itemHeight
= height
;
290 updateGeometry(lPtr
);
295 WMClearList(WMList
*lPtr
)
297 int selNo
= WMGetArrayItemCount(lPtr
->selectedItems
);
299 WMEmptyArray(lPtr
->selectedItems
);
300 WMEmptyArray(lPtr
->items
);
303 //lPtr->selectedItem = -1;
306 WMDeleteIdleHandler(lPtr
->idleID
);
309 if (lPtr
->view
->flags
.realized
) {
310 updateScroller(lPtr
);
313 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
319 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
321 lPtr
->action
= action
;
322 lPtr
->clientData
= clientData
;
327 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
329 lPtr
->doubleAction
= action
;
330 lPtr
->doubleClientData
= clientData
;
335 WMGetListSelectedItems(WMList
*lPtr
)
337 return lPtr
->selectedItems
;
342 WMGetListSelectedItem(WMList
*lPtr
)
344 return WMGetFromArray(lPtr
->selectedItems
, 0);
349 WMGetListSelectedItemRow(WMList
*lPtr
)
351 WMListItem
*item
= WMGetFromArray(lPtr
->selectedItems
, 0);
353 return (item
!=NULL
? WMGetFirstInArray(lPtr
->items
, item
) : WLNotFound
);
358 WMGetListItemHeight(WMList
*lPtr
)
360 return lPtr
->itemHeight
;
365 WMSetListPosition(WMList
*lPtr
, int row
)
368 if (lPtr
->topItem
+ lPtr
->fullFitLines
> WMGetArrayItemCount(lPtr
->items
))
369 lPtr
->topItem
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
371 if (lPtr
->topItem
< 0)
374 if (lPtr
->view
->flags
.realized
)
375 updateScroller(lPtr
);
380 WMSetListBottomPosition(WMList
*lPtr
, int row
)
382 if (WMGetArrayItemCount(lPtr
->items
) > lPtr
->fullFitLines
) {
383 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
384 if (lPtr
->topItem
< 0)
386 if (lPtr
->view
->flags
.realized
)
387 updateScroller(lPtr
);
393 WMGetListNumberOfRows(WMList
*lPtr
)
395 return WMGetArrayItemCount(lPtr
->items
);
400 WMGetListPosition(WMList
*lPtr
)
402 return lPtr
->topItem
;
407 WMListAllowsMultipleSelection(WMList
*lPtr
)
409 return lPtr
->flags
.allowMultipleSelection
;
414 WMListAllowsEmptySelection(WMList
*lPtr
)
416 return lPtr
->flags
.allowEmptySelection
;
421 scrollByAmount(WMList
*lPtr
, int amount
)
423 int itemCount
= WMGetArrayItemCount(lPtr
->items
);
425 if ((amount
< 0 && lPtr
->topItem
> 0) ||
426 (amount
> 0 && (lPtr
->topItem
+ lPtr
->fullFitLines
< itemCount
))) {
428 lPtr
->topItem
+= amount
;
429 if (lPtr
->topItem
< 0)
431 if (lPtr
->topItem
+ lPtr
->fullFitLines
> itemCount
)
432 lPtr
->topItem
= itemCount
- lPtr
->fullFitLines
;
434 updateScroller(lPtr
);
440 vScrollCallBack(WMWidget
*scroller
, void *self
)
442 WMList
*lPtr
= (WMList
*)self
;
444 int oldTopItem
= lPtr
->topItem
;
445 int itemCount
= WMGetArrayItemCount(lPtr
->items
);
447 height
= lPtr
->view
->size
.height
- 4;
449 switch (WMGetScrollerHitPart((WMScroller
*)scroller
)) {
450 case WSDecrementLine
:
451 //if (lPtr->topItem > 0) {
454 // updateScroller(lPtr);
456 scrollByAmount(lPtr
, -1);
459 case WSIncrementLine
:
460 //if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
463 // updateScroller(lPtr);
465 scrollByAmount(lPtr
, 1);
468 case WSDecrementPage
:
469 //if (lPtr->topItem > 0) {
470 // lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
471 // if (lPtr->topItem < 0)
472 // lPtr->topItem = 0;
474 // updateScroller(lPtr);
476 scrollByAmount(lPtr
, -lPtr
->fullFitLines
+(1-lPtr
->flags
.dontFitAll
)+1);
479 case WSIncrementPage
:
480 //if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
481 // lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
483 // if (lPtr->topItem + lPtr->fullFitLines > itemCount)
484 // lPtr->topItem = itemCount - lPtr->fullFitLines;
486 // updateScroller(lPtr);
488 scrollByAmount(lPtr
, lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1);
492 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
493 (float)(itemCount
- lPtr
->fullFitLines
);
495 if (oldTopItem
!= lPtr
->topItem
)
496 paintList(lPtr
); // use updateScroller(lPtr) here?
506 if (lPtr
->topItem
!= oldTopItem
)
507 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
512 paintItem(List
*lPtr
, int index
)
514 WMView
*view
= lPtr
->view
;
515 W_Screen
*scr
= view
->screen
;
516 int width
, height
, x
, y
;
519 itemPtr
= WMGetFromArray(lPtr
->items
, index
);
521 width
= lPtr
->view
->size
.width
- 2 - 19;
522 height
= lPtr
->itemHeight
;
524 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
526 if (lPtr
->flags
.userDrawn
) {
530 rect
.size
.width
= width
;
531 rect
.size
.height
= height
;
535 flags
= itemPtr
->uflags
;
536 if (itemPtr
->disabled
)
537 flags
|= WLDSDisabled
;
538 if (itemPtr
->selected
)
539 flags
|= WLDSSelected
;
540 if (itemPtr
->isBranch
)
541 flags
|= WLDSIsBranch
;
544 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
547 if (itemPtr
->selected
) {
548 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
),
549 x
, y
, width
, height
);
551 XClearArea(scr
->display
, view
->window
, x
, y
, width
, height
, False
);
554 W_PaintText(view
, view
->window
, scr
->normalFont
, x
+4, y
, width
,
555 WALeft
, WMColorGC(scr
->black
), False
,
556 itemPtr
->text
, strlen(itemPtr
->text
));
563 paintList(List
*lPtr
)
565 W_Screen
*scrPtr
= lPtr
->view
->screen
;
568 if (!lPtr
->view
->flags
.mapped
)
571 if (WMGetArrayItemCount(lPtr
->items
) > 0) {
572 if (lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
573 > WMGetArrayItemCount(lPtr
->items
)) {
575 lim
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->topItem
;
576 XClearArea(scrPtr
->display
, lPtr
->view
->window
, 19,
577 2+lim
*lPtr
->itemHeight
, lPtr
->view
->size
.width
-21,
578 lPtr
->view
->size
.height
-lim
*lPtr
->itemHeight
-3, False
);
580 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
582 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
586 XClearWindow(scrPtr
->display
, lPtr
->view
->window
);
588 W_DrawRelief(scrPtr
, lPtr
->view
->window
, 0, 0, lPtr
->view
->size
.width
,
589 lPtr
->view
->size
.height
, WRSunken
);
594 scrollTo(List
*lPtr
, int newTop
)
601 updateScroller(List
*lPtr
)
603 float knobProportion
, floatValue
, tmp
;
604 int count
= WMGetArrayItemCount(lPtr
->items
);
607 WMDeleteIdleHandler(lPtr
->idleID
);
612 if (count
== 0 || count
<= lPtr
->fullFitLines
)
613 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
615 tmp
= lPtr
->fullFitLines
;
616 knobProportion
= tmp
/(float)count
;
618 floatValue
= (float)lPtr
->topItem
/(float)(count
- lPtr
->fullFitLines
);
620 WMSetScrollerParameters(lPtr
->vScroller
, floatValue
, knobProportion
);
626 handleEvents(XEvent
*event
, void *data
)
628 List
*lPtr
= (List
*)data
;
630 CHECK_CLASS(data
, WC_List
);
633 switch (event
->type
) {
635 if (event
->xexpose
.count
!=0)
649 matchTitle(void *item
, void *title
)
651 return (strcmp(((WMListItem
*)item
)->text
, (char*)title
)==0 ? 1 : 0);
656 WMFindRowOfListItemWithTitle(WMList
*lPtr
, char *title
)
658 return WMFindInArray(lPtr
->items
, matchTitle
, title
);
663 WMSelectListItem(WMList
*lPtr
, int row
)
665 WMListItem
*item
, *oldSelected
;
667 if (row
>= WMGetArrayItemCount(lPtr
->items
))
670 /* Should row = -1 deselect all or just do nothing ?. Check it. -Dan */
671 if (!lPtr
->flags
.allowMultipleSelection
) {
672 WMUnselectAllListItems(lPtr
);
677 item
= WMGetFromArray(lPtr
->items
, row
);
679 return; /* Return if already selected */
681 oldSelected
= WMGetFromArray(lPtr
->selectedItems
, 0);
683 /* unselect previous selected item if case */
684 if (!lPtr
->flags
.allowMultipleSelection
&& oldSelected
) {
685 int oldSelectedRow
= WMGetListSelectedItemRow(lPtr
);
687 // better call WMUnselectAllListItems() here? -Dan
688 oldSelected
->selected
= 0;
689 WMDeleteFromArray(lPtr
->selectedItems
, 0);
690 // This is faster and have the same effect in the single selected case
691 // but may leave xxx->selected flags set after a multi->single selected
693 //WMEmptyArray(lPtr->selectedItems);
695 if (lPtr
->view
->flags
.mapped
&& oldSelectedRow
>=lPtr
->topItem
696 && oldSelectedRow
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
697 paintItem(lPtr
, oldSelectedRow
);
703 WMAddToArray(lPtr
->selectedItems
, item
);
705 if (lPtr
->view
->flags
.mapped
) {
706 paintItem(lPtr
, row
);
708 if ((row
-lPtr
->topItem
+lPtr
->fullFitLines
)*lPtr
->itemHeight
709 > lPtr
->view
->size
.height
-2)
710 W_DrawRelief(lPtr
->view
->screen
, lPtr
->view
->window
, 0, 0,
711 lPtr
->view
->size
.width
, lPtr
->view
->size
.height
,
714 //lPtr->selectedItem = row;
716 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
721 getItemIndexAt(List
*lPtr
, int clickY
)
725 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
727 if (index
< 0 || index
>= WMGetArrayItemCount(lPtr
->items
))
735 handleActionEvents(XEvent
*event
, void *data
)
737 List
*lPtr
= (List
*)data
;
739 int topItem
= lPtr
->topItem
;
741 CHECK_CLASS(data
, WC_List
);
743 switch (event
->type
) {
745 #define CHECK_WHEEL_PATCH
746 #ifdef CHECK_WHEEL_PATCH
747 /* Ignore mouse wheel events, they're not "real" button events */
748 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
||
749 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
754 lPtr
->flags
.buttonPressed
= 0;
755 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
757 if (tmp
== lPtr
->selectedItem
&& tmp
>= 0) {
759 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
764 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
765 lPtr
->flags
.buttonWasPressed
= 0;
769 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
770 lPtr
->flags
.buttonPressed
= 0;
774 if (event
->xbutton
.x
> WMWidgetWidth(lPtr
->vScroller
)) {
775 #ifdef CHECK_WHEEL_PATCH
779 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
781 /*int itemCount = WMGetArrayItemCount(lPtr->items);
782 if (lPtr->topItem + lPtr->fullFitLines < itemCount) {
783 int incr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
784 lPtr->topItem += incr;
786 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
787 lPtr->topItem = itemCount - lPtr->fullFitLines;
789 updateScroller(lPtr);
791 if (event
->xbutton
.state
& ShiftMask
) {
792 amount
= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
793 } else if (event
->xbutton
.state
& ControlMask
) {
796 amount
= lPtr
->fullFitLines
/ 3;
801 scrollByAmount(lPtr
, amount
);
805 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
807 /*if (lPtr->topItem > 0) {
808 int decr = lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
809 lPtr->topItem -= decr;
811 if (lPtr->topItem < 0)
814 updateScroller(lPtr);
816 if (event
->xbutton
.state
& ShiftMask
) {
817 amount
= -lPtr
->fullFitLines
+(1-lPtr
->flags
.dontFitAll
)+1;
818 } else if (event
->xbutton
.state
& ControlMask
) {
821 amount
= -(lPtr
->fullFitLines
/ 3);
826 scrollByAmount(lPtr
, amount
);
831 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
832 lPtr
->flags
.buttonPressed
= 1;
835 if (tmp
== lPtr
->selectedItem
&& WMIsDoubleClick(event
)) {
836 WMSelectListItem(lPtr
, tmp
);
837 if (lPtr
->doubleAction
)
838 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
840 WMSelectListItem(lPtr
, tmp
);
847 if (lPtr
->flags
.buttonPressed
) {
848 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
849 if (tmp
>=0 && tmp
!= lPtr
->selectedItem
) {
850 WMSelectListItem(lPtr
, tmp
);
855 if (lPtr
->topItem
!= topItem
)
856 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
861 updateGeometry(WMList
*lPtr
)
863 lPtr
->fullFitLines
= (lPtr
->view
->size
.height
- 4) / lPtr
->itemHeight
;
864 if (lPtr
->fullFitLines
* lPtr
->itemHeight
< lPtr
->view
->size
.height
- 4) {
865 lPtr
->flags
.dontFitAll
= 1;
867 lPtr
->flags
.dontFitAll
= 0;
870 if (WMGetArrayItemCount(lPtr
->items
) - lPtr
->topItem
<= lPtr
->fullFitLines
) {
871 lPtr
->topItem
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
872 if (lPtr
->topItem
< 0)
876 updateScroller(lPtr
);
881 didResizeList(W_ViewDelegate
*self
, WMView
*view
)
883 WMList
*lPtr
= (WMList
*)view
->self
;
885 WMResizeWidget(lPtr
->vScroller
, 1, view
->size
.height
-2);
887 updateGeometry(lPtr
);
892 destroyList(List
*lPtr
)
895 WMDeleteIdleHandler(lPtr
->idleID
);
898 WMFreeArray(lPtr
->items
);