fixed bug in popupbutton scrolling
[wmaker-crm.git] / WINGs / wpopupbutton.c
blob6a17c9706b1184ec36d288e39b8f5cacd04c3f0c
5 #include "WINGsP.h"
8 typedef struct W_PopUpButton {
9 W_Class widgetClass;
10 WMView *view;
12 void *clientData;
13 WMAction *action;
15 char *caption;
17 WMBag *items;
19 short selectedItemIndex;
21 short highlightedItem;
23 WMView *menuView; /* override redirect popup menu */
25 WMHandlerID timer; /* for autoscroll */
27 /**/
28 int scrollStartY; /* for autoscroll */
30 struct {
31 unsigned int pullsDown:1;
33 unsigned int configured:1;
35 unsigned int insideMenu:1;
37 unsigned int enabled:1;
39 } flags;
40 } PopUpButton;
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);
63 WMPopUpButton*
64 WMCreatePopUpButton(WMWidget *parent)
66 PopUpButton *bPtr;
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));
76 if (!bPtr->view) {
77 wfree(bPtr);
78 return NULL;
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->menuView = W_CreateTopView(scr);
97 bPtr->menuView->attribs.override_redirect = True;
98 bPtr->menuView->attribFlags |= CWOverrideRedirect;
100 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
102 WMCreateEventHandler(bPtr->menuView, ButtonPressMask|ButtonReleaseMask
103 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask
104 |ExposureMask, handleActionEvents, bPtr);
106 return bPtr;
110 void
111 WMSetPopUpButtonAction(WMPopUpButton *bPtr, WMAction *action, void *clientData)
113 CHECK_CLASS(bPtr, WC_PopUpButton);
115 bPtr->action = action;
117 bPtr->clientData = clientData;
121 WMMenuItem*
122 WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title)
124 WMMenuItem *item;
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)
134 resizeMenu(bPtr);
136 return item;
140 WMMenuItem*
141 WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title)
143 WMMenuItem *item;
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
153 * position */
154 if (index < bPtr->selectedItemIndex)
155 bPtr->selectedItemIndex++;
157 if (bPtr->menuView && bPtr->menuView->flags.realized)
158 resizeMenu(bPtr);
160 return item;
164 void
165 WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index)
167 WMMenuItem *item;
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
184 * selected one */
185 bPtr->selectedItemIndex = 0;
186 if (bPtr->view->flags.mapped)
187 paintPopUpButton(bPtr);
191 if (bPtr->menuView && bPtr->menuView->flags.realized)
192 resizeMenu(bPtr);
196 void
197 WMSetPopUpButtonEnabled(WMPopUpButton *bPtr, Bool flag)
199 bPtr->flags.enabled = flag;
200 if (bPtr->view->flags.mapped)
201 paintPopUpButton(bPtr);
205 Bool
206 WMGetPopUpButtonEnabled(WMPopUpButton *bPtr)
208 return bPtr->flags.enabled;
212 void
213 WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index)
215 if (index < 0) {
216 if (bPtr->view->flags.mapped)
217 paintPopUpButton(bPtr);
218 return;
221 bPtr->selectedItemIndex = index;
223 if (bPtr->view->flags.mapped)
224 paintPopUpButton(bPtr);
228 WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr)
230 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
231 return -1;
232 else
233 return bPtr->selectedItemIndex;
237 void
238 WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text)
240 if (bPtr->caption)
241 wfree(bPtr->caption);
242 if (text)
243 bPtr->caption = wstrdup(text);
244 else
245 bPtr->caption = NULL;
246 if (bPtr->view->flags.realized) {
247 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
248 paintPopUpButton(bPtr);
255 void
256 WMSetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index, Bool flag)
258 WMMenuItem *item;
260 item = WMGetFromBag(bPtr->items, index);
261 wassertr(item != NULL);
263 WMSetMenuItemEnabled(item, flag);
267 Bool
268 WMGetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index)
270 WMMenuItem *item;
272 item = WMGetFromBag(bPtr->items, index);
273 wassertrv(item != NULL, False);
275 return WMGetMenuItemEnabled(item);
279 void
280 WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag)
282 bPtr->flags.pullsDown = flag;
283 if (flag) {
284 bPtr->selectedItemIndex = -1;
287 if (bPtr->view->flags.mapped)
288 paintPopUpButton(bPtr);
293 WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
295 return WMGetBagItemCount(bPtr->items);
299 char*
300 WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
302 WMMenuItem *item;
304 if (index >= WMGetBagItemCount(bPtr->items) || index < 0)
305 return NULL;
307 item = WMGetFromBag(bPtr->items, index);
308 if (!item)
309 return NULL;
311 return WMGetMenuItemTitle(item);
315 WMMenuItem*
316 WMGetPopUpButtonMenuItem(WMPopUpButton *bPtr, int index)
318 WMMenuItem *item;
320 item = WMGetFromBag(bPtr->items, index);
322 return item;
327 static void
328 paintPopUpButton(PopUpButton *bPtr)
330 W_Screen *scr = bPtr->view->screen;
331 char *caption;
332 Pixmap pixmap;
335 if (bPtr->flags.pullsDown) {
336 caption = bPtr->caption;
337 } else {
338 if (bPtr->selectedItemIndex < 0) {
339 /* if no item selected, show the caption */
340 caption = bPtr->caption;
341 } else {
342 caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
346 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
347 bPtr->view->size.width, bPtr->view->size.height,
348 scr->depth);
349 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
350 bPtr->view->size.width, bPtr->view->size.height);
352 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
353 bPtr->view->size.height, WRRaised);
355 if (caption) {
356 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
357 (bPtr->view->size.height-WMFontHeight(scr->normalFont))/2,
358 bPtr->view->size.width, WALeft,
359 bPtr->flags.enabled ? WMColorGC(scr->black) : WMColorGC(scr->darkGray),
360 False, caption, strlen(caption));
363 if (bPtr->flags.pullsDown) {
364 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
365 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
366 scr->pullDownIndicator->height,
367 bPtr->view->size.width-scr->pullDownIndicator->width-4,
368 (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
369 } else {
370 int x, y;
372 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
373 y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
375 XSetClipOrigin(scr->display, scr->clipGC, x, y);
376 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
377 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
378 scr->clipGC, 0, 0, scr->popUpIndicator->width,
379 scr->popUpIndicator->height, x, y);
382 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
383 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
385 XFreePixmap(scr->display, pixmap);
390 static void
391 handleEvents(XEvent *event, void *data)
393 PopUpButton *bPtr = (PopUpButton*)data;
395 CHECK_CLASS(data, WC_PopUpButton);
398 switch (event->type) {
399 case Expose:
400 if (event->xexpose.count!=0)
401 break;
402 paintPopUpButton(bPtr);
403 break;
405 case DestroyNotify:
406 destroyPopUpButton(bPtr);
407 break;
413 static void
414 paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
416 W_Screen *scr = bPtr->view->screen;
417 int i;
418 int yo;
419 int width, height, itemHeight;
420 char *title;
422 itemHeight = bPtr->view->size.height;
423 width = bPtr->view->size.width;
424 height = itemHeight * WMGetBagItemCount(bPtr->items);
425 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
427 if (!highlight) {
428 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
429 width, itemHeight, False);
430 return;
431 } else if (index < 0 && bPtr->flags.pullsDown) {
432 return;
435 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
436 1, index*itemHeight+1, width-3, itemHeight-3);
438 title = WMGetPopUpButtonItem(bPtr, index);
440 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
441 width, itemHeight, WRRaised);
443 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
444 index*itemHeight + yo, width, WALeft, WMColorGC(scr->black),
445 False, title, strlen(title));
447 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
448 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
449 bPtr->menuView->window, scr->copyGC, 0, 0,
450 scr->popUpIndicator->width, scr->popUpIndicator->height,
451 width-scr->popUpIndicator->width-4,
452 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
457 Pixmap
458 makeMenuPixmap(PopUpButton *bPtr)
460 Pixmap pixmap;
461 W_Screen *scr = bPtr->view->screen;
462 int i;
463 int yo;
464 int width, height, itemHeight;
466 itemHeight = bPtr->view->size.height;
467 width = bPtr->view->size.width;
468 height = itemHeight * WMGetBagItemCount(bPtr->items);
469 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
471 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
472 scr->depth);
474 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
475 width, height);
477 for (i = 0; i < WMGetBagItemCount(bPtr->items); i++) {
478 GC gc;
479 WMMenuItem *item;
480 char *text;
482 item = (WMMenuItem*)WMGetFromBag(bPtr->items, i);
483 text = WMGetMenuItemTitle(item);
485 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
486 WRRaised);
488 if (!WMGetMenuItemEnabled(item))
489 gc = WMColorGC(scr->darkGray);
490 else
491 gc = WMColorGC(scr->black);
493 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
494 i*itemHeight + yo, width, WALeft, gc, False,
495 text, strlen(text));
497 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
498 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
499 scr->copyGC, 0, 0, scr->popUpIndicator->width,
500 scr->popUpIndicator->height,
501 width-scr->popUpIndicator->width-4,
502 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
506 return pixmap;
510 static void
511 resizeMenu(PopUpButton *bPtr)
513 int height;
515 height = WMGetBagItemCount(bPtr->items) * bPtr->view->size.height;
516 if (height > 0)
517 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
521 static void
522 popUpMenu(PopUpButton *bPtr)
524 W_Screen *scr = bPtr->view->screen;
525 Window dummyW;
526 int x, y;
528 if (!bPtr->menuView->flags.realized) {
529 W_RealizeView(bPtr->menuView);
530 resizeMenu(bPtr);
533 if (WMGetBagItemCount(bPtr->items) < 1)
534 return;
536 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
537 0, 0, &x, &y, &dummyW);
539 if (bPtr->flags.pullsDown) {
540 y += bPtr->view->size.height;
541 } else {
542 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
544 W_MoveView(bPtr->menuView, x, y);
546 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
547 makeMenuPixmap(bPtr));
548 XClearWindow(scr->display, bPtr->menuView->window);
550 W_MapView(bPtr->menuView);
552 bPtr->highlightedItem = 0;
553 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
554 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
558 static void
559 popDownMenu(PopUpButton *bPtr)
561 W_UnmapView(bPtr->menuView);
563 /* free the background pixmap used to draw the menu contents */
564 XSetWindowBackgroundPixmap(bPtr->view->screen->display,
565 bPtr->menuView->window, None);
569 static void
570 autoScroll(void *data)
572 PopUpButton *bPtr = (PopUpButton*)data;
573 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
574 int repeat = 0;
575 int dy = 0;
578 if (bPtr->scrollStartY > scrHeight-1
579 && bPtr->menuView->pos.y+bPtr->menuView->size.height > scrHeight-1) {
580 repeat = 1;
582 if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5
583 <= scrHeight - 1) {
584 dy = scrHeight - 1
585 - (bPtr->menuView->pos.y+bPtr->menuView->size.height);
586 } else
587 dy = -5;
589 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
590 repeat = 1;
592 if (bPtr->menuView->pos.y+5 > 1)
593 dy = 1 - bPtr->menuView->pos.y;
594 else
595 dy = 5;
598 if (repeat) {
599 int oldItem;
601 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x,
602 bPtr->menuView->pos.y + dy);
604 oldItem = bPtr->highlightedItem;
605 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
606 / bPtr->view->size.height;
608 if (bPtr->highlightedItem >= WMGetBagItemCount(bPtr->items))
609 bPtr->highlightedItem = WMGetBagItemCount(bPtr->items)-1;
611 if (oldItem!=bPtr->highlightedItem) {
612 WMMenuItem *item =
613 WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
615 paintMenuEntry(bPtr, oldItem, False);
617 paintMenuEntry(bPtr, bPtr->highlightedItem,
618 WMGetMenuItemEnabled(item));
621 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
622 } else {
623 bPtr->timer = NULL;
628 static void
629 handleActionEvents(XEvent *event, void *data)
631 PopUpButton *bPtr = (PopUpButton*)data;
632 int oldItem;
633 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
635 CHECK_CLASS(data, WC_PopUpButton);
637 if (WMGetBagItemCount(bPtr->items) < 1)
638 return;
640 switch (event->type) {
641 /* called for menuView */
642 case Expose:
643 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
644 break;
646 case LeaveNotify:
647 bPtr->flags.insideMenu = 0;
648 if (bPtr->menuView->flags.mapped)
649 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
650 bPtr->highlightedItem = -1;
651 break;
653 case EnterNotify:
654 bPtr->flags.insideMenu = 1;
655 break;
657 case MotionNotify:
658 if (bPtr->flags.insideMenu) {
659 oldItem = bPtr->highlightedItem;
660 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
661 if (oldItem!=bPtr->highlightedItem) {
662 WMMenuItem *item;
664 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
665 paintMenuEntry(bPtr, oldItem, False);
666 paintMenuEntry(bPtr, bPtr->highlightedItem,
667 WMGetMenuItemEnabled(item));
670 if (event->xmotion.y_root >= scrHeight-1
671 || event->xmotion.y_root <= 1) {
672 bPtr->scrollStartY = event->xmotion.y_root;
673 if (!bPtr->timer)
674 autoScroll(bPtr);
675 } else if (bPtr->timer) {
676 WMDeleteTimerHandler(bPtr->timer);
677 bPtr->timer = NULL;
680 break;
682 /* called for bPtr->view */
683 case ButtonPress:
684 if (!bPtr->flags.enabled)
685 break;
687 popUpMenu(bPtr);
688 if (!bPtr->flags.pullsDown) {
689 bPtr->highlightedItem = bPtr->selectedItemIndex;
690 bPtr->flags.insideMenu = 1;
691 } else {
692 bPtr->highlightedItem = -1;
693 bPtr->flags.insideMenu = 0;
695 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
696 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
697 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
698 None, None, CurrentTime);
699 break;
701 case ButtonRelease:
702 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
703 if (!bPtr->flags.pullsDown)
704 popDownMenu(bPtr);
706 if (bPtr->timer) {
707 WMDeleteTimerHandler(bPtr->timer);
708 bPtr->timer = NULL;
711 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
712 WMMenuItem *item;
714 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
716 if (WMGetMenuItemEnabled(item)) {
717 int i;
718 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
720 if (bPtr->flags.pullsDown) {
721 for (i=0; i<MENU_BLINK_COUNT; i++) {
722 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
723 XSync(bPtr->view->screen->display, 0);
724 wusleep(MENU_BLINK_DELAY);
725 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
726 XSync(bPtr->view->screen->display, 0);
727 wusleep(MENU_BLINK_DELAY);
730 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
731 popDownMenu(bPtr);
732 if (bPtr->action)
733 (*bPtr->action)(bPtr, bPtr->clientData);
736 if (bPtr->menuView->flags.mapped)
737 popDownMenu(bPtr);
738 break;
744 static void
745 destroyPopUpButton(PopUpButton *bPtr)
747 WMMenuItem *item;
748 int i;
750 if (bPtr->timer) {
751 WMDeleteTimerHandler(bPtr->timer);
754 for (i = 0; i < WMGetBagItemCount(bPtr->items); i++) {
755 item = WMGetFromBag(bPtr->items, i);
756 WMDestroyMenuItem(item);
758 WMFreeBag(bPtr->items);
760 if (bPtr->caption)
761 wfree(bPtr->caption);
763 /* have to destroy explicitly because the popup is a toplevel */
764 W_DestroyView(bPtr->menuView);
766 wfree(bPtr);