WINGs: Small assorted formatting changes
[wmaker-crm.git] / WINGs / wpopupbutton.c
blob648039a23726cf67d9666bd49cf28d258005d6c0
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));
61 bPtr->widgetClass = WC_PopUpButton;
63 bPtr->view = W_CreateView(W_VIEW(parent));
64 if (!bPtr->view) {
65 wfree(bPtr);
66 return NULL;
68 bPtr->view->self = bPtr;
70 WMCreateEventHandler(bPtr->view, ExposureMask | StructureNotifyMask
71 | ClientMessageMask, handleEvents, bPtr);
73 W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
74 bPtr->caption = wstrdup(DEFAULT_CAPTION);
76 WMCreateEventHandler(bPtr->view, ButtonPressMask | ButtonReleaseMask, handleActionEvents, bPtr);
78 bPtr->flags.enabled = 1;
80 bPtr->items = WMCreateArrayWithDestructor(4, (WMFreeDataProc *) WMDestroyMenuItem);
82 bPtr->selectedItemIndex = -1;
84 bPtr->menuView = W_CreateUnmanagedTopView(scr);
86 W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
88 WMCreateEventHandler(bPtr->menuView, ButtonPressMask | ButtonReleaseMask
89 | EnterWindowMask | LeaveWindowMask | ButtonMotionMask
90 | ExposureMask, handleActionEvents, bPtr);
92 return bPtr;
95 void WMSetPopUpButtonAction(WMPopUpButton * bPtr, WMAction * action, void *clientData)
97 CHECK_CLASS(bPtr, WC_PopUpButton);
99 bPtr->action = action;
101 bPtr->clientData = clientData;
104 WMMenuItem *WMAddPopUpButtonItem(WMPopUpButton * bPtr, char *title)
106 WMMenuItem *item;
108 CHECK_CLASS(bPtr, WC_PopUpButton);
110 item = WMCreateMenuItem();
111 WMSetMenuItemTitle(item, title);
113 WMAddToArray(bPtr->items, item);
115 if (bPtr->menuView && bPtr->menuView->flags.realized)
116 resizeMenu(bPtr);
118 return item;
121 WMMenuItem *WMInsertPopUpButtonItem(WMPopUpButton * bPtr, int index, char *title)
123 WMMenuItem *item;
125 CHECK_CLASS(bPtr, WC_PopUpButton);
127 item = WMCreateMenuItem();
128 WMSetMenuItemTitle(item, title);
130 WMInsertInArray(bPtr->items, index, item);
132 /* if there is an selected item, update it's index to match the new
133 * position */
134 if (index < bPtr->selectedItemIndex)
135 bPtr->selectedItemIndex++;
137 if (bPtr->menuView && bPtr->menuView->flags.realized)
138 resizeMenu(bPtr);
140 return item;
143 void WMRemovePopUpButtonItem(WMPopUpButton * bPtr, int index)
145 CHECK_CLASS(bPtr, WC_PopUpButton);
147 wassertr(index >= 0 && index < WMGetArrayItemCount(bPtr->items));
149 WMDeleteFromArray(bPtr->items, index);
151 if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) {
152 if (index < bPtr->selectedItemIndex)
153 bPtr->selectedItemIndex--;
154 else if (index == bPtr->selectedItemIndex) {
155 /* reselect first item if the removed item is the
156 * selected one */
157 bPtr->selectedItemIndex = 0;
158 if (bPtr->view->flags.mapped)
159 paintPopUpButton(bPtr);
163 if (bPtr->menuView && bPtr->menuView->flags.realized)
164 resizeMenu(bPtr);
167 void WMSetPopUpButtonEnabled(WMPopUpButton * bPtr, Bool flag)
169 bPtr->flags.enabled = ((flag == 0) ? 0 : 1);
170 if (bPtr->view->flags.mapped)
171 paintPopUpButton(bPtr);
174 Bool WMGetPopUpButtonEnabled(WMPopUpButton * bPtr)
176 return bPtr->flags.enabled;
179 void WMSetPopUpButtonSelectedItem(WMPopUpButton * bPtr, int index)
182 wassertr(index < WMGetArrayItemCount(bPtr->items));
184 /* if (index >= WMGetArrayCount(bPtr->items))
185 index = -1; */
187 bPtr->selectedItemIndex = index;
189 if (bPtr->view->flags.mapped)
190 paintPopUpButton(bPtr);
193 int WMGetPopUpButtonSelectedItem(WMPopUpButton * bPtr)
195 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
196 return -1;
197 else
198 return bPtr->selectedItemIndex;
201 void WMSetPopUpButtonText(WMPopUpButton * bPtr, char *text)
203 if (bPtr->caption)
204 wfree(bPtr->caption);
205 if (text)
206 bPtr->caption = wstrdup(text);
207 else
208 bPtr->caption = NULL;
209 if (bPtr->view->flags.realized) {
210 if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
211 paintPopUpButton(bPtr);
216 void WMSetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index, Bool flag)
218 WMSetMenuItemEnabled(WMGetFromArray(bPtr->items, index), (flag ? 1 : 0));
221 Bool WMGetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index)
223 return WMGetMenuItemEnabled(WMGetFromArray(bPtr->items, index));
226 void WMSetPopUpButtonPullsDown(WMPopUpButton * bPtr, Bool flag)
228 bPtr->flags.pullsDown = ((flag == 0) ? 0 : 1);
229 if (flag) {
230 bPtr->selectedItemIndex = -1;
233 if (bPtr->view->flags.mapped)
234 paintPopUpButton(bPtr);
237 int WMGetPopUpButtonNumberOfItems(WMPopUpButton * bPtr)
239 return WMGetArrayItemCount(bPtr->items);
242 char *WMGetPopUpButtonItem(WMPopUpButton * bPtr, int index)
244 if (index >= WMGetArrayItemCount(bPtr->items) || index < 0)
245 return NULL;
247 return WMGetMenuItemTitle(WMGetFromArray(bPtr->items, index));
250 WMMenuItem *WMGetPopUpButtonMenuItem(WMPopUpButton * bPtr, int index)
252 return WMGetFromArray(bPtr->items, index);
255 static void paintPopUpButton(PopUpButton * bPtr)
257 W_Screen *scr = bPtr->view->screen;
258 char *caption;
259 Pixmap pixmap;
261 if (bPtr->flags.pullsDown) {
262 caption = bPtr->caption;
263 } else {
264 if (bPtr->selectedItemIndex < 0) {
265 /* if no item selected, show the caption */
266 caption = bPtr->caption;
267 } else {
268 caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
272 pixmap = XCreatePixmap(scr->display, bPtr->view->window,
273 bPtr->view->size.width, bPtr->view->size.height, scr->depth);
274 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
275 bPtr->view->size.width, bPtr->view->size.height);
277 W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width, bPtr->view->size.height, WRRaised);
279 if (caption) {
280 W_PaintText(bPtr->view, pixmap, scr->normalFont, 6,
281 (bPtr->view->size.height - WMFontHeight(scr->normalFont)) / 2,
282 bPtr->view->size.width, WALeft,
283 bPtr->flags.enabled ? scr->black : scr->darkGray, False, caption, strlen(caption));
286 if (bPtr->flags.pullsDown) {
287 XCopyArea(scr->display, scr->pullDownIndicator->pixmap,
288 pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
289 scr->pullDownIndicator->height,
290 bPtr->view->size.width - scr->pullDownIndicator->width - 4,
291 (bPtr->view->size.height - scr->pullDownIndicator->height) / 2);
292 } else {
293 int x, y;
295 x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
296 y = (bPtr->view->size.height - scr->popUpIndicator->height) / 2;
298 XSetClipOrigin(scr->display, scr->clipGC, x, y);
299 XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
300 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
301 scr->clipGC, 0, 0, scr->popUpIndicator->width, scr->popUpIndicator->height, x, y);
304 XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
305 bPtr->view->size.width, bPtr->view->size.height, 0, 0);
307 XFreePixmap(scr->display, pixmap);
310 static void handleEvents(XEvent * event, void *data)
312 PopUpButton *bPtr = (PopUpButton *) data;
314 CHECK_CLASS(data, WC_PopUpButton);
316 switch (event->type) {
317 case Expose:
318 if (event->xexpose.count != 0)
319 break;
320 paintPopUpButton(bPtr);
321 break;
323 case DestroyNotify:
324 destroyPopUpButton(bPtr);
325 break;
329 static void paintMenuEntry(PopUpButton * bPtr, int index, int highlight)
331 W_Screen *scr = bPtr->view->screen;
332 int yo;
333 int width, height, itemHeight, itemCount;
334 char *title;
336 itemCount = WMGetArrayItemCount(bPtr->items);
337 if (index < 0 || index >= itemCount)
338 return;
340 itemHeight = bPtr->view->size.height;
341 width = bPtr->view->size.width;
342 height = itemHeight * itemCount;
343 yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
345 if (!highlight) {
346 XClearArea(scr->display, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, False);
347 return;
348 } else if (index < 0 && bPtr->flags.pullsDown) {
349 return;
352 XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
353 1, index * itemHeight + 1, width - 3, itemHeight - 3);
355 title = WMGetPopUpButtonItem(bPtr, index);
357 W_DrawRelief(scr, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, WRRaised);
359 W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
360 index * itemHeight + yo, width, WALeft, scr->black, False, title, strlen(title));
362 if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
363 XCopyArea(scr->display, scr->popUpIndicator->pixmap,
364 bPtr->menuView->window, scr->copyGC, 0, 0,
365 scr->popUpIndicator->width, scr->popUpIndicator->height,
366 width - scr->popUpIndicator->width - 4,
367 index * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
371 Pixmap makeMenuPixmap(PopUpButton * bPtr)
373 Pixmap pixmap;
374 W_Screen *scr = bPtr->view->screen;
375 WMMenuItem *item;
376 WMArrayIterator iter;
377 int yo, i;
378 int width, height, itemHeight;
380 itemHeight = bPtr->view->size.height;
381 width = bPtr->view->size.width;
382 height = itemHeight * WMGetArrayItemCount(bPtr->items);
383 yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
385 pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height, scr->depth);
387 XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0, width, height);
389 i = 0;
390 WM_ITERATE_ARRAY(bPtr->items, item, iter) {
391 WMColor *color;
392 char *text;
394 text = WMGetMenuItemTitle(item);
396 W_DrawRelief(scr, pixmap, 0, i * itemHeight, width, itemHeight, WRRaised);
398 if (!WMGetMenuItemEnabled(item))
399 color = scr->darkGray;
400 else
401 color = scr->black;
403 W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6,
404 i * itemHeight + yo, width, WALeft, color, False, text, strlen(text));
406 if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
407 XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
408 scr->copyGC, 0, 0, scr->popUpIndicator->width,
409 scr->popUpIndicator->height,
410 width - scr->popUpIndicator->width - 4,
411 i * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
414 i++;
417 return pixmap;
420 static void resizeMenu(PopUpButton * bPtr)
422 int height;
424 height = WMGetArrayItemCount(bPtr->items) * bPtr->view->size.height;
425 if (height > 0)
426 W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
429 static void popUpMenu(PopUpButton * bPtr)
431 W_Screen *scr = bPtr->view->screen;
432 Window dummyW;
433 int x, y;
435 if (!bPtr->flags.enabled)
436 return;
438 if (!bPtr->menuView->flags.realized) {
439 W_RealizeView(bPtr->menuView);
440 resizeMenu(bPtr);
443 if (WMGetArrayItemCount(bPtr->items) < 1)
444 return;
446 XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin, 0, 0, &x, &y, &dummyW);
448 if (bPtr->flags.pullsDown) {
449 y += bPtr->view->size.height;
450 } else {
451 y -= bPtr->view->size.height * bPtr->selectedItemIndex;
453 W_MoveView(bPtr->menuView, x, y);
455 XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window, makeMenuPixmap(bPtr));
456 XClearWindow(scr->display, bPtr->menuView->window);
458 if (W_VIEW_WIDTH(bPtr->menuView) != W_VIEW_WIDTH(bPtr->view))
459 resizeMenu(bPtr);
461 W_MapView(bPtr->menuView);
463 bPtr->highlightedItem = 0;
464 if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
465 paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
468 static void popDownMenu(PopUpButton * bPtr)
470 W_UnmapView(bPtr->menuView);
473 static void autoScroll(void *data)
475 PopUpButton *bPtr = (PopUpButton *) data;
476 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
477 int repeat = 0;
478 int dy = 0;
480 if (bPtr->scrollStartY >= scrHeight - 1
481 && bPtr->menuView->pos.y + bPtr->menuView->size.height >= scrHeight - 1) {
482 repeat = 1;
484 if (bPtr->menuView->pos.y + bPtr->menuView->size.height - 5 <= scrHeight - 1) {
485 dy = scrHeight - 1 - (bPtr->menuView->pos.y + bPtr->menuView->size.height);
486 } else
487 dy = -5;
489 } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
490 repeat = 1;
492 if (bPtr->menuView->pos.y + 5 > 1)
493 dy = 1 - bPtr->menuView->pos.y;
494 else
495 dy = 5;
498 if (repeat) {
499 int oldItem;
501 W_MoveView(bPtr->menuView, bPtr->menuView->pos.x, bPtr->menuView->pos.y + dy);
503 oldItem = bPtr->highlightedItem;
504 bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
505 / bPtr->view->size.height;
507 if (oldItem != bPtr->highlightedItem) {
508 WMMenuItem *item;
510 paintMenuEntry(bPtr, oldItem, False);
512 if (bPtr->highlightedItem >= 0 && bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
513 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
514 paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
515 } else {
516 bPtr->highlightedItem = -1;
520 bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
521 } else {
522 bPtr->timer = NULL;
526 static void wheelScrollUp(PopUpButton * bPtr)
528 int testIndex = bPtr->selectedItemIndex - 1;
530 while (testIndex >= 0 && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
531 testIndex--;
532 if (testIndex != -1) {
533 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
534 if (bPtr->action)
535 (*bPtr->action) (bPtr, bPtr->clientData);
539 static void wheelScrollDown(PopUpButton * bPtr)
541 int itemCount = WMGetArrayItemCount(bPtr->items);
542 int testIndex = bPtr->selectedItemIndex + 1;
544 while (testIndex < itemCount && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
545 testIndex++;
546 if (testIndex != itemCount) {
547 WMSetPopUpButtonSelectedItem(bPtr, testIndex);
548 if (bPtr->action)
549 (*bPtr->action) (bPtr, bPtr->clientData);
553 static void handleActionEvents(XEvent * event, void *data)
555 PopUpButton *bPtr = (PopUpButton *) data;
556 int oldItem;
557 int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
559 CHECK_CLASS(data, WC_PopUpButton);
561 if (WMGetArrayItemCount(bPtr->items) < 1)
562 return;
564 switch (event->type) {
565 /* called for menuView */
566 case Expose:
567 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
568 break;
570 case LeaveNotify:
571 bPtr->flags.insideMenu = 0;
572 if (bPtr->menuView->flags.mapped)
573 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
574 bPtr->highlightedItem = -1;
575 break;
577 case EnterNotify:
578 bPtr->flags.insideMenu = 1;
579 break;
581 case MotionNotify:
582 if (bPtr->flags.insideMenu) {
583 oldItem = bPtr->highlightedItem;
584 bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
585 if (oldItem != bPtr->highlightedItem) {
586 WMMenuItem *item;
588 paintMenuEntry(bPtr, oldItem, False);
589 if (bPtr->highlightedItem >= 0 &&
590 bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
591 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
592 paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
593 } else {
594 bPtr->highlightedItem = -1;
599 if (event->xmotion.y_root >= scrHeight - 1 || event->xmotion.y_root <= 1) {
600 bPtr->scrollStartY = event->xmotion.y_root;
601 if (!bPtr->timer)
602 autoScroll(bPtr);
603 } else if (bPtr->timer) {
604 WMDeleteTimerHandler(bPtr->timer);
605 bPtr->timer = NULL;
608 break;
610 /* called for bPtr->view */
611 case ButtonPress:
612 if (!bPtr->flags.enabled)
613 break;
615 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
616 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
617 wheelScrollUp(bPtr);
619 break;
620 } else if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
621 if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
622 wheelScrollDown(bPtr);
624 break;
626 popUpMenu(bPtr);
627 if (!bPtr->flags.pullsDown) {
628 bPtr->highlightedItem = bPtr->selectedItemIndex;
629 bPtr->flags.insideMenu = 1;
630 } else {
631 bPtr->highlightedItem = -1;
632 bPtr->flags.insideMenu = 0;
634 XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
635 False, ButtonReleaseMask | ButtonMotionMask | EnterWindowMask
636 | LeaveWindowMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
637 break;
639 case ButtonRelease:
640 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
641 event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
642 break;
644 XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
645 if (!bPtr->flags.pullsDown)
646 popDownMenu(bPtr);
648 if (bPtr->timer) {
649 WMDeleteTimerHandler(bPtr->timer);
650 bPtr->timer = NULL;
653 if (bPtr->flags.insideMenu && bPtr->highlightedItem >= 0) {
654 WMMenuItem *item;
656 item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
658 if (WMGetMenuItemEnabled(item)) {
659 int i;
660 WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
662 if (bPtr->flags.pullsDown) {
663 for (i = 0; i < MENU_BLINK_COUNT; i++) {
664 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
665 XSync(bPtr->view->screen->display, 0);
666 wusleep(MENU_BLINK_DELAY);
667 paintMenuEntry(bPtr, bPtr->highlightedItem, True);
668 XSync(bPtr->view->screen->display, 0);
669 wusleep(MENU_BLINK_DELAY);
672 paintMenuEntry(bPtr, bPtr->highlightedItem, False);
673 popDownMenu(bPtr);
674 if (bPtr->action)
675 (*bPtr->action) (bPtr, bPtr->clientData);
678 if (bPtr->menuView->flags.mapped)
679 popDownMenu(bPtr);
680 break;
684 static void destroyPopUpButton(PopUpButton * bPtr)
686 if (bPtr->timer) {
687 WMDeleteTimerHandler(bPtr->timer);
690 WMFreeArray(bPtr->items);
692 if (bPtr->caption)
693 wfree(bPtr->caption);
695 /* have to destroy explicitly because the popup is a toplevel */
696 W_DestroyView(bPtr->menuView);
698 wfree(bPtr);