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;
95 WMCreateArrayWithDestructor(4, (WMFreeDataProc
*)WMDestroyMenuItem
);
97 bPtr
->selectedItemIndex
= -1;
99 bPtr
->menuView
= W_CreateUnmanagedTopView(scr
);
101 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, 1);
103 WMCreateEventHandler(bPtr
->menuView
, ButtonPressMask
|ButtonReleaseMask
104 |EnterWindowMask
|LeaveWindowMask
|ButtonMotionMask
105 |ExposureMask
, handleActionEvents
, bPtr
);
112 WMSetPopUpButtonAction(WMPopUpButton
*bPtr
, WMAction
*action
, void *clientData
)
114 CHECK_CLASS(bPtr
, WC_PopUpButton
);
116 bPtr
->action
= action
;
118 bPtr
->clientData
= clientData
;
123 WMAddPopUpButtonItem(WMPopUpButton
*bPtr
, char *title
)
127 CHECK_CLASS(bPtr
, WC_PopUpButton
);
129 item
= WMCreateMenuItem();
130 WMSetMenuItemTitle(item
, title
);
132 WMAddToArray(bPtr
->items
, item
);
134 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
142 WMInsertPopUpButtonItem(WMPopUpButton
*bPtr
, int index
, char *title
)
146 CHECK_CLASS(bPtr
, WC_PopUpButton
);
148 item
= WMCreateMenuItem();
149 WMSetMenuItemTitle(item
, title
);
151 WMInsertInArray(bPtr
->items
, index
, item
);
153 /* if there is an selected item, update it's index to match the new
155 if (index
< bPtr
->selectedItemIndex
)
156 bPtr
->selectedItemIndex
++;
158 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
166 WMRemovePopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
168 CHECK_CLASS(bPtr
, WC_PopUpButton
);
170 wassertr(index
>= 0 && index
< WMGetArrayItemCount(bPtr
->items
));
173 WMDeleteFromArray(bPtr
->items
, index
);
175 if (bPtr
->selectedItemIndex
>= 0 && !bPtr
->flags
.pullsDown
) {
176 if (index
< bPtr
->selectedItemIndex
)
177 bPtr
->selectedItemIndex
--;
178 else if (index
== bPtr
->selectedItemIndex
) {
179 /* reselect first item if the removed item is the
181 bPtr
->selectedItemIndex
= 0;
182 if (bPtr
->view
->flags
.mapped
)
183 paintPopUpButton(bPtr
);
187 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
193 WMSetPopUpButtonEnabled(WMPopUpButton
*bPtr
, Bool flag
)
195 bPtr
->flags
.enabled
= ((flag
==0) ? 0 : 1);
196 if (bPtr
->view
->flags
.mapped
)
197 paintPopUpButton(bPtr
);
202 WMGetPopUpButtonEnabled(WMPopUpButton
*bPtr
)
204 return bPtr
->flags
.enabled
;
209 WMSetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
, int index
)
212 wassertr(index
< WMGetArrayItemCount(bPtr
->items
));
214 /* if (index >= WMGetArrayCount(bPtr->items))
217 bPtr
->selectedItemIndex
= index
;
219 if (bPtr
->view
->flags
.mapped
)
220 paintPopUpButton(bPtr
);
225 WMGetPopUpButtonSelectedItem(WMPopUpButton
*bPtr
)
227 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
230 return bPtr
->selectedItemIndex
;
235 WMSetPopUpButtonText(WMPopUpButton
*bPtr
, char *text
)
238 wfree(bPtr
->caption
);
240 bPtr
->caption
= wstrdup(text
);
242 bPtr
->caption
= NULL
;
243 if (bPtr
->view
->flags
.realized
) {
244 if (bPtr
->flags
.pullsDown
|| bPtr
->selectedItemIndex
< 0) {
245 paintPopUpButton(bPtr
);
253 WMSetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
, Bool flag
)
255 WMSetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
), (flag
? 1 : 0));
260 WMGetPopUpButtonItemEnabled(WMPopUpButton
*bPtr
, int index
)
262 return WMGetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
));
267 WMSetPopUpButtonPullsDown(WMPopUpButton
*bPtr
, Bool flag
)
269 bPtr
->flags
.pullsDown
= ((flag
==0) ? 0 : 1);
271 bPtr
->selectedItemIndex
= -1;
274 if (bPtr
->view
->flags
.mapped
)
275 paintPopUpButton(bPtr
);
280 WMGetPopUpButtonNumberOfItems(WMPopUpButton
*bPtr
)
282 return WMGetArrayItemCount(bPtr
->items
);
287 WMGetPopUpButtonItem(WMPopUpButton
*bPtr
, int index
)
289 if (index
>= WMGetArrayItemCount(bPtr
->items
) || index
< 0)
292 return WMGetMenuItemTitle(WMGetFromArray(bPtr
->items
, index
));
297 WMGetPopUpButtonMenuItem(WMPopUpButton
*bPtr
, int index
)
299 return WMGetFromArray(bPtr
->items
, index
);
305 paintPopUpButton(PopUpButton
*bPtr
)
307 W_Screen
*scr
= bPtr
->view
->screen
;
312 if (bPtr
->flags
.pullsDown
) {
313 caption
= bPtr
->caption
;
315 if (bPtr
->selectedItemIndex
< 0) {
316 /* if no item selected, show the caption */
317 caption
= bPtr
->caption
;
319 caption
= WMGetPopUpButtonItem(bPtr
, bPtr
->selectedItemIndex
);
323 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
,
324 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
,
326 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
327 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
);
329 W_DrawRelief(scr
, pixmap
, 0, 0, bPtr
->view
->size
.width
,
330 bPtr
->view
->size
.height
, WRRaised
);
333 W_PaintText(bPtr
->view
, pixmap
, scr
->normalFont
, 6,
334 (bPtr
->view
->size
.height
-WMFontHeight(scr
->normalFont
))/2,
335 bPtr
->view
->size
.width
, WALeft
,
336 bPtr
->flags
.enabled
? scr
->black
: scr
->darkGray
,
337 False
, caption
, strlen(caption
));
340 if (bPtr
->flags
.pullsDown
) {
341 XCopyArea(scr
->display
, scr
->pullDownIndicator
->pixmap
,
342 pixmap
, scr
->copyGC
, 0, 0, scr
->pullDownIndicator
->width
,
343 scr
->pullDownIndicator
->height
,
344 bPtr
->view
->size
.width
-scr
->pullDownIndicator
->width
-4,
345 (bPtr
->view
->size
.height
-scr
->pullDownIndicator
->height
)/2);
349 x
= bPtr
->view
->size
.width
- scr
->popUpIndicator
->width
- 4;
350 y
= (bPtr
->view
->size
.height
-scr
->popUpIndicator
->height
)/2;
352 XSetClipOrigin(scr
->display
, scr
->clipGC
, x
, y
);
353 XSetClipMask(scr
->display
, scr
->clipGC
, scr
->popUpIndicator
->mask
);
354 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
355 scr
->clipGC
, 0, 0, scr
->popUpIndicator
->width
,
356 scr
->popUpIndicator
->height
, x
, y
);
359 XCopyArea(scr
->display
, pixmap
, bPtr
->view
->window
, scr
->copyGC
, 0, 0,
360 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, 0, 0);
362 XFreePixmap(scr
->display
, pixmap
);
368 handleEvents(XEvent
*event
, void *data
)
370 PopUpButton
*bPtr
= (PopUpButton
*)data
;
372 CHECK_CLASS(data
, WC_PopUpButton
);
375 switch (event
->type
) {
377 if (event
->xexpose
.count
!=0)
379 paintPopUpButton(bPtr
);
383 destroyPopUpButton(bPtr
);
391 paintMenuEntry(PopUpButton
*bPtr
, int index
, int highlight
)
393 W_Screen
*scr
= bPtr
->view
->screen
;
395 int width
, height
, itemHeight
, itemCount
;
398 itemCount
= WMGetArrayItemCount(bPtr
->items
);
399 if (index
< 0 || index
>= itemCount
)
402 itemHeight
= bPtr
->view
->size
.height
;
403 width
= bPtr
->view
->size
.width
;
404 height
= itemHeight
* itemCount
;
405 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
408 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
409 width
, itemHeight
, False
);
411 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
415 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
416 1, index
*itemHeight
+1, width
-3, itemHeight
-3);
418 title
= WMGetPopUpButtonItem(bPtr
, index
);
420 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
*itemHeight
,
421 width
, itemHeight
, WRRaised
);
423 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
424 index
*itemHeight
+ yo
, width
, WALeft
, scr
->black
,
425 False
, title
, strlen(title
));
427 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
428 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
429 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
430 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
431 width
-scr
->popUpIndicator
->width
-4,
432 index
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
438 makeMenuPixmap(PopUpButton
*bPtr
)
441 W_Screen
*scr
= bPtr
->view
->screen
;
443 WMArrayIterator iter
;
445 int width
, height
, itemHeight
;
447 itemHeight
= bPtr
->view
->size
.height
;
448 width
= bPtr
->view
->size
.width
;
449 height
= itemHeight
* WMGetArrayItemCount(bPtr
->items
);
450 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
))/2;
452 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
,
455 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
459 WM_ITERATE_ARRAY(bPtr
->items
, item
, iter
) {
463 text
= WMGetMenuItemTitle(item
);
465 W_DrawRelief(scr
, pixmap
, 0, i
*itemHeight
, width
, itemHeight
,
468 if (!WMGetMenuItemEnabled(item
))
469 color
= scr
->darkGray
;
473 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
474 i
*itemHeight
+ yo
, width
, WALeft
, color
, False
,
477 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
478 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
479 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
480 scr
->popUpIndicator
->height
,
481 width
-scr
->popUpIndicator
->width
-4,
482 i
*itemHeight
+(itemHeight
-scr
->popUpIndicator
->height
)/2);
493 resizeMenu(PopUpButton
*bPtr
)
497 height
= WMGetArrayItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
499 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
504 popUpMenu(PopUpButton
*bPtr
)
506 W_Screen
*scr
= bPtr
->view
->screen
;
510 if (!bPtr
->flags
.enabled
)
513 if (!bPtr
->menuView
->flags
.realized
) {
514 W_RealizeView(bPtr
->menuView
);
518 if (WMGetArrayItemCount(bPtr
->items
) < 1)
521 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
,
522 0, 0, &x
, &y
, &dummyW
);
524 if (bPtr
->flags
.pullsDown
) {
525 y
+= bPtr
->view
->size
.height
;
527 y
-= bPtr
->view
->size
.height
*bPtr
->selectedItemIndex
;
529 W_MoveView(bPtr
->menuView
, x
, y
);
531 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
,
532 makeMenuPixmap(bPtr
));
533 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
535 if (W_VIEW_WIDTH(bPtr
->menuView
) != W_VIEW_WIDTH(bPtr
->view
))
538 W_MapView(bPtr
->menuView
);
540 bPtr
->highlightedItem
= 0;
541 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
542 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
547 popDownMenu(PopUpButton
*bPtr
)
549 W_UnmapView(bPtr
->menuView
);
554 autoScroll(void *data
)
556 PopUpButton
*bPtr
= (PopUpButton
*)data
;
557 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
562 if (bPtr
->scrollStartY
>= scrHeight
-1
563 && bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
>= scrHeight
-1) {
566 if (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
-5
569 - (bPtr
->menuView
->pos
.y
+bPtr
->menuView
->size
.height
);
573 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
576 if (bPtr
->menuView
->pos
.y
+5 > 1)
577 dy
= 1 - bPtr
->menuView
->pos
.y
;
585 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
,
586 bPtr
->menuView
->pos
.y
+ dy
);
588 oldItem
= bPtr
->highlightedItem
;
589 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
590 / bPtr
->view
->size
.height
;
592 if (oldItem
!=bPtr
->highlightedItem
) {
595 paintMenuEntry(bPtr
, oldItem
, False
);
597 if (bPtr
->highlightedItem
>= 0 &&
598 bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
599 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
600 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
601 WMGetMenuItemEnabled(item
));
603 bPtr
->highlightedItem
= -1;
607 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
615 wheelScrollUp(PopUpButton
*bPtr
)
617 int testIndex
= bPtr
->selectedItemIndex
- 1;
619 while (testIndex
>=0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
621 if (testIndex
!= -1) {
622 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
624 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
630 wheelScrollDown(PopUpButton
*bPtr
)
632 int itemCount
= WMGetArrayItemCount(bPtr
->items
);
633 int testIndex
= bPtr
->selectedItemIndex
+ 1;
635 while (testIndex
<itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
637 if (testIndex
!= itemCount
) {
638 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
640 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
646 handleActionEvents(XEvent
*event
, void *data
)
648 PopUpButton
*bPtr
= (PopUpButton
*)data
;
650 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
652 CHECK_CLASS(data
, WC_PopUpButton
);
654 if (WMGetArrayItemCount(bPtr
->items
) < 1)
657 switch (event
->type
) {
658 /* called for menuView */
660 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
664 bPtr
->flags
.insideMenu
= 0;
665 if (bPtr
->menuView
->flags
.mapped
)
666 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
667 bPtr
->highlightedItem
= -1;
671 bPtr
->flags
.insideMenu
= 1;
675 if (bPtr
->flags
.insideMenu
) {
676 oldItem
= bPtr
->highlightedItem
;
677 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
678 if (oldItem
!=bPtr
->highlightedItem
) {
681 paintMenuEntry(bPtr
, oldItem
, False
);
682 if (bPtr
->highlightedItem
>= 0 &&
683 bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
684 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
685 paintMenuEntry(bPtr
, bPtr
->highlightedItem
,
686 WMGetMenuItemEnabled(item
));
688 bPtr
->highlightedItem
= -1;
693 if (event
->xmotion
.y_root
>= scrHeight
-1
694 || event
->xmotion
.y_root
<= 1) {
695 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
698 } else if (bPtr
->timer
) {
699 WMDeleteTimerHandler(bPtr
->timer
);
705 /* called for bPtr->view */
707 if (!bPtr
->flags
.enabled
)
710 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
) {
711 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
715 } else if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
716 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
717 wheelScrollDown(bPtr
);
722 if (!bPtr
->flags
.pullsDown
) {
723 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
724 bPtr
->flags
.insideMenu
= 1;
726 bPtr
->highlightedItem
= -1;
727 bPtr
->flags
.insideMenu
= 0;
729 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
730 False
, ButtonReleaseMask
|ButtonMotionMask
|EnterWindowMask
731 |LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
,
732 None
, None
, CurrentTime
);
736 if (event
->xbutton
.button
==WINGsConfiguration
.mouseWheelUp
||
737 event
->xbutton
.button
==WINGsConfiguration
.mouseWheelDown
) {
740 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
741 if (!bPtr
->flags
.pullsDown
)
745 WMDeleteTimerHandler(bPtr
->timer
);
749 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>=0) {
752 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
754 if (WMGetMenuItemEnabled(item
)) {
756 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
758 if (bPtr
->flags
.pullsDown
) {
759 for (i
=0; i
<MENU_BLINK_COUNT
; i
++) {
760 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
761 XSync(bPtr
->view
->screen
->display
, 0);
762 wusleep(MENU_BLINK_DELAY
);
763 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
764 XSync(bPtr
->view
->screen
->display
, 0);
765 wusleep(MENU_BLINK_DELAY
);
768 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
771 (*bPtr
->action
)(bPtr
, bPtr
->clientData
);
774 if (bPtr
->menuView
->flags
.mapped
)
783 destroyPopUpButton(PopUpButton
*bPtr
)
786 WMDeleteTimerHandler(bPtr
->timer
);
789 WMFreeArray(bPtr
->items
);
792 wfree(bPtr
->caption
);
794 /* have to destroy explicitly because the popup is a toplevel */
795 W_DestroyView(bPtr
->menuView
);