fix overflow bug, try2
[wmaker-crm.git] / WINGs / wpopupbutton.c
blob81f5184486bbb738accea2210ec168d85c1f7f3a
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 WMArray *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 =
95 WMCreateArrayWithDestructor(4, (WMFreeDataProc*)WMDestroyMenuItem);
97 bPtr->selectedItemIndex = -1;
99 bPtr->menuView = W_CreateUnmanagedTopView(scr);
101 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
103 WMCreateEventHandler(bPtr->menuView, ButtonPressMask|ButtonReleaseMask
104 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask
105 |ExposureMask, handleActionEvents, bPtr);
107 return bPtr;
111 void
112 WMSetPopUpButtonAction(WMPopUpButton *bPtr, WMAction *action, void *clientData)
114 CHECK_CLASS(bPtr, WC_PopUpButton);
116 bPtr->action = action;
118 bPtr->clientData = clientData;
122 WMMenuItem*
123 WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title)
125 WMMenuItem *item;
127 CHECK_CLASS(bPtr, WC_PopUpButton);
129 item = WMCreateMenuItem();
130 WMSetMenuItemTitle(item, title);
132 WMAddToArray(bPtr->items, item);
134 if (bPtr->menuView && bPtr->menuView->flags.realized)
135 resizeMenu(bPtr);
137 return item;
141 WMMenuItem*
142 WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title)
144 WMMenuItem *item;
146 CHECK_CLASS(bPtr, WC_PopUpButton);
148 item = WMCreateMenuItem();
149 WMSetMenuItemTitle(item, title);
151 WMInsertInArray(bPtr->items, index, item);
153 /* if there is an selected item, update it's index to match the new
154 * position */
155 if (index < bPtr->selectedItemIndex)
156 bPtr->selectedItemIndex++;
158 if (bPtr->menuView && bPtr->menuView->flags.realized)
159 resizeMenu(bPtr);
161 return item;
165 void
166 WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index)
168 CHECK_CLASS(bPtr, WC_PopUpButton);
170 wassertr(index >= 0 && index < WMGetArrayItemCount(bPtr->items));
173 WMDeleteFromArray(bPtr->items, index);
175 if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) {
176 if (index < bPtr->selectedItemIndex)
177 bPtr->selectedItemIndex--;
178 else if (index == bPtr->selectedItemIndex) {
179 /* reselect first item if the removed item is the
180 * selected one */
181 bPtr->selectedItemIndex = 0;
182 if (bPtr->view->flags.mapped)
183 paintPopUpButton(bPtr);
187 if (bPtr->menuView && bPtr->menuView->flags.realized)
188 resizeMenu(bPtr);
192 void
193 WMSetPopUpButtonEnabled(WMPopUpButton *bPtr, Bool flag)
195 bPtr->flags.enabled = ((flag==0) ? 0 : 1);
196 if (bPtr->view->flags.mapped)
197 paintPopUpButton(bPtr);
201 Bool
202 WMGetPopUpButtonEnabled(WMPopUpButton *bPtr)
204 return bPtr->flags.enabled;
208 void
209 WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index)
212 wassertr(index < WMGetArrayItemCount(bPtr->items));
214 /* if (index >= WMGetArrayCount(bPtr->items))
215 index = -1;*/
217 bPtr->selectedItemIndex = index;
219 if (bPtr->view->flags.mapped)
220 paintPopUpButton(bPtr);
225 WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr)
227 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
228 return -1;
229 else
230 return bPtr->selectedItemIndex;
234 void
235 WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text)
237 if (bPtr->caption)
238 wfree(bPtr->caption);
239 if (text)
240 bPtr->caption = wstrdup(text);
241 else
242 bPtr->caption = NULL;
243 if (bPtr->view->flags.realized) {
244 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
245 paintPopUpButton(bPtr);
252 void
253 WMSetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index, Bool flag)
255 WMSetMenuItemEnabled(WMGetFromArray(bPtr->items, index), (flag ? 1 : 0));
259 Bool
260 WMGetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index)
262 return WMGetMenuItemEnabled(WMGetFromArray(bPtr->items, index));
266 void
267 WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag)
269 bPtr->flags.pullsDown = ((flag==0) ? 0 : 1);
270 if (flag) {
271 bPtr->selectedItemIndex = -1;
274 if (bPtr->view->flags.mapped)
275 paintPopUpButton(bPtr);
280 WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
282 return WMGetArrayItemCount(bPtr->items);
286 char*
287 WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
289 if (index >= WMGetArrayItemCount(bPtr->items) || index < 0)
290 return NULL;
292 return WMGetMenuItemTitle(WMGetFromArray(bPtr->items, index));
296 WMMenuItem*
297 WMGetPopUpButtonMenuItem(WMPopUpButton *bPtr, int index)
299 return WMGetFromArray(bPtr->items, index);
304 static void
305 paintPopUpButton(PopUpButton *bPtr)
307 W_Screen *scr = bPtr->view->screen;
308 char *caption;
309 Pixmap pixmap;
312 if (bPtr->flags.pullsDown) {
313 caption = bPtr->caption;
314 } else {
315 if (bPtr->selectedItemIndex < 0) {
316 /* if no item selected, show the caption */
317 caption = bPtr->caption;
318 } else {
319 caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
323 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
324 bPtr->view->size.width, bPtr->view->size.height,
325 scr->depth);
326 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
327 bPtr->view->size.width, bPtr->view->size.height);
329 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
330 bPtr->view->size.height, WRRaised);
332 if (caption) {
333 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
334 (bPtr->view->size.height-WMFontHeight(scr->normalFont))/2,
335 bPtr->view->size.width, WALeft,
336 bPtr->flags.enabled ? scr->black : scr->darkGray,
337 False, caption, strlen(caption));
340 if (bPtr->flags.pullsDown) {
341 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
342 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
343 scr->pullDownIndicator->height,
344 bPtr->view->size.width-scr->pullDownIndicator->width-4,
345 (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
346 } else {
347 int x, y;
349 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
350 y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
352 XSetClipOrigin(scr->display, scr->clipGC, x, y);
353 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
354 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
355 scr->clipGC, 0, 0, scr->popUpIndicator->width,
356 scr->popUpIndicator->height, x, y);
359 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
360 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
362 XFreePixmap(scr->display, pixmap);
367 static void
368 handleEvents(XEvent *event, void *data)
370 PopUpButton *bPtr = (PopUpButton*)data;
372 CHECK_CLASS(data, WC_PopUpButton);
375 switch (event->type) {
376 case Expose:
377 if (event->xexpose.count!=0)
378 break;
379 paintPopUpButton(bPtr);
380 break;
382 case DestroyNotify:
383 destroyPopUpButton(bPtr);
384 break;
390 static void
391 paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
393 W_Screen *scr = bPtr->view->screen;
394 int yo;
395 int width, height, itemHeight, itemCount;
396 char *title;
398 itemCount = WMGetArrayItemCount(bPtr->items);
399 if (index < 0 || index >= itemCount)
400 return;
402 itemHeight = bPtr->view->size.height;
403 width = bPtr->view->size.width;
404 height = itemHeight * itemCount;
405 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
407 if (!highlight) {
408 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
409 width, itemHeight, False);
410 return;
411 } else if (index < 0 && bPtr->flags.pullsDown) {
412 return;
415 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
416 1, index*itemHeight+1, width-3, itemHeight-3);
418 title = WMGetPopUpButtonItem(bPtr, index);
420 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
421 width, itemHeight, WRRaised);
423 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
424 index*itemHeight + yo, width, WALeft, scr->black,
425 False, title, strlen(title));
427 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
428 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
429 bPtr->menuView->window, scr->copyGC, 0, 0,
430 scr->popUpIndicator->width, scr->popUpIndicator->height,
431 width-scr->popUpIndicator->width-4,
432 index*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
437 Pixmap
438 makeMenuPixmap(PopUpButton *bPtr)
440 Pixmap pixmap;
441 W_Screen *scr = bPtr->view->screen;
442 WMMenuItem *item;
443 WMArrayIterator iter;
444 int yo, i;
445 int width, height, itemHeight;
447 itemHeight = bPtr->view->size.height;
448 width = bPtr->view->size.width;
449 height = itemHeight * WMGetArrayItemCount(bPtr->items);
450 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
452 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
453 scr->depth);
455 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
456 width, height);
458 i = 0;
459 WM_ITERATE_ARRAY(bPtr->items, item, iter) {
460 WMColor *color;
461 char *text;
463 text = WMGetMenuItemTitle(item);
465 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
466 WRRaised);
468 if (!WMGetMenuItemEnabled(item))
469 color = scr->darkGray;
470 else
471 color = scr->black;
473 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
474 i*itemHeight + yo, width, WALeft, color, False,
475 text, strlen(text));
477 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
478 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
479 scr->copyGC, 0, 0, scr->popUpIndicator->width,
480 scr->popUpIndicator->height,
481 width-scr->popUpIndicator->width-4,
482 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
485 i++;
488 return pixmap;
492 static void
493 resizeMenu(PopUpButton *bPtr)
495 int height;
497 height = WMGetArrayItemCount(bPtr->items) * bPtr->view->size.height;
498 if (height > 0)
499 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
503 static void
504 popUpMenu(PopUpButton *bPtr)
506 W_Screen *scr = bPtr->view->screen;
507 Window dummyW;
508 int x, y;
510 if (!bPtr->flags.enabled)
511 return;
513 if (!bPtr->menuView->flags.realized) {
514 W_RealizeView(bPtr->menuView);
515 resizeMenu(bPtr);
518 if (WMGetArrayItemCount(bPtr->items) < 1)
519 return;
521 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
522 0, 0, &x, &y, &dummyW);
524 if (bPtr->flags.pullsDown) {
525 y += bPtr->view->size.height;
526 } else {
527 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
529 W_MoveView(bPtr->menuView, x, y);
531 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
532 makeMenuPixmap(bPtr));
533 XClearWindow(scr->display, bPtr->menuView->window);
535 if (W_VIEW_WIDTH(bPtr->menuView) != W_VIEW_WIDTH(bPtr->view))
536 resizeMenu(bPtr);
538 W_MapView(bPtr->menuView);
540 bPtr->highlightedItem = 0;
541 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
542 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
546 static void
547 popDownMenu(PopUpButton *bPtr)
549 W_UnmapView(bPtr->menuView);
553 static void
554 autoScroll(void *data)
556 PopUpButton *bPtr = (PopUpButton*)data;
557 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
558 int repeat = 0;
559 int dy = 0;
562 if (bPtr->scrollStartY >= scrHeight-1
563 && bPtr->menuView->pos.y+bPtr->menuView->size.height >= scrHeight-1) {
564 repeat = 1;
566 if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5
567 <= scrHeight - 1) {
568 dy = scrHeight - 1
569 - (bPtr->menuView->pos.y+bPtr->menuView->size.height);
570 } else
571 dy = -5;
573 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
574 repeat = 1;
576 if (bPtr->menuView->pos.y+5 > 1)
577 dy = 1 - bPtr->menuView->pos.y;
578 else
579 dy = 5;
582 if (repeat) {
583 int oldItem;
585 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x,
586 bPtr->menuView->pos.y + dy);
588 oldItem = bPtr->highlightedItem;
589 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
590 / bPtr->view->size.height;
592 if (oldItem!=bPtr->highlightedItem) {
593 WMMenuItem *item;
595 paintMenuEntry(bPtr, oldItem, False);
597 if (bPtr->highlightedItem >= 0 &&
598 bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
599 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
600 paintMenuEntry(bPtr, bPtr->highlightedItem,
601 WMGetMenuItemEnabled(item));
602 } else {
603 bPtr->highlightedItem = -1;
607 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
608 } else {
609 bPtr->timer = NULL;
614 static void
615 wheelScrollUp(PopUpButton *bPtr)
617 int testIndex = bPtr->selectedItemIndex - 1;
619 while (testIndex>=0 && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
620 testIndex--;
621 if (testIndex != -1) {
622 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
623 if (bPtr->action)
624 (*bPtr->action)(bPtr, bPtr->clientData);
629 static void
630 wheelScrollDown(PopUpButton *bPtr)
632 int itemCount = WMGetArrayItemCount(bPtr->items);
633 int testIndex = bPtr->selectedItemIndex + 1;
635 while (testIndex<itemCount && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
636 testIndex++;
637 if (testIndex != itemCount) {
638 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
639 if (bPtr->action)
640 (*bPtr->action)(bPtr, bPtr->clientData);
645 static void
646 handleActionEvents(XEvent *event, void *data)
648 PopUpButton *bPtr = (PopUpButton*)data;
649 int oldItem;
650 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
652 CHECK_CLASS(data, WC_PopUpButton);
654 if (WMGetArrayItemCount(bPtr->items) < 1)
655 return;
657 switch (event->type) {
658 /* called for menuView */
659 case Expose:
660 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
661 break;
663 case LeaveNotify:
664 bPtr->flags.insideMenu = 0;
665 if (bPtr->menuView->flags.mapped)
666 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
667 bPtr->highlightedItem = -1;
668 break;
670 case EnterNotify:
671 bPtr->flags.insideMenu = 1;
672 break;
674 case MotionNotify:
675 if (bPtr->flags.insideMenu) {
676 oldItem = bPtr->highlightedItem;
677 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
678 if (oldItem!=bPtr->highlightedItem) {
679 WMMenuItem *item;
681 paintMenuEntry(bPtr, oldItem, False);
682 if (bPtr->highlightedItem >= 0 &&
683 bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
684 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
685 paintMenuEntry(bPtr, bPtr->highlightedItem,
686 WMGetMenuItemEnabled(item));
687 } else {
688 bPtr->highlightedItem = -1;
693 if (event->xmotion.y_root >= scrHeight-1
694 || event->xmotion.y_root <= 1) {
695 bPtr->scrollStartY = event->xmotion.y_root;
696 if (!bPtr->timer)
697 autoScroll(bPtr);
698 } else if (bPtr->timer) {
699 WMDeleteTimerHandler(bPtr->timer);
700 bPtr->timer = NULL;
703 break;
705 /* called for bPtr->view */
706 case ButtonPress:
707 if (!bPtr->flags.enabled)
708 break;
710 if (event->xbutton.button==WINGsConfiguration.mouseWheelUp) {
711 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
712 wheelScrollUp(bPtr);
714 break;
715 } else if (event->xbutton.button==WINGsConfiguration.mouseWheelDown) {
716 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
717 wheelScrollDown(bPtr);
719 break;
721 popUpMenu(bPtr);
722 if (!bPtr->flags.pullsDown) {
723 bPtr->highlightedItem = bPtr->selectedItemIndex;
724 bPtr->flags.insideMenu = 1;
725 } else {
726 bPtr->highlightedItem = -1;
727 bPtr->flags.insideMenu = 0;
729 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
730 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
731 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
732 None, None, CurrentTime);
733 break;
735 case ButtonRelease:
736 if (event->xbutton.button==WINGsConfiguration.mouseWheelUp ||
737 event->xbutton.button==WINGsConfiguration.mouseWheelDown) {
738 break;
740 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
741 if (!bPtr->flags.pullsDown)
742 popDownMenu(bPtr);
744 if (bPtr->timer) {
745 WMDeleteTimerHandler(bPtr->timer);
746 bPtr->timer = NULL;
749 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
750 WMMenuItem *item;
752 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
754 if (WMGetMenuItemEnabled(item)) {
755 int i;
756 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
758 if (bPtr->flags.pullsDown) {
759 for (i=0; i<MENU_BLINK_COUNT; i++) {
760 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
761 XSync(bPtr->view->screen->display, 0);
762 wusleep(MENU_BLINK_DELAY);
763 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
764 XSync(bPtr->view->screen->display, 0);
765 wusleep(MENU_BLINK_DELAY);
768 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
769 popDownMenu(bPtr);
770 if (bPtr->action)
771 (*bPtr->action)(bPtr, bPtr->clientData);
774 if (bPtr->menuView->flags.mapped)
775 popDownMenu(bPtr);
776 break;
782 static void
783 destroyPopUpButton(PopUpButton *bPtr)
785 if (bPtr->timer) {
786 WMDeleteTimerHandler(bPtr->timer);
789 WMFreeArray(bPtr->items);
791 if (bPtr->caption)
792 wfree(bPtr->caption);
794 /* have to destroy explicitly because the popup is a toplevel */
795 W_DestroyView(bPtr->menuView);
797 wfree(bPtr);