1052 lines
29 KiB
C
1052 lines
29 KiB
C
/* menus.c -- Eterm popup menu module
|
|
|
|
* This file is original work by Michael Jennings <mej@eterm.org> and
|
|
* Tuomo Venalainen <vendu@cc.hut.fi>. This file, and any other file
|
|
* bearing this same message or a similar one, is distributed under
|
|
* the GNU Public License (GPL) as outlined in the COPYING file.
|
|
*
|
|
* Copyright (C) 1997-1999, Michael Jennings and Tuomo Venalainen
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
static const char cvs_ident[] = "$Id$";
|
|
|
|
#include "config.h"
|
|
#include "feature.h"
|
|
|
|
#include <X11/cursorfont.h>
|
|
|
|
#include "../libmej/debug.h"
|
|
#include "../libmej/mem.h"
|
|
#include "../libmej/strings.h"
|
|
#include "command.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 "screen.h"
|
|
#include "term.h"
|
|
#include "windows.h"
|
|
|
|
event_dispatcher_data_t menu_event_data;
|
|
menulist_t *menu_list = NULL;
|
|
static GC topShadowGC, botShadowGC;
|
|
static Time button_press_time;
|
|
static menu_t *current_menu;
|
|
|
|
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(("grab_pointer(): 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(("ungrab_pointer(): 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(("draw_string(): 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)
|
|
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 = XCreateGC(Xdisplay, menu_list->menus[0]->win, GCForeground, &gcvalue);
|
|
gcvalue.foreground = PixColors[menuBottomShadowColor];
|
|
botShadowGC = XCreateGC(Xdisplay, menu_list->menus[0]->win, GCForeground, &gcvalue);
|
|
|
|
event_register_dispatcher(menu_dispatch_event, menu_event_init_dispatcher);
|
|
}
|
|
|
|
void
|
|
menu_event_init_dispatcher(void)
|
|
{
|
|
register unsigned char i;
|
|
|
|
MEMSET(&menu_event_data, 0, sizeof(event_dispatcher_data_t));
|
|
|
|
EVENT_DATA_ADD_HANDLER(menu_event_data, EnterNotify, menu_handle_enter_notify);
|
|
EVENT_DATA_ADD_HANDLER(menu_event_data, LeaveNotify, menu_handle_leave_notify);
|
|
EVENT_DATA_ADD_HANDLER(menu_event_data, GraphicsExpose, menu_handle_expose);
|
|
EVENT_DATA_ADD_HANDLER(menu_event_data, Expose, menu_handle_expose);
|
|
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 [0x%08x] 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 [0x%08x] 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 [0x%08x] 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 [0x%08x] 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_expose(event_t * ev)
|
|
{
|
|
|
|
XEvent unused_xevent;
|
|
|
|
D_EVENTS(("menu_handle_expose(ev [0x%08x] 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 0;
|
|
}
|
|
|
|
unsigned char
|
|
menu_handle_button_press(event_t * ev)
|
|
{
|
|
|
|
D_EVENTS(("menu_handle_button_press(ev [0x%08x] 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));
|
|
|
|
button_press_time = ev->xbutton.time;
|
|
|
|
return 1;
|
|
}
|
|
|
|
unsigned char
|
|
menu_handle_button_release(event_t * ev)
|
|
{
|
|
menuitem_t *item;
|
|
|
|
D_EVENTS(("menu_handle_button_release(ev [0x%08x] 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.\n"));
|
|
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 (current_menu) {
|
|
if ((item = menuitem_get_current(current_menu)) != NULL) {
|
|
if (item->type == MENUITEM_SUBMENU) {
|
|
menu_display_submenu(current_menu, item);
|
|
} else {
|
|
menu_action(item);
|
|
menuitem_deselect(current_menu);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Reset the state of the menu system. */
|
|
menu_reset_all(menu_list);
|
|
current_menu = NULL;
|
|
|
|
} else {
|
|
|
|
/* Single-click mode */
|
|
D_MENU(("Single click mode, detected click.\n"));
|
|
if ((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 (current_menu) {
|
|
if ((item = menuitem_get_current(current_menu)) != NULL) {
|
|
if (item->type == MENUITEM_SUBMENU) {
|
|
menu_display_submenu(current_menu, item);
|
|
} else {
|
|
menu_action(item);
|
|
menuitem_deselect(current_menu);
|
|
menu_reset_all(menu_list);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ungrab_pointer();
|
|
/* Reset the state of the menu system. */
|
|
menu_reset_all(menu_list);
|
|
current_menu = NULL;
|
|
}
|
|
}
|
|
button_press_time = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
unsigned char
|
|
menu_handle_motion_notify(event_t * ev)
|
|
{
|
|
|
|
register menuitem_t *item = NULL;
|
|
|
|
D_EVENTS(("menu_handle_motion_notify(ev [0x%08x] 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;
|
|
}
|
|
|
|
if (button_press_time) {
|
|
current_menu->state |= MENU_STATE_IS_DRAGGING;
|
|
}
|
|
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 */
|
|
item = find_item_by_coords(current_menu, ev->xbutton.x, ev->xbutton.y);
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
unsigned char
|
|
menu_dispatch_event(event_t * ev)
|
|
{
|
|
if (menu_event_data.handlers[ev->type] != NULL) {
|
|
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;
|
|
}
|
|
|
|
menu_t *
|
|
menu_create(char *title)
|
|
{
|
|
|
|
menu_t *menu;
|
|
static Cursor cursor;
|
|
static long mask;
|
|
static XGCValues gcvalue;
|
|
static XSetWindowAttributes xattr;
|
|
|
|
ASSERT_RVAL(title != NULL, NULL);
|
|
|
|
if (!mask) {
|
|
xattr.border_pixel = BlackPixel(Xdisplay, Xscreen);
|
|
xattr.save_under = TRUE;
|
|
xattr.backing_store = WhenMapped;
|
|
xattr.override_redirect = TRUE;
|
|
xattr.colormap = cmap;
|
|
|
|
cursor = XCreateFontCursor(Xdisplay, XC_left_ptr);
|
|
mask = EnterNotify | LeaveNotify | PointerMotionMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
|
|
| Button1MotionMask | Button2MotionMask | Button3MotionMask;
|
|
gcvalue.foreground = PixColors[menuTextColor];
|
|
}
|
|
menu = (menu_t *) MALLOC(sizeof(menu_t));
|
|
MEMSET(menu, 0, sizeof(menu_t));
|
|
menu->title = StrDup(title);
|
|
|
|
menu->win = XCreateWindow(Xdisplay, Xroot, 0, 0, 1, 1, 0, Xdepth, InputOutput, CopyFromParent,
|
|
CWOverrideRedirect | CWSaveUnder | CWBackingStore | 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 | CWBackingStore | CWBorderPixel | CWColormap, &xattr);
|
|
|
|
menu->gc = XCreateGC(Xdisplay, menu->win, GCForeground, &gcvalue);
|
|
menuitem_clear_current(menu);
|
|
|
|
return menu;
|
|
}
|
|
|
|
unsigned char
|
|
menu_set_font(menu_t * menu, const char *fontname)
|
|
{
|
|
|
|
XFontStruct *font;
|
|
XGCValues gcvalue;
|
|
|
|
ASSERT_RVAL(menu != NULL, 0);
|
|
ASSERT_RVAL(fontname != NULL, 0);
|
|
|
|
font = (XFontStruct *) load_font(fontname, "fixed", FONT_TYPE_X);
|
|
#ifdef MULTI_CHARSET
|
|
menu->fontset = create_fontset(fontname, rs_mfont[0]);
|
|
#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 != NULL) {
|
|
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(("menuitem_change_current(): 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 != NULL) {
|
|
if ((item && item->type == MENUITEM_SUBMENU && item->action.submenu != NULL
|
|
&& !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(("menuitem_change_current(): 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;
|
|
}
|
|
|
|
unsigned char
|
|
menuitem_set_icon(menuitem_t * item, image_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_STRING:
|
|
case MENUITEM_ECHO:
|
|
item->action.string = (char *) MALLOC(strlen(action) + 2);
|
|
strcpy(item->action.string, action);
|
|
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() called for menu \"%s\" (window 0x%08x)\n", 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() called\n"));
|
|
if (menuitem_get_current(current_menu) != NULL) {
|
|
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() called for menu \"%s\" (window 0x%08x)\n", 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 != NULL) {
|
|
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() called for menu \"%s\" (window 0x%08x)\n", menu->title, menu->win));
|
|
for (i = 0; i < menu->numitems; i++) {
|
|
item = menu->items[i];
|
|
if (item->type == MENUITEM_SUBMENU && item->action.submenu != NULL) {
|
|
menu_reset_tree(item->action.submenu);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
menuitem_select(menu_t * menu)
|
|
{
|
|
menuitem_t *item;
|
|
|
|
ASSERT(menu != NULL);
|
|
|
|
item = menuitem_get_current(menu);
|
|
REQUIRE(item != NULL);
|
|
D_MENU(("menuitem_select(): 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) {
|
|
paste_simage(images[image_submenu].selected, image_submenu, menu->swin, 0, 0, item->w - MENU_VGAP, item->h);
|
|
} else {
|
|
render_simage(images[image_menu].selected, menu->swin, item->w - MENU_VGAP, item->h, image_menu, 0);
|
|
if (image_mode_is(image_menu, MODE_AUTO)) {
|
|
enl_ipc_sync();
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
void
|
|
menuitem_deselect(menu_t * menu)
|
|
{
|
|
menuitem_t *item;
|
|
|
|
ASSERT(menu != NULL);
|
|
|
|
item = menuitem_get_current(menu);
|
|
REQUIRE(item != NULL);
|
|
D_MENU(("menuitem_deselect(): Deselecting item \"%s\"\n", item->text));
|
|
item->state &= ~(MENU_STATE_IS_CURRENT);
|
|
XUnmapWindow(Xdisplay, menu->swin);
|
|
if (item->type == MENUITEM_SUBMENU) {
|
|
paste_simage(images[image_submenu].norm, image_submenu, menu->win, item->x, item->y, item->w - MENU_VGAP, item->h);
|
|
}
|
|
draw_string(menu->win, menu->gc, 2 * MENU_HGAP, item->y + item->h - MENU_VGAP, item->text, item->len);
|
|
if (item->rtext) {
|
|
draw_string(menu->win, menu->gc, item->x + item->w - XTextWidth(menu->font, item->rtext, item->rlen) - 2 * MENU_HGAP, item->y + item->h - MENU_VGAP,
|
|
item->rtext, item->rlen);
|
|
}
|
|
}
|
|
|
|
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(("menu_display_submenu(): 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_draw(menu_t * menu)
|
|
{
|
|
|
|
register unsigned short i, len;
|
|
unsigned long width, height;
|
|
#if 0
|
|
char *safeaction;
|
|
#endif
|
|
unsigned short str_x, str_y;
|
|
XGCValues gcvalue;
|
|
int ascent, descent, direction;
|
|
XCharStruct chars;
|
|
Screen *scr;
|
|
|
|
ASSERT(menu != NULL);
|
|
|
|
scr = ScreenOfDisplay(Xdisplay, Xscreen);
|
|
if (!menu->font) {
|
|
menu_set_font(menu, rs_font[0]);
|
|
}
|
|
gcvalue.foreground = PixColors[menuTextColor];
|
|
XChangeGC(Xdisplay, menu->gc, GCForeground, &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;
|
|
}
|
|
menu->w = width;
|
|
menu->h = height;
|
|
|
|
/* Size and render menu window */
|
|
D_MENU((" -> width %hu, height %hu\n", menu->w, menu->h));
|
|
XResizeWindow(Xdisplay, menu->win, menu->w, menu->h);
|
|
render_simage(images[image_menu].norm, menu->win, menu->w, menu->h, image_menu, 0);
|
|
if (image_mode_is(image_menu, MODE_AUTO)) {
|
|
enl_ipc_sync();
|
|
}
|
|
|
|
/* Size and render selected item window */
|
|
XResizeWindow(Xdisplay, menu->swin, menu->w - 2 * MENU_HGAP, menu->fheight + MENU_VGAP);
|
|
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();
|
|
}
|
|
}
|
|
if (menu->w + menu->x > scr->width) {
|
|
menu->x = scr->width - menu->w;
|
|
}
|
|
if (menu->h + menu->y > scr->height) {
|
|
menu->y = scr->height - menu->h;
|
|
}
|
|
XMoveWindow(Xdisplay, menu->win, menu->x, menu->y);
|
|
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->win, 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->win, topShadowGC, botShadowGC, str_x, str_y - chars.descent - MENU_VGAP / 2 + 1, menu->w - (4 * MENU_HGAP), MENU_VGAP);
|
|
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->win, botShadowGC, topShadowGC, str_x, str_y - MENU_VGAP - MENU_VGAP / 2, menu->w - 4 * MENU_HGAP, MENU_VGAP);
|
|
|
|
} 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:
|
|
paste_simage(images[image_submenu].norm, image_submenu, menu->win, item->x, item->y, item->w - MENU_VGAP, item->h);
|
|
break;
|
|
case MENUITEM_STRING:
|
|
#if 0
|
|
safeaction = StrDup(item->action.string);
|
|
SafeStr(safeaction, strlen(safeaction));
|
|
D_MENU((" Item %hu: %s (string %s)\n", i, item->text, safeaction));
|
|
FREE(safeaction);
|
|
#endif
|
|
break;
|
|
case MENUITEM_ECHO:
|
|
#if 0
|
|
safeaction = StrDup(item->action.string);
|
|
SafeStr(safeaction, strlen(safeaction));
|
|
D_MENU((" Item %hu: %s (echo %s)\n", i, item->text, safeaction));
|
|
FREE(safeaction);
|
|
#endif
|
|
break;
|
|
default:
|
|
fatal_error("Internal Program Error: Unknown menuitem type: %u\n", item->type);
|
|
break;
|
|
}
|
|
draw_string(menu->win, menu->gc, str_x, str_y - MENU_VGAP / 2, item->text, item->len);
|
|
if (item->rtext) {
|
|
draw_string(menu->win, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
XMoveWindow(Xdisplay, menu->win, menu->x, menu->y);
|
|
XUnmapWindow(Xdisplay, menu->swin);
|
|
XMapWindow(Xdisplay, menu->win);
|
|
menu->state |= (MENU_STATE_IS_MAPPED);
|
|
|
|
menu_draw(menu);
|
|
|
|
/* 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(item->action.string, strlen(item->action.string));
|
|
break;
|
|
case MENUITEM_ECHO:
|
|
tt_write(item->action.string, strlen(item->action.string));
|
|
break;
|
|
default:
|
|
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 time)
|
|
{
|
|
|
|
int root_x, root_y;
|
|
Window unused;
|
|
|
|
REQUIRE(menu != NULL);
|
|
|
|
if (time != CurrentTime) {
|
|
button_press_time = time;
|
|
}
|
|
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 time)
|
|
{
|
|
|
|
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, time);
|
|
}
|