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
));
60 memset(bPtr
, 0, sizeof(PopUpButton
));
62 bPtr
->widgetClass
= WC_PopUpButton
;
64 bPtr
->view
= W_CreateView(W_VIEW(parent
));
69 bPtr
->view
->self
= bPtr
;
71 WMCreateEventHandler(bPtr
->view
, ExposureMask
| StructureNotifyMask
72 | ClientMessageMask
, handleEvents
, bPtr
);
74 W_ResizeView(bPtr
->view
, DEFAULT_WIDTH
, DEFAULT_HEIGHT
);
75 bPtr
->caption
= wstrdup(DEFAULT_CAPTION
);
77 WMCreateEventHandler(bPtr
->view
, ButtonPressMask
| ButtonReleaseMask
, handleActionEvents
, bPtr
);
79 bPtr
->flags
.enabled
= 1;
81 bPtr
->items
= WMCreateArrayWithDestructor(4, (WMFreeDataProc
*) WMDestroyMenuItem
);
83 bPtr
->selectedItemIndex
= -1;
85 bPtr
->menuView
= W_CreateUnmanagedTopView(scr
);
87 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, 1);
89 WMCreateEventHandler(bPtr
->menuView
, ButtonPressMask
| ButtonReleaseMask
90 | EnterWindowMask
| LeaveWindowMask
| ButtonMotionMask
91 | ExposureMask
, handleActionEvents
, bPtr
);
96 void WMSetPopUpButtonAction(WMPopUpButton
* bPtr
, WMAction
* action
, void *clientData
)
98 CHECK_CLASS(bPtr
, WC_PopUpButton
);
100 bPtr
->action
= action
;
102 bPtr
->clientData
= clientData
;
105 WMMenuItem
*WMAddPopUpButtonItem(WMPopUpButton
* bPtr
, char *title
)
109 CHECK_CLASS(bPtr
, WC_PopUpButton
);
111 item
= WMCreateMenuItem();
112 WMSetMenuItemTitle(item
, title
);
114 WMAddToArray(bPtr
->items
, item
);
116 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
122 WMMenuItem
*WMInsertPopUpButtonItem(WMPopUpButton
* bPtr
, int index
, char *title
)
126 CHECK_CLASS(bPtr
, WC_PopUpButton
);
128 item
= WMCreateMenuItem();
129 WMSetMenuItemTitle(item
, title
);
131 WMInsertInArray(bPtr
->items
, index
, item
);
133 /* if there is an selected item, update it's index to match the new
135 if (index
< bPtr
->selectedItemIndex
)
136 bPtr
->selectedItemIndex
++;
138 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
144 void WMRemovePopUpButtonItem(WMPopUpButton
* bPtr
, int index
)
146 CHECK_CLASS(bPtr
, WC_PopUpButton
);
148 wassertr(index
>= 0 && index
< WMGetArrayItemCount(bPtr
->items
));
150 WMDeleteFromArray(bPtr
->items
, index
);
152 if (bPtr
->selectedItemIndex
>= 0 && !bPtr
->flags
.pullsDown
) {
153 if (index
< bPtr
->selectedItemIndex
)
154 bPtr
->selectedItemIndex
--;
155 else if (index
== bPtr
->selectedItemIndex
) {
156 /* reselect first item if the removed item is the
158 bPtr
->selectedItemIndex
= 0;
159 if (bPtr
->view
->flags
.mapped
)
160 paintPopUpButton(bPtr
);
164 if (bPtr
->menuView
&& bPtr
->menuView
->flags
.realized
)
168 void WMSetPopUpButtonEnabled(WMPopUpButton
* bPtr
, Bool flag
)
170 bPtr
->flags
.enabled
= ((flag
== 0) ? 0 : 1);
171 if (bPtr
->view
->flags
.mapped
)
172 paintPopUpButton(bPtr
);
175 Bool
WMGetPopUpButtonEnabled(WMPopUpButton
* bPtr
)
177 return bPtr
->flags
.enabled
;
180 void WMSetPopUpButtonSelectedItem(WMPopUpButton
* bPtr
, int index
)
183 wassertr(index
< WMGetArrayItemCount(bPtr
->items
));
185 /* if (index >= WMGetArrayCount(bPtr->items))
188 bPtr
->selectedItemIndex
= index
;
190 if (bPtr
->view
->flags
.mapped
)
191 paintPopUpButton(bPtr
);
194 int WMGetPopUpButtonSelectedItem(WMPopUpButton
* bPtr
)
196 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
199 return bPtr
->selectedItemIndex
;
202 void WMSetPopUpButtonText(WMPopUpButton
* bPtr
, char *text
)
205 wfree(bPtr
->caption
);
207 bPtr
->caption
= wstrdup(text
);
209 bPtr
->caption
= NULL
;
210 if (bPtr
->view
->flags
.realized
) {
211 if (bPtr
->flags
.pullsDown
|| bPtr
->selectedItemIndex
< 0) {
212 paintPopUpButton(bPtr
);
217 void WMSetPopUpButtonItemEnabled(WMPopUpButton
* bPtr
, int index
, Bool flag
)
219 WMSetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
), (flag
? 1 : 0));
222 Bool
WMGetPopUpButtonItemEnabled(WMPopUpButton
* bPtr
, int index
)
224 return WMGetMenuItemEnabled(WMGetFromArray(bPtr
->items
, index
));
227 void WMSetPopUpButtonPullsDown(WMPopUpButton
* bPtr
, Bool flag
)
229 bPtr
->flags
.pullsDown
= ((flag
== 0) ? 0 : 1);
231 bPtr
->selectedItemIndex
= -1;
234 if (bPtr
->view
->flags
.mapped
)
235 paintPopUpButton(bPtr
);
238 int WMGetPopUpButtonNumberOfItems(WMPopUpButton
* bPtr
)
240 return WMGetArrayItemCount(bPtr
->items
);
243 char *WMGetPopUpButtonItem(WMPopUpButton
* bPtr
, int index
)
245 if (index
>= WMGetArrayItemCount(bPtr
->items
) || index
< 0)
248 return WMGetMenuItemTitle(WMGetFromArray(bPtr
->items
, index
));
251 WMMenuItem
*WMGetPopUpButtonMenuItem(WMPopUpButton
* bPtr
, int index
)
253 return WMGetFromArray(bPtr
->items
, index
);
256 static void paintPopUpButton(PopUpButton
* bPtr
)
258 W_Screen
*scr
= bPtr
->view
->screen
;
262 if (bPtr
->flags
.pullsDown
) {
263 caption
= bPtr
->caption
;
265 if (bPtr
->selectedItemIndex
< 0) {
266 /* if no item selected, show the caption */
267 caption
= bPtr
->caption
;
269 caption
= WMGetPopUpButtonItem(bPtr
, bPtr
->selectedItemIndex
);
273 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
,
274 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, scr
->depth
);
275 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0,
276 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
);
278 W_DrawRelief(scr
, pixmap
, 0, 0, bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, WRRaised
);
281 W_PaintText(bPtr
->view
, pixmap
, scr
->normalFont
, 6,
282 (bPtr
->view
->size
.height
- WMFontHeight(scr
->normalFont
)) / 2,
283 bPtr
->view
->size
.width
, WALeft
,
284 bPtr
->flags
.enabled
? scr
->black
: scr
->darkGray
, False
, caption
, strlen(caption
));
287 if (bPtr
->flags
.pullsDown
) {
288 XCopyArea(scr
->display
, scr
->pullDownIndicator
->pixmap
,
289 pixmap
, scr
->copyGC
, 0, 0, scr
->pullDownIndicator
->width
,
290 scr
->pullDownIndicator
->height
,
291 bPtr
->view
->size
.width
- scr
->pullDownIndicator
->width
- 4,
292 (bPtr
->view
->size
.height
- scr
->pullDownIndicator
->height
) / 2);
296 x
= bPtr
->view
->size
.width
- scr
->popUpIndicator
->width
- 4;
297 y
= (bPtr
->view
->size
.height
- scr
->popUpIndicator
->height
) / 2;
299 XSetClipOrigin(scr
->display
, scr
->clipGC
, x
, y
);
300 XSetClipMask(scr
->display
, scr
->clipGC
, scr
->popUpIndicator
->mask
);
301 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
302 scr
->clipGC
, 0, 0, scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
, x
, y
);
305 XCopyArea(scr
->display
, pixmap
, bPtr
->view
->window
, scr
->copyGC
, 0, 0,
306 bPtr
->view
->size
.width
, bPtr
->view
->size
.height
, 0, 0);
308 XFreePixmap(scr
->display
, pixmap
);
311 static void handleEvents(XEvent
* event
, void *data
)
313 PopUpButton
*bPtr
= (PopUpButton
*) data
;
315 CHECK_CLASS(data
, WC_PopUpButton
);
317 switch (event
->type
) {
319 if (event
->xexpose
.count
!= 0)
321 paintPopUpButton(bPtr
);
325 destroyPopUpButton(bPtr
);
330 static void paintMenuEntry(PopUpButton
* bPtr
, int index
, int highlight
)
332 W_Screen
*scr
= bPtr
->view
->screen
;
334 int width
, height
, itemHeight
, itemCount
;
337 itemCount
= WMGetArrayItemCount(bPtr
->items
);
338 if (index
< 0 || index
>= itemCount
)
341 itemHeight
= bPtr
->view
->size
.height
;
342 width
= bPtr
->view
->size
.width
;
343 height
= itemHeight
* itemCount
;
344 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
347 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, False
);
349 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
353 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
354 1, index
* itemHeight
+ 1, width
- 3, itemHeight
- 3);
356 title
= WMGetPopUpButtonItem(bPtr
, index
);
358 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, WRRaised
);
360 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
361 index
* itemHeight
+ yo
, width
, WALeft
, scr
->black
, False
, title
, strlen(title
));
363 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
364 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
365 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
366 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
367 width
- scr
->popUpIndicator
->width
- 4,
368 index
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
372 Pixmap
makeMenuPixmap(PopUpButton
* bPtr
)
375 W_Screen
*scr
= bPtr
->view
->screen
;
377 WMArrayIterator iter
;
379 int width
, height
, itemHeight
;
381 itemHeight
= bPtr
->view
->size
.height
;
382 width
= bPtr
->view
->size
.width
;
383 height
= itemHeight
* WMGetArrayItemCount(bPtr
->items
);
384 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
386 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
, scr
->depth
);
388 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0, width
, height
);
391 WM_ITERATE_ARRAY(bPtr
->items
, item
, iter
) {
395 text
= WMGetMenuItemTitle(item
);
397 W_DrawRelief(scr
, pixmap
, 0, i
* itemHeight
, width
, itemHeight
, WRRaised
);
399 if (!WMGetMenuItemEnabled(item
))
400 color
= scr
->darkGray
;
404 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
405 i
* itemHeight
+ yo
, width
, WALeft
, color
, False
, text
, strlen(text
));
407 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
408 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
409 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
410 scr
->popUpIndicator
->height
,
411 width
- scr
->popUpIndicator
->width
- 4,
412 i
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
421 static void resizeMenu(PopUpButton
* bPtr
)
425 height
= WMGetArrayItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
427 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
430 static void popUpMenu(PopUpButton
* bPtr
)
432 W_Screen
*scr
= bPtr
->view
->screen
;
436 if (!bPtr
->flags
.enabled
)
439 if (!bPtr
->menuView
->flags
.realized
) {
440 W_RealizeView(bPtr
->menuView
);
444 if (WMGetArrayItemCount(bPtr
->items
) < 1)
447 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
, 0, 0, &x
, &y
, &dummyW
);
449 if (bPtr
->flags
.pullsDown
) {
450 y
+= bPtr
->view
->size
.height
;
452 y
-= bPtr
->view
->size
.height
* bPtr
->selectedItemIndex
;
454 W_MoveView(bPtr
->menuView
, x
, y
);
456 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
, makeMenuPixmap(bPtr
));
457 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
459 if (W_VIEW_WIDTH(bPtr
->menuView
) != W_VIEW_WIDTH(bPtr
->view
))
462 W_MapView(bPtr
->menuView
);
464 bPtr
->highlightedItem
= 0;
465 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
466 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
469 static void popDownMenu(PopUpButton
* bPtr
)
471 W_UnmapView(bPtr
->menuView
);
474 static void autoScroll(void *data
)
476 PopUpButton
*bPtr
= (PopUpButton
*) data
;
477 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
481 if (bPtr
->scrollStartY
>= scrHeight
- 1
482 && bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
>= scrHeight
- 1) {
485 if (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
- 5 <= scrHeight
- 1) {
486 dy
= scrHeight
- 1 - (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
);
490 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
493 if (bPtr
->menuView
->pos
.y
+ 5 > 1)
494 dy
= 1 - bPtr
->menuView
->pos
.y
;
502 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
, bPtr
->menuView
->pos
.y
+ dy
);
504 oldItem
= bPtr
->highlightedItem
;
505 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
506 / bPtr
->view
->size
.height
;
508 if (oldItem
!= bPtr
->highlightedItem
) {
511 paintMenuEntry(bPtr
, oldItem
, False
);
513 if (bPtr
->highlightedItem
>= 0 && bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
514 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
515 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
517 bPtr
->highlightedItem
= -1;
521 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
527 static void wheelScrollUp(PopUpButton
* bPtr
)
529 int testIndex
= bPtr
->selectedItemIndex
- 1;
531 while (testIndex
>= 0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
533 if (testIndex
!= -1) {
534 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
536 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
540 static void wheelScrollDown(PopUpButton
* bPtr
)
542 int itemCount
= WMGetArrayItemCount(bPtr
->items
);
543 int testIndex
= bPtr
->selectedItemIndex
+ 1;
545 while (testIndex
< itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
547 if (testIndex
!= itemCount
) {
548 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
550 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
554 static void handleActionEvents(XEvent
* event
, void *data
)
556 PopUpButton
*bPtr
= (PopUpButton
*) data
;
558 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
560 CHECK_CLASS(data
, WC_PopUpButton
);
562 if (WMGetArrayItemCount(bPtr
->items
) < 1)
565 switch (event
->type
) {
566 /* called for menuView */
568 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
572 bPtr
->flags
.insideMenu
= 0;
573 if (bPtr
->menuView
->flags
.mapped
)
574 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
575 bPtr
->highlightedItem
= -1;
579 bPtr
->flags
.insideMenu
= 1;
583 if (bPtr
->flags
.insideMenu
) {
584 oldItem
= bPtr
->highlightedItem
;
585 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
586 if (oldItem
!= bPtr
->highlightedItem
) {
589 paintMenuEntry(bPtr
, oldItem
, False
);
590 if (bPtr
->highlightedItem
>= 0 &&
591 bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
592 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
593 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
595 bPtr
->highlightedItem
= -1;
600 if (event
->xmotion
.y_root
>= scrHeight
- 1 || event
->xmotion
.y_root
<= 1) {
601 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
604 } else if (bPtr
->timer
) {
605 WMDeleteTimerHandler(bPtr
->timer
);
611 /* called for bPtr->view */
613 if (!bPtr
->flags
.enabled
)
616 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
617 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
621 } else if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
622 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
623 wheelScrollDown(bPtr
);
628 if (!bPtr
->flags
.pullsDown
) {
629 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
630 bPtr
->flags
.insideMenu
= 1;
632 bPtr
->highlightedItem
= -1;
633 bPtr
->flags
.insideMenu
= 0;
635 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
636 False
, ButtonReleaseMask
| ButtonMotionMask
| EnterWindowMask
637 | LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
641 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
||
642 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
645 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
646 if (!bPtr
->flags
.pullsDown
)
650 WMDeleteTimerHandler(bPtr
->timer
);
654 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>= 0) {
657 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
659 if (WMGetMenuItemEnabled(item
)) {
661 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
663 if (bPtr
->flags
.pullsDown
) {
664 for (i
= 0; i
< MENU_BLINK_COUNT
; i
++) {
665 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
666 XSync(bPtr
->view
->screen
->display
, 0);
667 wusleep(MENU_BLINK_DELAY
);
668 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
669 XSync(bPtr
->view
->screen
->display
, 0);
670 wusleep(MENU_BLINK_DELAY
);
673 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
676 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
679 if (bPtr
->menuView
->flags
.mapped
)
685 static void destroyPopUpButton(PopUpButton
* bPtr
)
688 WMDeleteTimerHandler(bPtr
->timer
);
691 WMFreeArray(bPtr
->items
);
694 wfree(bPtr
->caption
);
696 /* have to destroy explicitly because the popup is a toplevel */
697 W_DestroyView(bPtr
->menuView
);