#include "WINGsP.h" typedef struct ItemList { char *text; struct ItemList *nextPtr; unsigned int disabled:1; } ItemList; typedef struct W_PopUpButton { W_Class widgetClass; WMView *view; void *clientData; WMAction *action; char *caption; ItemList *items; short itemCount; short selectedItemIndex; short highlightedItem; ItemList *selectedItem; /* selected item if it is a menu btn */ 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 W_ViewProcedureTable _PopUpButtonViewProcedures = { NULL, NULL, NULL }; #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)); memset(bPtr, 0, sizeof(PopUpButton)); bPtr->widgetClass = WC_PopUpButton; bPtr->view = W_CreateView(W_VIEW(parent)); if (!bPtr->view) { free(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->menuView = W_CreateTopView(scr); bPtr->menuView->attribs.override_redirect = True; bPtr->menuView->attribFlags |= CWOverrideRedirect; 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; } void WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title) { ItemList *itemPtr, *tmp; CHECK_CLASS(bPtr, WC_PopUpButton); itemPtr = wmalloc(sizeof(ItemList)); memset(itemPtr, 0, sizeof(ItemList)); itemPtr->text = wstrdup(title); /* append item to list */ tmp = bPtr->items; if (!tmp) bPtr->items = itemPtr; else { while (tmp->nextPtr!=NULL) tmp = tmp->nextPtr; tmp->nextPtr = itemPtr; } bPtr->itemCount++; if (bPtr->menuView && bPtr->menuView->flags.realized) resizeMenu(bPtr); } void WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title) { ItemList *itemPtr; CHECK_CLASS(bPtr, WC_PopUpButton); if (index < 0) index = 0; if (index >= bPtr->itemCount) { WMAddPopUpButtonItem(bPtr, title); return; } itemPtr = wmalloc(sizeof(ItemList)); memset(itemPtr, 0, sizeof(ItemList)); itemPtr->text = wstrdup(title); if (index == 0) { itemPtr->nextPtr = bPtr->items; bPtr->items = itemPtr; } else { ItemList *tmp; int i = index; tmp = bPtr->items; /* insert item in list */ while (--i > 0) { tmp = tmp->nextPtr; } itemPtr->nextPtr = tmp->nextPtr; tmp->nextPtr = itemPtr; } bPtr->itemCount++; /* 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); } void WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index) { ItemList *tmp; CHECK_CLASS(bPtr, WC_PopUpButton); if (index < 0 || index >= bPtr->itemCount) return; if (index == 0) { free(bPtr->items->text); tmp = bPtr->items->nextPtr; free(bPtr->items); bPtr->items = tmp; } else { ItemList *next; int i = index; tmp = bPtr->items; while (--i > 0) tmp = tmp->nextPtr; next = tmp->nextPtr->nextPtr; free(tmp->nextPtr->text); free(tmp->nextPtr); tmp->nextPtr = next; } bPtr->itemCount--; if (bPtr->selectedItem!=NULL && !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->selectedItem = bPtr->items; 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; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } Bool WMGetPopUpButtonEnabled(WMPopUpButton *bPtr) { return bPtr->flags.enabled; } void WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index) { ItemList *itemPtr = bPtr->items; int i = index; if (index < 0) { bPtr->selectedItem = NULL; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); return; } while (i-- > 0) { itemPtr = itemPtr->nextPtr; } bPtr->selectedItem = itemPtr; bPtr->selectedItemIndex = index; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } int WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr) { if (!bPtr->flags.pullsDown && bPtr->selectedItem==NULL) return -1; else return bPtr->selectedItemIndex; } void WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text) { if (bPtr->caption) free(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) { int i; ItemList *item = bPtr->items; if (index < 0 || index >= bPtr->itemCount) return; for (i = 0; inextPtr; item->disabled = !flag; } Bool WMGetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index) { int i; ItemList *item = bPtr->items; if (index < 0 || index >= bPtr->itemCount) return False; for (i = 0; inextPtr; return !item->disabled; } void WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag) { bPtr->flags.pullsDown = flag; if (!flag) { /* This code causes bugs. It should not select any item, * since it was not asked to. -Dan bPtr->selectedItem = bPtr->items; if (bPtr->selectedItem) bPtr->selectedItemIndex = 0; else bPtr->selectedItemIndex = -1; */ bPtr->selectedItem = NULL; /* the drawing routine, still draws things wrong if we put * the index to -1 (i.e. not selected). * Find out why. -Dan */ bPtr->selectedItemIndex = 0; } else bPtr->selectedItemIndex = -1; if (bPtr->view->flags.mapped) paintPopUpButton(bPtr); } int WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr) { return bPtr->itemCount; } char* WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index) { ItemList *itemPtr = bPtr->items; if ((index < 0) || (index >= bPtr->itemCount)) return NULL; while (index-->0) itemPtr = itemPtr->nextPtr; return itemPtr->text; } 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->selectedItem == NULL) { /* if no item selected, show the caption */ caption = bPtr->caption; } else { caption = bPtr->selectedItem->text; } } 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 ? WMColorGC(scr->black) : WMColorGC(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 i; int yo; ItemList *itemPtr; int width, height, itemHeight; itemHeight = bPtr->view->size.height; width = bPtr->view->size.width; height = itemHeight * bPtr->itemCount; 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); itemPtr = bPtr->items; for (i = 0; i < index; i++) itemPtr = itemPtr->nextPtr; 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, WMColorGC(scr->black), False, itemPtr->text, strlen(itemPtr->text)); 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, i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2); } } Pixmap makeMenuPixmap(PopUpButton *bPtr) { Pixmap pixmap; W_Screen *scr = bPtr->view->screen; int i; int yo; ItemList *itemPtr; int width, height, itemHeight; itemHeight = bPtr->view->size.height; width = bPtr->view->size.width; height = itemHeight * bPtr->itemCount; 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); itemPtr = bPtr->items; for (i = 0; i < bPtr->itemCount; i++) { GC gc; W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight, WRRaised); if (itemPtr->disabled) gc = WMColorGC(scr->darkGray); else gc = WMColorGC(scr->black); W_PaintText(bPtr->menuView, pixmap, scr->normalFont, 6, i*itemHeight + yo, width, WALeft, gc, False, itemPtr->text, strlen(itemPtr->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); } itemPtr = itemPtr->nextPtr; } return pixmap; } static void resizeMenu(PopUpButton *bPtr) { int height; height = bPtr->itemCount * 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->menuView->flags.realized) { W_RealizeView(bPtr->menuView); resizeMenu(bPtr); } if (bPtr->itemCount < 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); W_MapView(bPtr->menuView); bPtr->highlightedItem = 0; if (!bPtr->flags.pullsDown && bPtr->selectedItem != NULL) paintMenuEntry(bPtr, bPtr->selectedItemIndex, True); } static void popDownMenu(PopUpButton *bPtr) { W_UnmapView(bPtr->menuView); /* free the background pixmap used to draw the menu contents */ XSetWindowBackgroundPixmap(bPtr->view->screen->display, bPtr->menuView->window, None); } static int itemIsEnabled(PopUpButton *bPtr, int index) { ItemList *item = bPtr->items; while (index-- > 0 && item) item = item->nextPtr; return item ? !item->disabled : False; } 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) { paintMenuEntry(bPtr, oldItem, False); paintMenuEntry(bPtr, bPtr->highlightedItem, itemIsEnabled(bPtr, bPtr->highlightedItem)); } bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr); } else { bPtr->timer = NULL; } } 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 (bPtr->itemCount < 1) return; switch (event->type) { /* called for menuView */ case Expose: paintMenuEntry(bPtr, bPtr->highlightedItem, itemIsEnabled(bPtr, bPtr->highlightedItem)); 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) { paintMenuEntry(bPtr, oldItem, False); paintMenuEntry(bPtr, bPtr->highlightedItem, itemIsEnabled(bPtr, bPtr->highlightedItem)); } 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; 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: 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) { if (itemIsEnabled(bPtr, bPtr->highlightedItem)) { int i; WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem); if (bPtr->flags.pullsDown) { for (i=0; ihighlightedItem, 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) { ItemList *itemPtr, *tmp; if (bPtr->timer) { WMDeleteTimerHandler(bPtr->timer); } itemPtr = bPtr->items; while (itemPtr!=NULL) { free(itemPtr->text); tmp = itemPtr->nextPtr; free(itemPtr); itemPtr = tmp; } if (bPtr->caption) free(bPtr->caption); /* have to destroy explicitly because the popup is a toplevel */ W_DestroyView(bPtr->menuView); free(bPtr); }