/* * Copyright (C) 1997-2009, Michael Jennings * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ static const char cvs_ident[] = "$Id$"; #include "config.h" #include "feature.h" #include #include "command.h" #include "draw.h" #include "e.h" #include "events.h" #include "font.h" #include "startup.h" #include "menus.h" #include "misc.h" #include "options.h" #include "pixmap.h" #include "profile.h" #include "screen.h" #include "script.h" #include "term.h" #include "windows.h" #ifdef ESCREEN # include "screamcfg.h" #endif static GC topShadowGC, botShadowGC; static Time button_press_time; static int button_press_x = 0, button_press_y = 0; #ifndef ESCREEN static #endif menu_t *current_menu = NULL; menulist_t *menu_list = NULL; event_dispatcher_data_t menu_event_data; static inline void grab_pointer(Window win); static inline void ungrab_pointer(void); static inline void draw_string(Drawable d, GC gc, int x, int y, char *str, size_t len); static inline unsigned short center_coords(register unsigned short c1, register unsigned short c2); static inline void grab_pointer(Window win) { int success; D_EVENTS(("Grabbing control of pointer for window 0x%08x.\n", win)); success = XGrabPointer(Xdisplay, win, False, EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask | Button1MotionMask | Button2MotionMask | Button3MotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); if (success != GrabSuccess) { switch (success) { case GrabNotViewable: D_MENU((" -> Unable to grab pointer -- Grab window is not viewable.\n")); break; case AlreadyGrabbed: D_MENU((" -> Unable to grab pointer -- Pointer is already grabbed by another client.\n")); break; case GrabFrozen: D_MENU((" -> Unable to grab pointer -- Pointer is frozen by another grab.\n")); break; case GrabInvalidTime: D_MENU((" -> Unable to grab pointer -- Invalid grab time.\n")); break; default: break; } } } static inline void ungrab_pointer(void) { D_EVENTS(("Releasing pointer grab.\n")); XUngrabPointer(Xdisplay, CurrentTime); } static inline void draw_string(Drawable d, GC gc, int x, int y, char *str, size_t len) { D_MENU(("Writing string \"%s\" (length %lu) onto drawable 0x%08x at %d, %d\n", str, len, d, x, y)); #ifdef MULTI_CHARSET if (current_menu && current_menu->fontset && encoding_method != LATIN1) XmbDrawString(Xdisplay, d, current_menu->fontset, gc, x, y, str, len); else #endif XDrawString(Xdisplay, d, gc, x, y, str, len); } static inline unsigned short center_coords(register unsigned short c1, register unsigned short c2) { return (((c2 - c1) >> 1) + c1); } void menu_init(void) { XGCValues gcvalue; if (!menu_list || menu_list->nummenus == 0) { return; } gcvalue.foreground = PixColors[menuTopShadowColor]; topShadowGC = LIBAST_X_CREATE_GC(GCForeground, &gcvalue); gcvalue.foreground = PixColors[menuBottomShadowColor]; botShadowGC = LIBAST_X_CREATE_GC(GCForeground, &gcvalue); event_register_dispatcher(menu_dispatch_event, menu_event_init_dispatcher); } void menu_event_init_dispatcher(void) { register unsigned char i; EVENT_DATA_ADD_HANDLER(menu_event_data, EnterNotify, menu_handle_enter_notify); EVENT_DATA_ADD_HANDLER(menu_event_data, LeaveNotify, menu_handle_leave_notify); #if 0 EVENT_DATA_ADD_HANDLER(menu_event_data, GraphicsExpose, menu_handle_expose); EVENT_DATA_ADD_HANDLER(menu_event_data, Expose, menu_handle_expose); #endif EVENT_DATA_ADD_HANDLER(menu_event_data, ButtonPress, menu_handle_button_press); EVENT_DATA_ADD_HANDLER(menu_event_data, ButtonRelease, menu_handle_button_release); EVENT_DATA_ADD_HANDLER(menu_event_data, MotionNotify, menu_handle_motion_notify); for (i = 0; i < menu_list->nummenus; i++) { event_data_add_mywin(&menu_event_data, menu_list->menus[i]->win); } event_data_add_parent(&menu_event_data, TermWin.vt); event_data_add_parent(&menu_event_data, TermWin.parent); } unsigned char menu_handle_enter_notify(event_t *ev) { register menu_t *menu; D_EVENTS(("menu_handle_enter_notify(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); /* Take control of the pointer so we get all events for it, even those outside the menu window */ menu = find_menu_by_window(menu_list, ev->xany.window); if (menu && menu != current_menu) { ungrab_pointer(); if (menu->state & MENU_STATE_IS_MAPPED) { grab_pointer(menu->win); menu->state |= MENU_STATE_IS_FOCUSED; current_menu = menu; menu_reset_submenus(menu); menuitem_change_current(find_item_by_coords(current_menu, ev->xbutton.x, ev->xbutton.y)); } } return 1; } unsigned char menu_handle_leave_notify(event_t *ev) { D_EVENTS(("menu_handle_leave_notify(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); if (current_menu) { current_menu->state &= ~(MENU_STATE_IS_FOCUSED); } return 0; } unsigned char menu_handle_focus_in(event_t *ev) { D_EVENTS(("menu_handle_focus_in(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); return 0; } unsigned char menu_handle_focus_out(event_t *ev) { D_EVENTS(("menu_handle_focus_out(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); return 0; } #if 0 unsigned char menu_handle_expose(event_t *ev) { XEvent unused_xevent; D_EVENTS(("menu_handle_expose(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); while (XCheckTypedWindowEvent(Xdisplay, ev->xany.window, Expose, &unused_xevent)); while (XCheckTypedWindowEvent(Xdisplay, ev->xany.window, GraphicsExpose, &unused_xevent)); return 1; } #endif unsigned char menu_handle_button_press(event_t *ev) { D_EVENTS(("menu_handle_button_press(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); D_EVENTS(("ButtonPress at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); if (!current_menu || (ev->xbutton.x < 0) || (ev->xbutton.y < 0) || (ev->xbutton.x >= current_menu->w) || (ev->xbutton.y >= current_menu->h)) { Window unused_win, child_win; /* Click outside the current menu, or there is no current menu. Reset. */ ungrab_pointer(); menu_reset_all(menu_list); current_menu = NULL; XTranslateCoordinates(Xdisplay, ev->xany.window, Xroot, ev->xbutton.x, ev->xbutton.y, &(ev->xbutton.x), &(ev->xbutton.y), &unused_win); child_win = find_window_by_coords(Xroot, 0, 0, ev->xbutton.x, ev->xbutton.y); if (child_win != None) { XTranslateCoordinates(Xdisplay, Xroot, child_win, ev->xbutton.x, ev->xbutton.y, &(ev->xbutton.x), &(ev->xbutton.y), &unused_win); ev->xany.window = child_win; D_EVENTS(("Sending synthetic event on to window 0x%08x at %d, %d\n", child_win, ev->xbutton.x, ev->xbutton.y)); XSendEvent(Xdisplay, child_win, False, 0, ev); } } else { button_press_time = ev->xbutton.time; button_press_x = ev->xbutton.x; button_press_y = ev->xbutton.y; if (current_menu && (current_menu->state & MENU_STATE_IS_DRAGGING)) { current_menu->state &= ~MENU_STATE_IS_DRAGGING; } } return 1; } unsigned char menu_handle_button_release(event_t *ev) { menuitem_t *item; D_EVENTS(("menu_handle_button_release(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); D_EVENTS(("ButtonRelease at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); if (current_menu && (current_menu->state & MENU_STATE_IS_DRAGGING)) { /* Dragging-and-release mode */ D_MENU(("Drag-and-release mode, detected release. Button press time is %lu, release time is %lu\n", button_press_time, ev->xbutton.time)); ungrab_pointer(); if (button_press_time && (ev->xbutton.time - button_press_time > MENU_CLICK_TIME)) { /* Take action here based on the current menu item */ if ((item = menuitem_get_current(current_menu))) { if (item->type == MENUITEM_SUBMENU) { menu_display_submenu(current_menu, item); } else { menu_action(item); if (current_menu) { menuitem_deselect(current_menu); } } } /* Reset the state of the menu system. */ menu_reset_all(menu_list); current_menu = NULL; } else { current_menu->state &= ~MENU_STATE_IS_DRAGGING; /* Click, brief drag, release == single click */ } } else { /* Single-click mode */ D_MENU(("Single click mode, detected click. Button press time is %lu, release time is %lu\n", button_press_time, ev->xbutton.time)); if (current_menu && (ev->xbutton.x >= 0) && (ev->xbutton.y >= 0) && (ev->xbutton.x < current_menu->w) && (ev->xbutton.y < current_menu->h)) { /* Click inside the menu window. Activate the current item. */ if ((item = menuitem_get_current(current_menu))) { if (item->type == MENUITEM_SUBMENU) { menu_display_submenu(current_menu, item); } else { menu_action(item); if (current_menu) { /* menu_action() *could* clear current_menu and reset the list */ menuitem_deselect(current_menu); menu_reset_all(menu_list); } } } } else if (!(button_press_time && (ev->xbutton.time - button_press_time < MENU_CLICK_TIME)) || (button_press_x && button_press_y)) { /* Single click which lasted too long, or the second click occured outside the menu */ ungrab_pointer(); /* Reset the state of the menu system. */ menu_reset_all(menu_list); current_menu = NULL; } } button_press_time = 0; button_press_x = button_press_y = 0; return 1; } unsigned char menu_handle_motion_notify(event_t *ev) { register menuitem_t *item = NULL; D_EVENTS(("menu_handle_motion_notify(ev [%8p] on window 0x%08x)\n", ev, ev->xany.window)); REQUIRE_RVAL(XEVENT_IS_MYWIN(ev, &menu_event_data), 0); while (XCheckTypedWindowEvent(Xdisplay, ev->xany.window, MotionNotify, ev)); if (!current_menu) { return 1; } D_MENU(("Mouse is in motion. Button press time is %lu, motion time is %lu\n", button_press_time, ev->xbutton.time)); if ((ev->xbutton.x >= 0) && (ev->xbutton.y >= 0) && (ev->xbutton.x < current_menu->w) && (ev->xbutton.y < current_menu->h)) { /* Motion within the current menu */ if (button_press_time) { current_menu->state |= MENU_STATE_IS_DRAGGING; } item = find_item_by_coords(current_menu, ev->xbutton.x, ev->xbutton.y); if (!item || item != menuitem_get_current(current_menu)) { menu_reset_submenus(current_menu); } menuitem_change_current(item); } else { /* Motion outside the current menu */ int dest_x, dest_y; Window child; menu_t *menu; XTranslateCoordinates(Xdisplay, ev->xany.window, Xroot, ev->xbutton.x, ev->xbutton.y, &dest_x, &dest_y, &child); menu = find_menu_by_window(menu_list, child); if (menu && menu != current_menu) { D_MENU(("Mouse is actually over window 0x%08x belonging to menu \"%s\"\n", child, menu->title)); ungrab_pointer(); grab_pointer(menu->win); current_menu->state &= ~(MENU_STATE_IS_FOCUSED); menu->state |= MENU_STATE_IS_FOCUSED; if (!menu_is_child(current_menu, menu)) { menu_reset_tree(current_menu); } current_menu = menu; current_menu->state |= MENU_STATE_IS_DRAGGING; XTranslateCoordinates(Xdisplay, ev->xany.window, child, ev->xbutton.x, ev->xbutton.y, &dest_x, &dest_y, &child); item = find_item_by_coords(menu, dest_x, dest_y); if (!item || item != menuitem_get_current(current_menu)) { menu_reset_submenus(current_menu); } menuitem_change_current(item); } else if (!menu) { menuitem_change_current(NULL); } } return 1; } unsigned char menu_dispatch_event(event_t *ev) { if (menu_event_data.handlers[ev->type]) { return ((menu_event_data.handlers[ev->type]) (ev)); } return (0); } menulist_t *menulist_add_menu(menulist_t *list, menu_t *menu) { ASSERT_RVAL(menu != NULL, list); if (list) { list->nummenus++; list->menus = (menu_t **) REALLOC(list->menus, sizeof(menu_t *) * list->nummenus); } else { list = (menulist_t *) MALLOC(sizeof(menulist_t)); list->nummenus = 1; list->menus = (menu_t **) MALLOC(sizeof(menu_t *)); } list->menus[list->nummenus - 1] = menu; return list; } void menulist_clear(menulist_t *list) { unsigned long i; ASSERT(list != NULL); for (i = 0; i < list->nummenus; i++) { menu_delete(list->menus[i]); } FREE(list->menus); LIBAST_X_FREE_GC(topShadowGC); LIBAST_X_FREE_GC(botShadowGC); FREE(list); } menu_t *menu_create(char *title) { menu_t *menu; static Cursor cursor; static long mask; static XSetWindowAttributes xattr; if (!mask) { xattr.border_pixel = BlackPixel(Xdisplay, Xscreen); xattr.save_under = TRUE; xattr.override_redirect = TRUE; xattr.colormap = cmap; cursor = XCreateFontCursor(Xdisplay, XC_left_ptr); mask = KeyPressMask | PointerMotionMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask | Button1MotionMask | Button2MotionMask | Button3MotionMask; } menu = (menu_t *) MALLOC(sizeof(menu_t)); memset(menu, 0, sizeof(menu_t)); menu->title = STRDUP(title ? title : ""); menu->win = XCreateWindow(Xdisplay, Xroot, 0, 0, 1, 1, 0, Xdepth, InputOutput, CopyFromParent, CWOverrideRedirect | CWSaveUnder | CWBorderPixel | CWColormap, &xattr); XDefineCursor(Xdisplay, menu->win, cursor); XSelectInput(Xdisplay, menu->win, mask); XStoreName(Xdisplay, menu->win, menu->title); menu->swin = XCreateWindow(Xdisplay, menu->win, 0, 0, 1, 1, 0, Xdepth, InputOutput, CopyFromParent, CWOverrideRedirect | CWSaveUnder | CWBorderPixel | CWColormap, &xattr); menu->gc = LIBAST_X_CREATE_GC(0, NULL); menuitem_clear_current(menu); return menu; } void menu_delete(menu_t *menu) { unsigned short i; ASSERT(menu != NULL); D_MENU(("Deleting menu \"%s\"\n", menu->title)); for (i = 0; i < menu->numitems; i++) { menuitem_delete(menu->items[i]); } FREE(menu->items); if (menu->title) { FREE(menu->title); } if (menu->bg) { if (images[image_menu].norm->pmap->pixmap == menu->bg) { images[image_menu].norm->pmap->pixmap = None; } LIBAST_X_FREE_PIXMAP(menu->bg); } if (menu->gc) { LIBAST_X_FREE_GC(menu->gc); } #ifdef MULTI_CHARSET if (menu->fontset) { XFreeFontSet(Xdisplay, menu->fontset); } #endif if (menu->font) { free_font(menu->font); } if (menu->swin) { XDestroyWindow(Xdisplay, menu->swin); } if (menu->win) { XDestroyWindow(Xdisplay, menu->win); } FREE(menu); } unsigned char menu_set_title(menu_t *menu, const char *title) { ASSERT_RVAL(menu != NULL, 0); REQUIRE_RVAL(title != NULL, 0); FREE(menu->title); menu->title = STRDUP(title); XStoreName(Xdisplay, menu->win, menu->title); return 1; } unsigned char menu_set_font(menu_t *menu, const char *fontname) { XFontStruct *font; XGCValues gcvalue; ASSERT_RVAL(menu != NULL, 0); REQUIRE_RVAL(fontname != NULL, 0); font = (XFontStruct *) load_font(fontname, "fixed", FONT_TYPE_X); #ifdef MULTI_CHARSET menu->fontset = create_fontset(fontname, etmfonts[def_font_idx]); #endif menu->font = font; menu->fwidth = font->max_bounds.width; menu->fheight = font->ascent + font->descent + rs_line_space; gcvalue.font = font->fid; XChangeGC(Xdisplay, menu->gc, GCFont, &gcvalue); return 1; } unsigned char menu_add_item(menu_t *menu, menuitem_t *item) { ASSERT_RVAL(menu != NULL, 0); ASSERT_RVAL(item != NULL, 0); if (menu->numitems) { menu->numitems++; menu->items = (menuitem_t **) REALLOC(menu->items, sizeof(menuitem_t *) * menu->numitems); } else { menu->numitems = 1; menu->items = (menuitem_t **) MALLOC(sizeof(menuitem_t *)); } menu->items[menu->numitems - 1] = item; return 1; } /* Return 1 if submenu is a child of menu, 0 if not. */ unsigned char menu_is_child(menu_t *menu, menu_t *submenu) { register unsigned char i; register menuitem_t *item; ASSERT_RVAL(menu != NULL, 0); ASSERT_RVAL(submenu != NULL, 0); for (i = 0; i < menu->numitems; i++) { item = menu->items[i]; if (item->type == MENUITEM_SUBMENU && item->action.submenu) { if (item->action.submenu == submenu) { return 1; } else if (menu_is_child(item->action.submenu, submenu)) { return 1; } } } return 0; } menu_t *find_menu_by_title(menulist_t *list, char *title) { register unsigned char i; REQUIRE_RVAL(list != NULL, NULL); for (i = 0; i < list->nummenus; i++) { if (!strcasecmp(list->menus[i]->title, title)) { return (list->menus[i]); } } return NULL; } menu_t *find_menu_by_window(menulist_t *list, Window win) { register unsigned char i; REQUIRE_RVAL(list != NULL, NULL); for (i = 0; i < list->nummenus; i++) { if (list->menus[i]->win == win) { return (list->menus[i]); } } return NULL; } menuitem_t *find_item_by_coords(menu_t *menu, int x, int y) { register unsigned char i; register menuitem_t *item; ASSERT_RVAL(menu != NULL, NULL); for (i = 0; i < menu->numitems; i++) { item = menu->items[i]; if ((x > item->x) && (y > item->y) && (x < item->x + item->w) && (y < item->y + item->h) && (item->type != MENUITEM_SEP)) { return (item); } } return NULL; } unsigned short find_item_in_menu(menu_t *menu, menuitem_t *item) { register unsigned char i; ASSERT_RVAL(menu != NULL, (unsigned short) -1); ASSERT_RVAL(item != NULL, (unsigned short) -1); for (i = 0; i < menu->numitems; i++) { if (item == menu->items[i]) { return (i); } } return ((unsigned short) -1); } void menuitem_change_current(menuitem_t *item) { menuitem_t *current; ASSERT(current_menu != NULL); current = menuitem_get_current(current_menu); if (current != item) { D_MENU(("Changing current item in menu \"%s\" from \"%s\" to \"%s\"\n", current_menu->title, (current ? current->text : "(NULL)"), (item ? item->text : "(NULL)"))); if (current) { /* Reset the current item */ menuitem_deselect(current_menu); /* If we're changing from one submenu to another and neither is a child of the other, or if we're changing from a submenu to no current item at all, reset the tree for the current submenu */ if (current->type == MENUITEM_SUBMENU && current->action.submenu) { if ((item && item->type == MENUITEM_SUBMENU && item->action.submenu && !menu_is_child(current->action.submenu, item->action.submenu) && !menu_is_child(item->action.submenu, current->action.submenu)) || (!item)) { menu_reset_tree(current->action.submenu); } } } if (item) { menuitem_set_current(current_menu, find_item_in_menu(current_menu, item)); menuitem_select(current_menu); if (item->type == MENUITEM_SUBMENU) { /* Display the submenu */ menu_display_submenu(current_menu, item); } } else { menuitem_clear_current(current_menu); } } else { D_MENU(("Current item in menu \"%s\" does not require changing.\n", current_menu->title)); } } menuitem_t *menuitem_create(char *text) { menuitem_t *menuitem; menuitem = (menuitem_t *) MALLOC(sizeof(menuitem_t)); memset(menuitem, 0, sizeof(menuitem_t)); if (text) { menuitem->text = STRDUP(text); menuitem->len = strlen(text); } return menuitem; } void menuitem_delete(menuitem_t *item) { ASSERT(item != NULL); if (item->icon) { free_simage(item->icon); } if (item->type == MENUITEM_STRING || item->type == MENUITEM_LITERAL || item->type == MENUITEM_ECHO) { FREE(item->action.string); } else if (item->type == MENUITEM_SCRIPT) { FREE(item->action.script); } else if (item->type == MENUITEM_ALERT) { FREE(item->action.alert); } if (item->text) { FREE(item->text); } if (item->rtext) { FREE(item->rtext); } FREE(item); } unsigned char menuitem_set_text(menuitem_t *item, const char *text) { ASSERT_RVAL(item != NULL, 0); REQUIRE_RVAL(text != NULL, 0); if (item->text) { FREE(item->text); } item->text = STRDUP(text); item->len = strlen(text); return 1; } unsigned char menuitem_set_icon(menuitem_t *item, simage_t *icon) { ASSERT_RVAL(item != NULL, 0); ASSERT_RVAL(icon != NULL, 0); item->icon = icon; return 1; } unsigned char menuitem_set_action(menuitem_t *item, unsigned char type, char *action) { ASSERT_RVAL(item != NULL, 0); item->type = type; switch (type) { case MENUITEM_SUBMENU: item->action.submenu = find_menu_by_title(menu_list, action); break; case MENUITEM_SCRIPT: item->action.script = STRDUP(action); break; case MENUITEM_ALERT: item->action.alert = STRDUP(action); break; case MENUITEM_STRING: case MENUITEM_ECHO: case MENUITEM_LITERAL: item->action.string = (char *) MALLOC(strlen(action) + 2); strcpy(item->action.string, action); if (type != MENUITEM_LITERAL) parse_escaped_string(item->action.string); break; default: break; } return 1; } unsigned char menuitem_set_rtext(menuitem_t *item, char *rtext) { ASSERT_RVAL(item != NULL, 0); ASSERT_RVAL(rtext != NULL, 0); item->rtext = STRDUP(rtext); item->rlen = strlen(rtext); return 1; } void menu_reset(menu_t *menu) { ASSERT(menu != NULL); D_MENU(("menu_reset(menu %8p \"%s\"), window 0x%08x\n", menu, menu->title, menu->win)); if (!(menu->state & MENU_STATE_IS_MAPPED)) { return; } menu->state &= ~(MENU_STATE_IS_CURRENT | MENU_STATE_IS_DRAGGING | MENU_STATE_IS_MAPPED); XUnmapWindow(Xdisplay, menu->swin); XUnmapWindow(Xdisplay, menu->win); menuitem_clear_current(menu); } void menu_reset_all(menulist_t *list) { register unsigned short i; ASSERT(list != NULL); if (list->nummenus == 0) return; D_MENU(("menu_reset_all(%8p) called\n", list)); if (current_menu && menuitem_get_current(current_menu)) { menuitem_deselect(current_menu); } for (i = 0; i < list->nummenus; i++) { menu_reset(list->menus[i]); } current_menu = NULL; } void menu_reset_tree(menu_t *menu) { register unsigned short i; register menuitem_t *item; ASSERT(menu != NULL); D_MENU(("menu_reset_tree(menu %8p \"%s\"), window 0x%08x\n", menu, menu->title, menu->win)); if (!(menu->state & MENU_STATE_IS_MAPPED)) { return; } for (i = 0; i < menu->numitems; i++) { item = menu->items[i]; if (item->type == MENUITEM_SUBMENU && item->action.submenu) { menu_reset_tree(item->action.submenu); } } menu_reset(menu); } void menu_reset_submenus(menu_t *menu) { register unsigned short i; register menuitem_t *item; ASSERT(menu != NULL); D_MENU(("menu_reset_submenus(menu %8p \"%s\"), window 0x%08x\n", menu, menu->title, menu->win)); for (i = 0; i < menu->numitems; i++) { item = menu->items[i]; if (item->type == MENUITEM_SUBMENU && item->action.submenu) { menu_reset_tree(item->action.submenu); } } } void menuitem_select(menu_t *menu) { static Pixel top = 0, bottom = 0; menuitem_t *item; ASSERT(menu != NULL); if (top == 0) { top = get_top_shadow_color(images[image_submenu].selected->bg, "submenu top shadow color"); bottom = get_bottom_shadow_color(images[image_submenu].selected->bg, "submenu bottom shadow color"); } item = menuitem_get_current(menu); REQUIRE(item != NULL); D_MENU(("Selecting new current item \"%s\" within menu \"%s\" (window 0x%08x, selection window 0x%08x)\n", item->text, menu->title, menu->win, menu->swin)); item->state |= MENU_STATE_IS_CURRENT; XMoveWindow(Xdisplay, menu->swin, item->x, item->y); XMapWindow(Xdisplay, menu->swin); if (item->type == MENUITEM_SUBMENU) { render_simage(images[image_submenu].selected, menu->swin, item->w - MENU_VGAP, item->h, image_submenu, 0); if (image_mode_is(image_submenu, MODE_AUTO)) { enl_ipc_sync(); } else if (!image_mode_is(image_submenu, MODE_MASK)) { draw_shadow_from_colors(menu->swin, top, bottom, 0, 0, item->w - MENU_VGAP, item->h, 2); draw_arrow_from_colors(menu->swin, top, bottom, item->w - 3 * MENU_HGAP, (item->h - MENU_VGAP) / 2, MENU_VGAP, 2, DRAW_ARROW_RIGHT); } } else { if (image_mode_is(image_menu, MODE_MASK)) { render_simage(images[image_menu].selected, menu->swin, item->w - MENU_VGAP, item->h, image_menu, 0); } else { draw_shadow_from_colors(menu->swin, top, bottom, 0, 0, item->w - MENU_VGAP, item->h, 2); } if (image_mode_is(image_menu, MODE_AUTO)) { enl_ipc_sync(); } } XSetForeground(Xdisplay, menu->gc, images[image_menu].selected->fg); draw_string(menu->swin, menu->gc, MENU_HGAP, item->h - MENU_VGAP, item->text, item->len); if (item->rtext) { draw_string(menu->swin, menu->gc, item->w - XTextWidth(menu->font, item->rtext, item->rlen) - 2 * MENU_HGAP, item->h - MENU_VGAP, item->rtext, item->rlen); } XSetForeground(Xdisplay, menu->gc, images[image_menu].norm->fg); } void menuitem_deselect(menu_t *menu) { menuitem_t *item; ASSERT(menu != NULL); item = menuitem_get_current(menu); REQUIRE(item != NULL); D_MENU(("Deselecting item \"%s\"\n", item->text)); item->state &= ~(MENU_STATE_IS_CURRENT); XUnmapWindow(Xdisplay, menu->swin); } void menu_display_submenu(menu_t *menu, menuitem_t *item) { menu_t *submenu; ASSERT(menu != NULL); ASSERT(item != NULL); REQUIRE(item->action.submenu != NULL); submenu = item->action.submenu; D_MENU(("Displaying submenu \"%s\" (window 0x%08x) of menu \"%s\" (window 0x%08x)\n", submenu->title, submenu->win, menu->title, menu->win)); menu_invoke(item->x + item->w, item->y, menu->win, submenu, CurrentTime); /* Invoking the submenu makes it current. Undo that behavior. */ ungrab_pointer(); grab_pointer(menu->win); current_menu->state &= ~(MENU_STATE_IS_CURRENT); current_menu = menu; menu->state |= MENU_STATE_IS_CURRENT; } void menu_move(menu_t *menu, unsigned short x, unsigned short y) { ASSERT(menu != NULL); D_MENU(("Moving menu \"%s\" to %hu, %hu\n", menu->title, x, y)); menu->x = x; menu->y = y; XMoveWindow(Xdisplay, menu->win, menu->x, menu->y); if (image_mode_is(image_menu, (MODE_TRANS | MODE_VIEWPORT))) { menu_draw(menu); } } void menu_draw(menu_t *menu) { register unsigned short i, len; unsigned long width, height; unsigned short str_x, str_y; XGCValues gcvalue; int ascent, descent, direction, dx, dy; XCharStruct chars; Screen *scr; ASSERT(menu != NULL); scr = ScreenOfDisplay(Xdisplay, Xscreen); if (!menu->font) { menu_set_font(menu, etfonts[def_font_idx]); } gcvalue.foreground = images[image_menu].norm->fg; gcvalue.graphics_exposures = False; XChangeGC(Xdisplay, menu->gc, GCForeground | GCGraphicsExposures, &gcvalue); if (!menu->w) { unsigned short longest; len = strlen(menu->title); longest = XTextWidth(menu->font, menu->title, len); height = menu->fheight + 3 * MENU_VGAP; for (i = 0; i < menu->numitems; i++) { unsigned short j = menu->items[i]->len; menuitem_t *item = menu->items[i]; width = XTextWidth(menu->font, item->text, j); if (item->rtext) { width += XTextWidth(menu->font, item->rtext, item->rlen) + (2 * MENU_HGAP); } longest = (longest > width) ? longest : width; height += ((item->type == MENUITEM_SEP) ? (MENU_VGAP) : (menu->fheight)) + MENU_VGAP; } width = longest + (4 * MENU_HGAP); if (images[image_submenu].selected->iml->pad) { width += images[image_submenu].selected->iml->pad->left + images[image_submenu].selected->iml->pad->right; } if (!image_mode_is(image_menu, MODE_MASK) || !image_mode_is(image_submenu, MODE_MASK)) { width += 3 * MENU_VGAP; } menu->w = width; menu->h = height; } /* If the menu will come up offscreen, move all the other menus out of the way. */ dx = scr->width - menu->w - menu->x; dy = scr->height - menu->h - menu->y; D_MENU((" -> Menu is %hux%hu at %hu, %hu, dx is %d, dy is %d\n", menu->w, menu->h, menu->x, menu->y, dx, dy)); if (dx < 0 || dy < 0) { register short i; if (dx >= 0) { dx = 0; } else if (menu->w > scr->width) { dx = -menu->x; menu->x = 0; } else { menu->x = scr->width - menu->w; } if (dy >= 0) { dy = 0; } else if (menu->h > scr->height) { dy = -menu->y; menu->y = 0; } else { menu->y = scr->height - menu->h; } D_MENU((" -> New x, y is %hu, %hu\n", menu->x, menu->y)); for (i = menu_list->nummenus - 1; i >= 0; i--) { menu_t *tmp = menu_list->menus[i]; if (tmp == menu) { continue; } D_MENU((" -> Checking menu \"%s\" to see if it needs to be moved.\n", tmp->title)); if (tmp->state & MENU_STATE_IS_MAPPED) { int x = tmp->x + dx, y = tmp->y + dy; int this_dx, this_dy; if (x < 0) { x = 0; this_dx = -tmp->x; } else { this_dx = dx; } if (y < 0) { y = 0; this_dy = -tmp->y; } else { this_dy = 0; } D_MENU((" -> Moving menu to %d, %d (a change of %d, %d from %d, %d)\n", x, y, this_dx, this_dy, tmp->x, tmp->y)); XWarpPointer(Xdisplay, tmp->win, None, 0, 0, tmp->w, tmp->h, this_dx, this_dy); menu_move(tmp, x, y); } } } XMoveResizeWindow(Xdisplay, menu->win, menu->x, menu->y, menu->w, menu->h); /* Size and render selected item window */ XResizeWindow(Xdisplay, menu->swin, menu->w - 2 * MENU_HGAP, menu->fheight + MENU_VGAP); /* This must come before the rendering of the menu window so that pmap->pixmap is guaranteed to be the menu background. */ render_simage(images[image_menu].selected, menu->swin, menu->w - 2 * MENU_HGAP, menu->fheight + MENU_VGAP, image_menu, 0); if (image_mode_is(image_menu, MODE_AUTO)) { enl_ipc_sync(); } XUnmapWindow(Xdisplay, menu->swin); /* Draw menu background */ render_simage(images[image_menu].norm, menu->win, menu->w, menu->h, image_menu, RENDER_FORCE_PIXMAP); menu->bg = images[image_menu].norm->pmap->pixmap; if (!image_mode_is(image_menu, MODE_MASK)) { draw_shadow_from_colors(menu->bg, PixColors[menuTopShadowColor], PixColors[menuBottomShadowColor], 0, 0, menu->w, menu->h, 2); } D_MENU(("Menu background is 0x%08x\n", menu->bg)); XMapWindow(Xdisplay, menu->win); XRaiseWindow(Xdisplay, menu->win); str_x = 2 * MENU_HGAP; if (images[image_menu].selected->iml->pad) { str_x += images[image_menu].selected->iml->pad->left; } str_y = menu->fheight + MENU_VGAP; len = strlen(menu->title); XTextExtents(menu->font, menu->title, len, &direction, &ascent, &descent, &chars); draw_string(menu->bg, menu->gc, center_coords(2 * MENU_HGAP, menu->w - 2 * MENU_HGAP) - (chars.width >> 1), str_y - chars.descent - MENU_VGAP / 2, menu->title, len); draw_shadow(menu->bg, topShadowGC, botShadowGC, str_x, str_y - chars.descent - MENU_VGAP / 2 + 1, menu->w - (4 * MENU_HGAP), MENU_VGAP, 2); str_y += MENU_VGAP; for (i = 0; i < menu->numitems; i++) { menuitem_t *item = menu->items[i]; if (item->type == MENUITEM_SEP) { str_y += 2 * MENU_VGAP; if (!item->x) { item->x = MENU_HGAP; item->y = str_y - 2 * MENU_VGAP; item->w = menu->w - MENU_HGAP; item->h = 2 * MENU_VGAP; D_MENU(("Hot Area at %hu, %hu to %hu, %hu (width %hu, height %hu)\n", item->x, item->y, item->x + item->w, item->y + item->h, item->w, item->h)); } draw_shadow(menu->bg, botShadowGC, topShadowGC, str_x, str_y - MENU_VGAP - MENU_VGAP / 2, menu->w - 4 * MENU_HGAP, MENU_VGAP, 2); } else { str_y += menu->fheight + MENU_VGAP; if (!item->x) { item->x = MENU_HGAP; item->y = str_y - menu->fheight - MENU_VGAP / 2; item->w = menu->w - MENU_HGAP; item->h = menu->fheight + MENU_VGAP; D_MENU(("Hot Area at %hu, %hu to %hu, %hu (width %hu, height %hu)\n", item->x, item->y, item->x + item->w, item->y + item->h, item->w, item->h)); } switch (item->type) { case MENUITEM_SUBMENU: if (image_mode_is(image_submenu, MODE_MASK)) { paste_simage(images[image_submenu].norm, image_submenu, menu->win, menu->bg, item->x, item->y, item->w - MENU_VGAP, item->h); } else { draw_arrow_from_colors(menu->bg, PixColors[menuTopShadowColor], PixColors[menuBottomShadowColor], item->x + item->w - 3 * MENU_HGAP, item->y + (item->h - MENU_VGAP) / 2, MENU_VGAP, 2, DRAW_ARROW_RIGHT); } break; default: break; } draw_string(menu->bg, menu->gc, str_x, str_y - MENU_VGAP / 2, item->text, item->len); if (item->rtext) { draw_string(menu->bg, menu->gc, str_x + item->w - XTextWidth(menu->font, item->rtext, item->rlen) - 3 * MENU_HGAP, str_y - MENU_VGAP / 2, item->rtext, item->rlen); } } } XSetWindowBackgroundPixmap(Xdisplay, menu->win, menu->bg); XClearWindow(Xdisplay, menu->win); } void menu_display(int x, int y, menu_t *menu) { ASSERT(menu != NULL); menu->state |= (MENU_STATE_IS_CURRENT); current_menu = menu; /* Move, render, and map menu window */ menu->x = x; menu->y = y; D_MENU(("Displaying menu \"%s\" (window 0x%08x) at root coordinates %d, %d\n", menu->title, menu->win, menu->x, menu->y)); PROF_FUNC(menu_draw, menu_draw(menu)); menu->state |= (MENU_STATE_IS_MAPPED); /* Take control of the pointer so we get all events for it, even those outside the menu window */ grab_pointer(menu->win); } void menu_action(menuitem_t *item) { ASSERT(item != NULL); D_MENU(("menu_action() called to invoke %s\n", item->text)); switch (item->type) { case MENUITEM_SEP: D_MENU(("Internal Program Error: menu_action() called for a separator.\n")); break; case MENUITEM_SUBMENU: D_MENU(("Internal Program Error: menu_action() called for a submenu.\n")); break; case MENUITEM_STRING: cmd_write((unsigned char *) item->action.string, strlen(item->action.string)); break; case MENUITEM_ECHO: case MENUITEM_LITERAL: #ifdef ESCREEN if (TermWin.screen && TermWin.screen->backend) { /* translate escapes */ switch (TermWin.screen->backend) { # ifdef NS_HAVE_SCREEN case NS_MODE_SCREEN: if (item->type == MENUITEM_ECHO) { ns_parse_screen_interactive(TermWin.screen, item->action.string); } else { ns_screen_command(TermWin.screen, item->action.string); } break; # endif default: tt_write((unsigned char *) item->action.string, strlen(item->action.string)); } } else #endif tt_write((unsigned char *) item->action.string, strlen(item->action.string)); break; case MENUITEM_SCRIPT: script_parse((char *) item->action.script); break; case MENUITEM_ALERT: menu_dialog(NULL, item->action.alert, 0, NULL, NULL); break; default: libast_fatal_error("Internal Program Error: Unknown menuitem type: %u\n", item->type); break; } } void menu_invoke(int x, int y, Window win, menu_t *menu, Time timestamp) { int root_x = x, root_y = y; Window unused; REQUIRE(menu != NULL); if (timestamp != CurrentTime) { button_press_time = timestamp; } if (win != Xroot) { XTranslateCoordinates(Xdisplay, win, Xroot, x, y, &root_x, &root_y, &unused); } menu_display(root_x, root_y, menu); } void menu_invoke_by_title(int x, int y, Window win, char *title, Time timestamp) { menu_t *menu; REQUIRE(title != NULL); REQUIRE(menu_list != NULL); menu = find_menu_by_title(menu_list, title); if (!menu) { D_MENU(("Menu \"%s\" not found!\n", title)); return; } menu_invoke(x, y, win, menu, timestamp); } /* tab completion for screen-commands xd extra-data (current unused) sc keywords for tab-completion nsc entries in sc !b current entry (changes) l number of characters to compare in current entry m maximum number of characters in entry (size of input buffer) <- error code */ int menu_tab(void *xd, char *sc[], int nsc, char *b, size_t l, size_t m) { int n, n2 = 0; USE_VAR(xd); for (n = 0; n < nsc; n++) { /* second tab? cycle. */ if ((!strcasecmp(b, sc[n])) && (n < nsc - 1) && !strncasecmp(b, sc[n + 1], l)) { n2 = n + 1; break; } } for (n = n2; n < nsc; n++) { if (!strncasecmp(b, sc[n], l)) { if (strcmp(b, sc[n])) { if (strlen(sc[n]) >= m) /* buffer would overflow => fail */ return -1; strcpy(b, sc[n]); return 0; } } } return -1; } /* open a dialog. this is a bit of a hack and should really resize otf. xd extra-data (userdef) for inp_tab prompt the prompt, obviously. required. maxlen how long the input may get. 0 for an uneditable alert box. !retstr the address of a pointer. that actual pointer may be NULL, or point to a default value for the input. after completion, the pointer will reference the user's input, or be NULL if the user cancelled input inp_tab function doing tab-completion, NULL for none <- error code (0 succ, -1 fail, -2 cancel) */ int menu_dialog(void *xd, char *prompt, int maxlen, char **retstr, int (*inp_tab) (void *, char *, size_t, size_t)) { static unsigned char short_buf[256]; unsigned char *kbuf = short_buf; menu_t *m; menuitem_t *i; register int ch; int f = 0, len, ret = -1, tab = 0; XEvent ev; KeySym keysym; char *b, *old; int l; if (!prompt || !*prompt) return ret; if (!maxlen || !retstr) { inp_tab = NULL; maxlen = 0; retstr = NULL; if (!(b = STRDUP("Press \"Return\" to continue..."))) { return ret; } } else { if ((!(b = MALLOC(maxlen + 1)))) { return ret; } else if (*retstr) { strncpy(b, *retstr, maxlen); b[maxlen] = '\0'; } else { b[0] = '\0'; } } /* Hide any menu that might've brought up this dialog. */ menu_reset_all(menu_list); if ((m = menu_create(prompt))) { for (l = 0; l < menu_list->nummenus; l++) { if (menu_list->menus[l]->font) { /* copycat font entry to blend in with l&f */ m->font = menu_list->menus[l]->font; m->fwidth = menu_list->menus[l]->fwidth; m->fheight = menu_list->menus[l]->fheight; #ifdef MULTI_CHARSET m->fontset = menu_list->menus[l]->fontset; #endif break; } } if ((i = menuitem_create("..."))) { old = i->text; i->text = b; i->len = strlen(b); if (m->font) { /* pre-calc width so we can center the dialog */ l = strlen(prompt); if (i->len > l) { l = XTextWidth(m->font, i->text, i->len); } else { l = XTextWidth(m->font, prompt, l); } } else { l = 200; } menuitem_set_action(i, MENUITEM_STRING, "error"); menu_add_item(m, i); menu_invoke((int) ((TermWin_TotalWidth() - l) / 2), (int) (TermWin_TotalHeight() / 2) - 20, TermWin.parent, m, CurrentTime); ungrab_pointer(); do { int ret; for (;;) { ret = XNextEvent(Xdisplay, &ev); D_MENU(("In menu_dialog(%s): XNextEvent() returned %d with a %s event.\n", NONULL(prompt), ret, event_type_to_name(ev.type))); /* Handle all events normally *except* for keypresses; those are handled here. */ if (ev.type == KeyPress) { break; } else { process_x_event(&ev); if (ev.type == Expose) { /* Not very efficient, but we're waiting for user input, so screw it. */ scr_refresh(refresh_type); } } } len = XLookupString(&ev.xkey, (char *) kbuf, sizeof(short_buf), &keysym, NULL); ch = kbuf[0]; l = strlen(b); if (ch != '\t') tab = 0; if (ch >= ' ') { if (l < maxlen) { b[l + 1] = '\0'; b[l] = ch; if (!l && (maxlen == 1)) { /* special case: one char */ /* answer auto-returns */ f = 1; } } } else if ((ch == '\n') || (ch == '\r')) { f = 1; } else if (ch == '\x08') { if (maxlen && l) { b[--l] = '\0'; } } else if ((ch == '\t') && inp_tab) { if (!tab) { tab = l; } inp_tab(xd, b, tab, maxlen); } else if (ch == '\x1b') { f = 2; } i->len = strlen(b); menu_draw(m); } while (!f); i->text = old; i->len = strlen(old); /* we could just return b, but it might be longer than we need */ if (retstr) { if (*retstr) { /* Free the old string so we don't leak memory. */ FREE(*retstr); } *retstr = (!maxlen || (f == 2)) ? NULL : STRDUP(b); } ret = (f == 2) ? -2 : 0; } m->font = NULL; #ifdef MULTI_CHARSET m->fontset = NULL; #endif if (current_menu == m) { current_menu = NULL; } menu_delete(m); } FREE(b); return ret; }