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 */
19 short topItem
; /* index of first visible item */
21 short fullFitLines
; /* no of lines that fit entirely */
25 void *doubleClientData
;
26 WMAction
*doubleAction
;
30 WMHandlerID
*idleID
; /* for updating the scroller after adding elements */
32 WMScroller
*vScroller
;
35 unsigned int allowMultipleSelection
:1;
36 unsigned int allowEmptySelection
:1;
37 unsigned int userDrawn
:1;
38 unsigned int userItemHeight
:1;
39 unsigned int dontFitAll
:1; /* 1 = last item won't be fully visible */
40 unsigned int redrawPending
:1;
41 unsigned int buttonPressed
:1;
42 unsigned int buttonWasPressed
:1;
48 #define DEFAULT_WIDTH 150
49 #define DEFAULT_HEIGHT 150
52 static void destroyList(List
*lPtr
);
53 static void paintList(List
*lPtr
);
56 static void handleEvents(XEvent
*event
, void *data
);
57 static void handleActionEvents(XEvent
*event
, void *data
);
58 static void updateScroller(List
*lPtr
);
60 static void vScrollCallBack(WMWidget
*scroller
, void *self
);
62 static void updateGeometry(WMList
*lPtr
);
63 static void didResizeList();
66 W_ViewDelegate _ListViewDelegate
= {
76 releaseItem(void *data
)
78 WMListItem
*item
= (WMListItem
*)data
;
87 WMCreateList(WMWidget
*parent
)
90 W_Screen
*scrPtr
= W_VIEW(parent
)->screen
;
92 lPtr
= wmalloc(sizeof(List
));
93 memset(lPtr
, 0, sizeof(List
));
95 lPtr
->widgetClass
= WC_List
;
97 lPtr
->view
= W_CreateView(W_VIEW(parent
));
102 lPtr
->view
->self
= lPtr
;
104 lPtr
->view
->delegate
= &_ListViewDelegate
;
106 WMCreateEventHandler(lPtr
->view
, ExposureMask
|StructureNotifyMask
107 |ClientMessageMask
, handleEvents
, lPtr
);
109 WMCreateEventHandler(lPtr
->view
, ButtonPressMask
|ButtonReleaseMask
110 |EnterWindowMask
|LeaveWindowMask
|ButtonMotionMask
,
111 handleActionEvents
, lPtr
);
113 lPtr
->itemHeight
= WMFontHeight(scrPtr
->normalFont
) + 1;
115 lPtr
->items
= WMCreateArrayWithDestructor(4, releaseItem
);
116 lPtr
->selectedItems
= WMCreateArray(4);
118 /* create the vertical scroller */
119 lPtr
->vScroller
= WMCreateScroller(lPtr
);
120 WMMoveWidget(lPtr
->vScroller
, 1, 1);
121 WMSetScrollerArrowsPosition(lPtr
->vScroller
, WSAMaxEnd
);
123 WMSetScrollerAction(lPtr
->vScroller
, vScrollCallBack
, lPtr
);
125 /* make the scroller map itself when it's realized */
126 WMMapWidget(lPtr
->vScroller
);
128 W_ResizeView(lPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
135 WMSetListAllowMultipleSelection(WMList
*lPtr
, Bool flag
)
137 lPtr
->flags
.allowMultipleSelection
= flag
? 1 : 0;
143 WMSetListAllowEmptySelection(WMList
*lPtr
, Bool flag
)
145 lPtr
->flags
.allowEmptySelection
= flag
? 1 : 0;
150 comparator(const void *a
, const void *b
)
152 return (strcmp((*(WMListItem
**)a
)->text
, (*(WMListItem
**)b
)->text
));
157 WMSortListItems(WMList
*lPtr
)
159 WMSortArray(lPtr
->items
, comparator
);
167 WMSortListItemsWithComparer(WMList
*lPtr
, WMCompareDataProc
*func
)
169 WMSortArray(lPtr
->items
, func
);
177 WMInsertListItem(WMList
*lPtr
, int row
, char *text
)
181 CHECK_CLASS(lPtr
, WC_List
);
183 item
= wmalloc(sizeof(WMListItem
));
184 memset(item
, 0, sizeof(WMListItem
));
185 item
->text
= wstrdup(text
);
187 row
= WMIN(row
, WMGetArrayItemCount(lPtr
->items
));
190 WMAddToArray(lPtr
->items
, item
);
192 WMInsertInArray(lPtr
->items
, row
, item
);
194 /* update the scroller when idle, so that we don't waste time
195 * updating it when another item is going to be added later */
197 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
205 WMRemoveListItem(WMList
*lPtr
, int row
)
208 int topItem
= lPtr
->topItem
;
211 CHECK_CLASS(lPtr
, WC_List
);
213 if (row
< 0 || row
>= WMGetArrayItemCount(lPtr
->items
))
216 item
= WMGetFromArray(lPtr
->items
, row
);
217 if (item
->selected
) {
218 WMRemoveFromArray(lPtr
->selectedItems
, item
);
219 //WMUnselectListItem(lPtr, row);
223 if (row
<= lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
)
225 if (lPtr
->topItem
< 0)
228 WMDeleteFromArray(lPtr
->items
, row
);
231 lPtr
->idleID
= WMAddIdleHandler((WMCallback
*)updateScroller
, lPtr
);
233 if (lPtr
->topItem
!= topItem
) {
234 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
237 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
245 WMGetListItem(WMList
*lPtr
, int row
)
247 return WMGetFromArray(lPtr
->items
, row
);
252 WMGetListItems(WMList
*lPtr
)
259 WMSetListUserDrawProc(WMList
*lPtr
, WMListDrawProc
*proc
)
261 lPtr
->flags
.userDrawn
= 1;
267 WMSetListUserDrawItemHeight(WMList
*lPtr
, unsigned short height
)
271 lPtr
->flags
.userItemHeight
= 1;
272 lPtr
->itemHeight
= height
;
274 updateGeometry(lPtr
);
279 WMClearList(WMList
*lPtr
)
281 int selNo
= WMGetArrayItemCount(lPtr
->selectedItems
);
283 WMEmptyArray(lPtr
->selectedItems
);
284 WMEmptyArray(lPtr
->items
);
289 WMDeleteIdleHandler(lPtr
->idleID
);
292 if (lPtr
->view
->flags
.realized
) {
293 updateScroller(lPtr
);
296 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
302 WMSetListAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
304 lPtr
->action
= action
;
305 lPtr
->clientData
= clientData
;
310 WMSetListDoubleAction(WMList
*lPtr
, WMAction
*action
, void *clientData
)
312 lPtr
->doubleAction
= action
;
313 lPtr
->doubleClientData
= clientData
;
318 WMGetListSelectedItems(WMList
*lPtr
)
320 return lPtr
->selectedItems
;
325 WMGetListSelectedItem(WMList
*lPtr
)
327 return WMGetFromArray(lPtr
->selectedItems
, 0);
332 WMGetListSelectedItemRow(WMList
*lPtr
)
334 WMListItem
*item
= WMGetFromArray(lPtr
->selectedItems
, 0);
336 return (item
!=NULL
? WMGetFirstInArray(lPtr
->items
, item
) : WLNotFound
);
341 WMGetListItemHeight(WMList
*lPtr
)
343 return lPtr
->itemHeight
;
348 WMSetListPosition(WMList
*lPtr
, int row
)
351 if (lPtr
->topItem
+ lPtr
->fullFitLines
> WMGetArrayItemCount(lPtr
->items
))
352 lPtr
->topItem
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
354 if (lPtr
->topItem
< 0)
357 if (lPtr
->view
->flags
.realized
)
358 updateScroller(lPtr
);
363 WMSetListBottomPosition(WMList
*lPtr
, int row
)
365 if (WMGetArrayItemCount(lPtr
->items
) > lPtr
->fullFitLines
) {
366 lPtr
->topItem
= row
- lPtr
->fullFitLines
;
367 if (lPtr
->topItem
< 0)
369 if (lPtr
->view
->flags
.realized
)
370 updateScroller(lPtr
);
376 WMGetListNumberOfRows(WMList
*lPtr
)
378 return WMGetArrayItemCount(lPtr
->items
);
383 WMGetListPosition(WMList
*lPtr
)
385 return lPtr
->topItem
;
390 WMListAllowsMultipleSelection(WMList
*lPtr
)
392 return lPtr
->flags
.allowMultipleSelection
;
397 WMListAllowsEmptySelection(WMList
*lPtr
)
399 return lPtr
->flags
.allowEmptySelection
;
404 scrollByAmount(WMList
*lPtr
, int amount
)
406 int itemCount
= WMGetArrayItemCount(lPtr
->items
);
408 if ((amount
< 0 && lPtr
->topItem
> 0) ||
409 (amount
> 0 && (lPtr
->topItem
+ lPtr
->fullFitLines
< itemCount
))) {
411 lPtr
->topItem
+= amount
;
412 if (lPtr
->topItem
< 0)
414 if (lPtr
->topItem
+ lPtr
->fullFitLines
> itemCount
)
415 lPtr
->topItem
= itemCount
- lPtr
->fullFitLines
;
417 updateScroller(lPtr
);
423 vScrollCallBack(WMWidget
*scroller
, void *self
)
425 WMList
*lPtr
= (WMList
*)self
;
427 int oldTopItem
= lPtr
->topItem
;
428 int itemCount
= WMGetArrayItemCount(lPtr
->items
);
430 height
= lPtr
->view
->size
.height
- 4;
432 switch (WMGetScrollerHitPart((WMScroller
*)scroller
)) {
433 case WSDecrementLine
:
434 scrollByAmount(lPtr
, -1);
437 case WSIncrementLine
:
438 scrollByAmount(lPtr
, 1);
441 case WSDecrementPage
:
442 scrollByAmount(lPtr
, -lPtr
->fullFitLines
+(1-lPtr
->flags
.dontFitAll
)+1);
445 case WSIncrementPage
:
446 scrollByAmount(lPtr
, lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1);
450 lPtr
->topItem
= WMGetScrollerValue(lPtr
->vScroller
) *
451 (float)(itemCount
- lPtr
->fullFitLines
);
453 if (oldTopItem
!= lPtr
->topItem
)
454 paintList(lPtr
); // use updateScroller(lPtr) here?
464 if (lPtr
->topItem
!= oldTopItem
)
465 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
470 paintItem(List
*lPtr
, int index
)
472 WMView
*view
= lPtr
->view
;
473 W_Screen
*scr
= view
->screen
;
474 int width
, height
, x
, y
;
477 itemPtr
= WMGetFromArray(lPtr
->items
, index
);
479 width
= lPtr
->view
->size
.width
- 2 - 19;
480 height
= lPtr
->itemHeight
;
482 y
= 2 + (index
-lPtr
->topItem
) * lPtr
->itemHeight
+ 1;
484 if (lPtr
->flags
.userDrawn
) {
488 rect
.size
.width
= width
;
489 rect
.size
.height
= height
;
493 flags
= itemPtr
->uflags
;
494 if (itemPtr
->disabled
)
495 flags
|= WLDSDisabled
;
496 if (itemPtr
->selected
)
497 flags
|= WLDSSelected
;
498 if (itemPtr
->isBranch
)
499 flags
|= WLDSIsBranch
;
502 (*lPtr
->draw
)(lPtr
, index
, view
->window
, itemPtr
->text
, flags
,
505 if (itemPtr
->selected
) {
506 XFillRectangle(scr
->display
, view
->window
, WMColorGC(scr
->white
),
507 x
, y
, width
, height
);
509 XClearArea(scr
->display
, view
->window
, x
, y
, width
, height
, False
);
512 W_PaintText(view
, view
->window
, scr
->normalFont
, x
+4, y
, width
,
513 WALeft
, WMColorGC(scr
->black
), False
,
514 itemPtr
->text
, strlen(itemPtr
->text
));
521 paintList(List
*lPtr
)
523 W_Screen
*scrPtr
= lPtr
->view
->screen
;
526 if (!lPtr
->view
->flags
.mapped
)
529 if (WMGetArrayItemCount(lPtr
->items
) > 0) {
530 if (lPtr
->topItem
+lPtr
->fullFitLines
+lPtr
->flags
.dontFitAll
531 > WMGetArrayItemCount(lPtr
->items
)) {
533 lim
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->topItem
;
534 XClearArea(scrPtr
->display
, lPtr
->view
->window
, 19,
535 2+lim
*lPtr
->itemHeight
, lPtr
->view
->size
.width
-21,
536 lPtr
->view
->size
.height
-lim
*lPtr
->itemHeight
-3, False
);
538 lim
= lPtr
->fullFitLines
+ lPtr
->flags
.dontFitAll
;
540 for (i
= lPtr
->topItem
; i
< lPtr
->topItem
+ lim
; i
++) {
544 XClearWindow(scrPtr
->display
, lPtr
->view
->window
);
546 W_DrawRelief(scrPtr
, lPtr
->view
->window
, 0, 0, lPtr
->view
->size
.width
,
547 lPtr
->view
->size
.height
, WRSunken
);
552 scrollTo(List
*lPtr
, int newTop
)
559 updateScroller(List
*lPtr
)
561 float knobProportion
, floatValue
, tmp
;
562 int count
= WMGetArrayItemCount(lPtr
->items
);
565 WMDeleteIdleHandler(lPtr
->idleID
);
570 if (count
== 0 || count
<= lPtr
->fullFitLines
)
571 WMSetScrollerParameters(lPtr
->vScroller
, 0, 1);
573 tmp
= lPtr
->fullFitLines
;
574 knobProportion
= tmp
/(float)count
;
576 floatValue
= (float)lPtr
->topItem
/(float)(count
- lPtr
->fullFitLines
);
578 WMSetScrollerParameters(lPtr
->vScroller
, floatValue
, knobProportion
);
584 handleEvents(XEvent
*event
, void *data
)
586 List
*lPtr
= (List
*)data
;
588 CHECK_CLASS(data
, WC_List
);
591 switch (event
->type
) {
593 if (event
->xexpose
.count
!=0)
607 matchTitle(void *item
, void *title
)
609 return (strcmp(((WMListItem
*)item
)->text
, (char*)title
)==0 ? 1 : 0);
614 WMFindRowOfListItemWithTitle(WMList
*lPtr
, char *title
)
616 return WMFindInArray(lPtr
->items
, matchTitle
, title
);
621 WMSelectListItem(WMList
*lPtr
, int row
)
623 WMListItem
*item
, *oldSelected
;
625 if (row
>= WMGetArrayItemCount(lPtr
->items
))
628 /* Should row = -1 deselect all or just do nothing ?. Check it. -Dan */
629 if (!lPtr
->flags
.allowMultipleSelection
) {
630 WMUnselectAllListItems(lPtr
);
635 item
= WMGetFromArray(lPtr
->items
, row
);
637 return; /* Return if already selected */
639 oldSelected
= WMGetFromArray(lPtr
->selectedItems
, 0);
641 /* unselect previous selected item if case */
642 if (!lPtr
->flags
.allowMultipleSelection
&& oldSelected
) {
643 int oldSelectedRow
= WMGetListSelectedItemRow(lPtr
);
645 // better call WMUnselectAllListItems() here? -Dan
646 oldSelected
->selected
= 0;
647 WMDeleteFromArray(lPtr
->selectedItems
, 0);
648 // This is faster and have the same effect in the single selected case
649 // but may leave xxx->selected flags set after a multi->single selected
651 //WMEmptyArray(lPtr->selectedItems);
653 if (lPtr
->view
->flags
.mapped
&& oldSelectedRow
>=lPtr
->topItem
654 && oldSelectedRow
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
655 paintItem(lPtr
, oldSelectedRow
);
661 WMAddToArray(lPtr
->selectedItems
, item
);
663 if (lPtr
->view
->flags
.mapped
) {
664 paintItem(lPtr
, row
);
666 if ((row
-lPtr
->topItem
+lPtr
->fullFitLines
)*lPtr
->itemHeight
667 > lPtr
->view
->size
.height
-2)
668 W_DrawRelief(lPtr
->view
->screen
, lPtr
->view
->window
, 0, 0,
669 lPtr
->view
->size
.width
, lPtr
->view
->size
.height
,
673 WMPostNotificationName(WMListSelectionDidChangeNotification
, lPtr
, NULL
);
677 // make them return an int
680 WMUnselectListItem(WMList
*lPtr
, int row
)
682 WMListItem
*item
= WMGetFromArray(lPtr
->items
, row
);
684 if (!item
|| !item
->selected
)
687 // also add check for allowEmptySelection
690 WMRemoveFromArray(lPtr
->selectedItems
, item
);
692 if (lPtr
->view
->flags
.mapped
&& row
>=lPtr
->topItem
693 && row
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
694 paintItem(lPtr
, row
);
700 WMSelectAllListItems(WMList
*lPtr
)
705 if (!lPtr
->flags
.allowMultipleSelection
)
708 // implement some WMDuplicateArray() ?
709 for (i
=0; i
<WMGetArrayItemCount(lPtr
->items
); i
++) {
710 item
= WMGetFromArray(lPtr
->items
, i
);
711 if (!item
->selected
) {
713 WMAddToArray(lPtr
->selectedItems
, item
);
714 if (lPtr
->view
->flags
.mapped
&& i
>=lPtr
->topItem
715 && i
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
724 WMUnselectAllListItems(WMList
*lPtr
)
729 // check for allowEmptySelection
731 for (i
=0; i
<WMGetArrayItemCount(lPtr
->items
); i
++) {
732 item
= WMGetFromArray(lPtr
->items
, i
);
733 if (item
->selected
) {
735 if (lPtr
->view
->flags
.mapped
&& i
>=lPtr
->topItem
736 && i
<=lPtr
->topItem
+lPtr
->fullFitLines
) {
742 WMEmptyArray(lPtr
->selectedItems
);
747 getItemIndexAt(List
*lPtr
, int clickY
)
751 index
= (clickY
- 2) / lPtr
->itemHeight
+ lPtr
->topItem
;
753 if (index
< 0 || index
>= WMGetArrayItemCount(lPtr
->items
))
761 handleActionEvents(XEvent
*event
, void *data
)
763 List
*lPtr
= (List
*)data
;
765 int topItem
= lPtr
->topItem
;
766 static int oldClicked
= -1;
768 CHECK_CLASS(data
, WC_List
);
770 switch (event
->type
) {
772 #define CHECK_WHEEL_PATCH
773 #ifdef CHECK_WHEEL_PATCH
774 /* Ignore mouse wheel events, they're not "real" button events */
775 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
||
776 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
781 lPtr
->flags
.buttonPressed
= 0;
782 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
784 if (/*tmp == lPtr->selectedItem && */tmp
>= 0) {
786 (*lPtr
->action
)(lPtr
, lPtr
->clientData
);
793 lPtr
->flags
.buttonPressed
= lPtr
->flags
.buttonWasPressed
;
794 lPtr
->flags
.buttonWasPressed
= 0;
798 lPtr
->flags
.buttonWasPressed
= lPtr
->flags
.buttonPressed
;
799 lPtr
->flags
.buttonPressed
= 0;
803 if (event
->xbutton
.x
> WMWidgetWidth(lPtr
->vScroller
)) {
804 #ifdef CHECK_WHEEL_PATCH
805 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
||
806 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
809 if (event
->xbutton
.state
& ShiftMask
) {
810 amount
= lPtr
->fullFitLines
-(1-lPtr
->flags
.dontFitAll
)-1;
811 } else if (event
->xbutton
.state
& ControlMask
) {
814 amount
= lPtr
->fullFitLines
/ 3;
818 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
)
821 scrollByAmount(lPtr
, amount
);
826 tmp
= getItemIndexAt(lPtr
, event
->xbutton
.y
);
827 lPtr
->flags
.buttonPressed
= 1;
830 if (tmp
== oldClicked
&& WMIsDoubleClick(event
)) {
831 WMSelectListItem(lPtr
, tmp
);
832 if (lPtr
->doubleAction
)
833 (*lPtr
->doubleAction
)(lPtr
, lPtr
->doubleClientData
);
835 WMSelectListItem(lPtr
, tmp
);
844 if (lPtr
->flags
.buttonPressed
) {
845 tmp
= getItemIndexAt(lPtr
, event
->xmotion
.y
);
846 if (tmp
>=0 /*&& tmp != lPtr->selectedItem*/) {
847 WMSelectListItem(lPtr
, tmp
);
852 if (lPtr
->topItem
!= topItem
)
853 WMPostNotificationName(WMListDidScrollNotification
, lPtr
, NULL
);
858 updateGeometry(WMList
*lPtr
)
860 lPtr
->fullFitLines
= (lPtr
->view
->size
.height
- 4) / lPtr
->itemHeight
;
861 if (lPtr
->fullFitLines
* lPtr
->itemHeight
< lPtr
->view
->size
.height
- 4) {
862 lPtr
->flags
.dontFitAll
= 1;
864 lPtr
->flags
.dontFitAll
= 0;
867 if (WMGetArrayItemCount(lPtr
->items
) - lPtr
->topItem
<= lPtr
->fullFitLines
) {
868 lPtr
->topItem
= WMGetArrayItemCount(lPtr
->items
) - lPtr
->fullFitLines
;
869 if (lPtr
->topItem
< 0)
873 updateScroller(lPtr
);
878 didResizeList(W_ViewDelegate
*self
, WMView
*view
)
880 WMList
*lPtr
= (WMList
*)view
->self
;
882 WMResizeWidget(lPtr
->vScroller
, 1, view
->size
.height
-2);
884 updateGeometry(lPtr
);
889 destroyList(List
*lPtr
)
892 WMDeleteIdleHandler(lPtr
->idleID
);
895 WMFreeArray(lPtr
->items
);