4 typedef struct W_PopUpButton
{
15 short selectedItemIndex
;
17 short highlightedItem
;
19 WMView
*menuView
; /* override redirect popup menu */
21 WMHandlerID timer
; /* for autoscroll */
23 /**/ int scrollStartY
; /* for autoscroll */
26 unsigned int pullsDown
:1;
28 unsigned int configured
:1;
30 unsigned int insideMenu
:1;
32 unsigned int enabled
:1;
37 #define MENU_BLINK_DELAY 60000
38 #define MENU_BLINK_COUNT 2
40 #define SCROLL_DELAY 10
42 #define DEFAULT_WIDTH 60
43 #define DEFAULT_HEIGHT 20
44 #define DEFAULT_CAPTION ""
46 static void destroyPopUpButton(PopUpButton
* bPtr
);
47 static void paintPopUpButton(PopUpButton
* bPtr
);
49 static void handleEvents(XEvent
* event
, void *data
);
50 static void handleActionEvents(XEvent
* event
, void *data
);
52 static void resizeMenu(PopUpButton
* bPtr
);
54 WMPopUpButton
*WMCreatePopUpButton(WMWidget
* parent
)
57 W_Screen
*scr
= W_VIEW(parent
)->screen
;
59 bPtr
= wmalloc(sizeof(PopUpButton
));
61 bPtr
->widgetClass
= WC_PopUpButton
;
63 bPtr
->view
= W_CreateView(W_VIEW(parent
));
68 bPtr
->view
->self
= bPtr
;
70 WMCreateEventHandler(bPtr
->view
, ExposureMask
| StructureNotifyMask
71 | ClientMessageMask
, handleEvents
, bPtr
);
73 W_ResizeView(bPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
74 bPtr
->caption
= wstrdup(DEFAULT_CAPTION
);
76 WMCreateEventHandler(bPtr
->view
, ButtonPressMask
| ButtonReleaseMask
, handleActionEvents
, bPtr
);
78 bPtr
->flags
.enabled
= 1;
80 bPtr
->items
= WMCreateArrayWithDestructor(4, (WMFreeDataProc
*) WMDestroyMenuItem
);
82 bPtr
->selectedItemIndex
= -1;
84 bPtr
->menuView
= W_CreateUnmanagedTopView(scr
);
86 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, 1);
88 WMCreateEventHandler(bPtr
->menuView
, ButtonPressMask
| ButtonReleaseMask
89 | EnterWindowMask
| LeaveWindowMask
| ButtonMotionMask
90 | ExposureMask
, handleActionEvents
, bPtr
);
95 void WMSetPopUpButtonAction(WMPopUpButton
* bPtr
, WMAction
* action
, void *clientData
)
97 CHECK_CLASS(bPtr
, WC_PopUpButton
);
99 bPtr
->action
= action
;
101 bPtr
->clientData
= clientData
;
104 WMMenuItem
*WMAddPopUpButtonItem(WMPopUpButton
* bPtr
, char *title
)
108 CHECK_CLASS(bPtr
, WC_PopUpButton
);
110 item
= WMCreateMenuItem();
111 WMSetMenuItemTitle(item
, title
);
113 WMAddToArray(bPtr
->items
, item
);
115 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
121 WMMenuItem
*WMInsertPopUpButtonItem(WMPopUpButton
* bPtr
, int index
, char *title
)
125 CHECK_CLASS(bPtr
, WC_PopUpButton
);
127 item
= WMCreateMenuItem();
128 WMSetMenuItemTitle(item
, title
);
130 WMInsertInArray(bPtr
->items
, index
, item
);
132 /* if there is an selected item, update it's index to match the new
134 if (index
< bPtr
->selectedItemIndex
)
135 bPtr
->selectedItemIndex
++;
137 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
143 void WMRemovePopUpButtonItem(WMPopUpButton
* bPtr
, int index
)
145 CHECK_CLASS(bPtr
, WC_PopUpButton
);
147 wassertr(index
>= 0 && index
< WMGetArrayItemCount(bPtr
->items
));
149 WMDeleteFromArray(bPtr
->items
, index
);
151 if (bPtr
->selectedItemIndex
>= 0 && !bPtr
->flags
.pullsDown
) {
152 if (index
< bPtr
->selectedItemIndex
)
153 bPtr
->selectedItemIndex
--;
154 else if (index
== bPtr
->selectedItemIndex
) {
155 /* reselect first item if the removed item is the
157 bPtr
->selectedItemIndex
= 0;
158 if (bPtr
->view
->flags
.mapped
)
159 paintPopUpButton(bPtr
);
163 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
167 void WMSetPopUpButtonEnabled(WMPopUpButton
* bPtr
, Bool flag
)
169 bPtr
->flags
.enabled
= ((flag
== 0) ? 0 : 1);
170 if (bPtr
->view
->flags
.mapped
)
171 paintPopUpButton(bPtr
);
174 Bool
WMGetPopUpButtonEnabled(WMPopUpButton
* bPtr
)
176 return bPtr
->flags
.enabled
;
179 void WMSetPopUpButtonSelectedItem(WMPopUpButton
* bPtr
, int index
)
182 wassertr(index
< WMGetArrayItemCount(bPtr
->items
));
184 /* if (index >= WMGetArrayCount(bPtr->items))
187 bPtr
->selectedItemIndex
= index
;
189 if (bPtr
->view
->flags
.mapped
)
190 paintPopUpButton(bPtr
);
193 int WMGetPopUpButtonSelectedItem(WMPopUpButton
* bPtr
)
195 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
198 return bPtr
->selectedItemIndex
;
201 void WMSetPopUpButtonText(WMPopUpButton
* bPtr
, char *text
)
204 wfree(bPtr
->caption
);
206 bPtr
->caption
= wstrdup(text
);
208 bPtr
->caption
= NULL
;
209 if (bPtr
->view
->flags
.realized
) {
210 if (bPtr
->flags
.pullsDown
|| bPtr
->selectedItemIndex
< 0) {
211 paintPopUpButton(bPtr
);
216 void WMSetPopUpButtonItemEnabled(WMPopUpButton
* bPtr
, int index
, Bool flag
)
218 WMSetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
), (flag
? 1 : 0));
221 Bool
WMGetPopUpButtonItemEnabled(WMPopUpButton
* bPtr
, int index
)
223 return WMGetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
));
226 void WMSetPopUpButtonPullsDown(WMPopUpButton
* bPtr
, Bool flag
)
228 bPtr
->flags
.pullsDown
= ((flag
== 0) ? 0 : 1);
230 bPtr
->selectedItemIndex
= -1;
233 if (bPtr
->view
->flags
.mapped
)
234 paintPopUpButton(bPtr
);
237 int WMGetPopUpButtonNumberOfItems(WMPopUpButton
* bPtr
)
239 return WMGetArrayItemCount(bPtr
->items
);
242 char *WMGetPopUpButtonItem(WMPopUpButton
* bPtr
, int index
)
244 if (index
>= WMGetArrayItemCount(bPtr
->items
) || index
< 0)
247 return WMGetMenuItemTitle(WMGetFromArray(bPtr
->items
, index
));
250 WMMenuItem
*WMGetPopUpButtonMenuItem(WMPopUpButton
* bPtr
, int index
)
252 return WMGetFromArray(bPtr
->items
, index
);
255 static void paintPopUpButton(PopUpButton
* bPtr
)
257 W_Screen
*scr
= bPtr
->view
->screen
;
261 if (bPtr
->flags
.pullsDown
) {
262 caption
= bPtr
->caption
;
264 if (bPtr
->selectedItemIndex
< 0) {
265 /* if no item selected, show the caption */
266 caption
= bPtr
->caption
;
268 caption
= WMGetPopUpButtonItem(bPtr
, bPtr
->selectedItemIndex
);
272 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
,
273 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, scr
->depth
);
274 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
275 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
);
277 W_DrawRelief(scr
, pixmap
, 0, 0, bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, WRRaised
);
280 W_PaintText(bPtr
->view
, pixmap
, scr
->normalFont
, 6,
281 (bPtr
->view
->size
.height
- WMFontHeight(scr
->normalFont
)) / 2,
282 bPtr
->view
->size
.width
, WALeft
,
283 bPtr
->flags
.enabled
? scr
->black
: scr
->darkGray
, False
, caption
, strlen(caption
));
286 if (bPtr
->flags
.pullsDown
) {
287 XCopyArea(scr
->display
, scr
->pullDownIndicator
->pixmap
,
288 pixmap
, scr
->copyGC
, 0, 0, scr
->pullDownIndicator
->width
,
289 scr
->pullDownIndicator
->height
,
290 bPtr
->view
->size
.width
- scr
->pullDownIndicator
->width
- 4,
291 (bPtr
->view
->size
.height
- scr
->pullDownIndicator
->height
) / 2);
295 x
= bPtr
->view
->size
.width
- scr
->popUpIndicator
->width
- 4;
296 y
= (bPtr
->view
->size
.height
- scr
->popUpIndicator
->height
) / 2;
298 XSetClipOrigin(scr
->display
, scr
->clipGC
, x
, y
);
299 XSetClipMask(scr
->display
, scr
->clipGC
, scr
->popUpIndicator
->mask
);
300 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
301 scr
->clipGC
, 0, 0, scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
, x
, y
);
304 XCopyArea(scr
->display
, pixmap
, bPtr
->view
->window
, scr
->copyGC
, 0, 0,
305 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, 0, 0);
307 XFreePixmap(scr
->display
, pixmap
);
310 static void handleEvents(XEvent
* event
, void *data
)
312 PopUpButton
*bPtr
= (PopUpButton
*) data
;
314 CHECK_CLASS(data
, WC_PopUpButton
);
316 switch (event
->type
) {
318 if (event
->xexpose
.count
!= 0)
320 paintPopUpButton(bPtr
);
324 destroyPopUpButton(bPtr
);
329 static void paintMenuEntry(PopUpButton
* bPtr
, int index
, int highlight
)
331 W_Screen
*scr
= bPtr
->view
->screen
;
333 int width
, itemHeight
, itemCount
;
336 itemCount
= WMGetArrayItemCount(bPtr
->items
);
337 if (index
< 0 || index
>= itemCount
)
340 itemHeight
= bPtr
->view
->size
.height
;
341 width
= bPtr
->view
->size
.width
;
342 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
345 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, False
);
347 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
351 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
352 1, index
* itemHeight
+ 1, width
- 3, itemHeight
- 3);
354 title
= WMGetPopUpButtonItem(bPtr
, index
);
356 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, WRRaised
);
358 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
359 index
* itemHeight
+ yo
, width
, WALeft
, scr
->black
, False
, title
, strlen(title
));
361 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
362 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
363 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
364 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
365 width
- scr
->popUpIndicator
->width
- 4,
366 index
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
370 Pixmap
makeMenuPixmap(PopUpButton
* bPtr
)
373 W_Screen
*scr
= bPtr
->view
->screen
;
375 WMArrayIterator iter
;
377 int width
, height
, itemHeight
;
379 itemHeight
= bPtr
->view
->size
.height
;
380 width
= bPtr
->view
->size
.width
;
381 height
= itemHeight
* WMGetArrayItemCount(bPtr
->items
);
382 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
384 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
, scr
->depth
);
386 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0, width
, height
);
389 WM_ITERATE_ARRAY(bPtr
->items
, item
, iter
) {
393 text
= WMGetMenuItemTitle(item
);
395 W_DrawRelief(scr
, pixmap
, 0, i
* itemHeight
, width
, itemHeight
, WRRaised
);
397 if (!WMGetMenuItemEnabled(item
))
398 color
= scr
->darkGray
;
402 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
403 i
* itemHeight
+ yo
, width
, WALeft
, color
, False
, text
, strlen(text
));
405 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
406 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
407 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
408 scr
->popUpIndicator
->height
,
409 width
- scr
->popUpIndicator
->width
- 4,
410 i
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
419 static void resizeMenu(PopUpButton
* bPtr
)
423 height
= WMGetArrayItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
425 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
428 static void popUpMenu(PopUpButton
* bPtr
)
430 W_Screen
*scr
= bPtr
->view
->screen
;
434 if (!bPtr
->flags
.enabled
)
437 if (!bPtr
->menuView
->flags
.realized
) {
438 W_RealizeView(bPtr
->menuView
);
442 if (WMGetArrayItemCount(bPtr
->items
) < 1)
445 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
, 0, 0, &x
, &y
, &dummyW
);
447 if (bPtr
->flags
.pullsDown
) {
448 y
+= bPtr
->view
->size
.height
;
450 y
-= bPtr
->view
->size
.height
* bPtr
->selectedItemIndex
;
452 W_MoveView(bPtr
->menuView
, x
, y
);
454 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
, makeMenuPixmap(bPtr
));
455 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
457 if (W_VIEW_WIDTH(bPtr
->menuView
) != W_VIEW_WIDTH(bPtr
->view
))
460 W_MapView(bPtr
->menuView
);
462 bPtr
->highlightedItem
= 0;
463 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
464 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
467 static void popDownMenu(PopUpButton
* bPtr
)
469 W_UnmapView(bPtr
->menuView
);
472 static void autoScroll(void *data
)
474 PopUpButton
*bPtr
= (PopUpButton
*) data
;
475 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
479 if (bPtr
->scrollStartY
>= scrHeight
- 1
480 && bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
>= scrHeight
- 1) {
483 if (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
- 5 <= scrHeight
- 1) {
484 dy
= scrHeight
- 1 - (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
);
488 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
491 if (bPtr
->menuView
->pos
.y
+ 5 > 1)
492 dy
= 1 - bPtr
->menuView
->pos
.y
;
500 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
, bPtr
->menuView
->pos
.y
+ dy
);
502 oldItem
= bPtr
->highlightedItem
;
503 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
504 / bPtr
->view
->size
.height
;
506 if (oldItem
!= bPtr
->highlightedItem
) {
509 paintMenuEntry(bPtr
, oldItem
, False
);
511 if (bPtr
->highlightedItem
>= 0 && bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
512 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
513 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
515 bPtr
->highlightedItem
= -1;
519 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
525 static void wheelScrollUp(PopUpButton
* bPtr
)
527 int testIndex
= bPtr
->selectedItemIndex
- 1;
529 while (testIndex
>= 0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
531 if (testIndex
!= -1) {
532 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
534 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
538 static void wheelScrollDown(PopUpButton
* bPtr
)
540 int itemCount
= WMGetArrayItemCount(bPtr
->items
);
541 int testIndex
= bPtr
->selectedItemIndex
+ 1;
543 while (testIndex
< itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
545 if (testIndex
!= itemCount
) {
546 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
548 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
552 static void handleActionEvents(XEvent
* event
, void *data
)
554 PopUpButton
*bPtr
= (PopUpButton
*) data
;
556 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
558 CHECK_CLASS(data
, WC_PopUpButton
);
560 if (WMGetArrayItemCount(bPtr
->items
) < 1)
563 switch (event
->type
) {
564 /* called for menuView */
566 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
570 bPtr
->flags
.insideMenu
= 0;
571 if (bPtr
->menuView
->flags
.mapped
)
572 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
573 bPtr
->highlightedItem
= -1;
577 bPtr
->flags
.insideMenu
= 1;
581 if (bPtr
->flags
.insideMenu
) {
582 oldItem
= bPtr
->highlightedItem
;
583 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
584 if (oldItem
!= bPtr
->highlightedItem
) {
587 paintMenuEntry(bPtr
, oldItem
, False
);
588 if (bPtr
->highlightedItem
>= 0 &&
589 bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
590 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
591 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
593 bPtr
->highlightedItem
= -1;
598 if (event
->xmotion
.y_root
>= scrHeight
- 1 || event
->xmotion
.y_root
<= 1) {
599 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
602 } else if (bPtr
->timer
) {
603 WMDeleteTimerHandler(bPtr
->timer
);
609 /* called for bPtr->view */
611 if (!bPtr
->flags
.enabled
)
614 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
615 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
619 } else if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
620 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
621 wheelScrollDown(bPtr
);
626 if (!bPtr
->flags
.pullsDown
) {
627 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
628 bPtr
->flags
.insideMenu
= 1;
630 bPtr
->highlightedItem
= -1;
631 bPtr
->flags
.insideMenu
= 0;
633 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
634 False
, ButtonReleaseMask
| ButtonMotionMask
| EnterWindowMask
635 | LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
639 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
||
640 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
643 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
644 if (!bPtr
->flags
.pullsDown
)
648 WMDeleteTimerHandler(bPtr
->timer
);
652 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>= 0) {
655 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
657 if (WMGetMenuItemEnabled(item
)) {
659 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
661 if (bPtr
->flags
.pullsDown
) {
662 for (i
= 0; i
< MENU_BLINK_COUNT
; i
++) {
663 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
664 XSync(bPtr
->view
->screen
->display
, 0);
665 wusleep(MENU_BLINK_DELAY
);
666 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
667 XSync(bPtr
->view
->screen
->display
, 0);
668 wusleep(MENU_BLINK_DELAY
);
671 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
674 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
677 if (bPtr
->menuView
->flags
.mapped
)
683 static void destroyPopUpButton(PopUpButton
* bPtr
)
686 WMDeleteTimerHandler(bPtr
->timer
);
689 WMFreeArray(bPtr
->items
);
692 wfree(bPtr
->caption
);
694 /* have to destroy explicitly because the popup is a toplevel */
695 W_DestroyView(bPtr
->menuView
);