Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / wpopupbutton.c
blobba43ce7871eaf3c43d5c03f8d388fad151905869
2 #include "WINGsP.h"
4 typedef struct W_PopUpButton {
5 W_Class widgetClass;
6 WMView *view;
8 void *clientData;
9 WMAction *action;
11 char *caption;
13 WMArray *items;
15 short selectedItemIndex;
17 short highlightedItem;
19 WMView *menuView; /* override redirect popup menu */
21 WMHandlerID timer; /* for autoscroll */
23 /**/ int scrollStartY; /* for autoscroll */
25 struct {
26 unsigned int pullsDown:1;
28 unsigned int configured:1;
30 unsigned int insideMenu:1;
32 unsigned int enabled:1;
34 } flags;
35 } PopUpButton;
37 #define MENU_BLINK_DELAY 60000
38 #define MENU_BLINK_COUNT 2
40 #define SCROLL_DELAY 10
42 #define DEFAULT_WIDTH 60
43 #define DEFAULT_HEIGHT 20
44 #define DEFAULT_CAPTION ""
46 static void destroyPopUpButton(PopUpButton * bPtr);
47 static void paintPopUpButton(PopUpButton * bPtr);
49 static void handleEvents(XEvent * event, void *data);
50 static void handleActionEvents(XEvent * event, void *data);
52 static void resizeMenu(PopUpButton * bPtr);
54 WMPopUpButton *WMCreatePopUpButton(WMWidget * parent)
56 PopUpButton *bPtr;
57 W_Screen *scr = W_VIEW(parent)->screen;
59 bPtr = wmalloc(sizeof(PopUpButton));
60 memset(bPtr, 0, sizeof(PopUpButton));
62 bPtr->widgetClass = WC_PopUpButton;
64 bPtr->view = W_CreateView(W_VIEW(parent));
65 if (!bPtr->view) {
66 wfree(bPtr);
67 return NULL;
69 bPtr->view->self = bPtr;
71 WMCreateEventHandler(bPtr->view, ExposureMask | StructureNotifyMask
72 | ClientMessageMask, handleEvents, bPtr);
74 W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
75 bPtr->caption = wstrdup(DEFAULT_CAPTION);
77 WMCreateEventHandler(bPtr->view, ButtonPressMask | ButtonReleaseMask, handleActionEvents, bPtr);
79 bPtr->flags.enabled = 1;
81 bPtr->items = WMCreateArrayWithDestructor(4, (WMFreeDataProc *) WMDestroyMenuItem);
83 bPtr->selectedItemIndex = -1;
85 bPtr->menuView = W_CreateUnmanagedTopView(scr);
87 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
89 WMCreateEventHandler(bPtr->menuView, ButtonPressMask | ButtonReleaseMask
90 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask
91 | ExposureMask, handleActionEvents, bPtr);
93 return bPtr;
96 void WMSetPopUpButtonAction(WMPopUpButton * bPtr, WMAction * action, void *clientData)
98 CHECK_CLASS(bPtr, WC_PopUpButton);
100 bPtr->action = action;
102 bPtr->clientData = clientData;
105 WMMenuItem *WMAddPopUpButtonItem(WMPopUpButton * bPtr, char *title)
107 WMMenuItem *item;
109 CHECK_CLASS(bPtr, WC_PopUpButton);
111 item = WMCreateMenuItem();
112 WMSetMenuItemTitle(item, title);
114 WMAddToArray(bPtr->items, item);
116 if (bPtr->menuView && bPtr->menuView->flags.realized)
117 resizeMenu(bPtr);
119 return item;
122 WMMenuItem *WMInsertPopUpButtonItem(WMPopUpButton * bPtr, int index, char *title)
124 WMMenuItem *item;
126 CHECK_CLASS(bPtr, WC_PopUpButton);
128 item = WMCreateMenuItem();
129 WMSetMenuItemTitle(item, title);
131 WMInsertInArray(bPtr->items, index, item);
133 /* if there is an selected item, update it's index to match the new
134 * position */
135 if (index < bPtr->selectedItemIndex)
136 bPtr->selectedItemIndex++;
138 if (bPtr->menuView && bPtr->menuView->flags.realized)
139 resizeMenu(bPtr);
141 return item;
144 void WMRemovePopUpButtonItem(WMPopUpButton * bPtr, int index)
146 CHECK_CLASS(bPtr, WC_PopUpButton);
148 wassertr(index >= 0 && index < WMGetArrayItemCount(bPtr->items));
150 WMDeleteFromArray(bPtr->items, index);
152 if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) {
153 if (index < bPtr->selectedItemIndex)
154 bPtr->selectedItemIndex--;
155 else if (index == bPtr->selectedItemIndex) {
156 /* reselect first item if the removed item is the
157 * selected one */
158 bPtr->selectedItemIndex = 0;
159 if (bPtr->view->flags.mapped)
160 paintPopUpButton(bPtr);
164 if (bPtr->menuView && bPtr->menuView->flags.realized)
165 resizeMenu(bPtr);
168 void WMSetPopUpButtonEnabled(WMPopUpButton * bPtr, Bool flag)
170 bPtr->flags.enabled = ((flag == 0) ? 0 : 1);
171 if (bPtr->view->flags.mapped)
172 paintPopUpButton(bPtr);
175 Bool WMGetPopUpButtonEnabled(WMPopUpButton * bPtr)
177 return bPtr->flags.enabled;
180 void WMSetPopUpButtonSelectedItem(WMPopUpButton * bPtr, int index)
183 wassertr(index < WMGetArrayItemCount(bPtr->items));
185 /* if (index >= WMGetArrayCount(bPtr->items))
186 index = -1; */
188 bPtr->selectedItemIndex = index;
190 if (bPtr->view->flags.mapped)
191 paintPopUpButton(bPtr);
194 int WMGetPopUpButtonSelectedItem(WMPopUpButton * bPtr)
196 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
197 return -1;
198 else
199 return bPtr->selectedItemIndex;
202 void WMSetPopUpButtonText(WMPopUpButton * bPtr, char *text)
204 if (bPtr->caption)
205 wfree(bPtr->caption);
206 if (text)
207 bPtr->caption = wstrdup(text);
208 else
209 bPtr->caption = NULL;
210 if (bPtr->view->flags.realized) {
211 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
212 paintPopUpButton(bPtr);
217 void WMSetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index, Bool flag)
219 WMSetMenuItemEnabled(WMGetFromArray(bPtr->items, index), (flag ? 1 : 0));
222 Bool WMGetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index)
224 return WMGetMenuItemEnabled(WMGetFromArray(bPtr->items, index));
227 void WMSetPopUpButtonPullsDown(WMPopUpButton * bPtr, Bool flag)
229 bPtr->flags.pullsDown = ((flag == 0) ? 0 : 1);
230 if (flag) {
231 bPtr->selectedItemIndex = -1;
234 if (bPtr->view->flags.mapped)
235 paintPopUpButton(bPtr);
238 int WMGetPopUpButtonNumberOfItems(WMPopUpButton * bPtr)
240 return WMGetArrayItemCount(bPtr->items);
243 char *WMGetPopUpButtonItem(WMPopUpButton * bPtr, int index)
245 if (index >= WMGetArrayItemCount(bPtr->items) || index < 0)
246 return NULL;
248 return WMGetMenuItemTitle(WMGetFromArray(bPtr->items, index));
251 WMMenuItem *WMGetPopUpButtonMenuItem(WMPopUpButton * bPtr, int index)
253 return WMGetFromArray(bPtr->items, index);
256 static void paintPopUpButton(PopUpButton * bPtr)
258 W_Screen *scr = bPtr->view->screen;
259 char *caption;
260 Pixmap pixmap;
262 if (bPtr->flags.pullsDown) {
263 caption = bPtr->caption;
264 } else {
265 if (bPtr->selectedItemIndex < 0) {
266 /* if no item selected, show the caption */
267 caption = bPtr->caption;
268 } else {
269 caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
273 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
274 bPtr->view->size.width, bPtr->view->size.height, scr->depth);
275 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
276 bPtr->view->size.width, bPtr->view->size.height);
278 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width, bPtr->view->size.height, WRRaised);
280 if (caption) {
281 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
282 (bPtr->view->size.height - WMFontHeight(scr->normalFont)) / 2,
283 bPtr->view->size.width, WALeft,
284 bPtr->flags.enabled ? scr->black : scr->darkGray, False, caption, strlen(caption));
287 if (bPtr->flags.pullsDown) {
288 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
289 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
290 scr->pullDownIndicator->height,
291 bPtr->view->size.width - scr->pullDownIndicator->width - 4,
292 (bPtr->view->size.height - scr->pullDownIndicator->height) / 2);
293 } else {
294 int x, y;
296 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
297 y = (bPtr->view->size.height - scr->popUpIndicator->height) / 2;
299 XSetClipOrigin(scr->display, scr->clipGC, x, y);
300 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
301 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
302 scr->clipGC, 0, 0, scr->popUpIndicator->width, scr->popUpIndicator->height, x, y);
305 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
306 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
308 XFreePixmap(scr->display, pixmap);
311 static void handleEvents(XEvent * event, void *data)
313 PopUpButton *bPtr = (PopUpButton *) data;
315 CHECK_CLASS(data, WC_PopUpButton);
317 switch (event->type) {
318 case Expose:
319 if (event->xexpose.count != 0)
320 break;
321 paintPopUpButton(bPtr);
322 break;
324 case DestroyNotify:
325 destroyPopUpButton(bPtr);
326 break;
330 static void paintMenuEntry(PopUpButton * bPtr, int index, int highlight)
332 W_Screen *scr = bPtr->view->screen;
333 int yo;
334 int width, height, itemHeight, itemCount;
335 char *title;
337 itemCount = WMGetArrayItemCount(bPtr->items);
338 if (index < 0 || index >= itemCount)
339 return;
341 itemHeight = bPtr->view->size.height;
342 width = bPtr->view->size.width;
343 height = itemHeight * itemCount;
344 yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
346 if (!highlight) {
347 XClearArea(scr->display, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, False);
348 return;
349 } else if (index < 0 && bPtr->flags.pullsDown) {
350 return;
353 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
354 1, index * itemHeight + 1, width - 3, itemHeight - 3);
356 title = WMGetPopUpButtonItem(bPtr, index);
358 W_DrawRelief(scr, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, WRRaised);
360 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
361 index * itemHeight + yo, width, WALeft, scr->black, False, title, strlen(title));
363 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
364 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
365 bPtr->menuView->window, scr->copyGC, 0, 0,
366 scr->popUpIndicator->width, scr->popUpIndicator->height,
367 width - scr->popUpIndicator->width - 4,
368 index * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
372 Pixmap makeMenuPixmap(PopUpButton * bPtr)
374 Pixmap pixmap;
375 W_Screen *scr = bPtr->view->screen;
376 WMMenuItem *item;
377 WMArrayIterator iter;
378 int yo, i;
379 int width, height, itemHeight;
381 itemHeight = bPtr->view->size.height;
382 width = bPtr->view->size.width;
383 height = itemHeight * WMGetArrayItemCount(bPtr->items);
384 yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
386 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height, scr->depth);
388 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0, width, height);
390 i = 0;
391 WM_ITERATE_ARRAY(bPtr->items, item, iter) {
392 WMColor *color;
393 char *text;
395 text = WMGetMenuItemTitle(item);
397 W_DrawRelief(scr, pixmap, 0, i * itemHeight, width, itemHeight, WRRaised);
399 if (!WMGetMenuItemEnabled(item))
400 color = scr->darkGray;
401 else
402 color = scr->black;
404 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
405 i * itemHeight + yo, width, WALeft, color, False, text, strlen(text));
407 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
408 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
409 scr->copyGC, 0, 0, scr->popUpIndicator->width,
410 scr->popUpIndicator->height,
411 width - scr->popUpIndicator->width - 4,
412 i * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
415 i++;
418 return pixmap;
421 static void resizeMenu(PopUpButton * bPtr)
423 int height;
425 height = WMGetArrayItemCount(bPtr->items) * bPtr->view->size.height;
426 if (height > 0)
427 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
430 static void popUpMenu(PopUpButton * bPtr)
432 W_Screen *scr = bPtr->view->screen;
433 Window dummyW;
434 int x, y;
436 if (!bPtr->flags.enabled)
437 return;
439 if (!bPtr->menuView->flags.realized) {
440 W_RealizeView(bPtr->menuView);
441 resizeMenu(bPtr);
444 if (WMGetArrayItemCount(bPtr->items) < 1)
445 return;
447 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin, 0, 0, &x, &y, &dummyW);
449 if (bPtr->flags.pullsDown) {
450 y += bPtr->view->size.height;
451 } else {
452 y -= bPtr->view->size.height * bPtr->selectedItemIndex;
454 W_MoveView(bPtr->menuView, x, y);
456 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window, makeMenuPixmap(bPtr));
457 XClearWindow(scr->display, bPtr->menuView->window);
459 if (W_VIEW_WIDTH(bPtr->menuView) != W_VIEW_WIDTH(bPtr->view))
460 resizeMenu(bPtr);
462 W_MapView(bPtr->menuView);
464 bPtr->highlightedItem = 0;
465 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
466 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
469 static void popDownMenu(PopUpButton * bPtr)
471 W_UnmapView(bPtr->menuView);
474 static void autoScroll(void *data)
476 PopUpButton *bPtr = (PopUpButton *) data;
477 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
478 int repeat = 0;
479 int dy = 0;
481 if (bPtr->scrollStartY >= scrHeight - 1
482 && bPtr->menuView->pos.y + bPtr->menuView->size.height >= scrHeight - 1) {
483 repeat = 1;
485 if (bPtr->menuView->pos.y + bPtr->menuView->size.height - 5 <= scrHeight - 1) {
486 dy = scrHeight - 1 - (bPtr->menuView->pos.y + bPtr->menuView->size.height);
487 } else
488 dy = -5;
490 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
491 repeat = 1;
493 if (bPtr->menuView->pos.y + 5 > 1)
494 dy = 1 - bPtr->menuView->pos.y;
495 else
496 dy = 5;
499 if (repeat) {
500 int oldItem;
502 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x, bPtr->menuView->pos.y + dy);
504 oldItem = bPtr->highlightedItem;
505 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
506 / bPtr->view->size.height;
508 if (oldItem != bPtr->highlightedItem) {
509 WMMenuItem *item;
511 paintMenuEntry(bPtr, oldItem, False);
513 if (bPtr->highlightedItem >= 0 && bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
514 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
515 paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
516 } else {
517 bPtr->highlightedItem = -1;
521 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
522 } else {
523 bPtr->timer = NULL;
527 static void wheelScrollUp(PopUpButton * bPtr)
529 int testIndex = bPtr->selectedItemIndex - 1;
531 while (testIndex >= 0 && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
532 testIndex--;
533 if (testIndex != -1) {
534 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
535 if (bPtr->action)
536 (*bPtr->action) (bPtr, bPtr->clientData);
540 static void wheelScrollDown(PopUpButton * bPtr)
542 int itemCount = WMGetArrayItemCount(bPtr->items);
543 int testIndex = bPtr->selectedItemIndex + 1;
545 while (testIndex < itemCount && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
546 testIndex++;
547 if (testIndex != itemCount) {
548 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
549 if (bPtr->action)
550 (*bPtr->action) (bPtr, bPtr->clientData);
554 static void handleActionEvents(XEvent * event, void *data)
556 PopUpButton *bPtr = (PopUpButton *) data;
557 int oldItem;
558 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
560 CHECK_CLASS(data, WC_PopUpButton);
562 if (WMGetArrayItemCount(bPtr->items) < 1)
563 return;
565 switch (event->type) {
566 /* called for menuView */
567 case Expose:
568 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
569 break;
571 case LeaveNotify:
572 bPtr->flags.insideMenu = 0;
573 if (bPtr->menuView->flags.mapped)
574 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
575 bPtr->highlightedItem = -1;
576 break;
578 case EnterNotify:
579 bPtr->flags.insideMenu = 1;
580 break;
582 case MotionNotify:
583 if (bPtr->flags.insideMenu) {
584 oldItem = bPtr->highlightedItem;
585 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
586 if (oldItem != bPtr->highlightedItem) {
587 WMMenuItem *item;
589 paintMenuEntry(bPtr, oldItem, False);
590 if (bPtr->highlightedItem >= 0 &&
591 bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
592 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
593 paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
594 } else {
595 bPtr->highlightedItem = -1;
600 if (event->xmotion.y_root >= scrHeight - 1 || event->xmotion.y_root <= 1) {
601 bPtr->scrollStartY = event->xmotion.y_root;
602 if (!bPtr->timer)
603 autoScroll(bPtr);
604 } else if (bPtr->timer) {
605 WMDeleteTimerHandler(bPtr->timer);
606 bPtr->timer = NULL;
609 break;
611 /* called for bPtr->view */
612 case ButtonPress:
613 if (!bPtr->flags.enabled)
614 break;
616 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
617 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
618 wheelScrollUp(bPtr);
620 break;
621 } else if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
622 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
623 wheelScrollDown(bPtr);
625 break;
627 popUpMenu(bPtr);
628 if (!bPtr->flags.pullsDown) {
629 bPtr->highlightedItem = bPtr->selectedItemIndex;
630 bPtr->flags.insideMenu = 1;
631 } else {
632 bPtr->highlightedItem = -1;
633 bPtr->flags.insideMenu = 0;
635 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
636 False, ButtonReleaseMask | ButtonMotionMask | EnterWindowMask
637 | LeaveWindowMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
638 break;
640 case ButtonRelease:
641 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
642 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
643 break;
645 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
646 if (!bPtr->flags.pullsDown)
647 popDownMenu(bPtr);
649 if (bPtr->timer) {
650 WMDeleteTimerHandler(bPtr->timer);
651 bPtr->timer = NULL;
654 if (bPtr->flags.insideMenu && bPtr->highlightedItem >= 0) {
655 WMMenuItem *item;
657 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
659 if (WMGetMenuItemEnabled(item)) {
660 int i;
661 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
663 if (bPtr->flags.pullsDown) {
664 for (i = 0; i < MENU_BLINK_COUNT; i++) {
665 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
666 XSync(bPtr->view->screen->display, 0);
667 wusleep(MENU_BLINK_DELAY);
668 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
669 XSync(bPtr->view->screen->display, 0);
670 wusleep(MENU_BLINK_DELAY);
673 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
674 popDownMenu(bPtr);
675 if (bPtr->action)
676 (*bPtr->action) (bPtr, bPtr->clientData);
679 if (bPtr->menuView->flags.mapped)
680 popDownMenu(bPtr);
681 break;
685 static void destroyPopUpButton(PopUpButton * bPtr)
687 if (bPtr->timer) {
688 WMDeleteTimerHandler(bPtr->timer);
691 WMFreeArray(bPtr->items);
693 if (bPtr->caption)
694 wfree(bPtr->caption);
696 /* have to destroy explicitly because the popup is a toplevel */
697 W_DestroyView(bPtr->menuView);
699 wfree(bPtr);