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
, height
, 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 height
= itemHeight
* itemCount
;
343 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
346 XClearArea(scr
->display
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, False
);
348 } else if (index
< 0 && bPtr
->flags
.pullsDown
) {
352 XFillRectangle(scr
->display
, bPtr
->menuView
->window
, WMColorGC(scr
->white
),
353 1, index
* itemHeight
+ 1, width
- 3, itemHeight
- 3);
355 title
= WMGetPopUpButtonItem(bPtr
, index
);
357 W_DrawRelief(scr
, bPtr
->menuView
->window
, 0, index
* itemHeight
, width
, itemHeight
, WRRaised
);
359 W_PaintText(bPtr
->menuView
, bPtr
->menuView
->window
, scr
->normalFont
, 6,
360 index
* itemHeight
+ yo
, width
, WALeft
, scr
->black
, False
, title
, strlen(title
));
362 if (!bPtr
->flags
.pullsDown
&& index
== bPtr
->selectedItemIndex
) {
363 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
,
364 bPtr
->menuView
->window
, scr
->copyGC
, 0, 0,
365 scr
->popUpIndicator
->width
, scr
->popUpIndicator
->height
,
366 width
- scr
->popUpIndicator
->width
- 4,
367 index
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
371 Pixmap
makeMenuPixmap(PopUpButton
* bPtr
)
374 W_Screen
*scr
= bPtr
->view
->screen
;
376 WMArrayIterator iter
;
378 int width
, height
, itemHeight
;
380 itemHeight
= bPtr
->view
->size
.height
;
381 width
= bPtr
->view
->size
.width
;
382 height
= itemHeight
* WMGetArrayItemCount(bPtr
->items
);
383 yo
= (itemHeight
- WMFontHeight(scr
->normalFont
)) / 2;
385 pixmap
= XCreatePixmap(scr
->display
, bPtr
->view
->window
, width
, height
, scr
->depth
);
387 XFillRectangle(scr
->display
, pixmap
, WMColorGC(scr
->gray
), 0, 0, width
, height
);
390 WM_ITERATE_ARRAY(bPtr
->items
, item
, iter
) {
394 text
= WMGetMenuItemTitle(item
);
396 W_DrawRelief(scr
, pixmap
, 0, i
* itemHeight
, width
, itemHeight
, WRRaised
);
398 if (!WMGetMenuItemEnabled(item
))
399 color
= scr
->darkGray
;
403 W_PaintText(bPtr
->menuView
, pixmap
, scr
->normalFont
, 6,
404 i
* itemHeight
+ yo
, width
, WALeft
, color
, False
, text
, strlen(text
));
406 if (!bPtr
->flags
.pullsDown
&& i
== bPtr
->selectedItemIndex
) {
407 XCopyArea(scr
->display
, scr
->popUpIndicator
->pixmap
, pixmap
,
408 scr
->copyGC
, 0, 0, scr
->popUpIndicator
->width
,
409 scr
->popUpIndicator
->height
,
410 width
- scr
->popUpIndicator
->width
- 4,
411 i
* itemHeight
+ (itemHeight
- scr
->popUpIndicator
->height
) / 2);
420 static void resizeMenu(PopUpButton
* bPtr
)
424 height
= WMGetArrayItemCount(bPtr
->items
) * bPtr
->view
->size
.height
;
426 W_ResizeView(bPtr
->menuView
, bPtr
->view
->size
.width
, height
);
429 static void popUpMenu(PopUpButton
* bPtr
)
431 W_Screen
*scr
= bPtr
->view
->screen
;
435 if (!bPtr
->flags
.enabled
)
438 if (!bPtr
->menuView
->flags
.realized
) {
439 W_RealizeView(bPtr
->menuView
);
443 if (WMGetArrayItemCount(bPtr
->items
) < 1)
446 XTranslateCoordinates(scr
->display
, bPtr
->view
->window
, scr
->rootWin
, 0, 0, &x
, &y
, &dummyW
);
448 if (bPtr
->flags
.pullsDown
) {
449 y
+= bPtr
->view
->size
.height
;
451 y
-= bPtr
->view
->size
.height
* bPtr
->selectedItemIndex
;
453 W_MoveView(bPtr
->menuView
, x
, y
);
455 XSetWindowBackgroundPixmap(scr
->display
, bPtr
->menuView
->window
, makeMenuPixmap(bPtr
));
456 XClearWindow(scr
->display
, bPtr
->menuView
->window
);
458 if (W_VIEW_WIDTH(bPtr
->menuView
) != W_VIEW_WIDTH(bPtr
->view
))
461 W_MapView(bPtr
->menuView
);
463 bPtr
->highlightedItem
= 0;
464 if (!bPtr
->flags
.pullsDown
&& bPtr
->selectedItemIndex
< 0)
465 paintMenuEntry(bPtr
, bPtr
->selectedItemIndex
, True
);
468 static void popDownMenu(PopUpButton
* bPtr
)
470 W_UnmapView(bPtr
->menuView
);
473 static void autoScroll(void *data
)
475 PopUpButton
*bPtr
= (PopUpButton
*) data
;
476 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
480 if (bPtr
->scrollStartY
>= scrHeight
- 1
481 && bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
>= scrHeight
- 1) {
484 if (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
- 5 <= scrHeight
- 1) {
485 dy
= scrHeight
- 1 - (bPtr
->menuView
->pos
.y
+ bPtr
->menuView
->size
.height
);
489 } else if (bPtr
->scrollStartY
<= 1 && bPtr
->menuView
->pos
.y
< 1) {
492 if (bPtr
->menuView
->pos
.y
+ 5 > 1)
493 dy
= 1 - bPtr
->menuView
->pos
.y
;
501 W_MoveView(bPtr
->menuView
, bPtr
->menuView
->pos
.x
, bPtr
->menuView
->pos
.y
+ dy
);
503 oldItem
= bPtr
->highlightedItem
;
504 bPtr
->highlightedItem
= (bPtr
->scrollStartY
- bPtr
->menuView
->pos
.y
)
505 / bPtr
->view
->size
.height
;
507 if (oldItem
!= bPtr
->highlightedItem
) {
510 paintMenuEntry(bPtr
, oldItem
, False
);
512 if (bPtr
->highlightedItem
>= 0 && bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
513 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
514 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
516 bPtr
->highlightedItem
= -1;
520 bPtr
->timer
= WMAddTimerHandler(SCROLL_DELAY
, autoScroll
, bPtr
);
526 static void wheelScrollUp(PopUpButton
* bPtr
)
528 int testIndex
= bPtr
->selectedItemIndex
- 1;
530 while (testIndex
>= 0 && !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
532 if (testIndex
!= -1) {
533 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
535 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
539 static void wheelScrollDown(PopUpButton
* bPtr
)
541 int itemCount
= WMGetArrayItemCount(bPtr
->items
);
542 int testIndex
= bPtr
->selectedItemIndex
+ 1;
544 while (testIndex
< itemCount
&& !WMGetPopUpButtonItemEnabled(bPtr
, testIndex
))
546 if (testIndex
!= itemCount
) {
547 WMSetPopUpButtonSelectedItem(bPtr
, testIndex
);
549 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
553 static void handleActionEvents(XEvent
* event
, void *data
)
555 PopUpButton
*bPtr
= (PopUpButton
*) data
;
557 int scrHeight
= WMWidgetScreen(bPtr
)->rootView
->size
.height
;
559 CHECK_CLASS(data
, WC_PopUpButton
);
561 if (WMGetArrayItemCount(bPtr
->items
) < 1)
564 switch (event
->type
) {
565 /* called for menuView */
567 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
571 bPtr
->flags
.insideMenu
= 0;
572 if (bPtr
->menuView
->flags
.mapped
)
573 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
574 bPtr
->highlightedItem
= -1;
578 bPtr
->flags
.insideMenu
= 1;
582 if (bPtr
->flags
.insideMenu
) {
583 oldItem
= bPtr
->highlightedItem
;
584 bPtr
->highlightedItem
= event
->xmotion
.y
/ bPtr
->view
->size
.height
;
585 if (oldItem
!= bPtr
->highlightedItem
) {
588 paintMenuEntry(bPtr
, oldItem
, False
);
589 if (bPtr
->highlightedItem
>= 0 &&
590 bPtr
->highlightedItem
< WMGetArrayItemCount(bPtr
->items
)) {
591 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
592 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, WMGetMenuItemEnabled(item
));
594 bPtr
->highlightedItem
= -1;
599 if (event
->xmotion
.y_root
>= scrHeight
- 1 || event
->xmotion
.y_root
<= 1) {
600 bPtr
->scrollStartY
= event
->xmotion
.y_root
;
603 } else if (bPtr
->timer
) {
604 WMDeleteTimerHandler(bPtr
->timer
);
610 /* called for bPtr->view */
612 if (!bPtr
->flags
.enabled
)
615 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
) {
616 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
620 } else if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
621 if (!bPtr
->menuView
->flags
.mapped
&& !bPtr
->flags
.pullsDown
) {
622 wheelScrollDown(bPtr
);
627 if (!bPtr
->flags
.pullsDown
) {
628 bPtr
->highlightedItem
= bPtr
->selectedItemIndex
;
629 bPtr
->flags
.insideMenu
= 1;
631 bPtr
->highlightedItem
= -1;
632 bPtr
->flags
.insideMenu
= 0;
634 XGrabPointer(bPtr
->view
->screen
->display
, bPtr
->menuView
->window
,
635 False
, ButtonReleaseMask
| ButtonMotionMask
| EnterWindowMask
636 | LeaveWindowMask
, GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
640 if (event
->xbutton
.button
== WINGsConfiguration
.mouseWheelUp
||
641 event
->xbutton
.button
== WINGsConfiguration
.mouseWheelDown
) {
644 XUngrabPointer(bPtr
->view
->screen
->display
, event
->xbutton
.time
);
645 if (!bPtr
->flags
.pullsDown
)
649 WMDeleteTimerHandler(bPtr
->timer
);
653 if (bPtr
->flags
.insideMenu
&& bPtr
->highlightedItem
>= 0) {
656 item
= WMGetPopUpButtonMenuItem(bPtr
, bPtr
->highlightedItem
);
658 if (WMGetMenuItemEnabled(item
)) {
660 WMSetPopUpButtonSelectedItem(bPtr
, bPtr
->highlightedItem
);
662 if (bPtr
->flags
.pullsDown
) {
663 for (i
= 0; i
< MENU_BLINK_COUNT
; i
++) {
664 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
665 XSync(bPtr
->view
->screen
->display
, 0);
666 wusleep(MENU_BLINK_DELAY
);
667 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, True
);
668 XSync(bPtr
->view
->screen
->display
, 0);
669 wusleep(MENU_BLINK_DELAY
);
672 paintMenuEntry(bPtr
, bPtr
->highlightedItem
, False
);
675 (*bPtr
->action
) (bPtr
, bPtr
->clientData
);
678 if (bPtr
->menuView
->flags
.mapped
)
684 static void destroyPopUpButton(PopUpButton
* bPtr
)
687 WMDeleteTimerHandler(bPtr
->timer
);
690 WMFreeArray(bPtr
->items
);
693 wfree(bPtr
->caption
);
695 /* have to destroy explicitly because the popup is a toplevel */
696 W_DestroyView(bPtr
->menuView
);