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_CreateUnmanagedTopView(scr
);
100 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, 1);
102 WMCreateEventHandler(bPtr
->menuView
, ButtonPressMask
|ButtonReleaseMask
103 |EnterWindowMask
|LeaveWindowMask
|ButtonMotionMask
104 |ExposureMask
, handleActionEvents
, bPtr
);
111 WMSetPopUpButtonAction(WMPopUpButton
*bPtr
, WMAction
*action
, void *clientData
)
113 CHECK_CLASS(bPtr
, WC_PopUpButton
);
115 bPtr
->action
= action
;
117 bPtr
->clientData
= clientData
;
122 WMAddPopUpButtonItem(WMPopUpButton
*bPtr
, char *title
)
126 CHECK_CLASS(bPtr
, WC_PopUpButton
);
128 item
= WMCreateMenuItem();
129 WMSetMenuItemTitle(item
, title
);
131 WMPutInBag(bPtr
->items
, item
);
133 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
141 WMInsertPopUpButtonItem(WMPopUpButton
*bPtr
, int index
, char *title
)
145 CHECK_CLASS(bPtr
, WC_PopUpButton
);
147 item
= WMCreateMenuItem();
148 WMSetMenuItemTitle(item
, title
);
150 WMInsertInBag(bPtr
->items
, index
, item
);
152 /* if there is an selected item, update it's index to match the new
154 if (index
< bPtr
->selectedItemIndex
)
155 bPtr
->selectedItemIndex
++;
157 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
165 WMRemovePopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
169 CHECK_CLASS(bPtr
, WC_PopUpButton
);
171 wassertr(index
>= 0 && index
< WMGetBagItemCount(bPtr
->items
));
174 item
= WMGetFromBag(bPtr
->items
, index
);
175 WMDeleteFromBag(bPtr
->items
, index
);
177 WMDestroyMenuItem(item
);
179 if (bPtr
->selectedItemIndex
>= 0 && !bPtr
->flags
.pullsDown
) {
180 if (index
< bPtr
->selectedItemIndex
)
181 bPtr
->selectedItemIndex
--;
182 else if (index
== bPtr
->selectedItemIndex
) {
183 /* reselect first item if the removed item is the
185 bPtr
->selectedItemIndex
= 0;
186 if (bPtr
->view
->flags
.mapped
)
187 paintPopUpButton(bPtr
);
191 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
197 WMSetPopUpButtonEnabled(WMPopUpButton
*bPtr
, Bool flag
)
199 bPtr
->flags
.enabled
= flag
;
200 if (bPtr
->view
->flags
.mapped
)
201 paintPopUpButton(bPtr
);
206 WMGetPopUpButtonEnabled(WMPopUpButton
*bPtr
)
208 return bPtr
->flags
.enabled
;
213 WMSetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
, int index
)
216 wassertr(index
< WMGetBagItemCount(bPtr
->items
));
218 /* if (index >= WMGetBagCount(bPtr->items))
221 bPtr
->selectedItemIndex
= index
;
223 if (bPtr
->view
->flags
.mapped
)
224 paintPopUpButton(bPtr
);
229 WMGetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
)
231 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
234 return bPtr
->selectedItemIndex
;
239 WMSetPopUpButtonText(WMPopUpButton
*bPtr
, char *text
)
242 wfree(bPtr
->caption
);
244 bPtr
->caption
= wstrdup(text
);
246 bPtr
->caption
= NULL
;
247 if (bPtr
->view
->flags
.realized
) {
248 if (bPtr
->flags
.pullsDown
|| bPtr
->selectedItemIndex
< 0) {
249 paintPopUpButton(bPtr
);
257 WMSetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
, Bool flag
)
261 item
= WMGetFromBag(bPtr
->items
, index
);
262 wassertr(item
!= NULL
);
264 WMSetMenuItemEnabled(item
, flag
);
269 WMGetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
)
273 item
= WMGetFromBag(bPtr
->items
, index
);
274 wassertrv(item
!= NULL
, False
);
276 return WMGetMenuItemEnabled(item
);
281 WMSetPopUpButtonPullsDown(WMPopUpButton
*bPtr
, Bool flag
)
283 bPtr
->flags
.pullsDown
= flag
;
285 bPtr
->selectedItemIndex
= -1;
288 if (bPtr
->view
->flags
.mapped
)
289 paintPopUpButton(bPtr
);
294 WMGetPopUpButtonNumberOfItems(WMPopUpButton
*bPtr
)
296 return WMGetBagItemCount(bPtr
->items
);
301 WMGetPopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
305 if (index
>= WMGetBagItemCount(bPtr
->items
) || index
< 0)
308 item
= WMGetFromBag(bPtr
->items
, index
);
312 return WMGetMenuItemTitle(item
);
317 WMGetPopUpButtonMenuItem(WMPopUpButton
*bPtr
, int index
)
321 item
= WMGetFromBag(bPtr
->items
, index
);
329 paintPopUpButton(PopUpButton
*bPtr
)
331 W_Screen
*scr
= bPtr
->view
->screen
;
336 if (bPtr
->flags
.pullsDown
) {
337 caption
= bPtr
->caption
;
339 if (bPtr
->selectedItemIndex
< 0) {
340 /* if no item selected, show the caption */
341 caption
= bPtr
->caption
;
343 caption
= WMGetPopUpButtonItem(bPtr
, bPtr
->selectedItemIndex
);
347 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
,
348 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
,
350 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
351 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
);
353 W_DrawRelief(scr
, pixmap
, 0, 0, bPtr
->view
->size
.width
,
354 bPtr
->view
->size
.height
, WRRaised
);
357 W_PaintText(bPtr
->view
, pixmap
, scr
->normalFont
, 6,
358 (bPtr
->view
->size
.height
-WMFontHeight(scr
->normalFont
))/2,
359 bPtr
->view
->size
.width
, WALeft
,
360 bPtr
->flags
.enabled
? WMColorGC(scr
->black
) : WMColorGC(scr
->darkGray
),
361 False
, caption
, strlen(caption
));
364 if (bPtr
->flags
.pullsDown
) {
365 XCopyArea(scr
->display
, scr
->pullDownIndicator
->pixmap
,
366 pixmap
, scr
->copyGC
, 0, 0, scr
->pullDownIndicator
->width
,
367 scr
->pullDownIndicator
->height
,
368 bPtr
->view
->size
.width
-scr
->pullDownIndicator
->width
-4,
369 (bPtr
->view
->size
.height
-scr
->pullDownIndicator
->height
)/2);
373 x
= bPtr
->view
->size
.width
- scr
->popUpIndicator
->width
- 4;
374 y
= (bPtr
->view
->size
.height
-scr
->popUpIndicator
->height
)/2;
376 XSetClipOrigin(scr
->display
, scr
->clipGC
, x
, y
);
377 XSetClipMask(scr
->display
, scr
->clipGC
, scr
->popUpIndicator
->mask
);
378 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
379 scr
->clipGC
, 0, 0, scr
->popUpIndicator
->width
,
380 scr
->popUpIndicator
->height
, x
, y
);
383 XCopyArea(scr
->display
, pixmap
, bPtr
->view
->window
, scr
->copyGC
, 0, 0,
384 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, 0, 0);
386 XFreePixmap(scr
->display
, pixmap
);
392 handleEvents(XEvent
*event
, void *data
)
394 PopUpButton
*bPtr
= (PopUpButton
*)data
;
396 CHECK_CLASS(data
, WC_PopUpButton
);
399 switch (event
->type
) {
401 if (event
->xexpose
.count
!=0)
403 paintPopUpButton(bPtr
);
407 destroyPopUpButton(bPtr
);
415 paintMenuEntry(PopUpButton
*bPtr
, int index
, int highlight
)
417 W_Screen
*scr
= bPtr
->view
->screen
;
419 int width
, height
, itemHeight
, itemCount
;
422 itemCount
= WMGetBagItemCount(bPtr
->items
);
423 if (index
< 0 || index
>= itemCount
)
426 itemHeight
= bPtr
->view
->size
.height
;
427 width
= bPtr
->view
->size
.width
;
428 height
= itemHeight
* itemCount
;
429 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
432 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
433 width
, itemHeight
, False
);
435 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
439 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
440 1, index
*itemHeight
+1, width
-3, itemHeight
-3);
442 title
= WMGetPopUpButtonItem(bPtr
, index
);
444 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
445 width
, itemHeight
, WRRaised
);
447 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
448 index
*itemHeight
+ yo
, width
, WALeft
, WMColorGC(scr
->black
),
449 False
, title
, strlen(title
));
451 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
452 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
453 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
454 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
455 width
-scr
->popUpIndicator
->width
-4,
456 index
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
462 makeMenuPixmap(PopUpButton
*bPtr
)
465 W_Screen
*scr
= bPtr
->view
->screen
;
469 int width
, height
, itemHeight
;
471 itemHeight
= bPtr
->view
->size
.height
;
472 width
= bPtr
->view
->size
.width
;
473 height
= itemHeight
* WMGetBagItemCount(bPtr
->items
);
474 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
476 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
,
479 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
483 WM_ITERATE_BAG(bPtr
->items
, item
, iter
) {
487 text
= WMGetMenuItemTitle(item
);
489 W_DrawRelief(scr
, pixmap
, 0, i
*itemHeight
, width
, itemHeight
,
492 if (!WMGetMenuItemEnabled(item
))
493 gc
= WMColorGC(scr
->darkGray
);
495 gc
= WMColorGC(scr
->black
);
497 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
498 i
*itemHeight
+ yo
, width
, WALeft
, gc
, False
,
501 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
502 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
503 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
504 scr
->popUpIndicator
->height
,
505 width
-scr
->popUpIndicator
->width
-4,
506 i
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
517 resizeMenu(PopUpButton
*bPtr
)
521 height
= WMGetBagItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
523 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
528 popUpMenu(PopUpButton
*bPtr
)
530 W_Screen
*scr
= bPtr
->view
->screen
;
534 if (!bPtr
->menuView
->flags
.realized
) {
535 W_RealizeView(bPtr
->menuView
);
539 if (WMGetBagItemCount(bPtr
->items
) < 1)
542 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
,
543 0, 0, &x
, &y
, &dummyW
);
545 if (bPtr
->flags
.pullsDown
) {
546 y
+= bPtr
->view
->size
.height
;
548 y
-= bPtr
->view
->size
.height
*bPtr
->selectedItemIndex
;
550 W_MoveView(bPtr
->menuView
, x
, y
);
552 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
,
553 makeMenuPixmap(bPtr
));
554 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
556 W_MapView(bPtr
->menuView
);
558 bPtr
->highlightedItem
= 0;
559 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
560 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
565 popDownMenu(PopUpButton
*bPtr
)
567 W_UnmapView(bPtr
->menuView
);
569 /* free the background pixmap used to draw the menu contents */
570 XSetWindowBackgroundPixmap(bPtr
->view
->screen
->display
,
571 bPtr
->menuView
->window
, None
);
576 autoScroll(void *data
)
578 PopUpButton
*bPtr
= (PopUpButton
*)data
;
579 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
584 if (bPtr
->scrollStartY
>= scrHeight
-1
585 && bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
>= scrHeight
-1) {
588 if (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
-5
591 - (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
);
595 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
598 if (bPtr
->menuView
->pos
.y
+5 > 1)
599 dy
= 1 - bPtr
->menuView
->pos
.y
;
607 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
,
608 bPtr
->menuView
->pos
.y
+ dy
);
610 oldItem
= bPtr
->highlightedItem
;
611 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
612 / bPtr
->view
->size
.height
;
614 if (oldItem
!=bPtr
->highlightedItem
) {
617 paintMenuEntry(bPtr
, oldItem
, False
);
619 if (bPtr
->highlightedItem
>= 0 &&
620 bPtr
->highlightedItem
< WMGetBagItemCount(bPtr
->items
)) {
621 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
622 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
623 WMGetMenuItemEnabled(item
));
625 bPtr
->highlightedItem
= -1;
629 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
637 wheelScrollUp(PopUpButton
*bPtr
)
639 int testIndex
= bPtr
->selectedItemIndex
- 1;
641 while (testIndex
>=0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
643 if (testIndex
!= -1) {
644 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
646 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
652 wheelScrollDown(PopUpButton
*bPtr
)
654 int itemCount
= WMGetBagItemCount(bPtr
->items
);
655 int testIndex
= bPtr
->selectedItemIndex
+ 1;
657 while (testIndex
<itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
659 if (testIndex
!= itemCount
) {
660 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
662 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
668 handleActionEvents(XEvent
*event
, void *data
)
670 PopUpButton
*bPtr
= (PopUpButton
*)data
;
672 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
674 CHECK_CLASS(data
, WC_PopUpButton
);
676 if (WMGetBagItemCount(bPtr
->items
) < 1)
679 switch (event
->type
) {
680 /* called for menuView */
682 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
686 bPtr
->flags
.insideMenu
= 0;
687 if (bPtr
->menuView
->flags
.mapped
)
688 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
689 bPtr
->highlightedItem
= -1;
693 bPtr
->flags
.insideMenu
= 1;
697 if (bPtr
->flags
.insideMenu
) {
698 oldItem
= bPtr
->highlightedItem
;
699 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
700 if (oldItem
!=bPtr
->highlightedItem
) {
703 paintMenuEntry(bPtr
, oldItem
, False
);
704 if (bPtr
->highlightedItem
>= 0 &&
705 bPtr
->highlightedItem
< WMGetBagItemCount(bPtr
->items
)) {
706 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
707 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
708 WMGetMenuItemEnabled(item
));
710 bPtr
->highlightedItem
= -1;
715 if (event
->xmotion
.y_root
>= scrHeight
-1
716 || event
->xmotion
.y_root
<= 1) {
717 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
720 } else if (bPtr
->timer
) {
721 WMDeleteTimerHandler(bPtr
->timer
);
727 /* called for bPtr->view */
729 if (!bPtr
->flags
.enabled
)
732 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
) {
733 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
737 } else if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
738 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
739 wheelScrollDown(bPtr
);
744 if (!bPtr
->flags
.pullsDown
) {
745 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
746 bPtr
->flags
.insideMenu
= 1;
748 bPtr
->highlightedItem
= -1;
749 bPtr
->flags
.insideMenu
= 0;
751 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
752 False
, ButtonReleaseMask
|ButtonMotionMask
|EnterWindowMask
753 |LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
,
754 None
, None
, CurrentTime
);
758 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
||
759 event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
762 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
763 if (!bPtr
->flags
.pullsDown
)
767 WMDeleteTimerHandler(bPtr
->timer
);
771 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>=0) {
774 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
776 if (WMGetMenuItemEnabled(item
)) {
778 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
780 if (bPtr
->flags
.pullsDown
) {
781 for (i
=0; i
<MENU_BLINK_COUNT
; i
++) {
782 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
783 XSync(bPtr
->view
->screen
->display
, 0);
784 wusleep(MENU_BLINK_DELAY
);
785 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
786 XSync(bPtr
->view
->screen
->display
, 0);
787 wusleep(MENU_BLINK_DELAY
);
790 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
793 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
796 if (bPtr
->menuView
->flags
.mapped
)
805 destroyPopUpButton(PopUpButton
*bPtr
)
811 WMDeleteTimerHandler(bPtr
->timer
);
814 WM_ITERATE_BAG(bPtr
->items
, item
, i
) {
815 WMDestroyMenuItem(item
);
817 WMFreeBag(bPtr
->items
);
820 wfree(bPtr
->caption
);
822 /* have to destroy explicitly because the popup is a toplevel */
823 W_DestroyView(bPtr
->menuView
);