/**************************************************************************** * This module is based on Twm, but has been siginificantly modified * by Rob Nation ****************************************************************************/ /*****************************************************************************/ /** Copyright 1988 by Evans & Sutherland Computer Corporation, **/ /** Salt Lake City, Utah **/ /** Portions Copyright 1989 by the Massachusetts Institute of Technology **/ /** Cambridge, Massachusetts **/ /** **/ /** All Rights Reserved **/ /** **/ /** Permission to use, copy, modify, and distribute this software and **/ /** its documentation for any purpose and without fee is hereby **/ /** granted, provided that the above copyright notice appear in all **/ /** copies and that both that copyright notice and this permis- **/ /** sion notice appear in supporting documentation, and that the **/ /** names of Evans & Sutherland and M.I.T. not be used in advertising **/ /** in publicity pertaining to distribution of the software without **/ /** specific, written prior permission. **/ /** **/ /** EVANS & SUTHERLAND AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD **/ /** TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- **/ /** ABILITY AND FITNESS, IN NO EVENT SHALL EVANS & SUTHERLAND OR **/ /** M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAM- **/ /** AGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA **/ /** OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER **/ /** TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE **/ /** OR PERFORMANCE OF THIS SOFTWARE. **/ /*****************************************************************************/ /*********************************************************************** * * fvwm menu code * ***********************************************************************/ #include "../configure.h" #include #include #include #include #include #include #include #include "fvwm.h" #include "menus.h" #include "misc.h" #include "parse.h" #include "screen.h" int menu_on=0; MenuRoot *ActiveMenu = NULL; /* the active menu */ MenuItem *ActiveItem = NULL; /* the active menu item */ int menuFromFrameOrWindowOrTitlebar = FALSE; extern int Context,Button; extern FvwmWindow *ButtonWindow, *Tmp_win; extern XEvent Event; int Stashed_X, Stashed_Y,MenuY=0; void DrawTrianglePattern(Window,GC,GC,GC,int,int,int,int); void DrawSeparator(Window, GC,GC,int, int,int,int,int); void DrawUnderline(Window w, GC gc, int x, int y, char *txt, int off); int UpdateMenu(void); extern XContext MenuContext; /**************************************************************************** * * Initiates a menu pop-up * ***************************************************************************/ int do_menu (MenuRoot *menu) { int prevStashedX=0,prevStashedY=0; MenuRoot *PrevActiveMenu=0; MenuItem *PrevActiveItem=0; int retval=MENU_NOP; int x,y; /* this condition could get ugly */ if(menu->in_use) return MENU_ERROR; /* In case we wind up with a move from a menu which is * from a window border, we'll return to here to start * the move */ XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild, &x, &y, &JunkX, &JunkY, &JunkMask); if(menu_on) { prevStashedX = Stashed_X; prevStashedY = Stashed_Y; PrevActiveMenu = ActiveMenu; PrevActiveItem = ActiveItem; if(ActiveMenu) if(Scr.flags & MWMMenus) x = Stashed_X+ (ActiveMenu->width >> 1) + (menu->width >> 1) - 3; if(ActiveItem) y = ActiveItem->y_offset + MenuY + (Scr.EntryHeight >>1); } else { if(!GrabEm(MENU)) { XBell(dpy,Scr.screen); return MENU_DONE; } if(Scr.flags & MWMMenus) x += (menu->width >> 1) - 3; } if (PopUpMenu (menu, x, y)) { retval = UpdateMenu(); } else XBell (dpy, Scr.screen); ActiveMenu = PrevActiveMenu; ActiveItem = PrevActiveItem; if((ActiveItem)&&(menu_on)) ActiveItem->state = 1; Stashed_X = prevStashedX; Stashed_Y = prevStashedY; if(!menu_on) { UngrabEm(); WaitForButtonsUp(); } return retval; } /*********************************************************************** * * Procedure: * RelieveRectangle - add relief lines to a rectangular window * ***********************************************************************/ void RelieveRectangle(Window win,int x,int y,int w, int h,GC Hilite,GC Shadow) { XDrawLine(dpy, win, Hilite, x, y, w+x-1, y); XDrawLine(dpy, win, Hilite, x, y, x, h+y-1); XDrawLine(dpy, win, Shadow, x, h+y-1, w+x-1, h+y-1); XDrawLine(dpy, win, Shadow, w+x-1, y, w+x-1, h+y-1); } /*********************************************************************** * * Procedure: * RelieveHalfRectangle - add relief lines to the sides only of a * rectangular window * ***********************************************************************/ void RelieveHalfRectangle(Window win,int x,int y,int w,int h, GC Hilite,GC Shadow) { XDrawLine(dpy, win, Hilite, x, y-1, x, h+y); XDrawLine(dpy, win, Hilite, x+1, y, x+1, h+y-1); XDrawLine(dpy, win, Shadow, w+x-1, y-1, w+x-1, h+y); XDrawLine(dpy, win, Shadow, w+x-2, y, w+x-2, h+y-1); } /*********************************************************************** * * Procedure: * PaintEntry - draws a single entry in a poped up menu * ***********************************************************************/ void PaintEntry(MenuRoot *mr, MenuItem *mi) { int y_offset,text_y,d, y_height; GC ShadowGC, ReliefGC, currentGC; y_offset = mi->y_offset; y_height = mi->y_height; text_y = y_offset + Scr.StdFont.y; ShadowGC = Scr.MenuShadowGC; if(Scr.d_depth<2) ReliefGC = Scr.MenuShadowGC; else ReliefGC = Scr.MenuReliefGC; if(Scr.flags & MWMMenus) { if((!mi->prev)||(!mi->prev->state)) XClearArea(dpy, mr->w, 0,y_offset-1,mr->width,y_height+2,0); else XClearArea(dpy, mr->w, 0,y_offset+1,mr->width,y_height-1,0); if ((mi->state)&&(mi->func != F_TITLE)&&(mi->func != F_NOP)&&*mi->item) { RelieveRectangle(mr->w, 3, y_offset, mr->width-5, mi->y_height, ReliefGC,ShadowGC); RelieveRectangle(mr->w, 2, y_offset-1, mr->width-3, mi->y_height+2, ReliefGC,ShadowGC); } RelieveHalfRectangle(mr->w, 0, y_offset-1, mr->width, y_height+2, ReliefGC, ShadowGC); } else { XClearArea(dpy, mr->w, 0,y_offset,mr->width,y_height,0); if ((mi->state)&&(mi->func != F_TITLE)&&(mi->func != F_NOP)&&*mi->item) RelieveRectangle(mr->w, 2, y_offset, mr->width-4, mi->y_height, ReliefGC,ShadowGC); RelieveHalfRectangle(mr->w, 0, y_offset, mr->width, y_height, ReliefGC, ShadowGC); } if(mi->func == F_TITLE) { if(Scr.flags & MWMMenus) { text_y += HEIGHT_EXTRA>>1; XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset+y_height-2, mr->width-3, y_offset+y_height-2); XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset+y_height-4, mr->width-3, y_offset+y_height-4); } else { if(mi->next != NULL) { DrawSeparator(mr->w,ShadowGC,ReliefGC,5, y_offset+y_height-3, mr->width-6, y_offset+y_height-3,1); } if(mi != mr->first) { text_y += HEIGHT_EXTRA_TITLE>>1; DrawSeparator(mr->w,ShadowGC,ReliefGC,5, y_offset+1, mr->width-6, y_offset+1,1); } } } else text_y += HEIGHT_EXTRA>>1; if(mi->func == F_NOP && *mi->item==0) { if(Scr.flags & MWMMenus) DrawSeparator(mr->w,ShadowGC,ReliefGC,2,y_offset-1+HEIGHT_SEPARATOR/2, mr->width-3,y_offset-1+HEIGHT_SEPARATOR/2,0); else DrawSeparator(mr->w,ShadowGC,ReliefGC,5,y_offset-1+HEIGHT_SEPARATOR/2, mr->width-6,y_offset-1+HEIGHT_SEPARATOR/2,1); } if(mi->next == NULL) DrawSeparator(mr->w,ShadowGC,ShadowGC,1,mr->height-2, mr->width-2, mr->height-2,1); if(mi == mr->first) DrawSeparator(mr->w,ReliefGC,ReliefGC,0,0, mr->width-1,0,-1); if(check_allowed_function(mi)) currentGC = Scr.MenuGC; else /* should be a shaded out word, no just re-colored. */ currentGC = Scr.MenuStippleGC; if(*mi->item) XDrawString(dpy, mr->w, currentGC,mi->x,text_y, mi->item, mi->strlen); if(mi->strlen2>0) XDrawString(dpy, mr->w, currentGC,mi->x2,text_y, mi->item2,mi->strlen2); /* pete@tecc.co.uk: If the item has a hot key, underline it */ if (mi->hotkey > 0) DrawUnderline(mr->w, currentGC,mi->x,text_y,mi->item,mi->hotkey - 1); if (mi->hotkey < 0) DrawUnderline(mr->w, currentGC,mi->x2,text_y,mi->item2, -1 - mi->hotkey); d=(Scr.EntryHeight-7)/2; if(mi->func == F_POPUP) if(mi->state) DrawTrianglePattern(mr->w, ShadowGC, ReliefGC, ShadowGC,mr->width-d-8, y_offset+d-1, mr->width-d-1, y_offset+d+7); else DrawTrianglePattern(mr->w, ReliefGC, ShadowGC, ReliefGC,mr->width-d-8, y_offset+d-1, mr->width-d-1, y_offset+d+7); return; } /**************************************************************************** * Procedure: * DrawUnderline() - Underline a character in a string (pete@tecc.co.uk) * * Calculate the pixel offsets to the start of the character position we * want to underline and to the next character in the string. Shrink by * one pixel from each end and the draw a line that long two pixels below * the character... * ****************************************************************************/ void DrawUnderline(Window w, GC gc, int x, int y, char *txt, int posn) { int off1 = XTextWidth(Scr.StdFont.font, txt, posn); int off2 = XTextWidth(Scr.StdFont.font, txt, posn + 1) - 1; XDrawLine(dpy, w, gc, x + off1, y + 2, x + off2, y + 2); } /**************************************************************************** * * Draws two horizontal lines to form a separator * ****************************************************************************/ void DrawSeparator(Window w, GC TopGC, GC BottomGC,int x1,int y1,int x2,int y2, int extra_off) { XDrawLine(dpy, w, TopGC , x1, y1, x2, y2); XDrawLine(dpy, w, BottomGC, x1-extra_off, y1+1,x2+extra_off,y2+1); } /**************************************************************************** * * Draws a little Triangle pattern within a window * ****************************************************************************/ void DrawTrianglePattern(Window w,GC GC1,GC GC2,GC GC3,int l,int u,int r,int b) { int m; m = (u + b)/2; XDrawLine(dpy,w,GC1,l,u,l,b); XDrawLine(dpy,w,GC2,l,b,r,m); XDrawLine(dpy,w,GC3,r,m,l,u); } /*********************************************************************** * * Procedure: * PaintMenu - draws the entire menu * ***********************************************************************/ void PaintMenu(MenuRoot *mr, XEvent *e) { MenuItem *mi; for (mi = mr->first; mi != NULL; mi = mi->next) { /* be smart about handling the expose, redraw only the entries * that we need to */ if (e->xexpose.y < (mi->y_offset + mi->y_height) && (e->xexpose.y + e->xexpose.height) > mi->y_offset) { PaintEntry(mr, mi); } } XSync(dpy, 0); return; } MenuRoot *PrevMenu = NULL; MenuItem *PrevItem = NULL; int PrevY=0; /*********************************************************************** * * Procedure: * Updates menu display to reflect the highlighted item * ***********************************************************************/ int FindEntry(void) { MenuItem *mi; MenuRoot *actual_mr; int retval = MENU_NOP; MenuRoot *PrevPrevMenu; MenuItem *PrevPrevItem; int PrevPrevY; int x, y, ChildY; Window Child; XQueryPointer( dpy, Scr.Root, &JunkRoot, &Child, &JunkX,&ChildY, &x, &y, &JunkMask); XQueryPointer( dpy, ActiveMenu->w, &JunkRoot, &JunkChild, &JunkX, &ChildY, &x, &y, &JunkMask); /* look for the entry that the mouse is in */ for(mi=ActiveMenu->first; mi; mi=mi->next) if(y>=mi->y_offset && yy_offset+mi->y_height) break; if(x<0 || x>ActiveMenu->width) mi = NULL; /* if we weren't on the active entry, let's turn the old active one off */ if ((ActiveItem)&&(mi!=ActiveItem)) { ActiveItem->state = 0; PaintEntry(ActiveMenu, ActiveItem); } /* if we weren't on the active item, change the active item and turn it on */ if ((mi!=ActiveItem)&&(mi != NULL)) { mi->state = 1; PaintEntry(ActiveMenu, mi); } ActiveItem = mi; if(ActiveItem) { /* create a new sub-menu */ if((ActiveItem->func == F_POPUP)&& ((Scr.flags & MWMMenus)||(x>(3*ActiveMenu->width>>2)))) { PrevPrevMenu = PrevMenu; PrevPrevItem = PrevItem; PrevPrevY = PrevY; PrevY = MenuY; PrevMenu = ActiveMenu; PrevItem = ActiveItem; retval = do_menu(ActiveItem->menu); /* Unfortunately, this is needed (why?) for multi-screen operation */ flush_expose(ActiveMenu->w); for (mi = ActiveMenu->first; mi != NULL; mi = mi->next) { PaintEntry(ActiveMenu, mi); } XSync(dpy, 0); MenuY = PrevY; PrevMenu = PrevPrevMenu; PrevItem = PrevPrevItem; PrevY = PrevPrevY; } } /* end a sub-menu */ if (XFindContext (dpy, Child,MenuContext,(caddr_t *)&actual_mr)==XCNOENT) { return retval; } if(actual_mr != ActiveMenu) { if(actual_mr == PrevMenu) { if((PrevItem->y_offset + PrevY > ChildY)|| ((PrevItem->y_offset+PrevItem->y_height + PrevY) < ChildY)) { return SUBMENU_DONE; } } else return SUBMENU_DONE; } return retval; } /*********************************************************************** * Procedure * menuShortcuts() - Menu keyboard processing (pete@tecc.co.uk) * * Function called from UpdateMenu instead of Keyboard_Shortcuts() * when a KeyPress event is received. If the key is alphanumeric, * then the menu is scanned for a matching hot key. Otherwise if * it was the escape key then the menu processing is aborted. * If none of these conditions are true, then the default processing * routine is called. ***********************************************************************/ void menuShortcuts(XEvent *ev) { MenuItem *mi; KeySym keysym = XLookupKeysym(&ev->xkey,0); /* Try to match hot keys */ if (((keysym >= XK_a) && (keysym <= XK_z)) || /* Only consider alphabetic */ ((keysym >= XK_0) && (keysym <= XK_9))) /* ...or numeric keys */ { /* Search menu for matching hotkey */ for (mi = ActiveMenu->first; mi; mi = mi->next) { char key; if (mi->hotkey == 0) continue; /* Item has no hotkey */ key = (mi->hotkey > 0) ? /* Extract hot character */ mi->item[mi->hotkey - 1] : mi->item2[-1 - mi->hotkey]; /* Convert to lower case to match the keysym */ if (isupper(key)) key = tolower(key); if (keysym == key) { /* Are they equal? */ ActiveItem = mi; /* Yes: Make this the active item */ ev->type = ButtonRelease; /* Force a menu exit */ return; } } } switch(keysym) /* Other special keyboard handling */ { case XK_Escape: /* Escape key pressed. Abort */ ActiveItem = NULL; /* No selection */ ev->type = ButtonRelease; /* Make the menu exit */ break; /* Nothing special --- Allow other shortcuts (cursor movement) */ default: Keyboard_shortcuts(ev,ButtonRelease); break; } } /*********************************************************************** * * Procedure: * Updates menu display to reflect the highlighted item * * Returns: * 0 on error condition * 1 on return from submenu to parent menu * 2 on button release return * ***********************************************************************/ int UpdateMenu(void) { int done,func; int retval; MenuRoot *actual_mr; FindEntry(); while (TRUE) { /* block until there is an event */ XMaskEvent(dpy, ButtonPressMask|ButtonReleaseMask|ExposureMask | KeyPressMask|VisibilityChangeMask|ButtonMotionMask, &Event); StashEventTime(&Event); done = 0; if (Event.type == MotionNotify) { /* discard any extra motion events before a release */ while((XCheckMaskEvent(dpy,ButtonMotionMask|ButtonReleaseMask, &Event))&&(Event.type != ButtonRelease)); } /* Handle a limited number of key press events to allow mouseless * operation */ if(Event.type == KeyPress) menuShortcuts(&Event); switch(Event.type) { case ButtonRelease: /* The following lines holds the menu when the button is released */ if ((Scr.flags & MWMMenus) && !ActiveItem && (menu_on > 1)) { int x,y; XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild, &JunkX, &JunkY, &x, &y, &JunkMask); if((XFindContext(dpy, JunkChild,MenuContext, (caddr_t *)&actual_mr)!=XCNOENT)&& (actual_mr != ActiveMenu)) { done = 1; break; } } PopDownMenu(); if(ActiveItem) { func = ActiveItem->func; done = 1; if(ButtonWindow) { ExecuteFunction(func, ActiveItem->action, ButtonWindow->frame, ButtonWindow, &Event, Context, ActiveItem->val1,ActiveItem->val2, ActiveItem->val1_unit,ActiveItem->val2_unit, ActiveItem->menu,-1); } else { ExecuteFunction(func,ActiveItem->action, None,None, &Event, Context,ActiveItem->val1, ActiveItem->val2, ActiveItem->val1_unit,ActiveItem->val2_unit, ActiveItem->menu,-1); } } ActiveItem = NULL; ActiveMenu = NULL; menuFromFrameOrWindowOrTitlebar = FALSE; return MENU_DONE; case KeyPress: case VisibilityNotify: case ButtonPress: done=1; break; case MotionNotify: done = 1; retval = FindEntry(); if((retval == MENU_DONE)||(retval == SUBMENU_DONE)) { PopDownMenu(); ActiveItem = NULL; ActiveMenu = NULL; menuFromFrameOrWindowOrTitlebar = FALSE; } if(retval == MENU_DONE) return MENU_DONE; else if (retval == SUBMENU_DONE) return MENU_NOP; break; case Expose: /* grab our expose events, let the rest go through */ if((XFindContext(dpy, Event.xany.window,MenuContext, (caddr_t *)&actual_mr)!=XCNOENT)) { PaintMenu(actual_mr,&Event); done = 1; } break; default: break; } if(!done)DispatchEvent(); XFlush(dpy); } } /*********************************************************************** * * Procedure: * PopUpMenu - pop up a pull down menu * * Inputs: * menu - the root pointer of the menu to pop up * x, y - location of upper left of menu * center - whether or not to center horizontally over position * ***********************************************************************/ Bool PopUpMenu (MenuRoot *menu, int x, int y) { if ((!menu)||(menu->w == None)||(menu->items == 0)||(menu->in_use)) return False; menu_on++; InstallRootColormap(); Stashed_X = x; Stashed_Y = y; /* pop up the menu */ ActiveMenu = menu; ActiveItem = NULL; x -= (menu->width >> 1); y -= (Scr.EntryHeight >> 1); if((Tmp_win)&&(menu_on == 1)&&(Context&C_LALL)) { y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height+1; x = Tmp_win->frame_x + Tmp_win->boundary_width + Button*Tmp_win->title_height+1; } if((Tmp_win)&&(menu_on==1)&&(Context&C_RALL)) { y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height+1; x = Tmp_win->frame_x +Tmp_win->frame_width - Tmp_win->boundary_width- Button*Tmp_win->title_height - menu->width+1; } if((Tmp_win)&&(menu_on==1)&&(Context&C_TITLE)) { y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height+1; if(x < Tmp_win->frame_x + Tmp_win->title_x) x = Tmp_win->frame_x + Tmp_win->title_x; if((x + menu->width) > (Tmp_win->frame_x + Tmp_win->title_x +Tmp_win->title_width)) x = Tmp_win->frame_x + Tmp_win->title_x +Tmp_win->title_width- menu->width +1; } /* clip to screen */ if (x + menu->width > Scr.MyDisplayWidth-2) x = Scr.MyDisplayWidth - menu->width-2; if (x < 0) x = 0; if (y + menu->height > Scr.MyDisplayHeight-2) { y = Scr.MyDisplayHeight - menu->height-2; /* Warp pointer to middle of top line */ /* Not with MWMMenus! */ if((!(Scr.flags & MWMMenus))||(menu_on < 2)) XWarpPointer(dpy, Scr.Root, Scr.Root, 0, 0, Scr.MyDisplayWidth, Scr.MyDisplayHeight, x + (menu->width>>1), (y + (Scr.EntryHeight >> 1))); } if (y < 0) y = 0; MenuY = y; XMoveWindow(dpy, menu->w, x, y); XMapRaised(dpy, menu->w); menu->in_use = True; return True; } /*********************************************************************** * * Procedure: * PopDownMenu - unhighlight the current menu selection and * take down the menus * ***********************************************************************/ void PopDownMenu() { if (ActiveMenu == NULL) return; menu_on--; if (ActiveItem) ActiveItem->state = 0; XUnmapWindow(dpy, ActiveMenu->w); UninstallRootColormap(); XFlush(dpy); if (Context & (C_WINDOW | C_FRAME | C_TITLE | C_SIDEBAR)) menuFromFrameOrWindowOrTitlebar = TRUE; else menuFromFrameOrWindowOrTitlebar = FALSE; ActiveMenu->in_use = FALSE; } /*************************************************************************** * * Wait for all mouse buttons to be released * This can ease some confusion on the part of the user sometimes * * Discard superflous button events during this wait period. * ***************************************************************************/ void WaitForButtonsUp() { Bool AllUp = False; XEvent JunkEvent; unsigned int mask; while(!AllUp) { XAllowEvents(dpy,ReplayPointer,CurrentTime); XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild, &JunkX, &JunkY, &JunkX, &JunkY, &mask); if((mask& (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask))==0) AllUp = True; } XSync(dpy,0); while(XCheckMaskEvent(dpy, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask, &JunkEvent)) { StashEventTime (&JunkEvent); XAllowEvents(dpy,ReplayPointer,CurrentTime); } }