/* editmenu.c - editable menus * * WPrefs - Window Maker Preferences Program * * Copyright (c) 1999 Alfredo K. Kojima * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include #include #include #include #include #include "editmenu.h" typedef struct W_EditMenuItem { W_Class widgetClass; WMView *view; struct W_EditMenu *menu; char *label; WMTextField *textField; struct W_EditMenu *submenu; /* if it's a cascade, NULL otherwise */ } EditMenuItem; typedef struct W_EditMenu { W_Class widgetClass; WMView *view; struct W_EditMenu *parent; char *label; int itemCount; int itemsAlloced; struct W_EditMenuItem **items; int titleHeight; int itemHeight; struct W_EditMenu *next; struct W_EditMenu *prev; /* item dragging */ int draggedItem; int dragX, dragY; } EditMenu; /******************** WEditMenuItem ********************/ static void destroyEditMenuItem(WEditMenuItem *iPtr); static void paintEditMenuItem(WEditMenuItem *iPtr); static void handleItemEvents(XEvent *event, void *data); static void handleItemActionEvents(XEvent *event, void *data); static W_ViewProcedureTable WEditMenuItemViewProcedures = { NULL, NULL, NULL }; static W_Class EditMenuItemClass = 0; W_Class InitEditMenuItem(WMScreen *scr) { /* register our widget with WINGs and get our widget class ID */ if (!EditMenuItemClass) { EditMenuItemClass = W_RegisterUserWidget(&WEditMenuItemViewProcedures); } return EditMenuItemClass; } WEditMenuItem* WCreateEditMenuItem(WMWidget *parent, char *title) { WEditMenuItem *iPtr; if (!EditMenuItemClass) InitEditMenuItem(WMWidgetScreen(parent)); iPtr = wmalloc(sizeof(WEditMenuItem)); memset(iPtr, 0, sizeof(WEditMenuItem)); iPtr->widgetClass = EditMenuItemClass; iPtr->view = W_CreateView(W_VIEW(parent)); if (!iPtr->view) { free(iPtr); return NULL; } iPtr->view->self = iPtr; iPtr->label = wstrdup(title); WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask, handleItemEvents, iPtr); WMCreateEventHandler(iPtr->view, ButtonPressMask, handleItemActionEvents, iPtr); return iPtr; } static void paintEditMenuItem(WEditMenuItem *iPtr) { WMScreen *scr = WMWidgetScreen(iPtr); WMColor *black = scr->black; Window win = W_VIEW(iPtr)->window; int w = W_VIEW(iPtr)->size.width; int h = WMFontHeight(scr->normalFont) + 6; if (!iPtr->view->flags.realized) return; XClearWindow(scr->display, win); W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised); WMDrawString(scr, win, WMColorGC(black), scr->normalFont, 5, 3, iPtr->label, strlen(iPtr->label)); } static void handleItemEvents(XEvent *event, void *data) { WEditMenuItem *iPtr = (WEditMenuItem*)data; switch (event->type) { case Expose: if (event->xexpose.count!=0) break; paintEditMenuItem(iPtr); break; case DestroyNotify: destroyEditMenuItem(iPtr); break; } } static void handleItemActionEvents(XEvent *event, void *data) { WEditMenuItem *iPtr = (WEditMenuItem*)data; switch (event->type) { case ButtonPress: break; } } static void destroyEditMenuItem(WEditMenuItem *iPtr) { if (iPtr->label) free(iPtr->label); free(iPtr); } /******************** WEditMenu *******************/ static WEditMenu *EditMenuList = NULL; static void destroyEditMenu(WEditMenu *mPtr); static void paintEditMenu(WEditMenu *mPtr, int y); static void updateMenuContents(WEditMenu *mPtr); static void handleEvents(XEvent *event, void *data); static void handleActionEvents(XEvent *event, void *data); static void handleItemDrag(XEvent *event, void *data); static W_ViewProcedureTable WEditMenuViewProcedures = { NULL, NULL, NULL }; static W_Class EditMenuClass = 0; W_Class InitEditMenu(WMScreen *scr) { /* register our widget with WINGs and get our widget class ID */ if (!EditMenuClass) { EditMenuClass = W_RegisterUserWidget(&WEditMenuViewProcedures); } return EditMenuClass; } typedef struct { int flags; int window_style; int window_level; int reserved; Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */ Pixmap close_pixmap; /* pixmap for close button */ Pixmap miniaturize_mask; /* miniaturize pixmap mask */ Pixmap close_mask; /* close pixmap mask */ int extra_flags; } GNUstepWMAttributes; #define GSWindowStyleAttr (1<<0) #define GSWindowLevelAttr (1<<1) static void writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr) { unsigned long data[9]; /* handle idiot compilers where array of CARD32 != struct of CARD32 */ data[0] = attr->flags; data[1] = attr->window_style; data[2] = attr->window_level; data[3] = 0; /* reserved */ /* The X protocol says XIDs are 32bit */ data[4] = attr->miniaturize_pixmap; data[5] = attr->close_pixmap; data[6] = attr->miniaturize_mask; data[7] = attr->close_mask; data[8] = attr->extra_flags; XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom, 32, PropModeReplace, (unsigned char *)data, 9); } static void realizeObserver(void *self, WMNotification *not) { WEditMenu *menu = (WEditMenu*)self; GNUstepWMAttributes attribs; memset(&attribs, 0, sizeof(GNUstepWMAttributes)); attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr; attribs.window_style = WMBorderlessWindowMask; attribs.window_level = WMSubmenuWindowLevel; writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs); } WEditMenu* WCreateEditMenu(WMScreen *scr, char *title) { WEditMenu *mPtr; if (!EditMenuClass) InitEditMenu(scr); mPtr = wmalloc(sizeof(WEditMenu)); memset(mPtr, 0, sizeof(WEditMenu)); mPtr->widgetClass = EditMenuClass; mPtr->view = W_CreateTopView(scr); if (!mPtr->view) { free(mPtr); return NULL; } mPtr->view->self = mPtr; WMAddNotificationObserver(realizeObserver, mPtr, WMViewRealizedNotification, mPtr->view); W_SetViewBackgroundColor(mPtr->view, mPtr->view->screen->darkGray); mPtr->label = wstrdup(title); mPtr->itemsAlloced = 10; mPtr->items = wmalloc(sizeof(WEditMenuItem*)*mPtr->itemsAlloced); WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask, handleEvents, mPtr); WMCreateEventHandler(mPtr->view, ButtonPressMask,handleActionEvents, mPtr); updateMenuContents(mPtr); mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6; mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8; mPtr->draggedItem = -1; mPtr->next = EditMenuList; if (EditMenuList) EditMenuList->prev = mPtr; EditMenuList = mPtr; return mPtr; } WEditMenuItem* WInsertMenuItemWithTitle(WEditMenu *mPtr, char *title, int index) { WEditMenuItem *item; item = WCreateEditMenuItem(mPtr, title); item->menu = mPtr; WMCreateEventHandler(item->view, ButtonPressMask|ButtonReleaseMask |Button1MotionMask, handleItemDrag, item); WMMapWidget(item); if (index < 0) index = 0; else if (index > mPtr->itemCount) index = mPtr->itemCount; if (mPtr->itemCount == mPtr->itemsAlloced) { WEditMenuItem *newList; newList = wmalloc(sizeof(WEditMenuItem*)*(mPtr->itemsAlloced+10)); memset(newList, 0, sizeof(WEditMenuItem*)*(mPtr->itemsAlloced+10)); memcpy(newList, mPtr->items, mPtr->itemsAlloced*sizeof(WEditMenuItem*)); mPtr->itemsAlloced += 10; free(mPtr->items); mPtr->items = newList; } if (index < mPtr->itemCount) { memmove(&mPtr->items[index+1], &mPtr->items[index], sizeof(WEditMenuItem*)); mPtr->items[index] = item; mPtr->itemCount++; } else { mPtr->items[mPtr->itemCount++] = item; } updateMenuContents(mPtr); return item; } void WSetMenuSubmenu(WEditMenu *mPtr, WEditMenu *submenu, WEditMenuItem *item) { item->submenu = submenu; submenu->parent = mPtr; paintEditMenuItem(item); } void WRemoveMenuItem(WEditMenu *mPtr, WEditMenuItem *item) { } static void updateMenuContents(WEditMenu *mPtr) { WMScreen *scr = WMWidgetScreen(mPtr); int i; int newW, newH; int w; int iheight = mPtr->itemHeight; newW = WMWidthOfString(scr->boldFont, mPtr->label, strlen(mPtr->label)) + 12 + iheight; newH = mPtr->titleHeight; for (i = 0; i < mPtr->itemCount; i++) { w = WMWidthOfString(scr->normalFont, mPtr->items[i]->label, strlen(mPtr->items[i]->label)) + 5; if (w > newW) newW = w; W_MoveView(mPtr->items[i]->view, 0, newH); newH += iheight; } newH--; W_ResizeView(mPtr->view, newW, newH); for (i = 0; i < mPtr->itemCount; i++) { W_ResizeView(mPtr->items[i]->view, newW, iheight); } paintEditMenu(mPtr, -1); } static void paintMenuTitle(WEditMenu *mPtr) { WMScreen *scr = WMWidgetScreen(mPtr); WMColor *black = scr->black; WMColor *white = scr->white; Window win = W_VIEW(mPtr)->window; int w = W_VIEW(mPtr)->size.width; int h = mPtr->titleHeight; XFillRectangle(scr->display, win, WMColorGC(black), 0, 0, w, h); W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised); WMDrawString(scr, win, WMColorGC(white), scr->boldFont, 5, 4, mPtr->label, strlen(mPtr->label)); } static void paintEditMenu(WEditMenu *mPtr, int y) { if (!mPtr->view->flags.realized) return; if (y < mPtr->titleHeight || y < 0) paintMenuTitle(mPtr); } static void handleEvents(XEvent *event, void *data) { WEditMenu *mPtr = (WEditMenu*)data; switch (event->type) { case Expose: paintEditMenu(mPtr, event->xexpose.y); break; case DestroyNotify: destroyEditMenu(mPtr); break; } } static void handleActionEvents(XEvent *event, void *data) { WEditMenu *mPtr = (WEditMenu*)data; switch (event->type) { case ButtonPress: break; } } static void editItemLabel(WEditMenuItem *iPtr) { WMTextField *tPtr; tPtr = WMCreateTextField(iPtr); WMResizeWidget(tPtr, iPtr->view->size.width - 20, iPtr->view->size.height - 3); WMMoveWidget(tPtr, 4, 1); WMSetTextFieldBeveled(tPtr, False); WMMapWidget(tPtr); WMRealizeWidget(tPtr); iPtr->textField = tPtr; } static void handleItemDrag(XEvent *event, void *data) { WEditMenuItem *iPtr = (WEditMenuItem*)data; WEditMenu *mPtr = iPtr->menu; WMScreen *scr = WMWidgetScreen(mPtr); Bool done = False; int y; int i; int newIdx, oldIdx; int newY; switch (event->type) { case ButtonPress: if (WMIsDoubleClick(event)) { editItemLabel(iPtr); } else if (event->xbutton.button == Button1) { mPtr->draggedItem = 1; mPtr->dragX = event->xbutton.x; mPtr->dragY = event->xbutton.y; } return; case ButtonRelease: if (event->xbutton.button == Button1) { mPtr->draggedItem = -1; } return; case MotionNotify: if (mPtr->draggedItem >= 0) { if (abs(event->xmotion.y - mPtr->dragY) > 3 || abs(event->xmotion.x - mPtr->dragX) > 3) { mPtr->draggedItem = -1; } else { return; } } else { return; } break; default: return; } XRaiseWindow(scr->display, iPtr->view->window); XGrabPointer(scr->display, mPtr->view->window, False, PointerMotionMask|ButtonReleaseMask|ButtonPressMask |ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); y = iPtr->view->pos.y; while (!done) { XEvent ev; WMMaskEvent(scr->display, ButtonReleaseMask|PointerMotionMask |ExposureMask, &ev); switch (ev.type) { case ButtonRelease: if (ev.xbutton.button == Button1) done = True; break; case MotionNotify: y = ev.xbutton.y - mPtr->dragY; if (y < mPtr->titleHeight) y = mPtr->titleHeight; else if (y > mPtr->view->size.height - mPtr->itemHeight + 1) y = mPtr->view->size.height - mPtr->itemHeight + 1; W_MoveView(iPtr->view, 0, y); break; default: WMHandleEvent(&ev); break; } } XUngrabPointer(scr->display, CurrentTime); for (oldIdx = 0; oldIdx < mPtr->itemCount; oldIdx++) { if (mPtr->items[oldIdx] == iPtr) { break; } } assert(oldIdx < mPtr->itemCount); newIdx = (y - mPtr->titleHeight + mPtr->itemHeight/2) / mPtr->itemHeight; if (newIdx < 0) newIdx = 0; else if (newIdx >= mPtr->itemCount) newIdx = mPtr->itemCount - 1; newY = mPtr->titleHeight + newIdx * mPtr->itemHeight; for (i = 0; i <= 15; i++) { W_MoveView(iPtr->view, 0, ((newY*i)/15 + (y - (y*i)/15))); XFlush(scr->display); } if (oldIdx != newIdx) { WEditMenuItem *item; item = mPtr->items[oldIdx]; mPtr->items[oldIdx] = mPtr->items[newIdx]; mPtr->items[newIdx] = item; updateMenuContents(mPtr); } } static void destroyEditMenu(WEditMenu *mPtr) { WMRemoveNotificationObserver(mPtr); if (mPtr->next) mPtr->next->prev = mPtr->prev; if (mPtr->prev) mPtr->prev->next = mPtr->next; if (EditMenuList == mPtr) EditMenuList = mPtr->next; if (mPtr->label) free(mPtr->label); if (mPtr->items) free(mPtr->items); free(mPtr); }