Update for 0.51.2-pre2
[wmaker-crm.git] / WINGs / wpopupbutton.c
blobe29aceb98954f2eb9ed8f5dfa4fe8d44a4c97d28
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 itemPtr->nextPtr = tmp->nextPtr;
194 tmp->nextPtr = itemPtr;
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 /* This code causes bugs. It should not select any item,
342 * since it was not asked to. -Dan
343 bPtr->selectedItem = bPtr->items;
344 if (bPtr->selectedItem)
345 bPtr->selectedItemIndex = 0;
346 else
347 bPtr->selectedItemIndex = -1;
349 bPtr->selectedItem = NULL;
350 /* the drawing routine, still draws things wrong if we put
351 * the index to -1 (i.e. not selected).
352 * Find out why. -Dan */
353 bPtr->selectedItemIndex = 0;
354 } else
355 bPtr->selectedItemIndex = -1;
357 if (bPtr->view->flags.mapped)
358 paintPopUpButton(bPtr);
363 WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
365 return bPtr->itemCount;
369 char*
370 WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
372 ItemList *itemPtr = bPtr->items;
374 if ((index < 0) || (index >= bPtr->itemCount))
375 return NULL;
377 while (index-->0)
378 itemPtr = itemPtr->nextPtr;
380 return itemPtr->text;
384 static void
385 paintPopUpButton(PopUpButton *bPtr)
387 W_Screen *scr = bPtr->view->screen;
388 char *caption;
389 Pixmap pixmap;
392 if (bPtr->flags.pullsDown) {
393 caption = bPtr->caption;
394 } else {
395 if (bPtr->selectedItem == NULL) {
396 /* if no item selected, show the caption */
397 caption = bPtr->caption;
398 } else {
399 caption = bPtr->selectedItem->text;
403 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
404 bPtr->view->size.width, bPtr->view->size.height,
405 scr->depth);
406 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
407 bPtr->view->size.width, bPtr->view->size.height);
409 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
410 bPtr->view->size.height, WRRaised);
412 if (caption) {
413 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
414 (bPtr->view->size.height-WMFontHeight(scr->normalFont))/2,
415 bPtr->view->size.width, WALeft,
416 bPtr->flags.enabled ? WMColorGC(scr->black) : WMColorGC(scr->darkGray),
417 False, caption, strlen(caption));
420 if (bPtr->flags.pullsDown) {
421 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
422 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
423 scr->pullDownIndicator->height,
424 bPtr->view->size.width-scr->pullDownIndicator->width-4,
425 (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
426 } else {
427 int x, y;
429 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
430 y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
432 XSetClipOrigin(scr->display, scr->clipGC, x, y);
433 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
434 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
435 scr->clipGC, 0, 0, scr->popUpIndicator->width,
436 scr->popUpIndicator->height, x, y);
439 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
440 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
442 XFreePixmap(scr->display, pixmap);
447 static void
448 handleEvents(XEvent *event, void *data)
450 PopUpButton *bPtr = (PopUpButton*)data;
452 CHECK_CLASS(data, WC_PopUpButton);
455 switch (event->type) {
456 case Expose:
457 if (event->xexpose.count!=0)
458 break;
459 paintPopUpButton(bPtr);
460 break;
462 case DestroyNotify:
463 destroyPopUpButton(bPtr);
464 break;
470 static void
471 paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
473 W_Screen *scr = bPtr->view->screen;
474 int i;
475 int yo;
476 ItemList *itemPtr;
477 int width, height, itemHeight;
479 itemHeight = bPtr->view->size.height;
480 width = bPtr->view->size.width;
481 height = itemHeight * bPtr->itemCount;
482 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
484 if (!highlight) {
485 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
486 width, itemHeight, False);
487 return;
488 } else if (index < 0 && bPtr->flags.pullsDown) {
489 return;
492 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
493 1, index*itemHeight+1, width-3, itemHeight-3);
495 itemPtr = bPtr->items;
496 for (i = 0; i < index; i++)
497 itemPtr = itemPtr->nextPtr;
499 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
500 width, itemHeight, WRRaised);
502 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
503 index*itemHeight + yo, width, WALeft, WMColorGC(scr->black), False,
504 itemPtr->text, strlen(itemPtr->text));
506 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
507 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
508 bPtr->menuView->window, scr->copyGC, 0, 0,
509 scr->popUpIndicator->width, scr->popUpIndicator->height,
510 width-scr->popUpIndicator->width-4,
511 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
516 Pixmap
517 makeMenuPixmap(PopUpButton *bPtr)
519 Pixmap pixmap;
520 W_Screen *scr = bPtr->view->screen;
521 int i;
522 int yo;
523 ItemList *itemPtr;
524 int width, height, itemHeight;
526 itemHeight = bPtr->view->size.height;
527 width = bPtr->view->size.width;
528 height = itemHeight * bPtr->itemCount;
529 yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
531 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
532 scr->depth);
534 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0, width, height);
536 itemPtr = bPtr->items;
537 for (i = 0; i < bPtr->itemCount; i++) {
538 GC gc;
540 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
541 WRRaised);
543 if (itemPtr->disabled)
544 gc = WMColorGC(scr->darkGray);
545 else
546 gc = WMColorGC(scr->black);
548 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
549 i*itemHeight + yo, width, WALeft, gc, False,
550 itemPtr->text, strlen(itemPtr->text));
552 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
553 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
554 scr->copyGC, 0, 0, scr->popUpIndicator->width,
555 scr->popUpIndicator->height,
556 width-scr->popUpIndicator->width-4,
557 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
559 itemPtr = itemPtr->nextPtr;
562 return pixmap;
566 static void
567 resizeMenu(PopUpButton *bPtr)
569 int height;
571 height = bPtr->itemCount * bPtr->view->size.height;
572 if (height > 0)
573 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
577 static void
578 popUpMenu(PopUpButton *bPtr)
580 W_Screen *scr = bPtr->view->screen;
581 Window dummyW;
582 int x, y;
584 if (!bPtr->menuView->flags.realized) {
585 W_RealizeView(bPtr->menuView);
586 resizeMenu(bPtr);
589 if (bPtr->itemCount < 1)
590 return;
592 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
593 0, 0, &x, &y, &dummyW);
595 if (bPtr->flags.pullsDown) {
596 y += bPtr->view->size.height;
597 } else {
598 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
600 W_MoveView(bPtr->menuView, x, y);
602 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
603 makeMenuPixmap(bPtr));
604 XClearWindow(scr->display, bPtr->menuView->window);
606 W_MapView(bPtr->menuView);
608 bPtr->highlightedItem = 0;
609 if (!bPtr->flags.pullsDown && bPtr->selectedItem != NULL)
610 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
614 static void
615 popDownMenu(PopUpButton *bPtr)
617 W_UnmapView(bPtr->menuView);
619 /* free the background pixmap used to draw the menu contents */
620 XSetWindowBackgroundPixmap(bPtr->view->screen->display,
621 bPtr->menuView->window, None);
625 static int
626 itemIsEnabled(PopUpButton *bPtr, int index)
628 ItemList *item = bPtr->items;
630 while (index-- > 0 && item)
631 item = item->nextPtr;
633 return item ? !item->disabled : False;
637 static void
638 autoScroll(void *data)
640 PopUpButton *bPtr = (PopUpButton*)data;
641 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
642 int repeat = 0;
643 int dy = 0;
646 if (bPtr->scrollStartY >= scrHeight-1
647 && bPtr->menuView->pos.y+bPtr->menuView->size.height >= scrHeight-1) {
648 repeat = 1;
650 if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5
651 <= scrHeight - 1) {
652 dy = scrHeight - 1
653 - (bPtr->menuView->pos.y+bPtr->menuView->size.height);
654 } else
655 dy = -5;
657 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
658 repeat = 1;
660 if (bPtr->menuView->pos.y+5 > 1)
661 dy = 1 - bPtr->menuView->pos.y;
662 else
663 dy = 5;
666 if (repeat) {
667 int oldItem;
669 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x,
670 bPtr->menuView->pos.y + dy);
672 oldItem = bPtr->highlightedItem;
673 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
674 / bPtr->view->size.height;
676 if (oldItem!=bPtr->highlightedItem) {
677 paintMenuEntry(bPtr, oldItem, False);
678 paintMenuEntry(bPtr, bPtr->highlightedItem,
679 itemIsEnabled(bPtr, bPtr->highlightedItem));
682 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
683 } else {
684 bPtr->timer = NULL;
689 static void
690 handleActionEvents(XEvent *event, void *data)
692 PopUpButton *bPtr = (PopUpButton*)data;
693 int oldItem;
694 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
696 CHECK_CLASS(data, WC_PopUpButton);
698 if (bPtr->itemCount < 1)
699 return;
701 switch (event->type) {
702 /* called for menuView */
703 case Expose:
704 paintMenuEntry(bPtr, bPtr->highlightedItem,
705 itemIsEnabled(bPtr, bPtr->highlightedItem));
706 break;
708 case LeaveNotify:
709 bPtr->flags.insideMenu = 0;
710 if (bPtr->menuView->flags.mapped)
711 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
712 bPtr->highlightedItem = -1;
713 break;
715 case EnterNotify:
716 bPtr->flags.insideMenu = 1;
717 break;
719 case MotionNotify:
720 if (bPtr->flags.insideMenu) {
721 oldItem = bPtr->highlightedItem;
722 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
723 if (oldItem!=bPtr->highlightedItem) {
724 paintMenuEntry(bPtr, oldItem, False);
725 paintMenuEntry(bPtr, bPtr->highlightedItem,
726 itemIsEnabled(bPtr, bPtr->highlightedItem));
729 if (event->xmotion.y_root >= scrHeight-1
730 || event->xmotion.y_root <= 1) {
731 bPtr->scrollStartY = event->xmotion.y_root;
732 if (!bPtr->timer)
733 autoScroll(bPtr);
734 } else if (bPtr->timer) {
735 WMDeleteTimerHandler(bPtr->timer);
736 bPtr->timer = NULL;
739 break;
741 /* called for bPtr->view */
742 case ButtonPress:
743 if (!bPtr->flags.enabled)
744 break;
746 popUpMenu(bPtr);
747 if (!bPtr->flags.pullsDown) {
748 bPtr->highlightedItem = bPtr->selectedItemIndex;
749 bPtr->flags.insideMenu = 1;
750 } else {
751 bPtr->highlightedItem = -1;
752 bPtr->flags.insideMenu = 0;
754 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
755 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
756 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
757 None, None, CurrentTime);
758 break;
760 case ButtonRelease:
761 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
762 if (!bPtr->flags.pullsDown)
763 popDownMenu(bPtr);
765 if (bPtr->timer) {
766 WMDeleteTimerHandler(bPtr->timer);
767 bPtr->timer = NULL;
770 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
771 if (itemIsEnabled(bPtr, bPtr->highlightedItem)) {
772 int i;
773 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
775 if (bPtr->flags.pullsDown) {
776 for (i=0; i<MENU_BLINK_COUNT; i++) {
777 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
778 XSync(bPtr->view->screen->display, 0);
779 wusleep(MENU_BLINK_DELAY);
780 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
781 XSync(bPtr->view->screen->display, 0);
782 wusleep(MENU_BLINK_DELAY);
785 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
786 popDownMenu(bPtr);
787 if (bPtr->action)
788 (*bPtr->action)(bPtr, bPtr->clientData);
791 if (bPtr->menuView->flags.mapped)
792 popDownMenu(bPtr);
793 break;
799 static void
800 destroyPopUpButton(PopUpButton *bPtr)
802 ItemList *itemPtr, *tmp;
804 if (bPtr->timer) {
805 WMDeleteTimerHandler(bPtr->timer);
808 itemPtr = bPtr->items;
809 while (itemPtr!=NULL) {
810 free(itemPtr->text);
811 tmp = itemPtr->nextPtr;
812 free(itemPtr);
813 itemPtr = tmp;
816 if (bPtr->caption)
817 free(bPtr->caption);
819 /* have to destroy explicitly because the popup is a toplevel */
820 W_DestroyView(bPtr->menuView);
822 free(bPtr);