#include "WINGsP.h" typedef struct W_PopUpButton { W_Class widgetClass; WMView *view; void *clientData; WMAction *action; char *caption; WMArray *items; short selectedItemIndex; short highlightedItem; WMView *menuView; /* override redirect popup menu */ WMHandlerID timer; /* for autoscroll */ /**/ int scrollStartY; /* for autoscroll */ struct { unsigned int pullsDown:1; unsigned int configured:1; unsigned int insideMenu:1; unsigned int enabled:1; } flags; } PopUpButton; #define MENU_BLINK_DELAY 60000 #define MENU_BLINK_COUNT 2 #define SCROLL_DELAY 10 #define DEFAULT_WIDTH 60 #define DEFAULT_HEIGHT 20 #define DEFAULT_CAPTION "" static void destroyPopUpButton(PopUpButton * bPtr); static void paintPopUpButton(PopUpButton * bPtr); static void handleEvents(XEvent * event, void *data); static void handleActionEvents(XEvent * event, void *data); static void resizeMenu(PopUpButton * bPtr); WMPopUpButton *WMCreatePopUpButton(WMWidget * parent) { PopUpButton *bPtr; W_Screen *scr = W_VIEW(parent)->screen; bPtr = wmalloc(sizeof(PopUpButton)); bPtr->widgetClass = WC_PopUpButton; bPtr->view = W_CreateView(W_VIEW(parent)); if (!bPtr->view) { wfree(bPtr); return NULL; } bPtr->view->self = bPtr; WMCreateEventHandler(bPtr->view, ExposureMask | StructureNotifyMask | ClientMessageMask, handleEvents, bPtr); W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT); bPtr->caption = wstrdup(DEFAULT_CAPTION); WMCreateEventHandler(bPtr->view, ButtonPressMask | ButtonReleaseMask, handleActionEvents, bPtr); bPtr->flags.enabled = 1; bPtr->items = WMCreateArrayWithDestructor(4, (WMFreeDataProc *) WMDestroyMenuItem); bPtr->selectedItemIndex = -1; bPtr->menuView = W_CreateUnmanagedTopView(scr); W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1); WMCreateEventHandler(bPtr->menuView, ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | ButtonMotionMask | ExposureMask, handleActionEvents, bPtr); return bPtr; } void WMSetPopUpButtonAction(WMPopUpButton * bPtr, WMAction * action, void *clientData) { CHECK_CLASS(bPtr, WC_PopUpButton); bPtr->action = action; bPtr->clientData = clientData; } WMMenuItem *WMAddPopUpButtonItem(WMPopUpButton * bPtr, char *title) { WMMenuItem *item; CHECK_CLASS(bPtr, WC_PopUpButton); item = WMCreateMenuItem(); WMSetMenuItemTitle(item, title); WMAddToArray(bPtr->items, item); if (bPtr->menuView && bPtr->menuView->flags.realized) resizeMenu(bPtr); return item; } WMMenuItem *WMInsertPopUpButtonItem(WMPopUpButton * bPtr, int index, char *title) { WMMenuItem *item; CHECK_CLASS(bPtr, WC_PopUpButton); item = WMCreateMenuItem(); WMSetMenuItemTitle(item, title); WMInsertInArray(bPtr->items, index, item); /* if there is an selected item, update it's index to match the new * position */ if (index < bPtr->selectedItemIndex) bPtr->selectedItemIndex++; if (bPtr->menuView && bPtr->menuView->flags.realized) resizeMenu(bPtr); return item; } void WMRemovePopUpButtonItem(WMPopUpButton * bPtr, int index) { CHECK_CLASS(bPtr, WC_PopUpButton); wassertr(index >= 0 && index < WMGetArrayItemCount(bPtr->items)); WMDeleteFromArray(bPtr->items, index); if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) { if (index < bPtr->selectedItemIndex) bPtr->selectedItemIndex--; else if (index == bPtr->selectedItemIndex) { /* reselect first item if the removed item is the * selected one */ bPtr->selectedItemIndex = 0; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } } if (bPtr->menuView && bPtr->menuView->flags.realized) resizeMenu(bPtr); } void WMSetPopUpButtonEnabled(WMPopUpButton * bPtr, Bool flag) { bPtr->flags.enabled = ((flag == 0) ? 0 : 1); if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } Bool WMGetPopUpButtonEnabled(WMPopUpButton * bPtr) { return bPtr->flags.enabled; } void WMSetPopUpButtonSelectedItem(WMPopUpButton * bPtr, int index) { wassertr(index < WMGetArrayItemCount(bPtr->items)); /* if (index >= WMGetArrayCount(bPtr->items)) index = -1; */ bPtr->selectedItemIndex = index; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } int WMGetPopUpButtonSelectedItem(WMPopUpButton * bPtr) { if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0) return -1; else return bPtr->selectedItemIndex; } void WMSetPopUpButtonText(WMPopUpButton * bPtr, char *text) { if (bPtr->caption) wfree(bPtr->caption); if (text) bPtr->caption = wstrdup(text); else bPtr->caption = NULL; if (bPtr->view->flags.realized) { if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) { paintPopUpButton(bPtr); } } } void WMSetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index, Bool flag) { WMSetMenuItemEnabled(WMGetFromArray(bPtr->items, index), (flag ? 1 : 0)); } Bool WMGetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index) { return WMGetMenuItemEnabled(WMGetFromArray(bPtr->items, index)); } void WMSetPopUpButtonPullsDown(WMPopUpButton * bPtr, Bool flag) { bPtr->flags.pullsDown = ((flag == 0) ? 0 : 1); if (flag) { bPtr->selectedItemIndex = -1; } if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } int WMGetPopUpButtonNumberOfItems(WMPopUpButton * bPtr) { return WMGetArrayItemCount(bPtr->items); } char *WMGetPopUpButtonItem(WMPopUpButton * bPtr, int index) { if (index >= WMGetArrayItemCount(bPtr->items) || index < 0) return NULL; return WMGetMenuItemTitle(WMGetFromArray(bPtr->items, index)); } WMMenuItem *WMGetPopUpButtonMenuItem(WMPopUpButton * bPtr, int index) { return WMGetFromArray(bPtr->items, index); } static void paintPopUpButton(PopUpButton * bPtr) { W_Screen *scr = bPtr->view->screen; char *caption; Pixmap pixmap; if (bPtr->flags.pullsDown) { caption = bPtr->caption; } else { if (bPtr->selectedItemIndex < 0) { /* if no item selected, show the caption */ caption = bPtr->caption; } else { caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex); } } pixmap = XCreatePixmap(scr->display, bPtr->view->window, bPtr->view->size.width, bPtr->view->size.height, scr->depth); XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0, bPtr->view->size.width, bPtr->view->size.height); W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width, bPtr->view->size.height, WRRaised); if (caption) { W_PaintText(bPtr->view, pixmap, scr->normalFont, 6, (bPtr->view->size.height - WMFontHeight(scr->normalFont)) / 2, bPtr->view->size.width, WALeft, bPtr->flags.enabled ? scr->black : scr->darkGray, False, caption, strlen(caption)); } if (bPtr->flags.pullsDown) { XCopyArea(scr->display, scr->pullDownIndicator->pixmap, pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width, scr->pullDownIndicator->height, bPtr->view->size.width - scr->pullDownIndicator->width - 4, (bPtr->view->size.height - scr->pullDownIndicator->height) / 2); } else { int x, y; x = bPtr->view->size.width - scr->popUpIndicator->width - 4; y = (bPtr->view->size.height - scr->popUpIndicator->height) / 2; XSetClipOrigin(scr->display, scr->clipGC, x, y); XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask); XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap, scr->clipGC, 0, 0, scr->popUpIndicator->width, scr->popUpIndicator->height, x, y); } XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0, bPtr->view->size.width, bPtr->view->size.height, 0, 0); XFreePixmap(scr->display, pixmap); } static void handleEvents(XEvent * event, void *data) { PopUpButton *bPtr = (PopUpButton *) data; CHECK_CLASS(data, WC_PopUpButton); switch (event->type) { case Expose: if (event->xexpose.count != 0) break; paintPopUpButton(bPtr); break; case DestroyNotify: destroyPopUpButton(bPtr); break; } } static void paintMenuEntry(PopUpButton * bPtr, int index, int highlight) { W_Screen *scr = bPtr->view->screen; int yo; int width, itemHeight, itemCount; char *title; itemCount = WMGetArrayItemCount(bPtr->items); if (index < 0 || index >= itemCount) return; itemHeight = bPtr->view->size.height; width = bPtr->view->size.width; yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2; if (!highlight) { XClearArea(scr->display, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, False); return; } else if (index < 0 && bPtr->flags.pullsDown) { return; } XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white), 1, index * itemHeight + 1, width - 3, itemHeight - 3); title = WMGetPopUpButtonItem(bPtr, index); W_DrawRelief(scr, bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, WRRaised); W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6, index * itemHeight + yo, width, WALeft, scr->black, False, title, strlen(title)); if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) { XCopyArea(scr->display, scr->popUpIndicator->pixmap, bPtr->menuView->window, scr->copyGC, 0, 0, scr->popUpIndicator->width, scr->popUpIndicator->height, width - scr->popUpIndicator->width - 4, index * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2); } } Pixmap makeMenuPixmap(PopUpButton * bPtr) { Pixmap pixmap; W_Screen *scr = bPtr->view->screen; WMMenuItem *item; WMArrayIterator iter; int yo, i; int width, height, itemHeight; itemHeight = bPtr->view->size.height; width = bPtr->view->size.width; height = itemHeight * WMGetArrayItemCount(bPtr->items); yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2; pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height, scr->depth); XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0, width, height); i = 0; WM_ITERATE_ARRAY(bPtr->items, item, iter) { WMColor *color; char *text; text = WMGetMenuItemTitle(item); W_DrawRelief(scr, pixmap, 0, i * itemHeight, width, itemHeight, WRRaised); if (!WMGetMenuItemEnabled(item)) color = scr->darkGray; else color = scr->black; W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6, i * itemHeight + yo, width, WALeft, color, False, text, strlen(text)); if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) { XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap, scr->copyGC, 0, 0, scr->popUpIndicator->width, scr->popUpIndicator->height, width - scr->popUpIndicator->width - 4, i * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2); } i++; } return pixmap; } static void resizeMenu(PopUpButton * bPtr) { int height; height = WMGetArrayItemCount(bPtr->items) * bPtr->view->size.height; if (height > 0) W_ResizeView(bPtr->menuView, bPtr->view->size.width, height); } static void popUpMenu(PopUpButton * bPtr) { W_Screen *scr = bPtr->view->screen; Window dummyW; int x, y; if (!bPtr->flags.enabled) return; if (!bPtr->menuView->flags.realized) { W_RealizeView(bPtr->menuView); resizeMenu(bPtr); } if (WMGetArrayItemCount(bPtr->items) < 1) return; XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin, 0, 0, &x, &y, &dummyW); if (bPtr->flags.pullsDown) { y += bPtr->view->size.height; } else { y -= bPtr->view->size.height * bPtr->selectedItemIndex; } W_MoveView(bPtr->menuView, x, y); XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window, makeMenuPixmap(bPtr)); XClearWindow(scr->display, bPtr->menuView->window); if (W_VIEW_WIDTH(bPtr->menuView) != W_VIEW_WIDTH(bPtr->view)) resizeMenu(bPtr); W_MapView(bPtr->menuView); bPtr->highlightedItem = 0; if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0) paintMenuEntry(bPtr, bPtr->selectedItemIndex, True); } static void popDownMenu(PopUpButton * bPtr) { W_UnmapView(bPtr->menuView); } static void autoScroll(void *data) { PopUpButton *bPtr = (PopUpButton *) data; int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height; int repeat = 0; int dy = 0; if (bPtr->scrollStartY >= scrHeight - 1 && bPtr->menuView->pos.y + bPtr->menuView->size.height >= scrHeight - 1) { repeat = 1; if (bPtr->menuView->pos.y + bPtr->menuView->size.height - 5 <= scrHeight - 1) { dy = scrHeight - 1 - (bPtr->menuView->pos.y + bPtr->menuView->size.height); } else dy = -5; } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) { repeat = 1; if (bPtr->menuView->pos.y + 5 > 1) dy = 1 - bPtr->menuView->pos.y; else dy = 5; } if (repeat) { int oldItem; W_MoveView(bPtr->menuView, bPtr->menuView->pos.x, bPtr->menuView->pos.y + dy); oldItem = bPtr->highlightedItem; bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y) / bPtr->view->size.height; if (oldItem != bPtr->highlightedItem) { WMMenuItem *item; paintMenuEntry(bPtr, oldItem, False); if (bPtr->highlightedItem >= 0 && bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) { item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem); paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item)); } else { bPtr->highlightedItem = -1; } } bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr); } else { bPtr->timer = NULL; } } static void wheelScrollUp(PopUpButton * bPtr) { int testIndex = bPtr->selectedItemIndex - 1; while (testIndex >= 0 && !WMGetPopUpButtonItemEnabled(bPtr, testIndex)) testIndex--; if (testIndex != -1) { WMSetPopUpButtonSelectedItem(bPtr, testIndex); if (bPtr->action) (*bPtr->action) (bPtr, bPtr->clientData); } } static void wheelScrollDown(PopUpButton * bPtr) { int itemCount = WMGetArrayItemCount(bPtr->items); int testIndex = bPtr->selectedItemIndex + 1; while (testIndex < itemCount && !WMGetPopUpButtonItemEnabled(bPtr, testIndex)) testIndex++; if (testIndex != itemCount) { WMSetPopUpButtonSelectedItem(bPtr, testIndex); if (bPtr->action) (*bPtr->action) (bPtr, bPtr->clientData); } } static void handleActionEvents(XEvent * event, void *data) { PopUpButton *bPtr = (PopUpButton *) data; int oldItem; int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height; CHECK_CLASS(data, WC_PopUpButton); if (WMGetArrayItemCount(bPtr->items) < 1) return; switch (event->type) { /* called for menuView */ case Expose: paintMenuEntry(bPtr, bPtr->highlightedItem, True); break; case LeaveNotify: bPtr->flags.insideMenu = 0; if (bPtr->menuView->flags.mapped) paintMenuEntry(bPtr, bPtr->highlightedItem, False); bPtr->highlightedItem = -1; break; case EnterNotify: bPtr->flags.insideMenu = 1; break; case MotionNotify: if (bPtr->flags.insideMenu) { oldItem = bPtr->highlightedItem; bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height; if (oldItem != bPtr->highlightedItem) { WMMenuItem *item; paintMenuEntry(bPtr, oldItem, False); if (bPtr->highlightedItem >= 0 && bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) { item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem); paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item)); } else { bPtr->highlightedItem = -1; } } if (event->xmotion.y_root >= scrHeight - 1 || event->xmotion.y_root <= 1) { bPtr->scrollStartY = event->xmotion.y_root; if (!bPtr->timer) autoScroll(bPtr); } else if (bPtr->timer) { WMDeleteTimerHandler(bPtr->timer); bPtr->timer = NULL; } } break; /* called for bPtr->view */ case ButtonPress: if (!bPtr->flags.enabled) break; if (event->xbutton.button == WINGsConfiguration.mouseWheelUp) { if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) { wheelScrollUp(bPtr); } break; } else if (event->xbutton.button == WINGsConfiguration.mouseWheelDown) { if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) { wheelScrollDown(bPtr); } break; } popUpMenu(bPtr); if (!bPtr->flags.pullsDown) { bPtr->highlightedItem = bPtr->selectedItemIndex; bPtr->flags.insideMenu = 1; } else { bPtr->highlightedItem = -1; bPtr->flags.insideMenu = 0; } XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window, False, ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); break; case ButtonRelease: if (event->xbutton.button == WINGsConfiguration.mouseWheelUp || event->xbutton.button == WINGsConfiguration.mouseWheelDown) { break; } XUngrabPointer(bPtr->view->screen->display, event->xbutton.time); if (!bPtr->flags.pullsDown) popDownMenu(bPtr); if (bPtr->timer) { WMDeleteTimerHandler(bPtr->timer); bPtr->timer = NULL; } if (bPtr->flags.insideMenu && bPtr->highlightedItem >= 0) { WMMenuItem *item; item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem); if (WMGetMenuItemEnabled(item)) { int i; WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem); if (bPtr->flags.pullsDown) { for (i = 0; i < MENU_BLINK_COUNT; i++) { paintMenuEntry(bPtr, bPtr->highlightedItem, False); XSync(bPtr->view->screen->display, 0); wusleep(MENU_BLINK_DELAY); paintMenuEntry(bPtr, bPtr->highlightedItem, True); XSync(bPtr->view->screen->display, 0); wusleep(MENU_BLINK_DELAY); } } paintMenuEntry(bPtr, bPtr->highlightedItem, False); popDownMenu(bPtr); if (bPtr->action) (*bPtr->action) (bPtr, bPtr->clientData); } } if (bPtr->menuView->flags.mapped) popDownMenu(bPtr); break; } } static void destroyPopUpButton(PopUpButton * bPtr) { if (bPtr->timer) { WMDeleteTimerHandler(bPtr->timer); } WMFreeArray(bPtr->items); if (bPtr->caption) wfree(bPtr->caption); /* have to destroy explicitly because the popup is a toplevel */ W_DestroyView(bPtr->menuView); wfree(bPtr); }