- fixed a bug that crashed the programm when a popup button was scrolled.
[wmaker-crm.git] / WINGs / wpopupbutton.c
blob029bec3845ce61bee7be4012f53f631d99293f1e
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)
216 wassertr(index < WMGetBagItemCount(bPtr->items));
218 /* if (index >= WMGetBagCount(bPtr->items))
219 index = -1;*/
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 yo;
418 int width, height, itemHeight, itemCount;
419 char *title;
421 itemCount = WMGetBagItemCount(bPtr->items);
422 if (index < 0 || index >= itemCount)
423 return;
425 itemHeight = bPtr->view->size.height;
426 width = bPtr->view->size.width;
427 height = itemHeight * itemCount;
428 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
430 if (!highlight) {
431 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
432 width, itemHeight, False);
433 return;
434 } else if (index < 0 && bPtr->flags.pullsDown) {
435 return;
438 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
439 1, index*itemHeight+1, width-3, itemHeight-3);
441 title = WMGetPopUpButtonItem(bPtr, index);
443 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
444 width, itemHeight, WRRaised);
446 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
447 index*itemHeight + yo, width, WALeft, WMColorGC(scr->black),
448 False, title, strlen(title));
450 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
451 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
452 bPtr->menuView->window, scr->copyGC, 0, 0,
453 scr->popUpIndicator->width, scr->popUpIndicator->height,
454 width-scr->popUpIndicator->width-4,
455 index*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
460 Pixmap
461 makeMenuPixmap(PopUpButton *bPtr)
463 Pixmap pixmap;
464 W_Screen *scr = bPtr->view->screen;
465 int i;
466 int yo;
467 int width, height, itemHeight;
469 itemHeight = bPtr->view->size.height;
470 width = bPtr->view->size.width;
471 height = itemHeight * WMGetBagItemCount(bPtr->items);
472 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
474 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
475 scr->depth);
477 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
478 width, height);
480 for (i = 0; i < WMGetBagItemCount(bPtr->items); i++) {
481 GC gc;
482 WMMenuItem *item;
483 char *text;
485 item = (WMMenuItem*)WMGetFromBag(bPtr->items, i);
486 text = WMGetMenuItemTitle(item);
488 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
489 WRRaised);
491 if (!WMGetMenuItemEnabled(item))
492 gc = WMColorGC(scr->darkGray);
493 else
494 gc = WMColorGC(scr->black);
496 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
497 i*itemHeight + yo, width, WALeft, gc, False,
498 text, strlen(text));
500 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
501 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
502 scr->copyGC, 0, 0, scr->popUpIndicator->width,
503 scr->popUpIndicator->height,
504 width-scr->popUpIndicator->width-4,
505 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
509 return pixmap;
513 static void
514 resizeMenu(PopUpButton *bPtr)
516 int height;
518 height = WMGetBagItemCount(bPtr->items) * bPtr->view->size.height;
519 if (height > 0)
520 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
524 static void
525 popUpMenu(PopUpButton *bPtr)
527 W_Screen *scr = bPtr->view->screen;
528 Window dummyW;
529 int x, y;
531 if (!bPtr->menuView->flags.realized) {
532 W_RealizeView(bPtr->menuView);
533 resizeMenu(bPtr);
536 if (WMGetBagItemCount(bPtr->items) < 1)
537 return;
539 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
540 0, 0, &x, &y, &dummyW);
542 if (bPtr->flags.pullsDown) {
543 y += bPtr->view->size.height;
544 } else {
545 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
547 W_MoveView(bPtr->menuView, x, y);
549 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
550 makeMenuPixmap(bPtr));
551 XClearWindow(scr->display, bPtr->menuView->window);
553 W_MapView(bPtr->menuView);
555 bPtr->highlightedItem = 0;
556 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
557 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
561 static void
562 popDownMenu(PopUpButton *bPtr)
564 W_UnmapView(bPtr->menuView);
566 /* free the background pixmap used to draw the menu contents */
567 XSetWindowBackgroundPixmap(bPtr->view->screen->display,
568 bPtr->menuView->window, None);
572 static void
573 autoScroll(void *data)
575 PopUpButton *bPtr = (PopUpButton*)data;
576 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
577 int repeat = 0;
578 int dy = 0;
581 if (bPtr->scrollStartY >= scrHeight-1
582 && bPtr->menuView->pos.y+bPtr->menuView->size.height >= scrHeight-1) {
583 repeat = 1;
585 if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5
586 <= scrHeight - 1) {
587 dy = scrHeight - 1
588 - (bPtr->menuView->pos.y+bPtr->menuView->size.height);
589 } else
590 dy = -5;
592 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
593 repeat = 1;
595 if (bPtr->menuView->pos.y+5 > 1)
596 dy = 1 - bPtr->menuView->pos.y;
597 else
598 dy = 5;
601 if (repeat) {
602 int oldItem;
604 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x,
605 bPtr->menuView->pos.y + dy);
607 oldItem = bPtr->highlightedItem;
608 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
609 / bPtr->view->size.height;
611 if (oldItem!=bPtr->highlightedItem) {
612 WMMenuItem *item;
614 paintMenuEntry(bPtr, oldItem, False);
616 if (bPtr->highlightedItem >= 0 &&
617 bPtr->highlightedItem < WMGetBagItemCount(bPtr->items)) {
618 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
619 paintMenuEntry(bPtr, bPtr->highlightedItem,
620 WMGetMenuItemEnabled(item));
621 } else {
622 bPtr->highlightedItem = -1;
626 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
627 } else {
628 bPtr->timer = NULL;
633 static void
634 handleActionEvents(XEvent *event, void *data)
636 PopUpButton *bPtr = (PopUpButton*)data;
637 int oldItem;
638 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
640 CHECK_CLASS(data, WC_PopUpButton);
642 if (WMGetBagItemCount(bPtr->items) < 1)
643 return;
645 switch (event->type) {
646 /* called for menuView */
647 case Expose:
648 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
649 break;
651 case LeaveNotify:
652 bPtr->flags.insideMenu = 0;
653 if (bPtr->menuView->flags.mapped)
654 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
655 bPtr->highlightedItem = -1;
656 break;
658 case EnterNotify:
659 bPtr->flags.insideMenu = 1;
660 break;
662 case MotionNotify:
663 if (bPtr->flags.insideMenu) {
664 oldItem = bPtr->highlightedItem;
665 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
666 if (oldItem!=bPtr->highlightedItem) {
667 WMMenuItem *item;
669 paintMenuEntry(bPtr, oldItem, False);
670 if (bPtr->highlightedItem >= 0 &&
671 bPtr->highlightedItem < WMGetBagItemCount(bPtr->items)) {
672 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
673 paintMenuEntry(bPtr, bPtr->highlightedItem,
674 WMGetMenuItemEnabled(item));
675 } else {
676 bPtr->highlightedItem = -1;
681 if (event->xmotion.y_root >= scrHeight-1
682 || event->xmotion.y_root <= 1) {
683 bPtr->scrollStartY = event->xmotion.y_root;
684 if (!bPtr->timer)
685 autoScroll(bPtr);
686 } else if (bPtr->timer) {
687 WMDeleteTimerHandler(bPtr->timer);
688 bPtr->timer = NULL;
691 break;
693 /* called for bPtr->view */
694 case ButtonPress:
695 if (!bPtr->flags.enabled)
696 break;
698 popUpMenu(bPtr);
699 if (!bPtr->flags.pullsDown) {
700 bPtr->highlightedItem = bPtr->selectedItemIndex;
701 bPtr->flags.insideMenu = 1;
702 } else {
703 bPtr->highlightedItem = -1;
704 bPtr->flags.insideMenu = 0;
706 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
707 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
708 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
709 None, None, CurrentTime);
710 break;
712 case ButtonRelease:
713 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
714 if (!bPtr->flags.pullsDown)
715 popDownMenu(bPtr);
717 if (bPtr->timer) {
718 WMDeleteTimerHandler(bPtr->timer);
719 bPtr->timer = NULL;
722 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
723 WMMenuItem *item;
725 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
727 if (WMGetMenuItemEnabled(item)) {
728 int i;
729 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
731 if (bPtr->flags.pullsDown) {
732 for (i=0; i<MENU_BLINK_COUNT; i++) {
733 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
734 XSync(bPtr->view->screen->display, 0);
735 wusleep(MENU_BLINK_DELAY);
736 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
737 XSync(bPtr->view->screen->display, 0);
738 wusleep(MENU_BLINK_DELAY);
741 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
742 popDownMenu(bPtr);
743 if (bPtr->action)
744 (*bPtr->action)(bPtr, bPtr->clientData);
747 if (bPtr->menuView->flags.mapped)
748 popDownMenu(bPtr);
749 break;
755 static void
756 destroyPopUpButton(PopUpButton *bPtr)
758 WMMenuItem *item;
759 int i;
761 if (bPtr->timer) {
762 WMDeleteTimerHandler(bPtr->timer);
765 for (i = 0; i < WMGetBagItemCount(bPtr->items); i++) {
766 item = WMGetFromBag(bPtr->items, i);
767 WMDestroyMenuItem(item);
769 WMFreeBag(bPtr->items);
771 if (bPtr->caption)
772 wfree(bPtr->caption);
774 /* have to destroy explicitly because the popup is a toplevel */
775 W_DestroyView(bPtr->menuView);
777 wfree(bPtr);