Code update for Window Maker version 0.50.0
[wmaker-crm.git] / WINGs / wpopupbutton.c
blobf1ee440fcc938aeba2cefe5f9949603a3e84cd01
5 #include "WINGsP.h"
7 typedef struct ItemList {
8 char *text;
9 struct ItemList *nextPtr;
10 unsigned int disabled:1;
11 } ItemList;
14 typedef struct W_PopUpButton {
15 W_Class widgetClass;
16 WMView *view;
18 void *clientData;
19 WMAction *action;
21 char *caption;
23 ItemList *items;
24 short itemCount;
26 short selectedItemIndex;
28 short highlightedItem;
30 ItemList *selectedItem; /* selected item if it is a menu btn */
32 WMView *menuView; /* override redirect popup menu */
34 WMHandlerID timer; /* for autoscroll */
36 /**/
37 int scrollStartY; /* for autoscroll */
39 struct {
40 unsigned int pullsDown:1;
42 unsigned int configured:1;
44 unsigned int insideMenu:1;
46 unsigned int enabled:1;
48 } flags;
49 } PopUpButton;
52 #define MENU_BLINK_DELAY 60000
53 #define MENU_BLINK_COUNT 2
55 #define SCROLL_DELAY 30
58 W_ViewProcedureTable _PopUpButtonViewProcedures = {
59 NULL,
60 NULL,
61 NULL
65 #define DEFAULT_WIDTH 60
66 #define DEFAULT_HEIGHT 20
67 #define DEFAULT_CAPTION ""
70 static void destroyPopUpButton(PopUpButton *bPtr);
71 static void paintPopUpButton(PopUpButton *bPtr);
73 static void handleEvents(XEvent *event, void *data);
74 static void handleActionEvents(XEvent *event, void *data);
76 static void resizeMenu(PopUpButton *bPtr);
79 WMPopUpButton*
80 WMCreatePopUpButton(WMWidget *parent)
82 PopUpButton *bPtr;
83 W_Screen *scr = W_VIEW(parent)->screen;
86 bPtr = wmalloc(sizeof(PopUpButton));
87 memset(bPtr, 0, sizeof(PopUpButton));
89 bPtr->widgetClass = WC_PopUpButton;
91 bPtr->view = W_CreateView(W_VIEW(parent));
92 if (!bPtr->view) {
93 free(bPtr);
94 return NULL;
96 bPtr->view->self = bPtr;
98 WMCreateEventHandler(bPtr->view, ExposureMask|StructureNotifyMask
99 |ClientMessageMask, handleEvents, bPtr);
102 W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
103 bPtr->caption = wstrdup(DEFAULT_CAPTION);
105 WMCreateEventHandler(bPtr->view, ButtonPressMask|ButtonReleaseMask,
106 handleActionEvents, bPtr);
108 bPtr->flags.enabled = 1;
110 bPtr->menuView = W_CreateTopView(scr);
111 bPtr->menuView->attribs.override_redirect = True;
112 bPtr->menuView->attribFlags |= CWOverrideRedirect;
114 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
116 WMCreateEventHandler(bPtr->menuView, ButtonPressMask|ButtonReleaseMask
117 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask
118 |ExposureMask, handleActionEvents, bPtr);
120 return bPtr;
124 void
125 WMSetPopUpButtonAction(WMPopUpButton *bPtr, WMAction *action, void *clientData)
127 CHECK_CLASS(bPtr, WC_PopUpButton);
129 bPtr->action = action;
131 bPtr->clientData = clientData;
135 void
136 WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title)
138 ItemList *itemPtr, *tmp;
140 CHECK_CLASS(bPtr, WC_PopUpButton);
142 itemPtr = wmalloc(sizeof(ItemList));
143 memset(itemPtr, 0, sizeof(ItemList));
144 itemPtr->text = wstrdup(title);
146 /* append item to list */
147 tmp = bPtr->items;
148 if (!tmp)
149 bPtr->items = itemPtr;
150 else {
151 while (tmp->nextPtr!=NULL)
152 tmp = tmp->nextPtr;
153 tmp->nextPtr = itemPtr;
156 bPtr->itemCount++;
158 if (bPtr->menuView && bPtr->menuView->flags.realized)
159 resizeMenu(bPtr);
163 void
164 WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title)
166 ItemList *itemPtr;
168 CHECK_CLASS(bPtr, WC_PopUpButton);
170 if (index < 0)
171 index = 0;
172 if (index >= bPtr->itemCount) {
173 WMAddPopUpButtonItem(bPtr, title);
174 return;
177 itemPtr = wmalloc(sizeof(ItemList));
178 memset(itemPtr, 0, sizeof(ItemList));
179 itemPtr->text = wstrdup(title);
181 if (index == 0) {
182 itemPtr->nextPtr = bPtr->items;
183 bPtr->items = itemPtr;
184 } else {
185 ItemList *tmp;
186 int i = index;
188 tmp = bPtr->items;
189 /* insert item in list */
190 while (--i > 0) {
191 tmp = tmp->nextPtr;
193 bPtr->items->nextPtr = tmp->nextPtr;
194 tmp->nextPtr = bPtr->items;
197 bPtr->itemCount++;
199 /* if there is an selected item, update it's index to match the new
200 * position */
201 if (index < bPtr->selectedItemIndex)
202 bPtr->selectedItemIndex++;
204 if (bPtr->menuView && bPtr->menuView->flags.realized)
205 resizeMenu(bPtr);
209 void
210 WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index)
212 ItemList *tmp;
214 CHECK_CLASS(bPtr, WC_PopUpButton);
216 if (index < 0 || index >= bPtr->itemCount)
217 return;
220 if (index == 0) {
221 free(bPtr->items->text);
222 tmp = bPtr->items->nextPtr;
223 free(bPtr->items);
224 bPtr->items = tmp;
225 } else {
226 ItemList *next;
227 int i = index;
229 tmp = bPtr->items;
230 while (--i > 0)
231 tmp = tmp->nextPtr;
232 next = tmp->nextPtr->nextPtr;
234 free(tmp->nextPtr->text);
235 free(tmp->nextPtr);
237 tmp->nextPtr = next;
240 bPtr->itemCount--;
242 if (bPtr->selectedItem!=NULL && !bPtr->flags.pullsDown) {
243 if (index < bPtr->selectedItemIndex)
244 bPtr->selectedItemIndex--;
245 else if (index == bPtr->selectedItemIndex) {
246 /* reselect first item if the removed item is the
247 * selected one */
248 bPtr->selectedItem = bPtr->items;
249 bPtr->selectedItemIndex = 0;
250 if (bPtr->view->flags.mapped)
251 paintPopUpButton(bPtr);
255 if (bPtr->menuView && bPtr->menuView->flags.realized)
256 resizeMenu(bPtr);
260 void
261 WMSetPopUpButtonEnabled(WMPopUpButton *bPtr, Bool flag)
263 bPtr->flags.enabled = flag;
264 if (bPtr->view->flags.mapped)
265 paintPopUpButton(bPtr);
269 void
270 WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index)
272 ItemList *itemPtr = bPtr->items;
273 int i = index;
275 if (index < 0) {
276 bPtr->selectedItem = NULL;
277 if (bPtr->view->flags.mapped)
278 paintPopUpButton(bPtr);
279 return;
282 while (i-- > 0) {
283 itemPtr = itemPtr->nextPtr;
285 bPtr->selectedItem = itemPtr;
286 bPtr->selectedItemIndex = index;
288 if (bPtr->view->flags.mapped)
289 paintPopUpButton(bPtr);
293 WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr)
295 if (!bPtr->flags.pullsDown && bPtr->selectedItem==NULL)
296 return -1;
297 else
298 return bPtr->selectedItemIndex;
302 void
303 WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text)
305 if (bPtr->caption)
306 free(bPtr->caption);
307 if (text)
308 bPtr->caption = wstrdup(text);
309 else
310 bPtr->caption = NULL;
311 if (bPtr->view->flags.realized) {
312 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
313 paintPopUpButton(bPtr);
320 void
321 WMSetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index, Bool flag)
323 int i;
324 ItemList *item = bPtr->items;
326 if (index < 0 || index >= bPtr->itemCount)
327 return;
329 for (i = 0; i<index; i++)
330 item=item->nextPtr;
332 item->disabled = !flag;
336 void
337 WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag)
339 bPtr->flags.pullsDown = flag;
340 if (!flag) {
341 bPtr->selectedItem = bPtr->items;
342 if (bPtr->selectedItem)
343 bPtr->selectedItemIndex = 0;
344 else
345 bPtr->selectedItemIndex = -1;
346 } else
347 bPtr->selectedItemIndex = -1;
349 if (bPtr->view->flags.mapped)
350 paintPopUpButton(bPtr);
355 WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
357 return bPtr->itemCount;
361 char*
362 WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
364 ItemList *itemPtr = bPtr->items;
366 if ((index < 0) || (index >= bPtr->itemCount))
367 return NULL;
369 while (index-->0)
370 itemPtr = itemPtr->nextPtr;
372 return itemPtr->text;
376 static void
377 paintPopUpButton(PopUpButton *bPtr)
379 W_Screen *scr = bPtr->view->screen;
380 char *caption;
381 Pixmap pixmap;
384 if (bPtr->flags.pullsDown) {
385 caption = bPtr->caption;
386 } else {
387 if (bPtr->selectedItem == NULL) {
388 /* if no item selected, show the caption */
389 caption = bPtr->caption;
390 } else {
391 caption = bPtr->selectedItem->text;
395 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
396 bPtr->view->size.width, bPtr->view->size.height,
397 scr->depth);
398 XFillRectangle(scr->display, pixmap, W_GC(scr->gray), 0, 0,
399 bPtr->view->size.width, bPtr->view->size.height);
401 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
402 bPtr->view->size.height, WRRaised);
404 if (caption) {
405 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
406 (bPtr->view->size.height-WMFontHeight(scr->normalFont))/2,
407 bPtr->view->size.width, WALeft,
408 bPtr->flags.enabled ? W_GC(scr->black) : W_GC(scr->darkGray),
409 False, caption, strlen(caption));
412 if (bPtr->flags.pullsDown) {
413 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
414 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
415 scr->pullDownIndicator->height,
416 bPtr->view->size.width-scr->pullDownIndicator->width-4,
417 (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
418 } else {
419 int x, y;
421 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
422 y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
424 XSetClipOrigin(scr->display, scr->clipGC, x, y);
425 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
426 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
427 scr->clipGC, 0, 0, scr->popUpIndicator->width,
428 scr->popUpIndicator->height, x, y);
431 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
432 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
434 XFreePixmap(scr->display, pixmap);
439 static void
440 handleEvents(XEvent *event, void *data)
442 PopUpButton *bPtr = (PopUpButton*)data;
444 CHECK_CLASS(data, WC_PopUpButton);
447 switch (event->type) {
448 case Expose:
449 if (event->xexpose.count!=0)
450 break;
451 paintPopUpButton(bPtr);
452 break;
454 case DestroyNotify:
455 destroyPopUpButton(bPtr);
456 break;
462 static void
463 paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
465 W_Screen *scr = bPtr->view->screen;
466 int i;
467 int yo;
468 ItemList *itemPtr;
469 int width, height, itemHeight;
471 itemHeight = bPtr->view->size.height;
472 width = bPtr->view->size.width;
473 height = itemHeight * bPtr->itemCount;
474 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
476 if (!highlight) {
477 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
478 width, itemHeight, False);
479 return;
480 } else if (index < 0 && bPtr->flags.pullsDown) {
481 return;
484 XFillRectangle(scr->display, bPtr->menuView->window, W_GC(scr->white),
485 1, index*itemHeight+1, width-3, itemHeight-3);
487 itemPtr = bPtr->items;
488 for (i = 0; i < index; i++)
489 itemPtr = itemPtr->nextPtr;
491 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
492 width, itemHeight, WRRaised);
494 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
495 index*itemHeight + yo, width, WALeft, W_GC(scr->black), False,
496 itemPtr->text, strlen(itemPtr->text));
498 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
499 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
500 bPtr->menuView->window, scr->copyGC, 0, 0,
501 scr->popUpIndicator->width, scr->popUpIndicator->height,
502 width-scr->popUpIndicator->width-4,
503 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
508 Pixmap
509 makeMenuPixmap(PopUpButton *bPtr)
511 Pixmap pixmap;
512 W_Screen *scr = bPtr->view->screen;
513 int i;
514 int yo;
515 ItemList *itemPtr;
516 int width, height, itemHeight;
518 itemHeight = bPtr->view->size.height;
519 width = bPtr->view->size.width;
520 height = itemHeight * bPtr->itemCount;
521 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
523 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
524 scr->depth);
526 XFillRectangle(scr->display, pixmap, W_GC(scr->gray), 0, 0, width, height);
528 itemPtr = bPtr->items;
529 for (i = 0; i < bPtr->itemCount; i++) {
530 GC gc;
532 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
533 WRRaised);
535 if (itemPtr->disabled)
536 gc = W_GC(scr->darkGray);
537 else
538 gc = W_GC(scr->black);
540 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
541 i*itemHeight + yo, width, WALeft, gc, False,
542 itemPtr->text, strlen(itemPtr->text));
544 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
545 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
546 scr->copyGC, 0, 0, scr->popUpIndicator->width,
547 scr->popUpIndicator->height,
548 width-scr->popUpIndicator->width-4,
549 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
551 itemPtr = itemPtr->nextPtr;
554 return pixmap;
558 static void
559 resizeMenu(PopUpButton *bPtr)
561 int height;
563 height = bPtr->itemCount * bPtr->view->size.height;
564 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
568 static void
569 popUpMenu(PopUpButton *bPtr)
571 W_Screen *scr = bPtr->view->screen;
572 Window dummyW;
573 int x, y;
575 if (!bPtr->menuView->flags.realized) {
576 W_RealizeView(bPtr->menuView);
577 resizeMenu(bPtr);
580 if (bPtr->itemCount < 1)
581 return;
583 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
584 0, 0, &x, &y, &dummyW);
586 if (bPtr->flags.pullsDown) {
587 y += bPtr->view->size.height;
588 } else {
589 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
591 W_MoveView(bPtr->menuView, x, y);
593 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
594 makeMenuPixmap(bPtr));
595 XClearWindow(scr->display, bPtr->menuView->window);
597 W_MapView(bPtr->menuView);
599 bPtr->highlightedItem = 0;
600 if (!bPtr->flags.pullsDown && bPtr->selectedItem != NULL)
601 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
605 static void
606 popDownMenu(PopUpButton *bPtr)
608 W_UnmapView(bPtr->menuView);
610 /* free the background pixmap used to draw the menu contents */
611 XSetWindowBackgroundPixmap(bPtr->view->screen->display,
612 bPtr->menuView->window, None);
616 static int
617 itemIsEnabled(PopUpButton *bPtr, int index)
619 ItemList *item = bPtr->items;
621 while (index-- > 0 && item)
622 item = item->nextPtr;
624 return item ? !item->disabled : False;
628 static void
629 autoScroll(void *data)
631 PopUpButton *bPtr = (PopUpButton*)data;
632 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
633 int repeat = 0;
634 int dy = 0;
637 if (bPtr->scrollStartY >= scrHeight-1
638 && bPtr->menuView->pos.y+bPtr->menuView->size.height >= scrHeight-1) {
639 repeat = 1;
641 if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5
642 <= scrHeight - 1) {
643 dy = scrHeight - 1
644 - (bPtr->menuView->pos.y+bPtr->menuView->size.height);
645 } else
646 dy = -5;
648 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
649 repeat = 1;
651 if (bPtr->menuView->pos.y+5 > 1)
652 dy = 1 - bPtr->menuView->pos.y;
653 else
654 dy = 5;
657 if (repeat) {
658 int oldItem;
660 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x,
661 bPtr->menuView->pos.y + dy);
663 oldItem = bPtr->highlightedItem;
664 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
665 / bPtr->view->size.height;
667 if (oldItem!=bPtr->highlightedItem) {
668 paintMenuEntry(bPtr, oldItem, False);
669 paintMenuEntry(bPtr, bPtr->highlightedItem,
670 itemIsEnabled(bPtr, bPtr->highlightedItem));
673 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
674 } else {
675 bPtr->timer = NULL;
680 static void
681 handleActionEvents(XEvent *event, void *data)
683 PopUpButton *bPtr = (PopUpButton*)data;
684 int oldItem;
685 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
687 CHECK_CLASS(data, WC_PopUpButton);
689 if (bPtr->itemCount < 1)
690 return;
692 switch (event->type) {
693 /* called for menuView */
694 case Expose:
695 paintMenuEntry(bPtr, bPtr->highlightedItem,
696 itemIsEnabled(bPtr, bPtr->highlightedItem));
697 break;
699 case LeaveNotify:
700 bPtr->flags.insideMenu = 0;
701 if (bPtr->menuView->flags.mapped)
702 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
703 bPtr->highlightedItem = -1;
704 break;
706 case EnterNotify:
707 bPtr->flags.insideMenu = 1;
708 break;
710 case MotionNotify:
711 if (bPtr->flags.insideMenu) {
712 oldItem = bPtr->highlightedItem;
713 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
714 if (oldItem!=bPtr->highlightedItem) {
715 paintMenuEntry(bPtr, oldItem, False);
716 paintMenuEntry(bPtr, bPtr->highlightedItem,
717 itemIsEnabled(bPtr, bPtr->highlightedItem));
720 if (event->xmotion.y_root >= scrHeight-1
721 || event->xmotion.y_root <= 1) {
722 bPtr->scrollStartY = event->xmotion.y_root;
723 if (!bPtr->timer)
724 autoScroll(bPtr);
725 } else if (bPtr->timer) {
726 WMDeleteTimerHandler(bPtr->timer);
727 bPtr->timer = NULL;
730 break;
732 /* called for bPtr->view */
733 case ButtonPress:
734 if (!bPtr->flags.enabled)
735 break;
737 popUpMenu(bPtr);
738 if (!bPtr->flags.pullsDown) {
739 bPtr->highlightedItem = bPtr->selectedItemIndex;
740 bPtr->flags.insideMenu = 1;
741 } else {
742 bPtr->highlightedItem = -1;
743 bPtr->flags.insideMenu = 0;
745 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
746 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
747 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
748 None, None, CurrentTime);
749 break;
751 case ButtonRelease:
752 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
753 if (!bPtr->flags.pullsDown)
754 popDownMenu(bPtr);
756 if (bPtr->timer) {
757 WMDeleteTimerHandler(bPtr->timer);
758 bPtr->timer = NULL;
761 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
762 if (itemIsEnabled(bPtr, bPtr->highlightedItem)) {
763 int i;
764 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
766 if (bPtr->flags.pullsDown) {
767 for (i=0; i<MENU_BLINK_COUNT; i++) {
768 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
769 XSync(bPtr->view->screen->display, 0);
770 wusleep(MENU_BLINK_DELAY);
771 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
772 XSync(bPtr->view->screen->display, 0);
773 wusleep(MENU_BLINK_DELAY);
776 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
777 popDownMenu(bPtr);
778 if (bPtr->action)
779 (*bPtr->action)(bPtr, bPtr->clientData);
782 if (bPtr->menuView->flags.mapped)
783 popDownMenu(bPtr);
784 break;
790 static void
791 destroyPopUpButton(PopUpButton *bPtr)
793 ItemList *itemPtr, *tmp;
795 if (bPtr->timer) {
796 WMDeleteTimerHandler(bPtr->timer);
799 itemPtr = bPtr->items;
800 while (itemPtr!=NULL) {
801 free(itemPtr->text);
802 tmp = itemPtr->nextPtr;
803 free(itemPtr);
804 itemPtr = tmp;
807 if (bPtr->caption)
808 free(bPtr->caption);
810 /* have to destroy explicitly because the popup is a toplevel */
811 W_DestroyView(bPtr->menuView);
813 free(bPtr);