/* * Copyright 1988 by Evans & Sutherland Computer Corporation, * Salt Lake City, Utah * Portions Copyright 1989 by the Massachusetts Institute of Technology * Cambridge, Massachusetts * * Copyright 1992 Claude Lecommandeur. */ /*********************************************************************** * * $XConsortium: menus.c,v 1.186 91/07/17 13:58:00 dave Exp $ * * twm menu code * * 17-Nov-87 Thomas E. LaStrange File created * * Do the necessary modification to be integrated in ctwm. * Can no longer be used for the standard twm. * * 22-April-92 Claude Lecommandeur. * * ***********************************************************************/ #include "ctwm.h" #include #include #include #include #include "add_window.h" #include "colormaps.h" #include "drawing.h" #include "events.h" #include "functions.h" #include "functions_defs.h" #include "gram.tab.h" #include "iconmgr.h" #include "icons_builtin.h" #include "icons.h" #include "image.h" #include "list.h" #include "occupation.h" #include "otp.h" #include "screen.h" #ifdef SOUNDS # include "sound.h" #endif #include "util.h" #include "vscreen.h" #include "win_iconify.h" #include "win_resize.h" #include "win_utils.h" #include "workspace_manager.h" MenuRoot *ActiveMenu = NULL; /* the active menu */ MenuItem *ActiveItem = NULL; /* the active menu item */ bool menuFromFrameOrWindowOrTitlebar = false; char *CurrentSelectedWorkspace; /* Should probably move, since nothing in this file uses anymore */ int AlternateKeymap; bool AlternateContext; int MenuDepth = 0; /* number of menus up */ static struct { int x; int y; } MenuOrigins[MAXMENUDEPTH]; static bool addingdefaults = false; static void Paint3DEntry(MenuRoot *mr, MenuItem *mi, bool exposure); static void PaintNormalEntry(MenuRoot *mr, MenuItem *mi, bool exposure); static void DestroyMenu(MenuRoot *menu); #define SHADOWWIDTH 5 /* in pixels */ #define ENTRY_SPACING 4 /*********************************************************************** * * Procedure: * AddFuncKey - add a function key to the list * * Inputs: * name - the name of the key * cont - the context to look for the key press in * nmods - modifier keys that need to be pressed * func - the function to perform * win_name- the window name (if any) * action - the action string associated with the function (if any) * *********************************************************************** */ bool AddFuncKey(char *name, int cont, int nmods, int func, MenuRoot *menu, char *win_name, char *action) { FuncKey *tmp; KeySym keysym = NoSymbol; KeyCode keycode = 0; /* * Don't let a 0 keycode go through, since that means AnyKey to the * XGrabKey call in GrabKeys(). Conditionalize on dpy to handle * special cases where we don't have a server to talk to. */ keysym = XStringToKeysym(name); if(dpy) { keycode = XKeysymToKeycode(dpy, keysym); } if(keysym == NoSymbol || (dpy && keycode == 0)) { fprintf(stderr, "ignore %s key binding (%s)\n", name, keysym == NoSymbol ? "key symbol not found" : "key code not found"); return false; } /* see if there already is a key defined for this context */ for(tmp = Scr->FuncKeyRoot.next; tmp != NULL; tmp = tmp->next) { if(tmp->keysym == keysym && tmp->cont == cont && tmp->mods == nmods) { break; } } if(tmp == NULL) { tmp = malloc(sizeof(FuncKey)); tmp->next = Scr->FuncKeyRoot.next; Scr->FuncKeyRoot.next = tmp; } tmp->name = name; tmp->keysym = keysym; tmp->keycode = keycode; tmp->cont = cont; tmp->mods = nmods; tmp->func = func; tmp->menu = menu; tmp->win_name = win_name; tmp->action = action; return true; } /*********************************************************************** * * Procedure: * AddFuncButton - add a function button to the list * * Inputs: * num - the num of the button * cont - the context to look for the key press in * nmods - modifier keys that need to be pressed * func - the function to perform * menu - the menu (if any) * item - the menu item (if any) * *********************************************************************** */ void AddFuncButton(int num, int cont, int nmods, int func, MenuRoot *menu, MenuItem *item) { FuncButton *tmp; /* Find existing def for this button/context/modifier if any */ for(tmp = Scr->FuncButtonRoot.next; tmp != NULL; tmp = tmp->next) { if((tmp->num == num) && (tmp->cont == cont) && (tmp->mods == nmods)) { break; } } /* * If it's already set, and we're addingdefault (i.e., called from * AddDefaultFuncButtons()), just return. This lets us cram on * fallback mappings, without worrying about overriding user choices. */ if(tmp && addingdefaults) { return; } /* No mapping yet; create a shell */ if(tmp == NULL) { tmp = malloc(sizeof(FuncButton)); tmp->next = Scr->FuncButtonRoot.next; Scr->FuncButtonRoot.next = tmp; } /* Set the new details */ tmp->num = num; tmp->cont = cont; tmp->mods = nmods; tmp->func = func; tmp->menu = menu; tmp->item = item; return; } /* * AddDefaultFuncButtons - attach default bindings so that naive users * don't get messed up if they provide a minimal twmrc. * * This used to be in add_window.c, and maybe fits better in * decorations_init.c (only place it's called) now, but is currently here * so addingdefaults is in scope. * * XXX Probably better to adjust things so we can do that job _without_ * the magic global var... */ void AddDefaultFuncButtons(void) { addingdefaults = true; #define SETDEF(btn, ctx, func) AddFuncButton(btn, ctx, 0, func, NULL, NULL) SETDEF(Button1, C_TITLE, F_MOVE); SETDEF(Button1, C_ICON, F_ICONIFY); SETDEF(Button1, C_ICONMGR, F_ICONIFY); SETDEF(Button2, C_TITLE, F_RAISELOWER); SETDEF(Button2, C_ICON, F_ICONIFY); SETDEF(Button2, C_ICONMGR, F_ICONIFY); #undef SETDEF addingdefaults = false; } void PaintEntry(MenuRoot *mr, MenuItem *mi, bool exposure) { if(Scr->use3Dmenus) { Paint3DEntry(mr, mi, exposure); } else { PaintNormalEntry(mr, mi, exposure); } if(mi->state) { mr->lastactive = mi; } } static void Paint3DEntry(MenuRoot *mr, MenuItem *mi, bool exposure) { int y_offset; int text_y; GC gc; XRectangle ink_rect, logical_rect; XmbTextExtents(Scr->MenuFont.font_set, mi->item, mi->strlen, &ink_rect, &logical_rect); y_offset = mi->item_num * Scr->EntryHeight + Scr->MenuShadowDepth; text_y = y_offset + (Scr->EntryHeight - logical_rect.height) / 2 - logical_rect.y; if(mi->func != F_TITLE) { int x, y; gc = Scr->NormalGC; if(mi->state) { Draw3DBorder(mr->w, Scr->MenuShadowDepth, y_offset, mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight, 1, mi->highlight, off, true, false); FB(mi->highlight.fore, mi->highlight.back); XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, gc, mi->x + Scr->MenuShadowDepth, text_y, mi->item, mi->strlen); } else { if(mi->user_colors || !exposure) { XSetForeground(dpy, gc, mi->normal.back); XFillRectangle(dpy, mr->w, gc, Scr->MenuShadowDepth, y_offset, mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight); FB(mi->normal.fore, mi->normal.back); } else { gc = Scr->MenuGC; } XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, gc, mi->x + Scr->MenuShadowDepth, text_y, mi->item, mi->strlen); if(mi->separated) { FB(Scr->MenuC.shadd, Scr->MenuC.shadc); XDrawLine(dpy, mr->w, Scr->NormalGC, Scr->MenuShadowDepth, y_offset + Scr->EntryHeight - 2, mr->width - Scr->MenuShadowDepth, y_offset + Scr->EntryHeight - 2); FB(Scr->MenuC.shadc, Scr->MenuC.shadd); XDrawLine(dpy, mr->w, Scr->NormalGC, Scr->MenuShadowDepth, y_offset + Scr->EntryHeight - 1, mr->width - Scr->MenuShadowDepth, y_offset + Scr->EntryHeight - 1); } } if(mi->func == F_MENU) { /* create the pull right pixmap if needed */ if(Scr->pullPm == None) { Scr->pullPm = Create3DMenuIcon(Scr->EntryHeight - ENTRY_SPACING, &Scr->pullW, &Scr->pullH, Scr->MenuC); } x = mr->width - Scr->pullW - Scr->MenuShadowDepth - 2; y = y_offset + ((Scr->EntryHeight - ENTRY_SPACING - Scr->pullH) / 2) + 2; XCopyArea(dpy, Scr->pullPm, mr->w, gc, 0, 0, Scr->pullW, Scr->pullH, x, y); } } else { Draw3DBorder(mr->w, Scr->MenuShadowDepth, y_offset, mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight, 1, mi->normal, off, true, false); FB(mi->normal.fore, mi->normal.back); XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC, mi->x + 2, text_y, mi->item, mi->strlen); } } static void PaintNormalEntry(MenuRoot *mr, MenuItem *mi, bool exposure) { int y_offset; int text_y; GC gc; XRectangle ink_rect, logical_rect; XmbTextExtents(Scr->MenuFont.font_set, mi->item, mi->strlen, &ink_rect, &logical_rect); y_offset = mi->item_num * Scr->EntryHeight; text_y = y_offset + (Scr->EntryHeight - logical_rect.height) / 2 - logical_rect.y; if(mi->func != F_TITLE) { int x, y; if(mi->state) { XSetForeground(dpy, Scr->NormalGC, mi->highlight.back); XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset, mr->width, Scr->EntryHeight); FB(mi->highlight.fore, mi->highlight.back); XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC, mi->x, text_y, mi->item, mi->strlen); gc = Scr->NormalGC; } else { if(mi->user_colors || !exposure) { XSetForeground(dpy, Scr->NormalGC, mi->normal.back); XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset, mr->width, Scr->EntryHeight); FB(mi->normal.fore, mi->normal.back); gc = Scr->NormalGC; } else { gc = Scr->MenuGC; } XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, gc, mi->x, text_y, mi->item, mi->strlen); if(mi->separated) XDrawLine(dpy, mr->w, gc, 0, y_offset + Scr->EntryHeight - 1, mr->width, y_offset + Scr->EntryHeight - 1); } if(mi->func == F_MENU) { /* create the pull right pixmap if needed */ if(Scr->pullPm == None) { Scr->pullPm = CreateMenuIcon(Scr->MenuFont.height, &Scr->pullW, &Scr->pullH); } x = mr->width - Scr->pullW - 5; y = y_offset + ((Scr->MenuFont.height - Scr->pullH) / 2); XCopyPlane(dpy, Scr->pullPm, mr->w, gc, 0, 0, Scr->pullW, Scr->pullH, x, y, 1); } } else { int y; XSetForeground(dpy, Scr->NormalGC, mi->normal.back); /* fill the rectangle with the title background color */ XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset, mr->width, Scr->EntryHeight); { XSetForeground(dpy, Scr->NormalGC, mi->normal.fore); /* now draw the dividing lines */ if(y_offset) XDrawLine(dpy, mr->w, Scr->NormalGC, 0, y_offset, mr->width, y_offset); y = ((mi->item_num + 1) * Scr->EntryHeight) - 1; XDrawLine(dpy, mr->w, Scr->NormalGC, 0, y, mr->width, y); } FB(mi->normal.fore, mi->normal.back); /* finally render the title */ XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC, mi->x, text_y, mi->item, mi->strlen); } } void PaintMenu(MenuRoot *mr, XEvent *e) { MenuItem *mi; if(Scr->use3Dmenus) { Draw3DBorder(mr->w, 0, 0, mr->width, mr->height, Scr->MenuShadowDepth, Scr->MenuC, off, false, false); } for(mi = mr->first; mi != NULL; mi = mi->next) { int y_offset = mi->item_num * Scr->EntryHeight; /* be smart about handling the expose, redraw only the entries * that we need to */ if(e->xexpose.y <= (y_offset + Scr->EntryHeight) && (e->xexpose.y + e->xexpose.height) >= y_offset) { PaintEntry(mr, mi, true); } } XSync(dpy, 0); } void MakeWorkspacesMenu(void) { static char **actions = NULL; WorkSpace *wlist; char **act; if(! Scr->Workspaces) { return; } AddToMenu(Scr->Workspaces, "TWM Workspaces", NULL, NULL, F_TITLE, NULL, NULL); if(! actions) { int count = 0; for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL; wlist = wlist->next) { count++; } count++; actions = calloc(count, sizeof(char *)); act = actions; for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL; wlist = wlist->next) { asprintf(act, "WGOTO : %s", wlist->name); act++; } *act = NULL; } act = actions; for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL; wlist = wlist->next) { AddToMenu(Scr->Workspaces, wlist->name, *act, Scr->Windows, F_MENU, NULL, NULL); act++; } Scr->Workspaces->pinned = false; MakeMenu(Scr->Workspaces); } static bool fromMenu; bool cur_fromMenu(void) { return fromMenu; } void UpdateMenu(void) { MenuItem *mi; int i, x, y, x_root, y_root, entry; bool done; MenuItem *badItem = NULL; fromMenu = true; while(1) { /* block until there is an event */ if(!menuFromFrameOrWindowOrTitlebar) { XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | ExposureMask | VisibilityChangeMask | LeaveWindowMask | ButtonMotionMask, &Event); } if(Event.type == MotionNotify) { /* discard any extra motion events before a release */ while(XCheckMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask, &Event)) if(Event.type == ButtonRelease) { break; } } if(!DispatchEvent()) { continue; } if((! ActiveMenu) || Cancel) { menuFromFrameOrWindowOrTitlebar = false; fromMenu = false; return; } if(Event.type != MotionNotify) { continue; } done = false; XQueryPointer(dpy, ActiveMenu->w, &JunkRoot, &JunkChild, &x_root, &y_root, &x, &y, &JunkMask); /* if we haven't received the enter notify yet, wait */ if(!ActiveMenu->entered) { continue; } XFindContext(dpy, ActiveMenu->w, ScreenContext, (XPointer *)&Scr); if(x < 0 || y < 0 || x >= ActiveMenu->width || y >= ActiveMenu->height) { if(ActiveItem && ActiveItem->func != F_TITLE) { ActiveItem->state = false; PaintEntry(ActiveMenu, ActiveItem, false); } ActiveItem = NULL; continue; } /* look for the entry that the mouse is in */ entry = y / Scr->EntryHeight; for(i = 0, mi = ActiveMenu->first; mi != NULL; i++, mi = mi->next) { if(i == entry) { break; } } /* if there is an active item, we might have to turn it off */ if(ActiveItem) { /* is the active item the one we are on ? */ if(ActiveItem->item_num == entry && ActiveItem->state) { done = true; } /* if we weren't on the active entry, let's turn the old * active one off */ if(!done && ActiveItem->func != F_TITLE) { ActiveItem->state = false; PaintEntry(ActiveMenu, ActiveItem, false); } } /* if we weren't on the active item, change the active item and turn * it on */ if(!done) { ActiveItem = mi; if(ActiveItem && ActiveItem->func != F_TITLE && !ActiveItem->state) { ActiveItem->state = true; PaintEntry(ActiveMenu, ActiveItem, false); } } /* now check to see if we were over the arrow of a pull right entry */ if(ActiveItem && ActiveItem->func == F_MENU && ((ActiveMenu->width - x) < (ActiveMenu->width / 3))) { MenuRoot *save = ActiveMenu; int savex = MenuOrigins[MenuDepth - 1].x; int savey = MenuOrigins[MenuDepth - 1].y; if(MenuDepth < MAXMENUDEPTH) { if(ActiveMenu == Scr->Workspaces) { CurrentSelectedWorkspace = ActiveItem->item; } PopUpMenu(ActiveItem->sub, (savex + (((2 * ActiveMenu->width) / 3) - 1)), (savey + ActiveItem->item_num * Scr->EntryHeight) /*(savey + ActiveItem->item_num * Scr->EntryHeight + (Scr->EntryHeight >> 1))*/, False); CurrentSelectedWorkspace = NULL; } else if(!badItem) { XBell(dpy, 0); badItem = ActiveItem; } /* if the menu did get popped up, unhighlight the active item */ if(save != ActiveMenu && ActiveItem->state) { ActiveItem->state = false; PaintEntry(save, ActiveItem, false); ActiveItem = NULL; } } if(badItem != ActiveItem) { badItem = NULL; } XFlush(dpy); } } /*********************************************************************** * * Procedure: * NewMenuRoot - create a new menu root * * Returned Value: * (MenuRoot *) * * Inputs: * name - the name of the menu root * *********************************************************************** */ MenuRoot *NewMenuRoot(char *name) { MenuRoot *tmp; #define UNUSED_PIXEL ((unsigned long) (~0)) /* more than 24 bits */ tmp = malloc(sizeof(MenuRoot)); tmp->highlight.fore = UNUSED_PIXEL; tmp->highlight.back = UNUSED_PIXEL; tmp->name = name; tmp->prev = NULL; tmp->first = NULL; tmp->last = NULL; tmp->defaultitem = NULL; tmp->items = 0; tmp->width = 0; tmp->mapped = MRM_NEVER; tmp->pull = false; tmp->w = None; tmp->shadow = None; tmp->real_menu = false; if(Scr->MenuList == NULL) { Scr->MenuList = tmp; Scr->MenuList->next = NULL; } if(Scr->LastMenu == NULL) { Scr->LastMenu = tmp; Scr->LastMenu->next = NULL; } else { Scr->LastMenu->next = tmp; Scr->LastMenu = tmp; Scr->LastMenu->next = NULL; } if(strcmp(name, TWM_WINDOWS) == 0) { Scr->Windows = tmp; } if(strcmp(name, TWM_ICONS) == 0) { Scr->Icons = tmp; } if(strcmp(name, TWM_WORKSPACES) == 0) { Scr->Workspaces = tmp; if(!Scr->Windows) { NewMenuRoot(TWM_WINDOWS); } } if(strcmp(name, TWM_ALLWINDOWS) == 0) { Scr->AllWindows = tmp; } /* Added by dl 2004 */ if(strcmp(name, TWM_ALLICONS) == 0) { Scr->AllIcons = tmp; } /* Added by Dan Lilliehorn (dl@dl.nu) 2000-02-29 */ if(strcmp(name, TWM_KEYS) == 0) { Scr->Keys = tmp; } if(strcmp(name, TWM_VISIBLE) == 0) { Scr->Visible = tmp; } /* End addition */ return (tmp); } /*********************************************************************** * * 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 * sub - the menu root if it is a pull-right entry * func - the numeric function * fore - foreground color string * back - background color string * *********************************************************************** */ MenuItem *AddToMenu(MenuRoot *menu, char *item, char *action, MenuRoot *sub, int func, char *fore, char *back) { MenuItem *tmp; int width; char *itemname; XRectangle ink_rect; XRectangle logical_rect; #ifdef DEBUG_MENUS fprintf(stderr, "adding menu item=\"%s\", action=%s, sub=%d, f=%d\n", item, action, sub, func); #endif tmp = malloc(sizeof(MenuItem)); tmp->root = menu; if(menu->first == NULL) { menu->first = tmp; tmp->prev = NULL; } else { menu->last->next = tmp; tmp->prev = menu->last; } menu->last = tmp; if((menu == Scr->Workspaces) || (menu == Scr->Windows) || (menu == Scr->Icons) || (menu == Scr->AllWindows) || /* Added by dl 2004 */ (menu == Scr->AllIcons) || /* Added by Dan Lillehorn (dl@dl.nu) 2000-02-29 */ (menu == Scr->Keys) || (menu == Scr->Visible)) { itemname = item; } else if(*item == '*') { itemname = item + 1; menu->defaultitem = tmp; } else { itemname = item; } tmp->item = itemname; tmp->strlen = strlen(itemname); tmp->action = action; tmp->next = NULL; tmp->sub = NULL; tmp->state = false; tmp->func = func; tmp->separated = false; if(!Scr->HaveFonts) { CreateFonts(Scr); } if(dpy) { XmbTextExtents(Scr->MenuFont.font_set, itemname, tmp->strlen, &ink_rect, &logical_rect); width = logical_rect.width; } else { // Fake for non-dpy cases width = 25; } if(width <= 0) { width = 1; } if(width > menu->width) { menu->width = width; } tmp->user_colors = false; if(Scr->Monochrome == COLOR && fore != NULL) { bool save; save = Scr->FirstTime; Scr->FirstTime = true; GetColor(COLOR, &tmp->normal.fore, fore); GetColor(COLOR, &tmp->normal.back, back); if(Scr->use3Dmenus && !Scr->BeNiceToColormap) { GetShadeColors(&tmp->normal); } Scr->FirstTime = save; tmp->user_colors = true; } if(sub != NULL) { tmp->sub = sub; menu->pull = true; } tmp->item_num = menu->items++; return (tmp); } void MakeMenus(void) { MenuRoot *mr; for(mr = Scr->MenuList; mr != NULL; mr = mr->next) { if(mr->real_menu == false) { continue; } mr->pinned = false; MakeMenu(mr); } } void MakeMenu(MenuRoot *mr) { MenuItem *start, *tmp; XColor f1, f2, f3; XColor b1, b2, b3; XColor save_fore, save_back; int fred, fgreen, fblue; int bred, bgreen, bblue; int width, borderwidth; unsigned long valuemask; XSetWindowAttributes attributes; Colormap cmap = Scr->RootColormaps.cwins[0]->colormap->c; XRectangle ink_rect; XRectangle logical_rect; Scr->EntryHeight = Scr->MenuFont.height + 4; /* lets first size the window accordingly */ if(mr->mapped == MRM_NEVER) { int max_entry_height = 0; if(mr->pull == true) { mr->width += 16 + 10; } width = mr->width + 10; for(MenuItem *cur = mr->first; cur != NULL; cur = cur->next) { XmbTextExtents(Scr->MenuFont.font_set, cur->item, cur->strlen, &ink_rect, &logical_rect); max_entry_height = MAX(max_entry_height, logical_rect.height); if(cur->func != F_TITLE) { cur->x = 5; } else { cur->x = width - logical_rect.width; cur->x /= 2; } } Scr->EntryHeight = max_entry_height + ENTRY_SPACING; mr->height = mr->items * Scr->EntryHeight; mr->width += 10; if(Scr->use3Dmenus) { mr->width += 2 * Scr->MenuShadowDepth; mr->height += 2 * Scr->MenuShadowDepth; } if(Scr->Shadow && ! mr->pinned) { /* * Make sure that you don't draw into the shadow window or else * the background bits there will get saved */ valuemask = (CWBackPixel | CWBorderPixel); attributes.background_pixel = Scr->MenuShadowColor; attributes.border_pixel = Scr->MenuShadowColor; if(Scr->SaveUnder) { valuemask |= CWSaveUnder; attributes.save_under = True; } mr->shadow = XCreateWindow(dpy, Scr->Root, 0, 0, mr->width, mr->height, 0, CopyFromParent, CopyFromParent, CopyFromParent, valuemask, &attributes); } valuemask = (CWBackPixel | CWBorderPixel | CWEventMask); attributes.background_pixel = Scr->MenuC.back; attributes.border_pixel = Scr->MenuC.fore; if(mr->pinned) { attributes.event_mask = (ExposureMask | EnterWindowMask | LeaveWindowMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonMotionMask ); attributes.cursor = Scr->MenuCursor; valuemask |= CWCursor; } else { attributes.event_mask = (ExposureMask | EnterWindowMask); } if(Scr->SaveUnder && ! mr->pinned) { valuemask |= CWSaveUnder; attributes.save_under = True; } if(Scr->BackingStore) { valuemask |= CWBackingStore; attributes.backing_store = Always; } borderwidth = Scr->use3Dmenus ? 0 : 1; mr->w = XCreateWindow(dpy, Scr->Root, 0, 0, mr->width, mr->height, borderwidth, CopyFromParent, CopyFromParent, CopyFromParent, valuemask, &attributes); XSaveContext(dpy, mr->w, MenuContext, (XPointer)mr); XSaveContext(dpy, mr->w, ScreenContext, (XPointer)Scr); mr->mapped = MRM_UNMAPPED; } if(Scr->use3Dmenus && (Scr->Monochrome == COLOR) && (mr->highlight.back == UNUSED_PIXEL)) { XColor xcol; char colname [32]; bool save; xcol.pixel = Scr->MenuC.back; XQueryColor(dpy, cmap, &xcol); sprintf(colname, "#%04x%04x%04x", 5 * ((int)xcol.red / 6), 5 * ((int)xcol.green / 6), 5 * ((int)xcol.blue / 6)); save = Scr->FirstTime; Scr->FirstTime = true; GetColor(Scr->Monochrome, &mr->highlight.back, colname); Scr->FirstTime = save; } if(Scr->use3Dmenus && (Scr->Monochrome == COLOR) && (mr->highlight.fore == UNUSED_PIXEL)) { XColor xcol; char colname [32]; bool save; xcol.pixel = Scr->MenuC.fore; XQueryColor(dpy, cmap, &xcol); sprintf(colname, "#%04x%04x%04x", 5 * ((int)xcol.red / 6), 5 * ((int)xcol.green / 6), 5 * ((int)xcol.blue / 6)); save = Scr->FirstTime; Scr->FirstTime = true; GetColor(Scr->Monochrome, &mr->highlight.fore, colname); Scr->FirstTime = save; } if(Scr->use3Dmenus && !Scr->BeNiceToColormap) { GetShadeColors(&mr->highlight); } /* get the default colors into the menus */ for(tmp = mr->first; tmp != NULL; tmp = tmp->next) { if(!tmp->user_colors) { if(tmp->func != F_TITLE) { tmp->normal.fore = Scr->MenuC.fore; tmp->normal.back = Scr->MenuC.back; } else { tmp->normal.fore = Scr->MenuTitleC.fore; tmp->normal.back = Scr->MenuTitleC.back; } } if(mr->highlight.fore != UNUSED_PIXEL) { tmp->highlight.fore = mr->highlight.fore; tmp->highlight.back = mr->highlight.back; } else { tmp->highlight.fore = tmp->normal.back; tmp->highlight.back = tmp->normal.fore; } if(Scr->use3Dmenus && !Scr->BeNiceToColormap) { if(tmp->func != F_TITLE) { GetShadeColors(&tmp->highlight); } else { GetShadeColors(&tmp->normal); } } } mr->pmenu = NULL; if(Scr->Monochrome == MONOCHROME || !Scr->InterpolateMenuColors) { return; } // Do InterpolateMenuColors magic start = mr->first; while(1) { for(; start != NULL; start = start->next) { if(start->user_colors) { break; } } if(start == NULL) { break; } MenuItem *end; for(end = start->next; end != NULL; end = end->next) { if(end->user_colors) { break; } } if(end == NULL) { break; } /* we have a start and end to interpolate between */ int num = end->item_num - start->item_num; f1.pixel = start->normal.fore; XQueryColor(dpy, cmap, &f1); f2.pixel = end->normal.fore; XQueryColor(dpy, cmap, &f2); b1.pixel = start->normal.back; XQueryColor(dpy, cmap, &b1); b2.pixel = end->normal.back; XQueryColor(dpy, cmap, &b2); fred = ((int)f2.red - (int)f1.red) / num; fgreen = ((int)f2.green - (int)f1.green) / num; fblue = ((int)f2.blue - (int)f1.blue) / num; bred = ((int)b2.red - (int)b1.red) / num; bgreen = ((int)b2.green - (int)b1.green) / num; bblue = ((int)b2.blue - (int)b1.blue) / num; f3 = f1; f3.flags = DoRed | DoGreen | DoBlue; b3 = b1; b3.flags = DoRed | DoGreen | DoBlue; start->highlight.back = start->normal.fore; start->highlight.fore = start->normal.back; num -= 1; int i = 0; MenuItem *cur = start->next; // XXX Should be impossible to run out of cur's before num's, // unless the item_num's are wrong (which would break other // stuff), but add condition to quiet static analysis. for(; cur != NULL && i < num ; i++, cur = cur->next) { f3.red += fred; f3.green += fgreen; f3.blue += fblue; save_fore = f3; b3.red += bred; b3.green += bgreen; b3.blue += bblue; save_back = b3; XAllocColor(dpy, cmap, &f3); XAllocColor(dpy, cmap, &b3); cur->highlight.back = cur->normal.fore = f3.pixel; cur->highlight.fore = cur->normal.back = b3.pixel; cur->user_colors = true; f3 = save_fore; b3 = save_back; } start = end; start->highlight.back = start->normal.fore; start->highlight.fore = start->normal.back; } return; } /*********************************************************************** * * 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, bool center) { int WindowNameCount; TwmWindow **WindowNames; TwmWindow *tmp_win2, *tmp_win3; int i; bool clipped; if(!menu) { return false; } InstallRootColormap(); if((menu == Scr->Windows) || (menu == Scr->Icons) || (menu == Scr->AllWindows) || /* Added by Dan 'dl' Lilliehorn 040607 */ (menu == Scr->AllIcons) || /* Added by Dan Lilliehorn (dl@dl.nu) 2000-02-29 */ (menu == Scr->Visible)) { TwmWindow *tmp_win; WorkSpace *ws; bool all, icons, visible_, allicons; /* visible, allicons: Added by dl */ int func; /* this is the twm windows menu, let's go ahead and build it */ all = (menu == Scr->AllWindows); icons = (menu == Scr->Icons); visible_ = (menu == Scr->Visible); /* Added by dl */ allicons = (menu == Scr->AllIcons); DestroyMenu(menu); menu->first = NULL; menu->last = NULL; menu->items = 0; menu->width = 0; menu->mapped = MRM_NEVER; menu->highlight.fore = UNUSED_PIXEL; menu->highlight.back = UNUSED_PIXEL; if(menu == Scr->Windows) { AddToMenu(menu, "TWM Windows", NULL, NULL, F_TITLE, NULL, NULL); } else if(menu == Scr->Icons) { AddToMenu(menu, "TWM Icons", NULL, NULL, F_TITLE, NULL, NULL); } else if(menu == Scr->Visible) { /* Added by dl 2000 */ AddToMenu(menu, "TWM Visible", NULL, NULL, F_TITLE, NULL, NULL); } else if(menu == Scr->AllIcons) { /* Added by dl 2004 */ AddToMenu(menu, "TWM All Icons", NULL, NULL, F_TITLE, NULL, NULL); } else { AddToMenu(menu, "TWM All Windows", NULL, NULL, F_TITLE, NULL, NULL); } ws = NULL; if(!(all || allicons) && CurrentSelectedWorkspace && Scr->workSpaceManagerActive) { for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { if(strcmp(ws->name, CurrentSelectedWorkspace) == 0) { break; } } } if(!Scr->currentvs) { return false; } if(!ws) { ws = Scr->currentvs->wsw->currentwspc; } for(tmp_win = Scr->FirstWindow, WindowNameCount = 0; tmp_win != NULL; tmp_win = tmp_win->next) { if(tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win) { continue; } if(Scr->ShortAllWindowsMenus && (tmp_win->iswspmgr || tmp_win->isiconmgr)) { continue; } if(!(all || allicons) && !OCCUPY(tmp_win, ws)) { continue; } if(allicons && !tmp_win->isicon) { continue; } if(icons && !tmp_win->isicon) { continue; } if(visible_ && tmp_win->isicon) { continue; /* added by dl */ } WindowNameCount++; } // Hack: always pretend there's at least one window, even if // there are none; that lets us skip special cases for empty // lists... if(WindowNameCount == 0) { WindowNameCount = 1; } WindowNames = calloc(WindowNameCount, sizeof(TwmWindow *)); WindowNameCount = 0; for(tmp_win = Scr->FirstWindow; tmp_win != NULL; tmp_win = tmp_win->next) { if(LookInList(Scr->IconMenuDontShow, tmp_win->name, &tmp_win->class)) { continue; } if(tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win) { continue; } if(Scr->ShortAllWindowsMenus && tmp_win == Scr->currentvs->wsw->twm_win) { continue; } if(Scr->ShortAllWindowsMenus && tmp_win->isiconmgr) { continue; } if(!(all || allicons) && ! OCCUPY(tmp_win, ws)) { continue; } if(allicons && !tmp_win->isicon) { continue; } if(icons && !tmp_win->isicon) { continue; } if(visible_ && tmp_win->isicon) { continue; /* added by dl */ } tmp_win2 = tmp_win; for(i = 0; i < WindowNameCount; i++) { int compresult; char *tmpname1, *tmpname2; tmpname1 = tmp_win2->name; tmpname2 = WindowNames[i]->name; if(Scr->CaseSensitive) { compresult = strcmp(tmpname1, tmpname2); } else { compresult = strcasecmp(tmpname1, tmpname2); } if(compresult < 0) { tmp_win3 = tmp_win2; tmp_win2 = WindowNames[i]; WindowNames[i] = tmp_win3; } } WindowNames[WindowNameCount] = tmp_win2; WindowNameCount++; } func = (all || allicons || CurrentSelectedWorkspace) ? F_WINWARP : F_POPUP; for(i = 0; i < WindowNameCount; i++) { char *tmpname; tmpname = WindowNames[i]->name; AddToMenu(menu, tmpname, (char *)WindowNames[i], NULL, func, NULL, NULL); } free(WindowNames); menu->pinned = false; MakeMenu(menu); } /* Keys added by dl */ if(menu == Scr->Keys) { char *oldact = 0; int oldmod = 0; DestroyMenu(menu); menu->first = NULL; menu->last = NULL; menu->items = 0; menu->width = 0; menu->mapped = MRM_NEVER; menu->highlight.fore = UNUSED_PIXEL; menu->highlight.back = UNUSED_PIXEL; AddToMenu(menu, "Twm Keys", NULL, NULL, F_TITLE, NULL, NULL); for(const FuncKey *tmpKey = Scr->FuncKeyRoot.next; tmpKey != NULL; tmpKey = tmpKey->next) { char *tmpStr; if(tmpKey->func != F_EXEC) { continue; } if((tmpKey->action == oldact) && (tmpKey->mods == oldmod)) { continue; } tmpStr = mk_twmkeys_entry(tmpKey); if(tmpStr == NULL) { tmpStr = strdup("(error)"); } AddToMenu(menu, tmpStr, tmpKey->action, NULL, tmpKey->func, NULL, NULL); oldact = tmpKey->action; oldmod = tmpKey->mods; } menu->pinned = false; MakeMenu(menu); } if(menu->w == None || menu->items == 0) { return false; } /* Prevent recursively bringing up menus. */ if((!menu->pinned) && (menu->mapped == MRM_MAPPED)) { return false; } /* * Dynamically set the parent; this allows pull-ups to also be main * menus, or to be brought up from more than one place. */ menu->prev = ActiveMenu; if(menu->pinned) { ActiveMenu = menu; menu->mapped = MRM_MAPPED; menu->entered = true; MenuOrigins [MenuDepth].x = menu->x; MenuOrigins [MenuDepth].y = menu->y; MenuDepth++; XRaiseWindow(dpy, menu->w); return true; } XGrabPointer(dpy, Scr->Root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonMotionMask | PointerMotionHintMask, GrabModeAsync, GrabModeAsync, Scr->Root, Scr->MenuCursor, CurrentTime); XGrabKeyboard(dpy, Scr->Root, True, GrabModeAsync, GrabModeAsync, CurrentTime); ActiveMenu = menu; menu->mapped = MRM_MAPPED; menu->entered = false; if(center) { x -= (menu->width / 2); y -= (Scr->EntryHeight / 2); /* sticky menus would be nice here */ } /* * clip to screen */ clipped = ConstrainByLayout(Scr->Layout, -1, &x, menu->width, &y, menu->height); MenuOrigins[MenuDepth].x = x; MenuOrigins[MenuDepth].y = y; MenuDepth++; /* * Position and display the menu, and its shadow if it has one. We * start by positioning and raising (above everything else on screen) * the shadow. Then position the menu itself, raise it up above * that, and map it. Then map the shadow; doing that after raising * and mapping the menu avoids spending time drawing the bulk of the * window which the menu covers up anyway. */ if(Scr->Shadow) { XMoveWindow(dpy, menu->shadow, x + SHADOWWIDTH, y + SHADOWWIDTH); XRaiseWindow(dpy, menu->shadow); } XMoveWindow(dpy, menu->w, x, y); XMapRaised(dpy, menu->w); if(Scr->Shadow) { XMapWindow(dpy, menu->shadow); } /* Move mouse pointer if we're supposed to */ if(!Scr->NoWarpToMenuTitle && clipped && center) { const int xl = x + (menu->width / 2); const int yt = y + (Scr->EntryHeight / 2); XWarpPointer(dpy, Scr->Root, Scr->Root, x, y, menu->width, menu->height, xl, yt); } XSync(dpy, 0); return true; } /*********************************************************************** * * Procedure: * PopDownMenu - unhighlight the current menu selection and * take down the menus * *********************************************************************** */ void PopDownMenu(void) { MenuRoot *tmp; if(ActiveMenu == NULL) { return; } if(ActiveItem) { ActiveItem->state = false; PaintEntry(ActiveMenu, ActiveItem, false); } for(tmp = ActiveMenu; tmp != NULL; tmp = tmp->prev) { if(! tmp->pinned) { HideMenu(tmp); } UninstallRootColormap(); } XFlush(dpy); ActiveMenu = NULL; ActiveItem = NULL; MenuDepth = 0; XUngrabKeyboard(dpy, CurrentTime); if(Context == C_WINDOW || Context == C_FRAME || Context == C_TITLE || Context == C_ICON) { menuFromFrameOrWindowOrTitlebar = true; } return; } void HideMenu(MenuRoot *menu) { if(!menu) { return; } if(Scr->Shadow) { XUnmapWindow(dpy, menu->shadow); } XUnmapWindow(dpy, menu->w); menu->mapped = MRM_UNMAPPED; } /*********************************************************************** * * Procedure: * FindMenuRoot - look for a menu root * * Returned Value: * (MenuRoot *) - a pointer to the menu root structure * * Inputs: * name - the name of the menu root * *********************************************************************** */ MenuRoot *FindMenuRoot(char *name) { MenuRoot *tmp; for(tmp = Scr->MenuList; tmp != NULL; tmp = tmp->next) { if(strcmp(name, tmp->name) == 0) { return (tmp); } } return NULL; } static void DestroyMenu(MenuRoot *menu) { MenuItem *item; if(menu->w) { XDeleteContext(dpy, menu->w, MenuContext); XDeleteContext(dpy, menu->w, ScreenContext); if(Scr->Shadow) { XDestroyWindow(dpy, menu->shadow); } XDestroyWindow(dpy, menu->w); } for(item = menu->first; item;) { MenuItem *tmp = item; item = item->next; free(tmp); } } void MoveMenu(XEvent *eventp) { int XW, YW, newX, newY; bool cont; bool newev; unsigned long event_mask; XEvent ev; if(! ActiveMenu) { return; } if(! ActiveMenu->pinned) { return; } XW = eventp->xbutton.x_root - ActiveMenu->x; YW = eventp->xbutton.y_root - ActiveMenu->y; XGrabPointer(dpy, ActiveMenu->w, True, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, Scr->MoveCursor, CurrentTime); newX = ActiveMenu->x; newY = ActiveMenu->y; cont = true; event_mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask | ExposureMask; XMaskEvent(dpy, event_mask, &ev); while(cont) { ev.xbutton.x_root -= Scr->rootx; ev.xbutton.y_root -= Scr->rooty; switch(ev.xany.type) { case ButtonRelease : cont = false; case MotionNotify : if(!cont) { newev = false; while(XCheckMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask, &ev)) { newev = true; if(ev.type == ButtonRelease) { break; } } if(ev.type == ButtonRelease) { continue; } if(newev) { ev.xbutton.x_root -= Scr->rootx; ev.xbutton.y_root -= Scr->rooty; } } newX = ev.xbutton.x_root - XW; newY = ev.xbutton.y_root - YW; if(Scr->DontMoveOff) { ConstrainByBorders1(&newX, ActiveMenu->width, &newY, ActiveMenu->height); } XMoveWindow(dpy, ActiveMenu->w, newX, newY); XMaskEvent(dpy, event_mask, &ev); break; case ButtonPress : cont = false; newX = ActiveMenu->x; newY = ActiveMenu->y; break; case Expose: case NoExpose: Event = ev; DispatchEvent(); XMaskEvent(dpy, event_mask, &ev); break; } } XUngrabPointer(dpy, CurrentTime); if(ev.xany.type == ButtonRelease) { ButtonPressed = -1; } /*XPutBackEvent (dpy, &ev);*/ XMoveWindow(dpy, ActiveMenu->w, newX, newY); ActiveMenu->x = newX; ActiveMenu->y = newY; MenuOrigins [MenuDepth - 1].x = newX; MenuOrigins [MenuDepth - 1].y = newY; return; } void WarpCursorToDefaultEntry(MenuRoot *menu) { MenuItem *item; Window root; int i, x, y, xl, yt; unsigned int w, h, bw, d; for(i = 0, item = menu->first; item != menu->last; item = item->next) { if(item == menu->defaultitem) { break; } i++; } if(!XGetGeometry(dpy, menu->w, &root, &x, &y, &w, &h, &bw, &d)) { return; } xl = x + (menu->width / 2); yt = y + (i + 0.5) * Scr->EntryHeight; XWarpPointer(dpy, Scr->Root, Scr->Root, Event.xbutton.x_root, Event.xbutton.y_root, menu->width, menu->height, xl, yt); } /** * Generate up a string representation of a keybinding->action. * Internally used in generating TwmKeys menu. */ char * mk_twmkeys_entry(const FuncKey *key) { char *ret; // S+ C+ 5(Mx+) 5(Ax+) #define MSLEN (2 + 2 + 2 + 5 * 3 + 5 * 3 + 1 + 26) char modStr[MSLEN + 1]; char *modStrCur = modStr; // Init *modStrCur = '\0'; // Check and add prefixes for each modifier #define DO(mask, str) do { \ if(key->mods & mask##Mask) { \ const int tslen = sizeof(str) - 1; \ if((modStrCur - modStr + tslen) >= MSLEN) { \ fprintf(stderr, "BUG: No space to add '%s' " \ "in %s()\n", str, __func__); \ return NULL; \ } \ strcpy(modStrCur, str); \ modStrCur += tslen; \ } \ } while(0) // Mod1 is Meta (== Alt), so is special and comes first, apart and // differing from the other more generic ModX's. DO(Mod1, "M+"); // Shift/Ctrl are normal common bits. DO(Shift, "S+"); DO(Control, "C+"); // Other Mod's and Alt's are weirder, but possible. DO(Mod2, "M2+"); DO(Mod3, "M3+"); DO(Mod4, "M4+"); DO(Mod5, "M5+"); DO(Alt1, "A1+"); DO(Alt2, "A2+"); DO(Alt3, "A3+"); DO(Alt4, "A4+"); DO(Alt5, "A5+"); // Overflows for test. Watch out for colliding with X or our *Mask // defs. // +1 when combined with above, should be enough #define Over1Mask (1<<30) DO(Over1, "a"); // Way too big no matter what #define OverAllMask (1<<31) DO(OverAll, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); #undef OverAllMask #undef Over1Mask #undef DO asprintf(&ret, "[%s%s] %s", modStr, key->name, key->action); return ret; }