8 typedef struct W_PopUpButton
{
19 short selectedItemIndex
;
21 short highlightedItem
;
23 WMView
*menuView
; /* override redirect popup menu */
25 WMHandlerID timer
; /* for autoscroll */
28 int scrollStartY
; /* for autoscroll */
31 unsigned int pullsDown
:1;
33 unsigned int configured
:1;
35 unsigned int insideMenu
:1;
37 unsigned int enabled
:1;
43 #define MENU_BLINK_DELAY 60000
44 #define MENU_BLINK_COUNT 2
46 #define SCROLL_DELAY 10
49 #define DEFAULT_WIDTH 60
50 #define DEFAULT_HEIGHT 20
51 #define DEFAULT_CAPTION ""
54 static void destroyPopUpButton(PopUpButton
*bPtr
);
55 static void paintPopUpButton(PopUpButton
*bPtr
);
57 static void handleEvents(XEvent
*event
, void *data
);
58 static void handleActionEvents(XEvent
*event
, void *data
);
60 static void resizeMenu(PopUpButton
*bPtr
);
64 WMCreatePopUpButton(WMWidget
*parent
)
67 W_Screen
*scr
= W_VIEW(parent
)->screen
;
70 bPtr
= wmalloc(sizeof(PopUpButton
));
71 memset(bPtr
, 0, sizeof(PopUpButton
));
73 bPtr
->widgetClass
= WC_PopUpButton
;
75 bPtr
->view
= W_CreateView(W_VIEW(parent
));
80 bPtr
->view
->self
= bPtr
;
82 WMCreateEventHandler(bPtr
->view
, ExposureMask
|StructureNotifyMask
83 |ClientMessageMask
, handleEvents
, bPtr
);
86 W_ResizeView(bPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
87 bPtr
->caption
= wstrdup(DEFAULT_CAPTION
);
89 WMCreateEventHandler(bPtr
->view
, ButtonPressMask
|ButtonReleaseMask
,
90 handleActionEvents
, bPtr
);
92 bPtr
->flags
.enabled
= 1;
94 bPtr
->items
= WMCreateBag(4);
96 bPtr
->selectedItemIndex
= -1;
98 bPtr
->menuView
= W_CreateTopView(scr
);
99 bPtr
->menuView
->attribs
.override_redirect
= True
;
100 bPtr
->menuView
->attribFlags
|= CWOverrideRedirect
;
102 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, 1);
104 WMCreateEventHandler(bPtr
->menuView
, ButtonPressMask
|ButtonReleaseMask
105 |EnterWindowMask
|LeaveWindowMask
|ButtonMotionMask
106 |ExposureMask
, handleActionEvents
, bPtr
);
113 WMSetPopUpButtonAction(WMPopUpButton
*bPtr
, WMAction
*action
, void *clientData
)
115 CHECK_CLASS(bPtr
, WC_PopUpButton
);
117 bPtr
->action
= action
;
119 bPtr
->clientData
= clientData
;
124 WMAddPopUpButtonItem(WMPopUpButton
*bPtr
, char *title
)
128 CHECK_CLASS(bPtr
, WC_PopUpButton
);
130 item
= WMCreateMenuItem();
131 WMSetMenuItemTitle(item
, title
);
133 WMPutInBag(bPtr
->items
, item
);
135 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
143 WMInsertPopUpButtonItem(WMPopUpButton
*bPtr
, int index
, char *title
)
147 CHECK_CLASS(bPtr
, WC_PopUpButton
);
149 item
= WMCreateMenuItem();
150 WMSetMenuItemTitle(item
, title
);
152 WMInsertInBag(bPtr
->items
, index
, item
);
154 /* if there is an selected item, update it's index to match the new
156 if (index
< bPtr
->selectedItemIndex
)
157 bPtr
->selectedItemIndex
++;
159 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
167 WMRemovePopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
171 CHECK_CLASS(bPtr
, WC_PopUpButton
);
173 wassertr(index
>= 0 && index
< WMGetBagItemCount(bPtr
->items
));
176 item
= WMGetFromBag(bPtr
->items
, index
);
177 WMDeleteFromBag(bPtr
->items
, index
);
179 WMDestroyMenuItem(item
);
181 if (bPtr
->selectedItemIndex
>= 0 && !bPtr
->flags
.pullsDown
) {
182 if (index
< bPtr
->selectedItemIndex
)
183 bPtr
->selectedItemIndex
--;
184 else if (index
== bPtr
->selectedItemIndex
) {
185 /* reselect first item if the removed item is the
187 bPtr
->selectedItemIndex
= 0;
188 if (bPtr
->view
->flags
.mapped
)
189 paintPopUpButton(bPtr
);
193 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
199 WMSetPopUpButtonEnabled(WMPopUpButton
*bPtr
, Bool flag
)
201 bPtr
->flags
.enabled
= flag
;
202 if (bPtr
->view
->flags
.mapped
)
203 paintPopUpButton(bPtr
);
208 WMGetPopUpButtonEnabled(WMPopUpButton
*bPtr
)
210 return bPtr
->flags
.enabled
;
215 WMSetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
, int index
)
218 wassertr(index
< WMGetBagItemCount(bPtr
->items
));
220 /* if (index >= WMGetBagCount(bPtr->items))
223 bPtr
->selectedItemIndex
= index
;
225 if (bPtr
->view
->flags
.mapped
)
226 paintPopUpButton(bPtr
);
230 WMGetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
)
232 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
235 return bPtr
->selectedItemIndex
;
240 WMSetPopUpButtonText(WMPopUpButton
*bPtr
, char *text
)
243 wfree(bPtr
->caption
);
245 bPtr
->caption
= wstrdup(text
);
247 bPtr
->caption
= NULL
;
248 if (bPtr
->view
->flags
.realized
) {
249 if (bPtr
->flags
.pullsDown
|| bPtr
->selectedItemIndex
< 0) {
250 paintPopUpButton(bPtr
);
258 WMSetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
, Bool flag
)
262 item
= WMGetFromBag(bPtr
->items
, index
);
263 wassertr(item
!= NULL
);
265 WMSetMenuItemEnabled(item
, flag
);
270 WMGetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
)
274 item
= WMGetFromBag(bPtr
->items
, index
);
275 wassertrv(item
!= NULL
, False
);
277 return WMGetMenuItemEnabled(item
);
282 WMSetPopUpButtonPullsDown(WMPopUpButton
*bPtr
, Bool flag
)
284 bPtr
->flags
.pullsDown
= flag
;
286 bPtr
->selectedItemIndex
= -1;
289 if (bPtr
->view
->flags
.mapped
)
290 paintPopUpButton(bPtr
);
295 WMGetPopUpButtonNumberOfItems(WMPopUpButton
*bPtr
)
297 return WMGetBagItemCount(bPtr
->items
);
302 WMGetPopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
306 if (index
>= WMGetBagItemCount(bPtr
->items
) || index
< 0)
309 item
= WMGetFromBag(bPtr
->items
, index
);
313 return WMGetMenuItemTitle(item
);
318 WMGetPopUpButtonMenuItem(WMPopUpButton
*bPtr
, int index
)
322 item
= WMGetFromBag(bPtr
->items
, index
);
330 paintPopUpButton(PopUpButton
*bPtr
)
332 W_Screen
*scr
= bPtr
->view
->screen
;
337 if (bPtr
->flags
.pullsDown
) {
338 caption
= bPtr
->caption
;
340 if (bPtr
->selectedItemIndex
< 0) {
341 /* if no item selected, show the caption */
342 caption
= bPtr
->caption
;
344 caption
= WMGetPopUpButtonItem(bPtr
, bPtr
->selectedItemIndex
);
348 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
,
349 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
,
351 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
352 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
);
354 W_DrawRelief(scr
, pixmap
, 0, 0, bPtr
->view
->size
.width
,
355 bPtr
->view
->size
.height
, WRRaised
);
358 W_PaintText(bPtr
->view
, pixmap
, scr
->normalFont
, 6,
359 (bPtr
->view
->size
.height
-WMFontHeight(scr
->normalFont
))/2,
360 bPtr
->view
->size
.width
, WALeft
,
361 bPtr
->flags
.enabled
? WMColorGC(scr
->black
) : WMColorGC(scr
->darkGray
),
362 False
, caption
, strlen(caption
));
365 if (bPtr
->flags
.pullsDown
) {
366 XCopyArea(scr
->display
, scr
->pullDownIndicator
->pixmap
,
367 pixmap
, scr
->copyGC
, 0, 0, scr
->pullDownIndicator
->width
,
368 scr
->pullDownIndicator
->height
,
369 bPtr
->view
->size
.width
-scr
->pullDownIndicator
->width
-4,
370 (bPtr
->view
->size
.height
-scr
->pullDownIndicator
->height
)/2);
374 x
= bPtr
->view
->size
.width
- scr
->popUpIndicator
->width
- 4;
375 y
= (bPtr
->view
->size
.height
-scr
->popUpIndicator
->height
)/2;
377 XSetClipOrigin(scr
->display
, scr
->clipGC
, x
, y
);
378 XSetClipMask(scr
->display
, scr
->clipGC
, scr
->popUpIndicator
->mask
);
379 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
380 scr
->clipGC
, 0, 0, scr
->popUpIndicator
->width
,
381 scr
->popUpIndicator
->height
, x
, y
);
384 XCopyArea(scr
->display
, pixmap
, bPtr
->view
->window
, scr
->copyGC
, 0, 0,
385 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, 0, 0);
387 XFreePixmap(scr
->display
, pixmap
);
393 handleEvents(XEvent
*event
, void *data
)
395 PopUpButton
*bPtr
= (PopUpButton
*)data
;
397 CHECK_CLASS(data
, WC_PopUpButton
);
400 switch (event
->type
) {
402 if (event
->xexpose
.count
!=0)
404 paintPopUpButton(bPtr
);
408 destroyPopUpButton(bPtr
);
416 paintMenuEntry(PopUpButton
*bPtr
, int index
, int highlight
)
418 W_Screen
*scr
= bPtr
->view
->screen
;
420 int width
, height
, itemHeight
, itemCount
;
423 itemCount
= WMGetBagItemCount(bPtr
->items
);
424 if (index
< 0 || index
>= itemCount
)
427 itemHeight
= bPtr
->view
->size
.height
;
428 width
= bPtr
->view
->size
.width
;
429 height
= itemHeight
* itemCount
;
430 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
433 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
434 width
, itemHeight
, False
);
436 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
440 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
441 1, index
*itemHeight
+1, width
-3, itemHeight
-3);
443 title
= WMGetPopUpButtonItem(bPtr
, index
);
445 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
446 width
, itemHeight
, WRRaised
);
448 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
449 index
*itemHeight
+ yo
, width
, WALeft
, WMColorGC(scr
->black
),
450 False
, title
, strlen(title
));
452 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
453 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
454 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
455 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
456 width
-scr
->popUpIndicator
->width
-4,
457 index
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
463 makeMenuPixmap(PopUpButton
*bPtr
)
466 W_Screen
*scr
= bPtr
->view
->screen
;
470 int width
, height
, itemHeight
;
472 itemHeight
= bPtr
->view
->size
.height
;
473 width
= bPtr
->view
->size
.width
;
474 height
= itemHeight
* WMGetBagItemCount(bPtr
->items
);
475 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
477 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
,
480 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
484 WM_ITERATE_BAG(bPtr
->items
, item
, iter
) {
488 text
= WMGetMenuItemTitle(item
);
490 W_DrawRelief(scr
, pixmap
, 0, i
*itemHeight
, width
, itemHeight
,
493 if (!WMGetMenuItemEnabled(item
))
494 gc
= WMColorGC(scr
->darkGray
);
496 gc
= WMColorGC(scr
->black
);
498 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
499 i
*itemHeight
+ yo
, width
, WALeft
, gc
, False
,
502 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
503 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
504 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
505 scr
->popUpIndicator
->height
,
506 width
-scr
->popUpIndicator
->width
-4,
507 i
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
518 resizeMenu(PopUpButton
*bPtr
)
522 height
= WMGetBagItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
524 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
529 popUpMenu(PopUpButton
*bPtr
)
531 W_Screen
*scr
= bPtr
->view
->screen
;
535 if (!bPtr
->menuView
->flags
.realized
) {
536 W_RealizeView(bPtr
->menuView
);
540 if (WMGetBagItemCount(bPtr
->items
) < 1)
543 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
,
544 0, 0, &x
, &y
, &dummyW
);
546 if (bPtr
->flags
.pullsDown
) {
547 y
+= bPtr
->view
->size
.height
;
549 y
-= bPtr
->view
->size
.height
*bPtr
->selectedItemIndex
;
551 W_MoveView(bPtr
->menuView
, x
, y
);
553 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
,
554 makeMenuPixmap(bPtr
));
555 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
557 W_MapView(bPtr
->menuView
);
559 bPtr
->highlightedItem
= 0;
560 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
561 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
566 popDownMenu(PopUpButton
*bPtr
)
568 W_UnmapView(bPtr
->menuView
);
570 /* free the background pixmap used to draw the menu contents */
571 XSetWindowBackgroundPixmap(bPtr
->view
->screen
->display
,
572 bPtr
->menuView
->window
, None
);
577 autoScroll(void *data
)
579 PopUpButton
*bPtr
= (PopUpButton
*)data
;
580 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
585 if (bPtr
->scrollStartY
>= scrHeight
-1
586 && bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
>= scrHeight
-1) {
589 if (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
-5
592 - (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
);
596 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
599 if (bPtr
->menuView
->pos
.y
+5 > 1)
600 dy
= 1 - bPtr
->menuView
->pos
.y
;
608 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
,
609 bPtr
->menuView
->pos
.y
+ dy
);
611 oldItem
= bPtr
->highlightedItem
;
612 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
613 / bPtr
->view
->size
.height
;
615 if (oldItem
!=bPtr
->highlightedItem
) {
618 paintMenuEntry(bPtr
, oldItem
, False
);
620 if (bPtr
->highlightedItem
>= 0 &&
621 bPtr
->highlightedItem
< WMGetBagItemCount(bPtr
->items
)) {
622 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
623 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
624 WMGetMenuItemEnabled(item
));
626 bPtr
->highlightedItem
= -1;
630 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
638 wheelScrollUp(PopUpButton
*bPtr
)
640 int testIndex
= bPtr
->selectedItemIndex
- 1;
642 while (testIndex
>=0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
644 if (testIndex
!= -1) {
645 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
647 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
653 wheelScrollDown(PopUpButton
*bPtr
)
655 int itemCount
= WMGetBagItemCount(bPtr
->items
);
656 int testIndex
= bPtr
->selectedItemIndex
+ 1;
658 while (testIndex
<itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
660 if (testIndex
!= itemCount
) {
661 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
663 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
669 handleActionEvents(XEvent
*event
, void *data
)
671 PopUpButton
*bPtr
= (PopUpButton
*)data
;
673 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
675 CHECK_CLASS(data
, WC_PopUpButton
);
677 if (WMGetBagItemCount(bPtr
->items
) < 1)
680 switch (event
->type
) {
681 /* called for menuView */
683 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
687 bPtr
->flags
.insideMenu
= 0;
688 if (bPtr
->menuView
->flags
.mapped
)
689 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
690 bPtr
->highlightedItem
= -1;
694 bPtr
->flags
.insideMenu
= 1;
698 if (bPtr
->flags
.insideMenu
) {
699 oldItem
= bPtr
->highlightedItem
;
700 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
701 if (oldItem
!=bPtr
->highlightedItem
) {
704 paintMenuEntry(bPtr
, oldItem
, False
);
705 if (bPtr
->highlightedItem
>= 0 &&
706 bPtr
->highlightedItem
< WMGetBagItemCount(bPtr
->items
)) {
707 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
708 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
709 WMGetMenuItemEnabled(item
));
711 bPtr
->highlightedItem
= -1;
716 if (event
->xmotion
.y_root
>= scrHeight
-1
717 || event
->xmotion
.y_root
<= 1) {
718 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
721 } else if (bPtr
->timer
) {
722 WMDeleteTimerHandler(bPtr
->timer
);
728 /* called for bPtr->view */
730 if (!bPtr
->flags
.enabled
)
733 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
) {
734 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
738 } else if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
739 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
740 wheelScrollDown(bPtr
);
745 if (!bPtr
->flags
.pullsDown
) {
746 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
747 bPtr
->flags
.insideMenu
= 1;
749 bPtr
->highlightedItem
= -1;
750 bPtr
->flags
.insideMenu
= 0;
752 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
753 False
, ButtonReleaseMask
|ButtonMotionMask
|EnterWindowMask
754 |LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
,
755 None
, None
, CurrentTime
);
759 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
||
760 event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
763 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
764 if (!bPtr
->flags
.pullsDown
)
768 WMDeleteTimerHandler(bPtr
->timer
);
772 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>=0) {
775 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
777 if (WMGetMenuItemEnabled(item
)) {
779 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
781 if (bPtr
->flags
.pullsDown
) {
782 for (i
=0; i
<MENU_BLINK_COUNT
; i
++) {
783 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
784 XSync(bPtr
->view
->screen
->display
, 0);
785 wusleep(MENU_BLINK_DELAY
);
786 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
787 XSync(bPtr
->view
->screen
->display
, 0);
788 wusleep(MENU_BLINK_DELAY
);
791 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
794 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
797 if (bPtr
->menuView
->flags
.mapped
)
806 destroyPopUpButton(PopUpButton
*bPtr
)
812 WMDeleteTimerHandler(bPtr
->timer
);
815 WM_ITERATE_BAG(bPtr
->items
, item
, i
) {
816 WMDestroyMenuItem(item
);
818 WMFreeBag(bPtr
->items
);
821 wfree(bPtr
->caption
);
823 /* have to destroy explicitly because the popup is a toplevel */
824 W_DestroyView(bPtr
->menuView
);