/**************************************************************************** * This module is all original code * by Rob Nation * Copyright 1993, Robert Nation * You may use this code for any purpose, as long as the original * copyright remains in the source code and all documentation ****************************************************************************/ /*********************************************************************** * * fvwm menu code * ***********************************************************************/ #include #include #include #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,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(int sticks); int mouse_moved = 0; int menu_aborted = 0; int PrevActiveMenuX = -1; /* Menu side-picture support */ void PaintSidePic(MenuRoot *mr); extern XContext MenuContext; /**************************************************************************** * * Initiates a menu pop-up * * Style = 1 = sticky menu, stays up on initial button release. * Style = 0 = transient menu, drops on initial release. ***************************************************************************/ int do_menu (MenuRoot *menu, int style) { int prevStashedX=0,prevStashedY=0; MenuRoot *PrevActiveMenu=0; MenuItem *PrevActiveItem=0; int retval=MENU_NOP; int x,y; Time t0; extern Time lastTimestamp; int PrevMenuX = PrevActiveMenuX; /* 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) x = Stashed_X + ActiveMenu->width - 3; if(ActiveItem) y = ActiveItem->y_offset + MenuY; } else { mouse_moved = 0; t0 = lastTimestamp; if(!GrabEm(MENU)) { XBell(dpy,Scr.screen); return MENU_DONE; } x += 2; } if (PopUpMenu (menu, x, y)) { retval = UpdateMenu(style); } 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(); } if(((lastTimestamp-t0) < 3*Scr.ClickTime) && (mouse_moved == 0)) menu_aborted = 1; else menu_aborted = 0; PrevActiveMenuX = PrevMenuX; 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+1, y, x+1, h+y-1); XDrawLine(dpy, win, Scr.BlackGC, 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 * * If a side picture specified, all entries are shifted to the right * ***********************************************************************/ void PaintEntry(MenuRoot *mr, MenuItem *mi) { int y_offset, text_y, d, y_height, y, x; GC ShadowGC, ReliefGC, TextGC; y_offset = mi->y_offset; y_height = mi->y_height; text_y = y_offset + Scr.StdFont.y; if (mi->picture) text_y += mi->picture->height; /* center text vertically if the pixmap is taller */ if (mi->lpicture) { y = mi->lpicture->height - Scr.StdFont.height; if (y > 1) text_y += y >> 1; } ShadowGC = Scr.MenuShadowGC; if(Scr.d_depth<2) ReliefGC = Scr.MenuShadowGC; else ReliefGC = Scr.MenuReliefGC; TextGC = Scr.MenuGC; /* active cursor over entry? */ if ((mi->state)&&(mi->func_type != F_TITLE)&& (mi->func_type != F_NOP)&&*mi->item) { Globalgcv.foreground = Scr.SelColors.back; Globalgcm = GCForeground; XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv); XFillRectangle(dpy, mr->w, Scr.ScratchGC1, mr->xoffset+3, y_offset, mr->width - mr->xoffset-6, y_height); TextGC = Scr.SelGC; } else { if((!mi->prev)||(!mi->prev->state)) XClearArea(dpy, mr->w, mr->xoffset, y_offset-1, mr->width,y_height+2,0); else XClearArea(dpy, mr->w, mr->xoffset, y_offset+1, mr->width,y_height-1,0); } RelieveHalfRectangle(mr->w, 0, y_offset-1, mr->width, y_height+2, ReliefGC, ShadowGC); if(mi->func_type == F_TITLE) { text_y += HEIGHT_EXTRA>>1; if(mi->next != NULL) { DrawSeparator(mr->w,ShadowGC,ReliefGC,5+mr->xoffset, 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+mr->xoffset, y_offset+1, mr->width-6, y_offset+1,1); } } else { text_y += HEIGHT_EXTRA>>1; } if(mi->func_type == F_NOP && *mi->item==0) { DrawSeparator(mr->w, ShadowGC, ReliefGC, 2+mr->xoffset, y_offset-1+(HEIGHT_SEPARATOR >> 1), mr->width-3,y_offset-1+(HEIGHT_SEPARATOR >> 1), 0); } /* Top of the menu */ if(mi == mr->first) /*DrawSeparator(mr->w,ReliefGC,ReliefGC,0,0, mr->width-1,0,-1);*/ XDrawLine(dpy, mr->w, ReliefGC, 1, 1, mr->width-3, 1); /* Botton of the menu */ if(mi->next == NULL) DrawSeparator(mr->w,ShadowGC,Scr.BlackGC,1,mr->height-2, mr->width-2, mr->height-2,1); #ifdef I18N #undef FONTSET #define FONTSET Scr.StdFont.fontset #endif if (*mi->item) if (check_allowed_function(mi)) { XDrawString(dpy, mr->w, TextGC, mi->x + mr->xoffset, text_y, mi->item, mi->strlen); } else { /* should be a shaded out word, no just re-colored. */ if (!mi->state) XDrawString(dpy, mr->w, ReliefGC, mi->x + mr->xoffset+1, text_y+1, mi->item, mi->strlen); XDrawString(dpy, mr->w, ShadowGC, mi->x+mr->xoffset, text_y, mi->item, mi->strlen); } if (mi->strlen2>0) if (check_allowed_function(mi)) { XDrawString(dpy, mr->w, TextGC, mi->x2+mr->xoffset, text_y, mi->item2, mi->strlen2); } else { /* should be a shaded out word, no just re-colored. */ if (!mi->state) XDrawString(dpy, mr->w, ReliefGC, mi->x2+1 + mr->xoffset, text_y+1, mi->item2, mi->strlen2); XDrawString(dpy, mr->w, ShadowGC, mi->x2 + mr->xoffset, text_y, mi->item2, mi->strlen2); } /* pete@tecc.co.uk: If the item has a hot key, underline it */ if (mi->hotkey > 0) if (check_allowed_function(mi)) { DrawUnderline(mr->w, TextGC, mi->x + mr->xoffset, text_y, mi->item, mi->hotkey - 1); } else { /* should be a shaded out underline, no just re-colored. */ if (!mi->state) DrawUnderline(mr->w, ReliefGC, mi->x+1 + mr->xoffset, text_y+1, mi->item, mi->hotkey - 1); DrawUnderline(mr->w, ShadowGC, mi->x + mr->xoffset, text_y, mi->item, mi->hotkey - 1); } if (mi->hotkey < 0) if (check_allowed_function(mi)) { DrawUnderline(mr->w, TextGC, mi->x2 + mr->xoffset, text_y, mi->item2, -1 - mi->hotkey); } else { /* should be a shaded out underline, no just re-colored. */ if (!mi->state) DrawUnderline(mr->w, ReliefGC, mi->x2+1 + mr->xoffset, text_y+1, mi->item2, -1 - mi->hotkey); DrawUnderline(mr->w, ShadowGC, mi->x2 + mr->xoffset, text_y, mi->item2, -1 - mi->hotkey); } d=(Scr.EntryHeight-7) >> 1; if(mi->func_type == F_POPUP) { int ym; ym = y_offset + (y_height >> 1) -1; DrawTrianglePattern(mr->w, TextGC, mr->width-d-8, ym-4, mr->width-d-4, ym+4); } if(mi->picture) { x = (mr->width - mi->picture->width) >> 1; if(mi->lpicture && x < mr->width0 + 5) x = mr->width0 + 5; if(mi->picture->depth > 0) /* pixmap? */ { Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin; Globalgcv.clip_mask = mi->picture->mask; Globalgcv.clip_x_origin = x; Globalgcv.clip_y_origin = y_offset+1; XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv); XCopyArea(dpy,mi->picture->picture,mr->w,ReliefGC, 0,0,mi->picture->width, mi->picture->height, x,y_offset+1); Globalgcm = GCClipMask; Globalgcv.clip_mask = None; XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv); } else { XCopyPlane(dpy,mi->picture->picture,mr->w, TextGC,0,0,mi->picture->width,mi->picture->height, x,y_offset+1,1); } } if(mi->lpicture) { if(mi->picture && *mi->item != 0) y = y_offset + mi->y_height - mi->lpicture->height-1; else y = y_offset + (mi->y_height >> 1) - (mi->lpicture->height >> 1); if(mi->lpicture->depth > 0) /* pixmap? */ { Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin; Globalgcv.clip_mask = mi->lpicture->mask; Globalgcv.clip_x_origin = 9 + mr->xoffset; Globalgcv.clip_y_origin = y; XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv); XCopyArea(dpy, mi->lpicture->picture, mr->w, ReliefGC, 0, 0, mi->lpicture->width, mi->lpicture->height, 9 + mr->xoffset, y); Globalgcm = GCClipMask; Globalgcv.clip_mask = None; XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv); } else { XCopyPlane(dpy, mi->lpicture->picture, mr->w, TextGC, 0, 0, mi->lpicture->width, mi->lpicture->height, 9 + mr->xoffset, y, 1); } } 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 + 1, x + off2, y + 1); } /**************************************************************************** * * 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 gc, int l, int t, int r, int b) { int m; XPoint points[3]; m = (t + b) >> 1; points[0].x = l; points[0].y = t; points[1].x = l; points[1].y = b; points[2].x = r; points[2].y = m; XFillPolygon(dpy, w, gc, points, 3, Convex, CoordModeOrigin); } /************************************************************ * * Draws a picture on the left side of the menu * ************************************************************/ void PaintSidePic(MenuRoot *mr) { GC ReliefGC, TextGC; if(mr->sidePic) { if(Scr.d_depth<2) ReliefGC = Scr.WinShadowGC; else ReliefGC = Scr.WinReliefGC; TextGC = Scr.WinGC; if(mr->colorize) { Globalgcv.foreground = mr->sideColor; Globalgcm = GCForeground; XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv); XFillRectangle(dpy, mr->w, Scr.ScratchGC1, 3, 3, mr->sidePic->width, mr->height - 6); } if(mr->sidePic->depth > 0) /* pixmap? */ { Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin; Globalgcv.clip_mask = mr->sidePic->mask; Globalgcv.clip_x_origin = 3; Globalgcv.clip_y_origin = mr->height - mr->sidePic->height -3; XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv); XCopyArea(dpy, mr->sidePic->picture, mr->w, ReliefGC, 0, 0, mr->sidePic->width, mr->sidePic->height, Globalgcv.clip_x_origin, Globalgcv.clip_y_origin); Globalgcm = GCClipMask; Globalgcv.clip_mask = None; XChangeGC(dpy,ReliefGC,Globalgcm,&Globalgcv); } else { XCopyPlane(dpy, mr->sidePic->picture, mr->w, TextGC, 0, 0, mr->sidePic->width, mr->sidePic->height, 1, mr->height - mr->sidePic->height, 1); } } } /*********************************************************************** * * 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); } } /* Side picture support */ PaintSidePic(mr); 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<3 || x>ActiveMenu->width-3) 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) { MenuRoot *menu; /* create a new sub-menu */ if((ActiveItem->func_type == F_POPUP) /* use this to prevent automatic popup, so you have to place the cursor near the |> symbol && (x > (3*ActiveMenu->width>>2)) */) { PrevPrevMenu = PrevMenu; PrevPrevItem = PrevItem; PrevPrevY = PrevY; PrevY = MenuY; PrevMenu = ActiveMenu; PrevItem = ActiveItem; /*******************************************/ menu = FindPopup(&ActiveItem->action[5]); if(menu != NULL) {/* if (ActiveMenu->has_coords) { menu->x = ActiveMenu->x + ActiveMenu->width; menu->y = ActiveMenu->y; }*/ retval = do_menu(menu,0); /* 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 * * Input * sticks = 0, transient style menu, drops on button release * sticks = 1, sticky style, stays up if initial release is close to initial press. * Returns: * 0 on error condition * 1 on return from submenu to parent menu * 2 on button release return * ***********************************************************************/ int UpdateMenu(int sticks) { int done; int retval; MenuItem *InitialMI; MenuRoot *actual_mr; int x_init, y_init,x,y; XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild, &JunkX,&JunkY, &x_init, &y_init, &JunkMask); FindEntry(); InitialMI = ActiveItem; 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(sticks) { sticks = 0; break; } if (!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) { done = 1; ExecuteFunction(ActiveItem->action, ButtonWindow, &Event, Context,-1); } ActiveItem = NULL; ActiveMenu = NULL; menuFromFrameOrWindowOrTitlebar = FALSE; return MENU_DONE; case KeyPress: case VisibilityNotify: case ButtonPress: done=1; break; case MotionNotify: XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild, &JunkX,&JunkY, &x, &y, &JunkMask); if(((x-x_init > 3)||(x_init-x > 3))&& ((y-y_init > 3)||(y_init-y > 3))) mouse_moved = 1; done = 1; retval = FindEntry(); if(ActiveItem != InitialMI) sticks = 0; 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; if((Tmp_win)&&(menu_on == 1)&&(Context&C_LALL)) { y = Tmp_win->frame_y+Tmp_win->boundary_width+Tmp_win->title_height; x = Tmp_win->frame_x + Tmp_win->boundary_width + ButtonPosition(Context,Tmp_win)*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; x = Tmp_win->frame_x +Tmp_win->frame_width - Tmp_win->boundary_width- ButtonPosition(Context,Tmp_win)*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; 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; } if (menu->has_coords) { /* we already have coordinates explicitly given */ if (menu->x >= 0) x = menu->x; else x = Scr.MyDisplayWidth - menu->width + menu->x; if (menu->y >= 0) y = menu->y; /* - menu->height; */ else y = Scr.MyDisplayHeight - menu->height + menu->y; Stashed_X = x; Stashed_Y = y; } if (PrevActiveMenuX != -1) if (x + menu->width > Scr.MyDisplayWidth - 2) { x = PrevActiveMenuX - menu->width + 2; } /* 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 */ /* No with MWM menus! at least only in this case */ if (menu_on < 1) /* was 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; PrevActiveMenuX = x; 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); } } void DestroyMenu(MenuRoot *mr) { MenuItem *mi,*tmp2; MenuRoot *tmp, *prev; if(mr == NULL) return; tmp = Scr.AllMenus; prev = NULL; while((tmp != NULL)&&(tmp != mr)) { prev = tmp; tmp = tmp->next; } if(tmp != mr) return; if(prev == NULL) Scr.AllMenus = mr->next; else prev->next = mr->next; XDestroyWindow(dpy,mr->w); XDeleteContext(dpy, mr->w, MenuContext); if(mr->sidePic) DestroyPicture(dpy, mr->sidePic); /* need to free the window list ? */ mi = mr->first; while(mi != NULL) { tmp2 = mi->next; if (mi->item != NULL) free(mi->item); if (mi->item2 != NULL) free(mi->item2); if (mi->action != NULL) free(mi->action); if(mi->picture) DestroyPicture(dpy,mi->picture); if(mi->lpicture) DestroyPicture(dpy,mi->lpicture); free(mi); mi = tmp2; } free(mr); } /**************************************************************************** * * Generates the windows for all menus * ****************************************************************************/ void MakeMenus(void) { MenuRoot *mr; mr = Scr.AllMenus; while(mr != NULL) { MakeMenu(mr); mr = mr->next; } } /**************************************************************************** * * Generates the window for a menu * ****************************************************************************/ void MakeMenu(MenuRoot *mr) { MenuItem *cur; unsigned long valuemask; XSetWindowAttributes attributes; int y,width; if((mr->func != F_POPUP)||(!Scr.flags & WindowsCaptured)) return; /* allow two pixels for top border */ mr->width = 0; mr->width2 = 0; mr->width0 = 0; for (cur = mr->first; cur != NULL; cur = cur->next) { width = XTextWidth(Scr.StdFont.font, cur->item, cur->strlen); if(cur->picture && width < cur->picture->width) width = cur->picture->width; if(cur->func_type == F_POPUP) width += 15; if (width <= 0) width = 1; if (width > mr->width) mr->width = width; width = XTextWidth(Scr.StdFont.font, cur->item2, cur->strlen2); if (width < 0) width = 0; if (width > mr->width2) mr->width2 = width; if((width==0)&&(cur->strlen2>0)) mr->width2 = 1; if(cur->lpicture) if(mr->width0 < (cur->lpicture->width+3)) mr->width0 = cur->lpicture->width+3; } /* lets first size the window accordingly */ mr->width += 10; if(mr->width2 > 0) mr->width += 5; mr->width2 += 15; /*22*/; mr->width0 += 7; for (y=2, cur = mr->first; cur != NULL; cur = cur->next) { cur->y_offset = y; cur->x = 5+mr->width0; if(cur->func_type==F_TITLE) { /* Title */ if(cur->strlen2 == 0) cur->x = (mr->width+mr->width2+mr->width0 - XTextWidth(Scr.StdFont.font, cur->item, cur->strlen))>>1; if((cur->strlen > 0)||(cur->strlen2 > 0)) cur->y_height = Scr.EntryHeight + HEIGHT_EXTRA_TITLE; else cur->y_height = HEIGHT_SEPARATOR; } else if((cur->strlen==0)&&(cur->strlen2 == 0)) /* Separator */ cur->y_height = HEIGHT_SEPARATOR; else /* Normal text entry */ cur->y_height = Scr.EntryHeight; if(cur->picture) cur->y_height += cur->picture->height; if(cur->lpicture && cur->y_height < cur->lpicture->height+4) cur->y_height = cur->lpicture->height+4; y += cur->y_height; if(mr->width2 == 0) { cur->x2 = cur->x; } else { cur->x2 = mr->width -5 + mr->width0; } } mr->in_use = 0; mr->height = y+3; /* for Win95 look, was y+2 */ #ifndef NO_SAVEUNDERS valuemask = (CWBackPixel | CWEventMask | CWCursor | CWSaveUnder); #else valuemask = (CWBackPixel | CWEventMask | CWCursor); #endif attributes.background_pixel = Scr.MenuColors.back; attributes.event_mask = (ExposureMask | EnterWindowMask); attributes.cursor = Scr.FvwmCursors[MENU]; #ifndef NO_SAVEUNDERS attributes.save_under = TRUE; #endif if(mr->w != None) XDestroyWindow(dpy,mr->w); /* Side picture support: this makes the window wider for the picture and calculates offset */ mr->xoffset = 0; if(mr->sidePic) if(mr->sidePic->height < mr->height) { mr->xoffset = mr->sidePic->width + 3; } else { DestroyPicture(dpy, mr->sidePic); mr->sidePic = NULL; } mr->width = mr->width0 + mr->width + mr->width2 + mr->xoffset; mr->w = XCreateWindow (dpy, Scr.Root, 0, 0, (unsigned int) (mr->width), (unsigned int) mr->height, (unsigned int) 0, CopyFromParent, (unsigned int) InputOutput, (Visual *) CopyFromParent, valuemask, &attributes); XSaveContext(dpy,mr->w,MenuContext,(caddr_t)mr); return; } /*********************************************************************** * Procedure: * scanForHotkeys - Look for hotkey markers in a MenuItem * (pete@tecc.co.uk) * * Inputs: * it - MenuItem to scan * which - +1 to look in it->item1 and -1 to look in it->item2. * ***********************************************************************/ void scanForHotkeys(MenuItem *it, int which) { char *start, *txt; start = (which > 0) ? it->item : it->item2; /* Get start of string */ for (txt = start; *txt != '\0'; txt++) { /* Scan whole string */ if (*txt == '&') { /* A hotkey marker? */ if (txt[1] == '&') { /* Just an escaped & */ char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } /* It's a hot key marker - work out the offset value */ it->hotkey = (1 + (txt - start)) * which; for (; *txt != '\0'; txt++) txt[0] = txt[1]; /* Copy down.. */ return; /* Only one hotkey per item... */ } } it->hotkey = 0; /* No hotkey found. Set offset to zero */ } void scanForPixmap(char *instring, Picture **p, char identifier) { char *tstart, *txt, *save_instring; int i; Picture *pp; char name[100]; extern char *IconPath; #ifdef XPM extern char *PixmapPath; #endif /* save instring in case can't find pixmap */ save_instring = (char *)safemalloc(strlen(instring)+1); strcpy(save_instring,instring); /* Scan whole string */ for (txt = instring; *txt != '\0'; txt++) { /* A hotkey marker? */ if (*txt == identifier) { /* Just an escaped & */ if (txt[1] == identifier) { char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } /* It's a hot key marker - work out the offset value */ tstart = txt; txt++; i=0; while((*txt != identifier)&&(*txt != '\0')&&(i<99)) { name[i] = *txt; txt++; i++; } name[i] = 0; /* Next, check for a color pixmap */ #ifdef XPM pp=CachePicture(dpy,Scr.Root,IconPath,PixmapPath,name); #else pp=CachePicture(dpy,Scr.Root,IconPath,IconPath,name); #endif if (*txt != '\0') txt++; while(*txt != '\0') { *tstart++ = *txt++; } *tstart = 0; if(!pp) strcpy(instring,save_instring); else *p=pp; free(save_instring); return; } } } /* Side picture support: this scans for a color int the menu name for colorization */ void scanForColor(char *instring, Pixel *p, Bool *c, char identifier) { char *tstart, *txt, *save_instring; int i; char name[100]; *c = False; /* save instring in case can't find pixmap */ save_instring = (char *)safemalloc(strlen(instring)+1); strcpy(save_instring,instring); /* Scan whole string */ for (txt = instring; *txt != '\0'; txt++) { /* A hotkey marker? */ if (*txt == identifier) { /* Just an escaped '^' */ if (txt[1] == identifier) { char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } /* It's a hot key marker - work out the offset value */ tstart = txt; txt++; i=0; while((*txt != identifier)&&(*txt != '\0')&&(i<99)) { name[i] = *txt; txt++; i++; } name[i] = 0; *p = GetColor(name); *c = True; if(*txt != '\0')txt++; while(*txt != '\0') { *tstart++ = *txt++; } *tstart = 0; free(save_instring); return; } } } /*********************************************************************** * * Procedure: * AddToMenu - add an item to a root menu * * Returned Value: * (MenuItem *) * * Inputs: * menu - pointer to the root menu to add the item * item - the text to appear in the menu * action - the string to possibly execute * func - the numeric function * * ckh - need to add boolean to say whether or not to expand for pixmaps, * so built in window list can handle windows w/ * and % in title. * ***********************************************************************/ void AddToMenu(MenuRoot *menu, char *item, char *action) { MenuItem *tmp; char *start,*end; if(item == NULL) return; tmp = (MenuItem *)safemalloc(sizeof(MenuItem)); if (menu->first == NULL) { menu->first = tmp; tmp->prev = NULL; } else { menu->last->next = tmp; tmp->prev = menu->last; } menu->last = tmp; tmp->picture=NULL; tmp->lpicture=NULL; /* skip leading spaces */ /*while(isspace(*item)&&(item != NULL)) item++;*/ /* up to first tab goes in "item" field */ start = item; end=item; while((*end != '\t')&&(*end != 0)) end++; tmp->item = safemalloc(end-start+1); strncpy(tmp->item,start,end-start); tmp->item[end-start] = 0; tmp->item2 = NULL; if(*end=='\t') { start = end+1; while(*end != 0) end++; if(end-start != 0) { tmp->item2 = safemalloc(end-start+1); strncpy(tmp->item2,start,end-start); tmp->item2[end-start] = 0; } } if (item != (char *)0) { scanForPixmap(tmp->item,&tmp->picture,'*'); scanForPixmap(tmp->item,&tmp->lpicture,'%'); scanForHotkeys(tmp, 1); /* pete@tecc.co.uk */ tmp->strlen = strlen(tmp->item); } else tmp->strlen = 0; if (tmp->item2 != (char *)0) { if(!tmp->picture) scanForPixmap(tmp->item2,&tmp->picture,'*'); if(!tmp->lpicture) scanForPixmap(tmp->item2,&tmp->lpicture,'%'); if (tmp->hotkey == 0) scanForHotkeys(tmp, -1); /* pete@tecc.co.uk */ tmp->strlen2 = strlen(tmp->item2); } else tmp->strlen2 = 0; tmp->action = stripcpy(action); tmp->next = NULL; tmp->state = 0; tmp->func_type = find_func_type(tmp->action); tmp->item_num = menu->items++; } /*********************************************************************** * * Procedure: * NewMenuRoot - create a new menu root * * Returned Value: * (MenuRoot *) * * Inputs: * name - the name of the menu root * ***********************************************************************/ MenuRoot *NewMenuRoot(char *name, int function_or_popup) { MenuRoot *tmp; tmp = (MenuRoot *) safemalloc(sizeof(MenuRoot)); tmp->func = function_or_popup; tmp->name = stripcpy(name); /* Side picture support */ tmp->sidePic = NULL; scanForPixmap(tmp->name, &tmp->sidePic, '@'); scanForColor(tmp->name, &tmp->sideColor, &tmp->colorize, '^'); tmp->first = NULL; tmp->last = NULL; tmp->items = 0; tmp->width = 0; tmp->width2 = 0; tmp->w = None; tmp->next = Scr.AllMenus; Scr.AllMenus = tmp; return (tmp); } /*********************************************************************** * change by KitS@bartley.demon.co.uk to correct popups off title buttons * * Procedure: *ButtonPosition - find the actual position of the button * since some buttons may be disabled * * Returned Value: *The button count from left or right taking in to account *that some buttons may not be enabled for this window * * Inputs: * context - context as per the global Context * t - the window (FvwmWindow) to test against * ***********************************************************************/ int ButtonPosition(int context, FvwmWindow * t) { int i; int buttons = -1; if (context&C_RALL) { for(i=0;iright_w[i]) { buttons++; } /* is this the button ? */ if (((1<left_w[i]) { buttons++; } /* is this the button ? */ if (((1<