Initial revision
[wmaker-crm.git] / WINGs / wpopupbutton.c
blobdf5d24863f0a4939028f68e6175ca7646d9a755f
5 #include "WINGsP.h"
7 typedef struct ItemList {
8 char *text;
9 struct ItemList *nextPtr;
10 unsigned int disabled:1;
11 } ItemList;
13 typedef struct W_PopUpButton {
14 W_Class widgetClass;
15 WMView *view;
17 void *clientData;
18 WMAction *action;
20 char *caption;
22 ItemList *items;
23 short itemCount;
25 short selectedItemIndex;
27 short highlightedItem;
29 ItemList *selectedItem; /* selected item if it is a menu btn */
31 WMView *menuView; /* override redirect popup menu */
33 struct {
34 unsigned int pullsDown:1;
36 unsigned int configured:1;
38 unsigned int insideMenu:1;
39 } flags;
40 } PopUpButton;
43 #define MENU_BLINK_DELAY 60000
44 #define MENU_BLINK_COUNT 2
47 W_ViewProcedureTable _PopUpButtonViewProcedures = {
48 NULL,
49 NULL,
50 NULL
54 #define DEFAULT_WIDTH 60
55 #define DEFAULT_HEIGHT 20
56 #define DEFAULT_CAPTION ""
59 static void destroyPopUpButton(PopUpButton *bPtr);
60 static void paintPopUpButton(PopUpButton *bPtr);
62 static void handleEvents(XEvent *event, void *data);
63 static void handleActionEvents(XEvent *event, void *data);
65 static void resizeMenu(PopUpButton *bPtr);
68 WMPopUpButton*
69 WMCreatePopUpButton(WMWidget *parent)
71 PopUpButton *bPtr;
72 W_Screen *scr = W_VIEW(parent)->screen;
75 bPtr = wmalloc(sizeof(PopUpButton));
76 memset(bPtr, 0, sizeof(PopUpButton));
78 bPtr->widgetClass = WC_PopUpButton;
80 bPtr->view = W_CreateView(W_VIEW(parent));
81 if (!bPtr->view) {
82 free(bPtr);
83 return NULL;
85 bPtr->view->self = bPtr;
87 WMCreateEventHandler(bPtr->view, ExposureMask|StructureNotifyMask
88 |ClientMessageMask, handleEvents, bPtr);
91 W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
92 bPtr->caption = wstrdup(DEFAULT_CAPTION);
94 WMCreateEventHandler(bPtr->view, ButtonPressMask|ButtonReleaseMask,
95 handleActionEvents, bPtr);
97 bPtr->menuView = W_CreateTopView(scr);
98 bPtr->menuView->attribs.override_redirect = True;
99 bPtr->menuView->attribFlags |= CWOverrideRedirect;
101 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
103 WMCreateEventHandler(bPtr->menuView, ButtonPressMask|ButtonReleaseMask
104 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
105 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 void
123 WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title)
125 ItemList *itemPtr, *tmp;
127 CHECK_CLASS(bPtr, WC_PopUpButton);
129 itemPtr = wmalloc(sizeof(ItemList));
130 memset(itemPtr, 0, sizeof(ItemList));
131 itemPtr->text = wstrdup(title);
133 /* append item to list */
134 tmp = bPtr->items;
135 if (!tmp)
136 bPtr->items = itemPtr;
137 else {
138 while (tmp->nextPtr!=NULL)
139 tmp = tmp->nextPtr;
140 tmp->nextPtr = itemPtr;
143 bPtr->itemCount++;
145 if (bPtr->menuView && bPtr->menuView->flags.realized)
146 resizeMenu(bPtr);
150 void
151 WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title)
153 ItemList *itemPtr;
155 CHECK_CLASS(bPtr, WC_PopUpButton);
157 if (index < 0)
158 index = 0;
159 if (index >= bPtr->itemCount) {
160 WMAddPopUpButtonItem(bPtr, title);
161 return;
164 itemPtr = wmalloc(sizeof(ItemList));
165 memset(itemPtr, 0, sizeof(ItemList));
166 itemPtr->text = wstrdup(title);
168 if (index == 0) {
169 itemPtr->nextPtr = bPtr->items;
170 bPtr->items = itemPtr;
171 } else {
172 ItemList *tmp;
173 int i = index;
175 tmp = bPtr->items;
176 /* insert item in list */
177 while (--i > 0) {
178 tmp = tmp->nextPtr;
180 bPtr->items->nextPtr = tmp->nextPtr;
181 tmp->nextPtr = bPtr->items;
184 bPtr->itemCount++;
186 /* if there is an selected item, update it's index to match the new
187 * position */
188 if (index < bPtr->selectedItemIndex)
189 bPtr->selectedItemIndex++;
191 if (bPtr->menuView && bPtr->menuView->flags.realized)
192 resizeMenu(bPtr);
196 void
197 WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index)
199 ItemList *tmp;
201 CHECK_CLASS(bPtr, WC_PopUpButton);
203 if (index < 0 || index >= bPtr->itemCount)
204 return;
207 if (index == 0) {
208 free(bPtr->items->text);
209 tmp = bPtr->items->nextPtr;
210 free(bPtr->items);
211 bPtr->items = tmp;
212 } else {
213 ItemList *next;
214 int i = index;
216 tmp = bPtr->items;
217 while (--i > 0)
218 tmp = tmp->nextPtr;
219 next = tmp->nextPtr->nextPtr;
221 free(tmp->nextPtr->text);
222 free(tmp->nextPtr);
224 tmp->nextPtr = next;
227 bPtr->itemCount--;
229 if (bPtr->selectedItem!=NULL && !bPtr->flags.pullsDown) {
230 if (index < bPtr->selectedItemIndex)
231 bPtr->selectedItemIndex--;
232 else if (index == bPtr->selectedItemIndex) {
233 /* reselect first item if the removed item is the
234 * selected one */
235 bPtr->selectedItem = bPtr->items;
236 bPtr->selectedItemIndex = 0;
237 if (bPtr->view->flags.mapped)
238 paintPopUpButton(bPtr);
242 if (bPtr->menuView && bPtr->menuView->flags.realized)
243 resizeMenu(bPtr);
247 void
248 WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index)
250 ItemList *itemPtr = bPtr->items;
251 int i = index;
253 if (index < 0) {
254 bPtr->selectedItem = NULL;
255 if (bPtr->view->flags.mapped)
256 paintPopUpButton(bPtr);
257 return;
260 while (i-- > 0) {
261 itemPtr = itemPtr->nextPtr;
263 bPtr->selectedItem = itemPtr;
264 bPtr->selectedItemIndex = index;
266 if (bPtr->view->flags.mapped)
267 paintPopUpButton(bPtr);
271 WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr)
273 if (!bPtr->flags.pullsDown && bPtr->selectedItem==NULL)
274 return -1;
275 else
276 return bPtr->selectedItemIndex;
280 void
281 WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text)
283 if (bPtr->caption)
284 free(bPtr->caption);
285 if (text)
286 bPtr->caption = wstrdup(text);
287 else
288 bPtr->caption = NULL;
289 if (bPtr->view->flags.realized) {
290 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
291 paintPopUpButton(bPtr);
298 void
299 WMSetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index, Bool flag)
301 int i;
302 ItemList *item = bPtr->items;
304 if (index < 0 || index >= bPtr->itemCount)
305 return;
307 for (i = 0; i<index; i++)
308 item=item->nextPtr;
310 item->disabled = !flag;
314 void
315 WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag)
317 bPtr->flags.pullsDown = flag;
318 if (!flag) {
319 bPtr->selectedItem = bPtr->items;
320 if (bPtr->selectedItem)
321 bPtr->selectedItemIndex = 0;
322 else
323 bPtr->selectedItemIndex = -1;
324 } else
325 bPtr->selectedItemIndex = -1;
327 if (bPtr->view->flags.mapped)
328 paintPopUpButton(bPtr);
333 WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
335 return bPtr->itemCount;
339 char*
340 WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
342 ItemList *itemPtr = bPtr->items;
344 if ((index < 0) || (index >= bPtr->itemCount))
345 return NULL;
347 while (index-->0)
348 itemPtr = itemPtr->nextPtr;
350 return itemPtr->text;
354 static void
355 paintPopUpButton(PopUpButton *bPtr)
357 W_Screen *scr = bPtr->view->screen;
358 char *caption;
359 Pixmap pixmap;
362 if (bPtr->flags.pullsDown) {
363 caption = bPtr->caption;
364 } else {
365 if (bPtr->selectedItem == NULL) {
366 /* if no item selected, show the caption */
367 caption = bPtr->caption;
368 } else {
369 caption = bPtr->selectedItem->text;
373 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
374 bPtr->view->size.width, bPtr->view->size.height,
375 scr->depth);
376 XFillRectangle(scr->display, pixmap, W_GC(scr->gray), 0, 0,
377 bPtr->view->size.width, bPtr->view->size.height);
379 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
380 bPtr->view->size.height, WRRaised);
382 if (caption) {
383 W_PaintText(bPtr->view, pixmap, scr->normalFont,
384 6, (bPtr->view->size.height-scr->normalFont->height)/2,
385 bPtr->view->size.width, WALeft, W_GC(scr->black), False,
386 caption, strlen(caption));
389 if (bPtr->flags.pullsDown) {
390 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
391 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
392 scr->pullDownIndicator->height,
393 bPtr->view->size.width-scr->pullDownIndicator->width-4,
394 (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
395 } else {
396 int x, y;
398 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
399 y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
401 XSetClipOrigin(scr->display, scr->clipGC, x, y);
402 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
403 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
404 scr->clipGC, 0, 0, scr->popUpIndicator->width,
405 scr->popUpIndicator->height, x, y);
408 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
409 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
411 XFreePixmap(scr->display, pixmap);
416 static void
417 handleEvents(XEvent *event, void *data)
419 PopUpButton *bPtr = (PopUpButton*)data;
421 CHECK_CLASS(data, WC_PopUpButton);
424 switch (event->type) {
425 case Expose:
426 if (event->xexpose.count!=0)
427 break;
428 paintPopUpButton(bPtr);
429 break;
431 case DestroyNotify:
432 destroyPopUpButton(bPtr);
433 break;
439 static void
440 paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
442 W_Screen *scr = bPtr->view->screen;
443 int i;
444 int yo;
445 ItemList *itemPtr;
446 int width, height, itemHeight;
448 itemHeight = bPtr->view->size.height;
449 width = bPtr->view->size.width;
450 height = itemHeight * bPtr->itemCount;
451 yo = (itemHeight - scr->normalFont->height)/2;
453 if (!highlight) {
454 XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
455 width, itemHeight, False);
456 return;
457 } else if (index < 0 && bPtr->flags.pullsDown) {
458 return;
461 XFillRectangle(scr->display, bPtr->menuView->window, W_GC(scr->white),
462 1, index*itemHeight+1, width-3, itemHeight-3);
464 itemPtr = bPtr->items;
465 for (i = 0; i < index; i++)
466 itemPtr = itemPtr->nextPtr;
468 W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight,
469 width, itemHeight, WRRaised);
471 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
472 index*itemHeight + yo, width, WALeft, W_GC(scr->black), False,
473 itemPtr->text, strlen(itemPtr->text));
475 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
476 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
477 bPtr->menuView->window, scr->copyGC, 0, 0,
478 scr->popUpIndicator->width, scr->popUpIndicator->height,
479 width-scr->popUpIndicator->width-4,
480 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
485 Pixmap
486 makeMenuPixmap(PopUpButton *bPtr)
488 Pixmap pixmap;
489 W_Screen *scr = bPtr->view->screen;
490 int i;
491 int yo;
492 ItemList *itemPtr;
493 int width, height, itemHeight;
495 itemHeight = bPtr->view->size.height;
496 width = bPtr->view->size.width;
497 height = itemHeight * bPtr->itemCount;
498 yo = (itemHeight - scr->normalFont->height)/2;
500 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height,
501 scr->depth);
503 XFillRectangle(scr->display, pixmap, W_GC(scr->gray), 0, 0, width, height);
505 itemPtr = bPtr->items;
506 for (i = 0; i < bPtr->itemCount; i++) {
507 GC gc;
509 W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight,
510 WRRaised);
512 if (itemPtr->disabled)
513 gc = W_GC(scr->darkGray);
514 else
515 gc = W_GC(scr->black);
517 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
518 i*itemHeight + yo, width, WALeft, gc, False,
519 itemPtr->text, strlen(itemPtr->text));
521 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
522 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
523 scr->copyGC, 0, 0, scr->popUpIndicator->width,
524 scr->popUpIndicator->height,
525 width-scr->popUpIndicator->width-4,
526 i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
528 itemPtr = itemPtr->nextPtr;
531 return pixmap;
535 static void
536 resizeMenu(PopUpButton *bPtr)
538 int height;
540 height = bPtr->itemCount * bPtr->view->size.height;
541 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
545 static void
546 popUpMenu(PopUpButton *bPtr)
548 W_Screen *scr = bPtr->view->screen;
549 Window dummyW;
550 int x, y;
552 if (!bPtr->menuView->flags.realized) {
553 W_RealizeView(bPtr->menuView);
554 resizeMenu(bPtr);
557 if (bPtr->itemCount < 1)
558 return;
560 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
561 0, 0, &x, &y, &dummyW);
563 if (bPtr->flags.pullsDown) {
564 y += bPtr->view->size.height;
565 } else {
566 y -= bPtr->view->size.height*bPtr->selectedItemIndex;
568 W_MoveView(bPtr->menuView, x, y);
570 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
571 makeMenuPixmap(bPtr));
572 XClearWindow(scr->display, bPtr->menuView->window);
574 W_MapView(bPtr->menuView);
576 bPtr->highlightedItem = 0;
577 if (!bPtr->flags.pullsDown && bPtr->selectedItem != NULL)
578 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
582 static void
583 popDownMenu(PopUpButton *bPtr)
585 W_UnmapView(bPtr->menuView);
587 /* free the background pixmap used to draw the menu contents */
588 XSetWindowBackgroundPixmap(bPtr->view->screen->display,
589 bPtr->menuView->window, None);
593 static int
594 itemIsEnabled(PopUpButton *bPtr, int index)
596 ItemList *item = bPtr->items;
598 while (index-- > 0)
599 item = item->nextPtr;
601 return !item->disabled;
604 static void
605 handleActionEvents(XEvent *event, void *data)
607 PopUpButton *bPtr = (PopUpButton*)data;
608 int oldItem;
610 CHECK_CLASS(data, WC_PopUpButton);
612 if (bPtr->itemCount < 1)
613 return;
615 switch (event->type) {
616 /* called for menuView */
617 case LeaveNotify:
618 bPtr->flags.insideMenu = 0;
619 if (bPtr->menuView->flags.mapped)
620 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
621 bPtr->highlightedItem = -1;
622 break;
624 case EnterNotify:
625 bPtr->flags.insideMenu = 1;
626 break;
628 case MotionNotify:
629 if (bPtr->flags.insideMenu) {
630 oldItem = bPtr->highlightedItem;
631 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
632 if (oldItem!=bPtr->highlightedItem) {
633 paintMenuEntry(bPtr, oldItem, False);
634 paintMenuEntry(bPtr, bPtr->highlightedItem,
635 itemIsEnabled(bPtr, bPtr->highlightedItem));
638 break;
640 /* called for bPtr->view */
641 case ButtonPress:
642 popUpMenu(bPtr);
643 if (!bPtr->flags.pullsDown) {
644 bPtr->highlightedItem = bPtr->selectedItemIndex;
645 bPtr->flags.insideMenu = 1;
646 } else {
647 bPtr->highlightedItem = -1;
648 bPtr->flags.insideMenu = 0;
650 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
651 False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
652 |LeaveWindowMask, GrabModeAsync, GrabModeAsync,
653 None, None, CurrentTime);
654 break;
656 case ButtonRelease:
657 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
658 if (!bPtr->flags.pullsDown)
659 popDownMenu(bPtr);
660 if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {
661 if (itemIsEnabled(bPtr, bPtr->highlightedItem)) {
662 int i;
663 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
665 if (bPtr->flags.pullsDown) {
666 for (i=0; i<MENU_BLINK_COUNT; i++) {
667 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
668 XSync(bPtr->view->screen->display, 0);
669 wusleep(MENU_BLINK_DELAY);
670 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
671 XSync(bPtr->view->screen->display, 0);
672 wusleep(MENU_BLINK_DELAY);
675 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
676 popDownMenu(bPtr);
677 if (bPtr->action)
678 (*bPtr->action)(bPtr, bPtr->clientData);
681 if (bPtr->menuView->flags.mapped)
682 popDownMenu(bPtr);
683 break;
689 static void
690 destroyPopUpButton(PopUpButton *bPtr)
692 ItemList *itemPtr, *tmp;
694 itemPtr = bPtr->items;
695 while (itemPtr!=NULL) {
696 free(itemPtr->text);
697 tmp = itemPtr->nextPtr;
698 free(itemPtr);
699 itemPtr = tmp;
702 if (bPtr->caption)
703 free(bPtr->caption);
705 /* have to destroy explicitly because the popup is a toplevel */
706 W_DestroyView(bPtr->menuView);
708 free(bPtr);