2568 lines
57 KiB
C
2568 lines
57 KiB
C
/*--------------------------------*-C-*---------------------------------*
|
|
* File: menubar.c
|
|
*
|
|
* Copyright 1996,97
|
|
* mj olesen <olesen@me.QueensU.CA> Queen's Univ at Kingston
|
|
*
|
|
* You can do what you like with this source code provided you don't make
|
|
* money from it and you include an unaltered copy of this message
|
|
* (including the copyright). As usual, the author accepts no
|
|
* responsibility for anything, nor does he guarantee anything whatsoever.
|
|
*
|
|
*----------------------------------------------------------------------*/
|
|
|
|
static const char cvs_ident[] = "$Id$";
|
|
|
|
#include "main.h"
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
# include <sys/select.h>
|
|
#endif
|
|
#ifdef TIME_WITH_SYS_TIME
|
|
# include <sys/time.h>
|
|
# include <time.h>
|
|
#else
|
|
# ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# else
|
|
# include <time.h>
|
|
# endif
|
|
#endif
|
|
|
|
#include "menubar.h"
|
|
#include "command.h"
|
|
#include "debug.h"
|
|
#include "../libmej/debug.h"
|
|
#include "mem.h"
|
|
#include "misc.h"
|
|
#ifdef PIXMAP_MENUBAR
|
|
# include "pixmap.h"
|
|
# include "options.h"
|
|
#endif
|
|
|
|
#ifdef KANJI
|
|
# ifdef NO_XLOCALE
|
|
# include <locale.h>
|
|
# else
|
|
# include <X11/Xlocale.h>
|
|
# endif
|
|
static XFontSet fontset = 0;
|
|
|
|
#endif
|
|
|
|
int delay_menu_drawing;
|
|
|
|
extern XSetWindowAttributes Attributes;
|
|
|
|
#ifdef PIXMAP_MENUBAR
|
|
pixmap_t mbPixmap, mb_selPixmap;
|
|
|
|
# ifdef USE_IMLIB
|
|
imlib_t imlib_mb, imlib_ms;
|
|
|
|
# endif
|
|
#endif
|
|
|
|
#if (MENUBAR_MAX)
|
|
menuitem_t *menuitem_find(menu_t * menu, char *name);
|
|
void menuitem_free(menu_t * menu, menuitem_t * item);
|
|
int action_type(action_t * action, unsigned char *str);
|
|
int action_dispatch(action_t * action);
|
|
int menuarrow_find(char name);
|
|
void menuarrow_free(char name);
|
|
void menuarrow_add(char *string);
|
|
menuitem_t *menuitem_add(menu_t * menu, char *name, char *name2, char *action);
|
|
char *menu_find_base(menu_t ** menu, char *path);
|
|
menu_t *menu_delete(menu_t * menu);
|
|
menu_t *menu_add(menu_t * parent, char *path);
|
|
void drawbox_menubar(int x, int len, int state);
|
|
void drawtriangle(int x, int y, int state);
|
|
void drawbox_menuitem(int y, int state);
|
|
void print_menu_ancestors(menu_t * menu);
|
|
void print_menu_descendants(menu_t * menu);
|
|
void menu_show(void);
|
|
void menu_display(void (*update) (void));
|
|
void menu_hide_all(void);
|
|
void menu_hide(void);
|
|
void menu_clear(menu_t * menu);
|
|
void menubar_clear(void);
|
|
bar_t *menubar_find(const char *name);
|
|
int menubar_push(const char *name);
|
|
void menubar_remove(const char *name);
|
|
void action_decode(FILE * fp, action_t * act);
|
|
void menu_dump(FILE * fp, menu_t * menu);
|
|
void menubar_dump(FILE * fp);
|
|
void menubar_read(const char *filename);
|
|
void menubar_dispatch(char *str);
|
|
void draw_Arrows(int name, int state);
|
|
void menubar_expose(void);
|
|
int menubar_mapping(int map);
|
|
int menu_select(XButtonEvent * ev);
|
|
void menubar_select(XButtonEvent * ev);
|
|
void menubar_control(XButtonEvent * ev);
|
|
|
|
#define HSPACE 2
|
|
#define MENU_MARGIN 2
|
|
#define menu_height() (TermWin.fheight + 2 * MENU_MARGIN)
|
|
|
|
#define MENU_DELAY_USEC 250000 /* 1/4 sec */
|
|
|
|
#define SEPARATOR_HALFHEIGHT (SHADOW + 1)
|
|
#define SEPARATOR_HEIGHT (2 * SEPARATOR_HALFHEIGHT)
|
|
#define isSeparator(name) ((name)[0] == '\0')
|
|
|
|
#define SEPARATOR_NAME "-"
|
|
#define MENUITEM_BEG '{'
|
|
#define MENUITEM_END '}'
|
|
#define COMMENT_CHAR '#'
|
|
|
|
#define DOT "."
|
|
#define DOTS ".."
|
|
|
|
#define Menu_PixelWidth(menu) (2 * SHADOW + Width2Pixel ((menu)->width + 3 * HSPACE))
|
|
|
|
static GC topShadowGC, botShadowGC, neutralGC, menubarGC;
|
|
|
|
struct menu_t;
|
|
|
|
static int menu_readonly = 1; /* okay to alter menu? */
|
|
static int Arrows_x = 0;
|
|
|
|
static const struct {
|
|
char name; /* (l)eft, (u)p, (d)own, (r)ight */
|
|
unsigned char *str; /* str[0] = strlen (str+1) */
|
|
} Arrows[NARROWS] = {
|
|
|
|
{
|
|
'l', "\003\033[D"
|
|
},
|
|
{
|
|
'u', "\003\033[A"
|
|
},
|
|
{
|
|
'd', "\003\033[B"
|
|
},
|
|
{
|
|
'r', "\003\033[C"
|
|
}
|
|
};
|
|
|
|
#if (MENUBAR_MAX > 1)
|
|
static int Nbars = 0;
|
|
static bar_t *CurrentBar = NULL;
|
|
|
|
#else /* (MENUBAR_MAX > 1) */
|
|
static bar_t BarList;
|
|
static bar_t *CurrentBar = &BarList;
|
|
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
|
|
#ifdef PIXMAP_MENUBAR
|
|
menu_t *ActiveMenu = NULL; /* currently active menu */
|
|
|
|
#else
|
|
static menu_t *ActiveMenu = NULL; /* currently active menu */
|
|
|
|
#endif
|
|
|
|
#ifndef HAVE_MEMMOVE
|
|
/* Replacement for memmove() if it's not there -- mej */
|
|
inline void *
|
|
memmove(void *s1, const void *s2, size_t size)
|
|
{
|
|
|
|
register char *tmp;
|
|
register char *dest = (char *) s1;
|
|
register const char *source = (const char *) s2;
|
|
|
|
if ((tmp = (char *) malloc(size)) == NULL) {
|
|
fprintf(stderr, "memmove(): allocation failure for %lu bytes\n",
|
|
size);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memcpy(tmp, source, size);
|
|
memcpy(dest, tmp, size);
|
|
free(tmp);
|
|
return (dest);
|
|
}
|
|
#endif /* HAVE_MEMMOVE */
|
|
|
|
/*
|
|
* find an item called NAME in MENU
|
|
*/
|
|
menuitem_t *
|
|
menuitem_find(menu_t * menu, char *name)
|
|
{
|
|
|
|
menuitem_t *item;
|
|
|
|
assert(name != NULL);
|
|
assert(menu != NULL);
|
|
|
|
D_MENUBAR(("menuitem_find(\"%s\", \"%s\")\n", menu->name, name));
|
|
/* find the last item in the menu, this is good for separators */
|
|
for (item = menu->tail; item != NULL; item = item->prev) {
|
|
if (item->entry.type == MenuSubMenu) {
|
|
if (!strcmp(name, (item->entry.submenu.menu)->name))
|
|
break;
|
|
} else if ((isSeparator(name) && isSeparator(item->name)) ||
|
|
!strcmp(name, item->name))
|
|
break;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
/*
|
|
* unlink ITEM from its MENU and free its memory
|
|
*/
|
|
void
|
|
menuitem_free(menu_t * menu, menuitem_t * item)
|
|
{
|
|
|
|
/* disconnect */
|
|
menuitem_t *prev, *next;
|
|
|
|
assert(menu != NULL);
|
|
assert(item != NULL);
|
|
|
|
D_MENUBAR(("menuitem_free(\"%s\", \"%s\")\n", menu->name, item->name));
|
|
prev = item->prev;
|
|
next = item->next;
|
|
if (prev != NULL)
|
|
prev->next = next;
|
|
if (next != NULL)
|
|
next->prev = prev;
|
|
|
|
/* new head, tail */
|
|
if (menu->tail == item)
|
|
menu->tail = prev;
|
|
if (menu->head == item)
|
|
menu->head = next;
|
|
|
|
switch (item->entry.type) {
|
|
case MenuAction:
|
|
case MenuTerminalAction:
|
|
FREE(item->entry.action.str);
|
|
break;
|
|
case MenuSubMenu:
|
|
menu_delete(item->entry.submenu.menu);
|
|
break;
|
|
}
|
|
if (item->name != NULL)
|
|
FREE(item->name);
|
|
if (item->name2 != NULL)
|
|
FREE(item->name2);
|
|
FREE(item);
|
|
}
|
|
|
|
/*
|
|
* sort command vs. terminal actions and
|
|
* remove the first character of STR if it's '\0'
|
|
*/
|
|
int
|
|
action_type(action_t * action, unsigned char *str)
|
|
{
|
|
|
|
unsigned int len;
|
|
|
|
len = parse_escaped_string(str);
|
|
D_MENUBAR(("New string is %u bytes\n", len));
|
|
|
|
ASSERT(action != NULL);
|
|
|
|
if (!len)
|
|
return -1;
|
|
|
|
/* sort command vs. terminal actions */
|
|
action->type = MenuAction;
|
|
if (str[0] == '\0') {
|
|
/* the functional equivalent: memmove (str, str+1, len); */
|
|
unsigned char *dst = (str);
|
|
unsigned char *src = (str + 1);
|
|
unsigned char *end = (str + len);
|
|
|
|
while (src <= end)
|
|
*dst++ = *src++;
|
|
|
|
len--; /* decrement length */
|
|
if (str[0] != '\0')
|
|
action->type = MenuTerminalAction;
|
|
}
|
|
action->str = str;
|
|
action->len = len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
action_dispatch(action_t * action)
|
|
{
|
|
|
|
assert(action != NULL);
|
|
D_MENUBAR(("action_dispatch(\"%s\")\n", action->str));
|
|
|
|
switch (action->type) {
|
|
case MenuTerminalAction:
|
|
cmd_write(action->str, action->len);
|
|
break;
|
|
|
|
case MenuAction:
|
|
tt_write(action->str, action->len);
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* return the arrow index corresponding to NAME */
|
|
int
|
|
menuarrow_find(char name)
|
|
{
|
|
|
|
int i;
|
|
|
|
D_MENUARROWS(("menuarrow_find(\'%c\')\n", name));
|
|
|
|
for (i = 0; i < NARROWS; i++) {
|
|
if (name == Arrows[i].name)
|
|
return (i);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
/* free the memory associated with arrow NAME of the current menubar */
|
|
void
|
|
menuarrow_free(char name)
|
|
{
|
|
|
|
int i;
|
|
|
|
D_MENUARROWS(("menuarrow_free(\'%c\')\n", name));
|
|
|
|
if (name) {
|
|
i = menuarrow_find(name);
|
|
if (i >= 0) {
|
|
action_t *act = &(CurrentBar->arrows[i]);
|
|
|
|
switch (act->type) {
|
|
case MenuAction:
|
|
case MenuTerminalAction:
|
|
FREE(act->str);
|
|
act->str = NULL;
|
|
act->len = 0;
|
|
break;
|
|
}
|
|
act->type = MenuLabel;
|
|
}
|
|
} else {
|
|
for (i = 0; i < NARROWS; i++)
|
|
menuarrow_free(Arrows[i].name);
|
|
}
|
|
}
|
|
|
|
void
|
|
menuarrow_add(char *string)
|
|
{
|
|
|
|
int i;
|
|
unsigned xtra_len;
|
|
char *p;
|
|
struct {
|
|
char *str;
|
|
int len;
|
|
} beg = {
|
|
NULL, 0
|
|
}, end = {
|
|
NULL, 0
|
|
}, *cur, parse[NARROWS];
|
|
|
|
D_MENUARROWS(("menuarrow_add(\"%s\")\n", string));
|
|
|
|
memset(parse, 0, sizeof(parse));
|
|
|
|
for (p = string; p != NULL && *p; string = p) {
|
|
p = (string + 3);
|
|
D_MENUARROWS(("parsing at %s\n", string));
|
|
switch (string[1]) {
|
|
case 'b':
|
|
cur = &beg;
|
|
break;
|
|
case 'e':
|
|
cur = &end;
|
|
break;
|
|
default:
|
|
i = menuarrow_find(string[1]);
|
|
if (i >= 0)
|
|
cur = &(parse[i]);
|
|
else
|
|
continue; /* not found */
|
|
break;
|
|
}
|
|
|
|
string = p;
|
|
cur->str = string;
|
|
cur->len = 0;
|
|
|
|
if (cur == &end) {
|
|
p = strchr(string, '\0');
|
|
} else {
|
|
char *next = string;
|
|
|
|
while (1) {
|
|
p = strchr(next, '<');
|
|
if (p != NULL) {
|
|
if (p[1] && p[2] == '>')
|
|
break;
|
|
/* parsed */
|
|
} else {
|
|
if (beg.str == NULL) /* no end needed */
|
|
p = strchr(next, '\0');
|
|
break;
|
|
}
|
|
next = (p + 1);
|
|
}
|
|
}
|
|
|
|
if (p == NULL)
|
|
return;
|
|
cur->len = (p - string);
|
|
}
|
|
|
|
#if DEBUG >= DEBUG_MENUARROWS
|
|
if (debug_level >= DEBUG_MENUARROWS) {
|
|
cur = &beg;
|
|
DPRINTF1(("<b>(len %d) = %.*s\n", cur->len, cur->len, (cur->str ? cur->str : "")));
|
|
for (i = 0; i < NARROWS; i++) {
|
|
cur = &(parse[i]);
|
|
DPRINTF1(("<%c>(len %d) = %.*s\n", Arrows[i].name, cur->len, cur->len, (cur->str ? cur->str : "")));
|
|
}
|
|
cur = &end;
|
|
DPRINTF1(("<e>(len %d) = %.*s\n", cur->len, cur->len, (cur->str ? cur->str : "")));
|
|
}
|
|
#endif
|
|
|
|
xtra_len = (beg.len + end.len);
|
|
for (i = 0; i < NARROWS; i++) {
|
|
if (xtra_len || parse[i].len)
|
|
menuarrow_free(Arrows[i].name);
|
|
}
|
|
|
|
for (i = 0; i < NARROWS; i++) {
|
|
|
|
unsigned char *str;
|
|
unsigned int len;
|
|
|
|
if (!parse[i].len)
|
|
continue;
|
|
|
|
str = MALLOC(parse[i].len + xtra_len + 1);
|
|
if (str == NULL)
|
|
continue;
|
|
|
|
len = 0;
|
|
if (beg.len) {
|
|
strncpy(str + len, beg.str, beg.len);
|
|
len += beg.len;
|
|
}
|
|
strncpy(str + len, parse[i].str, parse[i].len);
|
|
len += parse[i].len;
|
|
|
|
if (end.len) {
|
|
strncpy(str + len, end.str, end.len);
|
|
len += end.len;
|
|
}
|
|
str[len] = '\0';
|
|
|
|
D_MENUARROWS(("<%c>(len %d) = %s\n", Arrows[i].name, len, str));
|
|
if (action_type(&(CurrentBar->arrows[i]), str) < 0)
|
|
FREE(str);
|
|
}
|
|
}
|
|
|
|
menuitem_t *
|
|
menuitem_add(menu_t * menu, char *name, char *name2, char *action)
|
|
{
|
|
|
|
menuitem_t *item = NULL;
|
|
unsigned int len;
|
|
unsigned char found = 0;
|
|
|
|
assert(name != NULL);
|
|
assert(action != NULL);
|
|
|
|
if (menu == NULL)
|
|
return NULL;
|
|
|
|
D_MENUBAR(("menuitem_add(\"%s\", \"%s\", \"%s\", \"%s\")\n", menu->name, name, (name2 ? name2 : "<nil>"), action));
|
|
|
|
if (isSeparator(name)) {
|
|
/* add separator, no action */
|
|
name = "";
|
|
action = "";
|
|
} else {
|
|
/*
|
|
* add/replace existing menu item
|
|
*/
|
|
item = menuitem_find(menu, name);
|
|
if (item != NULL) {
|
|
if (item->name2 != NULL && name2 != NULL) {
|
|
FREE(item->name2);
|
|
item->len2 = 0;
|
|
item->name2 = NULL;
|
|
}
|
|
switch (item->entry.type) {
|
|
case MenuAction:
|
|
case MenuTerminalAction:
|
|
FREE(item->entry.action.str);
|
|
item->entry.action.str = NULL;
|
|
break;
|
|
}
|
|
found = 1;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
/* allocate a new itemect */
|
|
if ((item = (menuitem_t *) MALLOC(sizeof(menuitem_t))) == NULL)
|
|
return NULL;
|
|
|
|
item->len2 = 0;
|
|
item->name2 = NULL;
|
|
|
|
len = strlen(name);
|
|
item->name = MALLOC(len + 1);
|
|
if (item->name != NULL) {
|
|
strcpy(item->name, name);
|
|
if (name[0] == '.' && name[1] != '.')
|
|
len = 0; /* hidden menu name */
|
|
} else {
|
|
FREE(item);
|
|
return NULL;
|
|
}
|
|
item->len = len;
|
|
|
|
/* add to tail of list */
|
|
item->prev = menu->tail;
|
|
item->next = NULL;
|
|
|
|
if (menu->tail != NULL)
|
|
(menu->tail)->next = item;
|
|
menu->tail = item;
|
|
/* fix head */
|
|
if (menu->head == NULL)
|
|
menu->head = item;
|
|
}
|
|
/*
|
|
* add action
|
|
*/
|
|
if (name2 != NULL && item->name2 == NULL) {
|
|
len = strlen(name2);
|
|
if (len == 0 || (item->name2 = MALLOC(len + 1)) == NULL) {
|
|
len = 0;
|
|
item->name2 = NULL;
|
|
} else {
|
|
strcpy(item->name2, name2);
|
|
}
|
|
item->len2 = len;
|
|
}
|
|
item->entry.type = MenuLabel;
|
|
len = strlen(action);
|
|
|
|
if (len == 0 && item->name2 != NULL) {
|
|
action = item->name2;
|
|
len = item->len2;
|
|
}
|
|
if (len) {
|
|
unsigned char *str = MALLOC(len + 1);
|
|
|
|
if (str == NULL) {
|
|
menuitem_free(menu, item);
|
|
return NULL;
|
|
}
|
|
strcpy(str, action);
|
|
|
|
if (action_type(&(item->entry.action), str) < 0)
|
|
FREE(str);
|
|
}
|
|
/* new item and a possible increase in width */
|
|
if (menu->width < (item->len + item->len2))
|
|
menu->width = (item->len + item->len2);
|
|
|
|
return item;
|
|
}
|
|
|
|
/*
|
|
* search for the base starting menu for NAME.
|
|
* return a pointer to the portion of NAME that remains
|
|
*/
|
|
char *
|
|
menu_find_base(menu_t ** menu, char *path)
|
|
{
|
|
|
|
menu_t *m = NULL;
|
|
menuitem_t *item;
|
|
|
|
assert(menu != NULL);
|
|
assert(CurrentBar != NULL);
|
|
|
|
D_MENUBAR(("menu_find_base(0x%08x, \"%s\")\n", menu, path));
|
|
|
|
if (path[0] == '\0')
|
|
return path;
|
|
|
|
if (strchr(path, '/') != NULL) {
|
|
register char *p = path;
|
|
|
|
while ((p = strchr(p, '/')) != NULL) {
|
|
p++;
|
|
if (*p == '/')
|
|
path = p;
|
|
}
|
|
if (path[0] == '/') {
|
|
path++;
|
|
*menu = NULL;
|
|
}
|
|
while ((p = strchr(path, '/')) != NULL) {
|
|
p[0] = '\0';
|
|
if (path[0] == '\0')
|
|
return NULL;
|
|
if (!strcmp(path, DOT)) {
|
|
/* nothing to do */
|
|
} else if (!strcmp(path, DOTS)) {
|
|
if (*menu != NULL)
|
|
*menu = (*menu)->parent;
|
|
} else {
|
|
path = menu_find_base(menu, path);
|
|
if (path[0] != '\0') { /* not found */
|
|
p[0] = '/'; /* fix-up name again */
|
|
return path;
|
|
}
|
|
}
|
|
|
|
path = (p + 1);
|
|
}
|
|
}
|
|
if (!strcmp(path, DOTS)) {
|
|
path += strlen(DOTS);
|
|
if (*menu != NULL)
|
|
*menu = (*menu)->parent;
|
|
return path;
|
|
}
|
|
/* find this menu */
|
|
if (*menu == NULL) {
|
|
for (m = CurrentBar->tail; m != NULL; m = m->prev) {
|
|
if (!strcmp(path, m->name))
|
|
break;
|
|
}
|
|
} else {
|
|
/* find this menu */
|
|
for (item = (*menu)->tail; item != NULL; item = item->prev) {
|
|
if (item->entry.type == MenuSubMenu &&
|
|
!strcmp(path, (item->entry.submenu.menu)->name)) {
|
|
m = (item->entry.submenu.menu);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (m != NULL) {
|
|
*menu = m;
|
|
path += strlen(path);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* delete this entire menu
|
|
*/
|
|
menu_t *
|
|
menu_delete(menu_t * menu)
|
|
{
|
|
|
|
menu_t *parent = NULL, *prev, *next;
|
|
menuitem_t *item;
|
|
|
|
assert(CurrentBar != NULL);
|
|
if (menu == NULL)
|
|
return NULL;
|
|
|
|
D_MENUBAR(("menu_delete(\"%s\")\n", menu->name));
|
|
|
|
/* delete the entire menu */
|
|
parent = menu->parent;
|
|
|
|
/* unlink MENU */
|
|
prev = menu->prev;
|
|
next = menu->next;
|
|
if (prev != NULL)
|
|
prev->next = next;
|
|
if (next != NULL)
|
|
next->prev = prev;
|
|
|
|
/* fix the index */
|
|
if (parent == NULL) {
|
|
const int len = (menu->len + HSPACE);
|
|
|
|
if (CurrentBar->tail == menu)
|
|
CurrentBar->tail = prev;
|
|
if (CurrentBar->head == menu)
|
|
CurrentBar->head = next;
|
|
|
|
for (next = menu->next; next != NULL; next = next->next)
|
|
next->x -= len;
|
|
} else {
|
|
for (item = parent->tail; item != NULL; item = item->prev) {
|
|
if (item->entry.type == MenuSubMenu &&
|
|
item->entry.submenu.menu == menu) {
|
|
item->entry.submenu.menu = NULL;
|
|
menuitem_free(menu->parent, item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
item = menu->tail;
|
|
while (item != NULL) {
|
|
menuitem_t *p = item->prev;
|
|
|
|
menuitem_free(menu, item);
|
|
item = p;
|
|
}
|
|
|
|
if (menu->name != NULL)
|
|
FREE(menu->name);
|
|
FREE(menu);
|
|
|
|
return parent;
|
|
}
|
|
|
|
menu_t *
|
|
menu_add(menu_t * parent, char *path)
|
|
{
|
|
|
|
menu_t *menu;
|
|
|
|
assert(CurrentBar != NULL);
|
|
|
|
D_MENUBAR(("menu_add(\"%s\", \"%s\")\n", (parent ? parent->name : "<nil>"), path));
|
|
|
|
if (strchr(path, '/') != NULL) {
|
|
register char *p;
|
|
|
|
if (path[0] == '/') {
|
|
/* shouldn't happen */
|
|
path++;
|
|
parent = NULL;
|
|
}
|
|
while ((p = strchr(path, '/')) != NULL) {
|
|
p[0] = '\0';
|
|
if (path[0] == '\0')
|
|
return NULL;
|
|
|
|
parent = menu_add(parent, path);
|
|
path = (p + 1);
|
|
}
|
|
}
|
|
if (!strcmp(path, DOTS))
|
|
return (parent != NULL ? parent->parent : parent);
|
|
|
|
if (!strcmp(path, DOT) || path[0] == '\0')
|
|
return parent;
|
|
|
|
/* allocate a new menu */
|
|
if ((menu = (menu_t *) MALLOC(sizeof(menu_t))) == NULL)
|
|
return parent;
|
|
|
|
menu->width = 0;
|
|
menu->parent = parent;
|
|
menu->len = strlen(path);
|
|
menu->name = MALLOC((menu->len + 1));
|
|
if (menu->name == NULL) {
|
|
FREE(menu);
|
|
return parent;
|
|
}
|
|
strcpy(menu->name, path);
|
|
|
|
/* initialize head/tail */
|
|
menu->head = menu->tail = NULL;
|
|
menu->prev = menu->next = NULL;
|
|
|
|
menu->win = None;
|
|
menu->x = menu->y = menu->w = menu->h = 0;
|
|
menu->item = NULL;
|
|
|
|
/* add to tail of list */
|
|
if (parent == NULL) {
|
|
menu->prev = CurrentBar->tail;
|
|
if (CurrentBar->tail != NULL)
|
|
CurrentBar->tail->next = menu;
|
|
CurrentBar->tail = menu;
|
|
if (CurrentBar->head == NULL)
|
|
CurrentBar->head = menu; /* fix head */
|
|
if (menu->prev)
|
|
menu->x = (menu->prev->x + menu->prev->len + HSPACE);
|
|
} else {
|
|
menuitem_t *item;
|
|
|
|
item = menuitem_add(parent, path, "", "");
|
|
if (item == NULL) {
|
|
FREE(menu);
|
|
return parent;
|
|
}
|
|
assert(item->entry.type == MenuLabel);
|
|
item->entry.type = MenuSubMenu;
|
|
item->entry.submenu.menu = menu;
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
void
|
|
drawbox_menubar(int x, int len, int state)
|
|
{
|
|
|
|
GC top = None, bot = None;
|
|
|
|
x = Width2Pixel(x);
|
|
len = Width2Pixel(len + HSPACE);
|
|
if (x >= TermWin.width)
|
|
return;
|
|
else if (x + len >= TermWin.width)
|
|
len = (TermWin_TotalWidth() - x);
|
|
|
|
#ifdef MENUBAR_SHADOW_IN
|
|
state = -state;
|
|
#endif
|
|
switch (state) {
|
|
case +1:
|
|
top = topShadowGC;
|
|
bot = botShadowGC;
|
|
break; /* SHADOW_OUT */
|
|
case -1:
|
|
top = botShadowGC;
|
|
bot = topShadowGC;
|
|
break; /* SHADOW_IN */
|
|
case 0:
|
|
top = bot = neutralGC;
|
|
break; /* neutral */
|
|
}
|
|
|
|
if (!(menubar_is_pixmapped()))
|
|
Draw_Shadow(menuBar.win, top, bot,
|
|
x, 0, len, menuBar_TotalHeight());
|
|
}
|
|
|
|
void
|
|
drawtriangle(int x, int y, int state)
|
|
{
|
|
|
|
GC top = None, bot = None;
|
|
int w;
|
|
|
|
#ifdef MENUBAR_SHADOW_IN
|
|
state = -state;
|
|
#endif
|
|
switch (state) {
|
|
case +1:
|
|
top = topShadowGC;
|
|
bot = botShadowGC;
|
|
break; /* SHADOW_OUT */
|
|
case -1:
|
|
top = botShadowGC;
|
|
bot = topShadowGC;
|
|
break; /* SHADOW_IN */
|
|
case 0:
|
|
top = bot = neutralGC;
|
|
break; /* neutral */
|
|
}
|
|
|
|
w = menu_height() / 2;
|
|
|
|
x -= (SHADOW + MENU_MARGIN) + (3 * w / 2);
|
|
y += (SHADOW + MENU_MARGIN) + (w / 2);
|
|
|
|
Draw_Triangle(ActiveMenu->win, top, bot, x, y, w, 'r');
|
|
}
|
|
|
|
void
|
|
drawbox_menuitem(int y, int state)
|
|
{
|
|
|
|
GC top = None, bot = None;
|
|
|
|
#ifdef MENU_SHADOW_IN
|
|
state = -state;
|
|
#endif
|
|
switch (state) {
|
|
case +1:
|
|
top = topShadowGC;
|
|
bot = botShadowGC;
|
|
break; /* SHADOW_OUT */
|
|
case -1:
|
|
top = botShadowGC;
|
|
bot = topShadowGC;
|
|
break; /* SHADOW_IN */
|
|
case 0:
|
|
top = bot = neutralGC;
|
|
break; /* neutral */
|
|
}
|
|
|
|
if (!(menubar_is_pixmapped()))
|
|
Draw_Shadow(ActiveMenu->win, top, bot,
|
|
SHADOW + 0,
|
|
SHADOW + y,
|
|
ActiveMenu->w - 2 * (SHADOW),
|
|
menu_height() + 2 * MENU_MARGIN);
|
|
XFlush(Xdisplay);
|
|
}
|
|
|
|
#if DEBUG >= DEBUG_MENU_LAYOUT
|
|
void
|
|
print_menu_ancestors(menu_t * menu)
|
|
{
|
|
|
|
if (menu == NULL) {
|
|
D_MENU_LAYOUT(("Top Level menu\n"));
|
|
return;
|
|
}
|
|
D_MENU_LAYOUT(("menu %s ", menu->name));
|
|
if (menu->parent != NULL) {
|
|
menuitem_t *item;
|
|
|
|
for (item = menu->parent->head; item != NULL; item = item->next) {
|
|
if (item->entry.type == MenuSubMenu &&
|
|
item->entry.submenu.menu == menu) {
|
|
break;
|
|
}
|
|
}
|
|
if (item == NULL) {
|
|
fprintf(stderr, "is an orphan!\n");
|
|
return;
|
|
}
|
|
}
|
|
fprintf(stderr, "\n");
|
|
print_menu_ancestors(menu->parent);
|
|
}
|
|
|
|
void
|
|
print_menu_descendants(menu_t * menu)
|
|
{
|
|
|
|
menuitem_t *item;
|
|
menu_t *parent;
|
|
int i, level = 0;
|
|
|
|
parent = menu;
|
|
do {
|
|
level++;
|
|
parent = parent->parent;
|
|
}
|
|
while (parent != NULL);
|
|
|
|
for (i = 0; i < level; i++)
|
|
fprintf(stderr, ">");
|
|
fprintf(stderr, "%s\n", menu->name);
|
|
|
|
for (item = menu->head; item != NULL; item = item->next) {
|
|
if (item->entry.type == MenuSubMenu) {
|
|
if (item->entry.submenu.menu == NULL)
|
|
fprintf(stderr, "> %s == NULL\n", item->name);
|
|
else
|
|
print_menu_descendants(item->entry.submenu.menu);
|
|
} else {
|
|
for (i = 0; i < level; i++)
|
|
fprintf(stderr, "+");
|
|
if (item->entry.type == MenuLabel)
|
|
fprintf(stderr, "label: ");
|
|
fprintf(stderr, "%s\n", item->name);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < level; i++)
|
|
fprintf(stderr, "<");
|
|
fprintf(stderr, "\n");
|
|
}
|
|
#endif
|
|
|
|
/* pop up/down the current menu and redraw the menuBar button */
|
|
void
|
|
menu_show(void)
|
|
{
|
|
|
|
int x, y, newx, newy, xright;
|
|
menuitem_t *item;
|
|
XSetWindowAttributes attr = Attributes;
|
|
Window true_parent;
|
|
|
|
if (ActiveMenu == NULL)
|
|
return;
|
|
|
|
attr.override_redirect = TRUE;
|
|
x = ActiveMenu->x;
|
|
if (ActiveMenu->parent == NULL) {
|
|
register int h;
|
|
|
|
drawbox_menubar(x, ActiveMenu->len, -1);
|
|
x = Width2Pixel(x);
|
|
|
|
ActiveMenu->y = 1;
|
|
ActiveMenu->w = Menu_PixelWidth(ActiveMenu);
|
|
|
|
/* find the height */
|
|
for (h = 0, item = ActiveMenu->head; item != NULL; item = item->next) {
|
|
if (isSeparator(item->name))
|
|
h += SEPARATOR_HEIGHT;
|
|
else
|
|
h += menu_height();
|
|
}
|
|
ActiveMenu->h = h + 2 * (SHADOW + MENU_MARGIN);
|
|
}
|
|
if (ActiveMenu->win == None) {
|
|
XTranslateCoordinates(Xdisplay, TermWin.vt, Xroot, 0, 0, &newx, &newy, &true_parent);
|
|
if (x < newx) {
|
|
x += newx;
|
|
}
|
|
if (x + ActiveMenu->w >= (ScreenOfDisplay(Xdisplay, Xscreen))->width) {
|
|
register int dx = ((ActiveMenu->w + x) - (ScreenOfDisplay(Xdisplay, Xscreen))->width);
|
|
|
|
x -= dx;
|
|
ActiveMenu->x -= dx;
|
|
}
|
|
y = ActiveMenu->y + newy;
|
|
if (y + ActiveMenu->h >= (ScreenOfDisplay(Xdisplay, Xscreen))->height) {
|
|
register int dy = ((ActiveMenu->h + y) - (ScreenOfDisplay(Xdisplay, Xscreen))->height);
|
|
|
|
y -= dy;
|
|
ActiveMenu->y -= dy;
|
|
}
|
|
ActiveMenu->win = XCreateWindow(Xdisplay, Xroot,
|
|
x,
|
|
y,
|
|
ActiveMenu->w,
|
|
ActiveMenu->h,
|
|
0,
|
|
Xdepth, InputOutput,
|
|
DefaultVisual(Xdisplay, Xscreen),
|
|
CWBackPixel | CWBorderPixel
|
|
| CWColormap | CWOverrideRedirect
|
|
| CWSaveUnder | CWBackingStore,
|
|
&attr
|
|
);
|
|
|
|
XMapWindow(Xdisplay, ActiveMenu->win);
|
|
}
|
|
if (!(menubar_is_pixmapped()))
|
|
Draw_Shadow(ActiveMenu->win,
|
|
topShadowGC, botShadowGC,
|
|
0, 0,
|
|
ActiveMenu->w, ActiveMenu->h);
|
|
|
|
/* determine the correct right-alignment */
|
|
for (xright = 0, item = ActiveMenu->head; item != NULL; item = item->next) {
|
|
if (item->len2 > xright) {
|
|
xright = item->len2;
|
|
}
|
|
}
|
|
D_MENU_LAYOUT(("xright == %d\n", xright));
|
|
|
|
for (y = 0, item = ActiveMenu->head; item != NULL; item = item->next) {
|
|
|
|
const int xoff = (SHADOW + Width2Pixel(HSPACE) / 2);
|
|
const int yoff = (SHADOW + MENU_MARGIN);
|
|
register int h;
|
|
GC gc = menubarGC;
|
|
|
|
if (isSeparator(item->name)) {
|
|
if (!(menubar_is_pixmapped()))
|
|
Draw_Shadow(ActiveMenu->win,
|
|
topShadowGC, botShadowGC,
|
|
xoff,
|
|
yoff + y + SEPARATOR_HALFHEIGHT,
|
|
ActiveMenu->w - (2 * xoff),
|
|
0);
|
|
h = SEPARATOR_HEIGHT;
|
|
} else {
|
|
char *name = item->name;
|
|
int len = item->len;
|
|
|
|
if (item->entry.type == MenuLabel) {
|
|
gc = botShadowGC;
|
|
} else if (item->entry.type == MenuSubMenu) {
|
|
register int x1, y1;
|
|
menuitem_t *it;
|
|
menu_t *menu = item->entry.submenu.menu;
|
|
|
|
drawtriangle(ActiveMenu->w, y, +1);
|
|
|
|
name = menu->name;
|
|
len = menu->len;
|
|
|
|
y1 = ActiveMenu->y + y;
|
|
|
|
/* place sub-menu at midpoint of parent menu */
|
|
menu->w = Menu_PixelWidth(menu);
|
|
x1 = ActiveMenu->w / 2;
|
|
|
|
/* right-flush menu if it's too small */
|
|
if (x1 > menu->w)
|
|
x1 += (x1 - menu->w);
|
|
x1 += x;
|
|
|
|
/* find the height of this submenu */
|
|
for (h = 0, it = menu->head; it != NULL; it = it->next) {
|
|
if (isSeparator(it->name))
|
|
h += SEPARATOR_HEIGHT;
|
|
else
|
|
h += menu_height();
|
|
}
|
|
menu->h = h + 2 * (SHADOW + MENU_MARGIN);
|
|
|
|
menu->x = x1;
|
|
menu->y = y1;
|
|
} else if (item->name2 && !strcmp(name, item->name2))
|
|
name = NULL;
|
|
|
|
if (len && name) {
|
|
D_MENU_LAYOUT(("len == %d, name == %s\n", len, name));
|
|
#ifdef KANJI
|
|
if (fontset)
|
|
XmbDrawString(Xdisplay,
|
|
ActiveMenu->win, fontset, gc,
|
|
xoff,
|
|
yoff + y + menu_height() - (MENU_MARGIN + TermWin.font->descent),
|
|
name, len);
|
|
else
|
|
#endif
|
|
XDrawString(Xdisplay,
|
|
ActiveMenu->win, gc,
|
|
xoff,
|
|
yoff + y + menu_height() - (MENU_MARGIN + TermWin.font->descent),
|
|
name, len);
|
|
}
|
|
len = item->len2;
|
|
name = item->name2;
|
|
if (len && name) {
|
|
D_MENU_LAYOUT(("len2 == %d, name2 == %s\n", len, name));
|
|
#ifdef KANJI
|
|
if (fontset)
|
|
XmbDrawString(Xdisplay,
|
|
ActiveMenu->win, fontset, gc,
|
|
ActiveMenu->w - (xoff + Width2Pixel(xright)),
|
|
yoff + y + menu_height() - (MENU_MARGIN + TermWin.font->descent),
|
|
name, len);
|
|
else
|
|
#endif
|
|
XDrawString(Xdisplay,
|
|
ActiveMenu->win, gc,
|
|
ActiveMenu->w - (xoff + Width2Pixel(xright)),
|
|
yoff + y + menu_height() - (MENU_MARGIN + TermWin.font->descent),
|
|
name, len);
|
|
}
|
|
h = menu_height();
|
|
}
|
|
y += h;
|
|
}
|
|
}
|
|
void
|
|
menu_display(void (*update) (void))
|
|
{
|
|
|
|
D_MENUBAR(("menu_display(0x%08x)\n", update));
|
|
|
|
if (ActiveMenu == NULL)
|
|
return;
|
|
|
|
if (ActiveMenu->win != None) {
|
|
XDestroyWindow(Xdisplay, ActiveMenu->win);
|
|
ActiveMenu->win = None;
|
|
}
|
|
ActiveMenu->item = NULL;
|
|
|
|
if (ActiveMenu->parent == NULL)
|
|
drawbox_menubar(ActiveMenu->x, ActiveMenu->len, +1);
|
|
ActiveMenu = ActiveMenu->parent;
|
|
update();
|
|
}
|
|
|
|
void
|
|
menu_hide_all(void)
|
|
{
|
|
|
|
D_MENUBAR(("menu_hide_all()\n"));
|
|
menu_display(menu_hide_all);
|
|
} void
|
|
menu_hide(void)
|
|
{
|
|
|
|
D_MENUBAR(("menu_hide()\n"));
|
|
menu_display(menu_show);
|
|
} void
|
|
|
|
menu_clear(menu_t * menu)
|
|
{
|
|
|
|
D_MENUBAR(("menu_clear(\"%s\")\n", (menu ? menu->name : "<nil>")));
|
|
|
|
if (menu != NULL) {
|
|
menuitem_t *item = menu->tail;
|
|
|
|
while (item != NULL) {
|
|
menuitem_free(menu, item);
|
|
/* it didn't get freed ... why? */
|
|
if (item == menu->tail)
|
|
return;
|
|
item = menu->tail;
|
|
}
|
|
menu->width = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
menubar_clear(void)
|
|
{
|
|
|
|
if (CurrentBar != NULL) {
|
|
menu_t *menu = CurrentBar->tail;
|
|
|
|
while (menu != NULL) {
|
|
menu_t *prev = menu->prev;
|
|
|
|
menu_delete(menu);
|
|
menu = prev;
|
|
}
|
|
CurrentBar->head = CurrentBar->tail = ActiveMenu = NULL;
|
|
|
|
if (CurrentBar->title) {
|
|
FREE(CurrentBar->title);
|
|
CurrentBar->title = NULL;
|
|
}
|
|
menuarrow_free(0); /* remove all arrow functions */
|
|
}
|
|
ActiveMenu = NULL;
|
|
}
|
|
|
|
#if (MENUBAR_MAX > 1)
|
|
/* find if menu already exists */
|
|
bar_t *
|
|
menubar_find(const char *name)
|
|
{
|
|
|
|
bar_t *bar = CurrentBar;
|
|
|
|
D_MENUBAR_STACKING(("looking for [menu:%s]...\n", name ? name : "(nil)"));
|
|
if (bar == NULL || name == NULL)
|
|
return NULL;
|
|
|
|
if (strlen(name) && strcmp(name, "*")) {
|
|
do {
|
|
if (!strcmp(bar->name, name)) {
|
|
D_MENUBAR_STACKING(("Found!\n"));
|
|
return bar;
|
|
}
|
|
bar = bar->next;
|
|
}
|
|
while (bar != CurrentBar);
|
|
bar = NULL;
|
|
}
|
|
D_MENUBAR_STACKING(("%s found!\n", (bar ? "" : " NOT")));
|
|
return bar;
|
|
}
|
|
|
|
int
|
|
menubar_push(const char *name)
|
|
{
|
|
|
|
int ret = 1;
|
|
bar_t *bar;
|
|
|
|
if (CurrentBar == NULL) {
|
|
/* allocate first one */
|
|
bar = (bar_t *) MALLOC(sizeof(bar_t));
|
|
|
|
if (bar == NULL)
|
|
return 0;
|
|
|
|
memset(bar, 0, sizeof(bar_t));
|
|
/* circular linked-list */
|
|
bar->next = bar->prev = bar;
|
|
bar->head = bar->tail = NULL;
|
|
bar->title = NULL;
|
|
CurrentBar = bar;
|
|
Nbars++;
|
|
|
|
menubar_clear();
|
|
} else {
|
|
/* find if menu already exists */
|
|
bar = menubar_find(name);
|
|
if (bar != NULL) {
|
|
/* found it, use it */
|
|
CurrentBar = bar;
|
|
} else {
|
|
/* create if needed, or reuse the existing empty menubar */
|
|
if (CurrentBar->head != NULL) {
|
|
/* need to malloc another one */
|
|
if (Nbars < MENUBAR_MAX)
|
|
bar = (bar_t *) MALLOC(sizeof(bar_t));
|
|
else
|
|
bar = NULL;
|
|
|
|
/* malloc failed or too many menubars, reuse another */
|
|
if (bar == NULL) {
|
|
bar = CurrentBar->next;
|
|
ret = -1;
|
|
} else {
|
|
bar->head = bar->tail = NULL;
|
|
bar->title = NULL;
|
|
|
|
bar->next = CurrentBar->next;
|
|
CurrentBar->next = bar;
|
|
bar->prev = CurrentBar;
|
|
bar->next->prev = bar;
|
|
|
|
Nbars++;
|
|
}
|
|
CurrentBar = bar;
|
|
|
|
}
|
|
menubar_clear();
|
|
}
|
|
}
|
|
|
|
/* give menubar this name */
|
|
strncpy(CurrentBar->name, name, MAXNAME);
|
|
CurrentBar->name[MAXNAME - 1] = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* switch to a menu called NAME and remove it */
|
|
void
|
|
menubar_remove(const char *name)
|
|
{
|
|
|
|
bar_t *bar;
|
|
|
|
if ((bar = menubar_find(name)) == NULL)
|
|
return;
|
|
CurrentBar = bar;
|
|
|
|
do {
|
|
menubar_clear();
|
|
/*
|
|
* pop a menubar, clean it up first
|
|
*/
|
|
if (CurrentBar != NULL) {
|
|
bar_t *prev = CurrentBar->prev;
|
|
bar_t *next = CurrentBar->next;
|
|
|
|
if (prev == next && prev == CurrentBar) { /* only 1 left */
|
|
prev = NULL;
|
|
Nbars = 0; /* safety */
|
|
} else {
|
|
next->prev = prev;
|
|
prev->next = next;
|
|
Nbars--;
|
|
}
|
|
|
|
FREE(CurrentBar);
|
|
CurrentBar = prev;
|
|
}
|
|
}
|
|
while (CurrentBar && !strcmp(name, "*"));
|
|
}
|
|
|
|
void
|
|
action_decode(FILE * fp, action_t * act)
|
|
{
|
|
|
|
unsigned char *str;
|
|
short len;
|
|
|
|
if (act == NULL || (len = act->len) == 0 || (str = act->str) == NULL)
|
|
return;
|
|
|
|
if (act->type == MenuTerminalAction) {
|
|
fprintf(fp, "^@");
|
|
/* can strip trailing ^G from XTerm sequence */
|
|
if (str[0] == 033 && str[1] == ']' && str[len - 1] == 007)
|
|
len--;
|
|
} else if (str[0] == 033) {
|
|
switch (str[1]) {
|
|
case '[':
|
|
case ']':
|
|
break;
|
|
|
|
case 'x':
|
|
/* can strip trailing '\r' from M-x sequence */
|
|
if (str[len - 1] == '\r')
|
|
len--;
|
|
/* drop */
|
|
|
|
default:
|
|
fprintf(fp, "M-"); /* meta prefix */
|
|
str++;
|
|
len--;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* control character form is preferred, since backslash-escaping
|
|
* can be really ugly looking when the backslashes themselves also
|
|
* have to be escaped to avoid Shell (or whatever scripting
|
|
* language) interpretation
|
|
*/
|
|
while (len > 0) {
|
|
unsigned char ch = *str++;
|
|
|
|
switch (ch) {
|
|
case 033:
|
|
fprintf(fp, "\\e");
|
|
break; /* escape */
|
|
case '\r':
|
|
fprintf(fp, "\\r");
|
|
break; /* carriage-return */
|
|
case '\\':
|
|
fprintf(fp, "\\\\");
|
|
break; /* backslash */
|
|
case '^':
|
|
fprintf(fp, "\\^");
|
|
break; /* caret */
|
|
case 127:
|
|
fprintf(fp, "^?");
|
|
default:
|
|
if (ch <= 31)
|
|
fprintf(fp, "^%c", ('@' + ch));
|
|
else if (ch > 127)
|
|
fprintf(fp, "\\%o", ch);
|
|
else
|
|
fprintf(fp, "%c", ch);
|
|
break;
|
|
}
|
|
len--;
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
void
|
|
menu_dump(FILE * fp, menu_t * menu)
|
|
{
|
|
|
|
menuitem_t *item;
|
|
|
|
/* create a new menu and clear it */
|
|
fprintf(fp, (menu->parent ? "./%s/*\n" : "/%s/*\n"), menu->name);
|
|
|
|
for (item = menu->head; item != NULL; item = item->next) {
|
|
switch (item->entry.type) {
|
|
case MenuSubMenu:
|
|
if (item->entry.submenu.menu == NULL)
|
|
fprintf(fp, "> %s == NULL\n", item->name);
|
|
else
|
|
menu_dump(fp, item->entry.submenu.menu);
|
|
break;
|
|
|
|
case MenuLabel:
|
|
fprintf(fp, "{%s}\n",
|
|
(strlen(item->name) ? item->name : "-"));
|
|
break;
|
|
|
|
case MenuTerminalAction:
|
|
case MenuAction:
|
|
fprintf(fp, "{%s}", item->name);
|
|
if (item->name2 != NULL && strlen(item->name2))
|
|
fprintf(fp, "{%s}", item->name2);
|
|
fprintf(fp, "\t");
|
|
action_decode(fp, &(item->entry.action));
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, (menu->parent ? "../\n" : "/\n\n"));
|
|
}
|
|
|
|
void
|
|
menubar_dump(FILE * fp)
|
|
{
|
|
|
|
bar_t *bar = CurrentBar;
|
|
time_t t;
|
|
|
|
if (bar == NULL || fp == NULL)
|
|
return;
|
|
time(&t);
|
|
|
|
fprintf(fp,
|
|
"# " APL_NAME " (%s) Pid: %u\n# Date: %s\n\n",
|
|
rs_name, (unsigned int) getpid(), ctime(&t));
|
|
|
|
/* dump in reverse order */
|
|
bar = CurrentBar->prev;
|
|
do {
|
|
menu_t *menu;
|
|
int i;
|
|
|
|
fprintf(fp, "[menu:%s]\n", bar->name);
|
|
|
|
if (bar->title != NULL)
|
|
fprintf(fp, "[title:%s]\n", bar->title);
|
|
|
|
for (i = 0; i < NARROWS; i++) {
|
|
switch (bar->arrows[i].type) {
|
|
case MenuTerminalAction:
|
|
case MenuAction:
|
|
fprintf(fp, "<%c>", Arrows[i].name);
|
|
action_decode(fp, &(bar->arrows[i]));
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
for (menu = bar->head; menu != NULL; menu = menu->next)
|
|
menu_dump(fp, menu);
|
|
|
|
fprintf(fp, "\n[done:%s]\n\n", bar->name);
|
|
bar = bar->prev;
|
|
}
|
|
while (bar != CurrentBar->prev);
|
|
}
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
|
|
/*
|
|
* read in menubar commands from FILENAME
|
|
* ignore all input before the tag line [menu] or [menu:???]
|
|
*
|
|
* Note that since File_find () is used, FILENAME can be semi-colon
|
|
* delimited such that the second part can refer to a tag
|
|
* so that a large `database' of menus can be collected together
|
|
*
|
|
* FILENAME = "file"
|
|
* FILENAME = "file;"
|
|
* read `file' starting with first [menu] or [menu:???] line
|
|
*
|
|
* FILENAME = "file;tag"
|
|
* read `file' starting with [menu:tag]
|
|
*/
|
|
void
|
|
menubar_read(const char *filename)
|
|
{
|
|
|
|
/* read in a menu from a file */
|
|
FILE *fp;
|
|
char buffer[256];
|
|
char *p, *tag = NULL;
|
|
const char *file;
|
|
|
|
if (!filename || !strlen(filename))
|
|
return;
|
|
|
|
file = find_file(filename, ".menu");
|
|
if (file == NULL || (fp = fopen(file, "rb")) == NULL) {
|
|
return;
|
|
}
|
|
#if (MENUBAR_MAX > 1)
|
|
/* semi-colon delimited */
|
|
if ((tag = strchr(filename, ';')) != NULL) {
|
|
tag++;
|
|
if (*tag == '\0') {
|
|
tag = NULL;
|
|
}
|
|
}
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
|
|
D_MENUBAR(("looking for [menu:%s]\n", tag ? tag : "(nil)"));
|
|
|
|
while ((p = fgets(buffer, sizeof(buffer), fp)) != NULL) {
|
|
|
|
int n;
|
|
|
|
D_MENUBAR(("Got \"%s\"\n", p));
|
|
|
|
if ((n = str_leading_match(p, "[menu")) != 0) {
|
|
if (tag) {
|
|
/* looking for [menu:tag] */
|
|
if (p[n] == ':' && p[n + 1] != ']') {
|
|
n++;
|
|
n += str_leading_match(p + n, tag);
|
|
if (p[n] == ']') {
|
|
D_MENUBAR(("[menu:%s]\n", tag));
|
|
break;
|
|
}
|
|
}
|
|
} else if (p[n] == ':' || p[n] == ']')
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* found [menu], [menu:???] tag */
|
|
while (p != NULL) {
|
|
|
|
int n;
|
|
|
|
D_MENUBAR(("read line = %s\n", p));
|
|
|
|
/* looking for [done:tag] or [done:] */
|
|
if ((n = str_leading_match(p, "[done")) != 0) {
|
|
if (p[n] == ']') {
|
|
menu_readonly = 1;
|
|
break;
|
|
} else if (p[n] == ':') {
|
|
n++;
|
|
if (p[n] == ']') {
|
|
menu_readonly = 1;
|
|
break;
|
|
} else if (tag) {
|
|
n += str_leading_match(p + n, tag);
|
|
if (p[n] == ']') {
|
|
D_MENUBAR(("[done:%s]\n", tag));
|
|
menu_readonly = 1;
|
|
break;
|
|
}
|
|
} else {
|
|
/* what? ... skip this line */
|
|
p[0] = COMMENT_CHAR;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* remove leading/trailing space
|
|
* and strip-off leading/trailing quotes
|
|
* skip blank or comment lines
|
|
*/
|
|
p = str_trim(p);
|
|
if (p != NULL && *p && *p != COMMENT_CHAR) {
|
|
menu_readonly = 0; /* if case we read another file */
|
|
menubar_dispatch(p);
|
|
}
|
|
/* get another line */
|
|
p = fgets(buffer, sizeof(buffer), fp);
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* user interface for building/deleting and otherwise managing menus
|
|
*/
|
|
void
|
|
menubar_dispatch(char *str)
|
|
{
|
|
|
|
static menu_t *BuildMenu = NULL; /* the menu currently being built */
|
|
int n, cmd;
|
|
char *path, *name, *name2;
|
|
|
|
D_MENUBAR(("menubar_dispatch(%s) called\n", str));
|
|
|
|
if (menubar_visible() && ActiveMenu != NULL)
|
|
menubar_expose();
|
|
else
|
|
ActiveMenu = NULL;
|
|
|
|
cmd = *str;
|
|
switch (cmd) {
|
|
case '.':
|
|
case '/': /* absolute & relative path */
|
|
case MENUITEM_BEG: /* menuitem */
|
|
/* add `+' prefix for these cases */
|
|
cmd = '+';
|
|
break;
|
|
|
|
case '+':
|
|
case '-':
|
|
str++; /* skip cmd character */
|
|
break;
|
|
|
|
case '<':
|
|
#if (MENUBAR_MAX > 1)
|
|
if (CurrentBar == NULL)
|
|
break;
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
if (str[1] && str[2] == '>') /* arrow commands */
|
|
menuarrow_add(str);
|
|
break;
|
|
|
|
case '=':
|
|
D_MENUBAR(("Setting title\n"));
|
|
str++;
|
|
if (CurrentBar != NULL && !menu_readonly) {
|
|
if (*str) {
|
|
name = REALLOC(CurrentBar->title, strlen(str) + 1);
|
|
if (name != NULL) {
|
|
strcpy(name, str);
|
|
CurrentBar->title = name;
|
|
}
|
|
menubar_expose();
|
|
} else {
|
|
FREE(CurrentBar->title);
|
|
CurrentBar->title = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '[': /* extended command */
|
|
while (str[0] == '[') {
|
|
char *next = (++str); /* skip leading '[' */
|
|
|
|
if (str[0] == ':') { /* [:command:] */
|
|
do {
|
|
next++;
|
|
if ((next = strchr(next, ':')) == NULL)
|
|
return; /* parse error */
|
|
} while (next[1] != ']');
|
|
|
|
/* remove and skip ':]' */
|
|
*next = '\0';
|
|
next += 2;
|
|
} else {
|
|
if ((next = strchr(next, ']')) == NULL)
|
|
return; /* parse error */
|
|
/* remove and skip ']' */
|
|
*next = '\0';
|
|
next++;
|
|
}
|
|
|
|
if (str[0] == ':') {
|
|
int saved;
|
|
|
|
/* try and dispatch it, regardless of read/write status */
|
|
D_MENUBAR(("Ignoring read-only status to parse command %s\n", str + 1));
|
|
saved = menu_readonly;
|
|
menu_readonly = 0;
|
|
menubar_dispatch(str + 1);
|
|
menu_readonly = saved;
|
|
}
|
|
/* these ones don't require menu stacking */
|
|
else if (!strcmp(str, "clear")) {
|
|
D_MENUBAR(("Extended command \"clear\"\n"));
|
|
menubar_clear();
|
|
} else if (!strcmp(str, "done") || str_leading_match(str, "done:")) {
|
|
D_MENUBAR(("Extended command \"done\"\n"));
|
|
menu_readonly = 1;
|
|
} else if (!strcmp(str, "show")) {
|
|
D_MENUBAR(("Extended command \"show\"\n"));
|
|
map_menuBar(1);
|
|
menu_readonly = 1;
|
|
} else if (!strcmp(str, "hide")) {
|
|
D_MENUBAR(("Extended command \"hide\"\n"));
|
|
map_menuBar(0);
|
|
menu_readonly = 1;
|
|
} else if ((n = str_leading_match(str, "read:")) != 0) {
|
|
/* read in a menu from a file */
|
|
D_MENUBAR(("Extended command \"read\"\n"));
|
|
str += n;
|
|
menubar_read(str);
|
|
} else if ((n = str_leading_match(str, "echo:")) != 0) {
|
|
D_MENUBAR(("Extended command \"echo\"\n"));
|
|
str += n;
|
|
tt_write(str, strlen(str));
|
|
tt_write("\r", 1);
|
|
} else if ((n = str_leading_match(str, "apptitle:")) != 0) {
|
|
D_MENUBAR(("Extended command \"apptitle\"\n"));
|
|
str += n;
|
|
xterm_seq(XTerm_title, str);
|
|
} else if ((n = str_leading_match(str, "title:")) != 0) {
|
|
D_MENUBAR(("Extended command \"title\"\n"));
|
|
str += n;
|
|
if (CurrentBar != NULL && !menu_readonly) {
|
|
if (*str) {
|
|
name = REALLOC(CurrentBar->title, strlen(str) + 1);
|
|
if (name != NULL) {
|
|
strcpy(name, str);
|
|
CurrentBar->title = name;
|
|
}
|
|
menubar_expose();
|
|
} else {
|
|
FREE(CurrentBar->title);
|
|
CurrentBar->title = NULL;
|
|
}
|
|
}
|
|
} else if ((n = str_leading_match(str, "pixmap:")) != 0) {
|
|
D_MENUBAR(("Extended command \"pixmap\"\n"));
|
|
str += n;
|
|
xterm_seq(XTerm_Pixmap, str);
|
|
}
|
|
#if (MENUBAR_MAX > 1)
|
|
else if ((n = str_leading_match(str, "rm")) != 0) {
|
|
D_MENUBAR(("Extended command \"rm\"\n"));
|
|
str += n;
|
|
switch (str[0]) {
|
|
case ':':
|
|
str++;
|
|
menubar_remove(str);
|
|
break;
|
|
|
|
case '\0':
|
|
menubar_remove(str);
|
|
break;
|
|
|
|
case '*':
|
|
menubar_remove(str);
|
|
break;
|
|
}
|
|
menu_readonly = 1;
|
|
} else if ((n = str_leading_match(str, "menu")) != 0) {
|
|
D_MENUBAR(("Extended command \"menu\"\n"));
|
|
str += n;
|
|
switch (str[0]) {
|
|
case ':':
|
|
str++;
|
|
/* add/access menuBar */
|
|
if (*str != '\0' && *str != '*')
|
|
menubar_push(str);
|
|
break;
|
|
default:
|
|
if (CurrentBar == NULL) {
|
|
menubar_push("default");
|
|
}
|
|
}
|
|
|
|
|
|
if (CurrentBar != NULL)
|
|
menu_readonly = 0; /* allow menu build commands */
|
|
} else if (!strcmp(str, "dump")) {
|
|
/* dump current menubars to a file */
|
|
FILE *fp;
|
|
|
|
/* enough space to hold the results */
|
|
char buffer[32];
|
|
|
|
D_MENUBAR(("Extended command \"dump\"\n"));
|
|
sprintf(buffer, "/tmp/" APL_NAME "-%u",
|
|
(unsigned int) getpid());
|
|
|
|
if ((fp = fopen(buffer, "wb")) != NULL) {
|
|
xterm_seq(XTerm_title, buffer);
|
|
menubar_dump(fp);
|
|
fclose(fp);
|
|
}
|
|
} else if (!strcmp(str, "next")) {
|
|
if (CurrentBar) {
|
|
CurrentBar = CurrentBar->next;
|
|
menu_readonly = 1;
|
|
}
|
|
} else if (!strcmp(str, "prev")) {
|
|
if (CurrentBar) {
|
|
CurrentBar = CurrentBar->prev;
|
|
menu_readonly = 1;
|
|
}
|
|
} else if (!strcmp(str, "swap")) {
|
|
/* swap the top 2 menus */
|
|
if (CurrentBar) {
|
|
bar_t *prev = CurrentBar->prev;
|
|
bar_t *next = CurrentBar->next;
|
|
|
|
prev->next = next;
|
|
next->prev = prev;
|
|
|
|
CurrentBar->next = prev;
|
|
CurrentBar->prev = prev->prev;
|
|
|
|
prev->prev->next = CurrentBar;
|
|
prev->prev = CurrentBar;
|
|
|
|
CurrentBar = prev;
|
|
menu_readonly = 1;
|
|
}
|
|
}
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
str = next;
|
|
|
|
BuildMenu = ActiveMenu = NULL;
|
|
menubar_expose();
|
|
D_MENUBAR_STACKING(("menus are read%s\n", menu_readonly ? "only" : "/write"));
|
|
}
|
|
return;
|
|
break;
|
|
}
|
|
|
|
#if (MENUBAR_MAX > 1)
|
|
if (CurrentBar == NULL)
|
|
return;
|
|
if (menu_readonly) {
|
|
D_MENUBAR_STACKING(("menus are read%s\n", menu_readonly ? "only" : "/write"));
|
|
return;
|
|
}
|
|
#endif /* (MENUBAR_MAX > 1) */
|
|
|
|
switch (cmd) {
|
|
case '+':
|
|
case '-':
|
|
path = name = str;
|
|
|
|
name2 = NULL;
|
|
/* parse STR, allow spaces inside (name) */
|
|
if (path[0] != '\0') {
|
|
name = strchr(path, MENUITEM_BEG);
|
|
str = strchr(path, MENUITEM_END);
|
|
if (name != NULL || str != NULL) {
|
|
if (name == NULL || str == NULL || str <= (name + 1)
|
|
|| (name > path && name[-1] != '/')) {
|
|
print_error("menu error <%s>\n", path);
|
|
break;
|
|
}
|
|
if (str[1] == MENUITEM_BEG) {
|
|
name2 = (str + 2);
|
|
str = strchr(name2, MENUITEM_END);
|
|
|
|
if (str == NULL) {
|
|
print_error("menu error <%s>\n", path);
|
|
break;
|
|
}
|
|
name2[-2] = '\0'; /* remove prev MENUITEM_END */
|
|
}
|
|
if (name > path && name[-1] == '/')
|
|
name[-1] = '\0';
|
|
|
|
*name++ = '\0'; /* delimit */
|
|
*str++ = '\0'; /* delimit */
|
|
|
|
while (isspace(*str))
|
|
str++; /* skip space */
|
|
}
|
|
D_MENUBAR(("`%c' path = <%s>, name = <%s>, name2 = <%s>, action = <%s>\n", cmd,
|
|
(path ? path : "(nil)"), (name ? name : "(nil)"), (name2 ? name2 : "(nil)"),
|
|
(str ? str : "(nil)")));
|
|
}
|
|
/* process the different commands */
|
|
switch (cmd) {
|
|
case '+': /* add/replace existing menu or menuitem */
|
|
if (path[0] != '\0') {
|
|
int len;
|
|
|
|
path = menu_find_base(&BuildMenu, path);
|
|
len = strlen(path);
|
|
|
|
/* don't allow menus called `*' */
|
|
if (path[0] == '*') {
|
|
menu_clear(BuildMenu);
|
|
break;
|
|
} else if (len >= 2 && !strcmp((path + len - 2), "/*")) {
|
|
path[len - 2] = '\0';
|
|
}
|
|
if (path[0] != '\0')
|
|
BuildMenu = menu_add(BuildMenu, path);
|
|
}
|
|
if (name != NULL && name[0] != '\0') {
|
|
if (!strcmp(name, SEPARATOR_NAME))
|
|
name = "";
|
|
menuitem_add(BuildMenu, name, name2, str);
|
|
}
|
|
break;
|
|
|
|
case '-': /* delete menu entry */
|
|
if (!strcmp(path, "/*") && (name == NULL || name[0] == '\0')) {
|
|
menubar_clear();
|
|
BuildMenu = NULL;
|
|
menubar_expose();
|
|
break;
|
|
} else if (path[0] != '\0') {
|
|
int len;
|
|
menu_t *menu = BuildMenu;
|
|
|
|
path = menu_find_base(&menu, path);
|
|
len = strlen(path);
|
|
|
|
/* submenu called `*' clears all menu items */
|
|
if (path[0] == '*') {
|
|
menu_clear(menu);
|
|
break; /* done */
|
|
} else if (len >= 2 && !strcmp(&path[len - 2], "/*")) {
|
|
/* done */
|
|
break;
|
|
} else if (path[0] != '\0') {
|
|
BuildMenu = NULL;
|
|
break;
|
|
} else {
|
|
BuildMenu = menu;
|
|
}
|
|
}
|
|
if (BuildMenu != NULL) {
|
|
if (name == NULL || name[0] == '\0') {
|
|
BuildMenu = menu_delete(BuildMenu);
|
|
} else {
|
|
menuitem_t *item;
|
|
|
|
if (!strcmp(name, SEPARATOR_NAME))
|
|
name = "";
|
|
item = menuitem_find(BuildMenu, name);
|
|
|
|
if (item != NULL && item->entry.type != MenuSubMenu) {
|
|
menuitem_free(BuildMenu, item);
|
|
|
|
/* fix up the width */
|
|
BuildMenu->width = 0;
|
|
for (item = BuildMenu->head;
|
|
item != NULL;
|
|
item = item->next) {
|
|
if (BuildMenu->width < (item->len + item->len2))
|
|
BuildMenu->width = (item->len + item->len2);
|
|
}
|
|
}
|
|
}
|
|
menubar_expose();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
draw_Arrows(int name, int state)
|
|
{
|
|
|
|
GC top = None, bot = None;
|
|
|
|
int i;
|
|
|
|
#ifdef MENU_SHADOW_IN
|
|
state = -state;
|
|
#endif
|
|
switch (state) {
|
|
case +1:
|
|
top = topShadowGC;
|
|
bot = botShadowGC;
|
|
break; /* SHADOW_OUT */
|
|
case -1:
|
|
top = botShadowGC;
|
|
bot = topShadowGC;
|
|
break; /* SHADOW_IN */
|
|
case 0:
|
|
top = bot = neutralGC;
|
|
break; /* neutral */
|
|
}
|
|
if (!Arrows_x)
|
|
return;
|
|
|
|
for (i = 0; i < NARROWS; i++) {
|
|
const int w = Width2Pixel(1);
|
|
const int y = (menuBar_TotalHeight() - w) / 2;
|
|
int x = Arrows_x + (5 * Width2Pixel(i)) / 4;
|
|
|
|
if (!name || name == Arrows[i].name)
|
|
Draw_Triangle(menuBar.win, top, bot, x, y, w,
|
|
Arrows[i].name);
|
|
}
|
|
XFlush(Xdisplay);
|
|
}
|
|
|
|
void
|
|
menubar_expose(void)
|
|
{
|
|
|
|
menu_t *menu;
|
|
int x;
|
|
|
|
# ifdef CHANGE_SCROLLCOLOR_ON_FOCUS
|
|
static int focus = -1;
|
|
|
|
# endif
|
|
# ifdef KANJI
|
|
static int fsTry = 0;
|
|
|
|
# endif
|
|
|
|
if (delay_menu_drawing || !menubar_visible())
|
|
return;
|
|
|
|
#ifdef KANJI
|
|
if (!fontset && !fsTry) {
|
|
char *fontname = malloc(strlen(rs_font[0]) + strlen(rs_kfont[0]) + 2);
|
|
int i, mc;
|
|
char **ml, *ds;
|
|
|
|
fsTry = 1;
|
|
if (fontname) {
|
|
setlocale(LC_ALL, "");
|
|
strcpy(fontname, rs_font[0]);
|
|
strcat(fontname, ",");
|
|
strcat(fontname, rs_kfont[0]);
|
|
fontset = XCreateFontSet(Xdisplay, fontname, &ml, &mc, &ds);
|
|
free(fontname);
|
|
if (mc) {
|
|
XFreeStringList(ml);
|
|
fontset = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif /* KANJI */
|
|
|
|
if (menubarGC == None) {
|
|
/* Create the graphics context */
|
|
XGCValues gcvalue;
|
|
|
|
gcvalue.font = TermWin.font->fid;
|
|
|
|
gcvalue.foreground = (Xdepth <= 2 ?
|
|
PixColors[fgColor] :
|
|
PixColors[menuTextColor]);
|
|
menubarGC = XCreateGC(Xdisplay, menuBar.win,
|
|
GCForeground | GCFont,
|
|
&gcvalue);
|
|
|
|
#ifdef KEEP_SCROLLCOLOR
|
|
gcvalue.foreground = PixColors[scrollColor];
|
|
#endif
|
|
neutralGC = XCreateGC(Xdisplay, menuBar.win,
|
|
GCForeground,
|
|
&gcvalue);
|
|
|
|
#ifdef KEEP_SCROLLCOLOR
|
|
gcvalue.foreground = PixColors[bottomShadowColor];
|
|
#endif
|
|
botShadowGC = XCreateGC(Xdisplay, menuBar.win,
|
|
GCForeground | GCFont,
|
|
&gcvalue);
|
|
|
|
#ifdef KEEP_SCROLLCOLOR
|
|
gcvalue.foreground = PixColors[topShadowColor];
|
|
#endif
|
|
topShadowGC = XCreateGC(Xdisplay, menuBar.win,
|
|
GCForeground,
|
|
&gcvalue);
|
|
}
|
|
# ifdef CHANGE_SCROLLCOLOR_ON_FOCUS
|
|
/* Update colors on focus change */
|
|
if (focus != TermWin.focus) {
|
|
XGCValues gcvalue;
|
|
|
|
focus = TermWin.focus;
|
|
|
|
gcvalue.foreground = PixColors[fgColor];
|
|
|
|
# ifdef KEEP_SCROLLCOLOR
|
|
if (Xdepth > 2)
|
|
gcvalue.foreground = PixColors[focus ? scrollColor : unfocusedScrollColor];
|
|
# endif
|
|
|
|
XChangeGC(Xdisplay, neutralGC, GCForeground,
|
|
&gcvalue);
|
|
|
|
gcvalue.background = gcvalue.foreground;
|
|
XChangeGC(Xdisplay, menubarGC, GCBackground,
|
|
&gcvalue);
|
|
XChangeGC(Xdisplay, neutralGC, GCForeground,
|
|
&gcvalue);
|
|
|
|
XSetWindowBackground(Xdisplay, menuBar.win, gcvalue.foreground);
|
|
|
|
gcvalue.foreground = PixColors[bgColor];
|
|
|
|
# ifdef KEEP_SCROLLCOLOR
|
|
gcvalue.foreground = PixColors[focus ? topShadowColor : unfocusedTopShadowColor];
|
|
# endif
|
|
XChangeGC(Xdisplay, topShadowGC,
|
|
GCForeground,
|
|
&gcvalue);
|
|
|
|
# ifdef KEEP_SCROLLCOLOR
|
|
gcvalue.foreground = PixColors[focus ? bottomShadowColor : unfocusedBottomShadowColor];
|
|
# endif
|
|
XChangeGC(Xdisplay, botShadowGC,
|
|
GCForeground,
|
|
&gcvalue);
|
|
|
|
}
|
|
#endif
|
|
|
|
/* make sure the font is correct */
|
|
XSetFont(Xdisplay, menubarGC, TermWin.font->fid);
|
|
XSetFont(Xdisplay, botShadowGC, TermWin.font->fid);
|
|
XClearWindow(Xdisplay, menuBar.win);
|
|
|
|
menu_hide_all();
|
|
|
|
x = 0;
|
|
if (CurrentBar != NULL) {
|
|
for (menu = CurrentBar->head; menu != NULL; menu = menu->next) {
|
|
int len = menu->len;
|
|
|
|
x = (menu->x + menu->len + HSPACE);
|
|
|
|
#if DEBUG >= DEBUG_MENU_LAYOUT
|
|
if (debug_level >= DEBUG_MENU_LAYOUT) {
|
|
print_menu_descendants(menu);
|
|
}
|
|
#endif
|
|
|
|
if (x >= TermWin.ncol)
|
|
len = (TermWin.ncol - (menu->x + HSPACE));
|
|
|
|
drawbox_menubar(menu->x, len, +1);
|
|
|
|
#ifdef KANJI
|
|
if (fontset)
|
|
XmbDrawString(Xdisplay, menuBar.win, fontset, menubarGC, (Width2Pixel(menu->x) + Width2Pixel(HSPACE) / 2),
|
|
menuBar_height() - (TermWin.font->descent) + 1, menu->name, len);
|
|
else
|
|
#endif
|
|
XDrawString(Xdisplay, menuBar.win, menubarGC, (Width2Pixel(menu->x) + Width2Pixel(HSPACE) / 2),
|
|
menuBar_height() - (TermWin.font->descent) + 1, menu->name, len);
|
|
if (x >= TermWin.ncol)
|
|
break;
|
|
}
|
|
}
|
|
drawbox_menubar(x, TermWin.ncol, 1);
|
|
|
|
/* add the menuBar title, if it exists and there's plenty of room */
|
|
Arrows_x = 0;
|
|
if (x < TermWin.ncol) {
|
|
char *str, title[256];
|
|
int len, ncol = TermWin.ncol;
|
|
|
|
if (x < (ncol - (NARROWS + 1))) {
|
|
ncol -= (NARROWS + 1);
|
|
Arrows_x = menuBar_TotalWidth() - (2 * SHADOW + ((5 * Width2Pixel(1)) / 4 * NARROWS) + HSPACE);
|
|
}
|
|
draw_Arrows(0, -1);
|
|
|
|
str = (CurrentBar && CurrentBar->title ? CurrentBar->title : "%n");
|
|
for (len = 0; str[0] && len < sizeof(title) - 1; str++) {
|
|
const char *s = NULL;
|
|
|
|
switch (str[0]) {
|
|
case '%':
|
|
str++;
|
|
switch (str[0]) {
|
|
case 'n':
|
|
s = rs_name;
|
|
break; /* resource name */
|
|
case 'v':
|
|
s = VERSION;
|
|
break; /* version number */
|
|
case '%':
|
|
s = "%";
|
|
break; /* literal '%' */
|
|
}
|
|
if (s != NULL)
|
|
while (*s && len < sizeof(title) - 1)
|
|
title[len++] = *s++;
|
|
break;
|
|
|
|
default:
|
|
title[len++] = str[0];
|
|
break;
|
|
}
|
|
}
|
|
title[len] = '\0';
|
|
|
|
ncol = Pixel2Width(Arrows_x - Width2Pixel(x) - Width2Pixel(len) - Width2Pixel(4));
|
|
#ifdef KANJI
|
|
if (fontset) {
|
|
if (len > 0 && ncol >= 0)
|
|
XmbDrawString(Xdisplay, menuBar.win, fontset, menubarGC,
|
|
Width2Pixel(x) + ((Arrows_x - Width2Pixel(x)) / 2 - (Width2Pixel(len) / 2)),
|
|
menuBar_height() - (TermWin.font->descent) + 1, title, len);
|
|
} else
|
|
#endif
|
|
if (len > 0 && ncol >= 0)
|
|
XDrawString(Xdisplay, menuBar.win, menubarGC,
|
|
Width2Pixel(x) + ((Arrows_x - Width2Pixel(x + len + 1)) / 2),
|
|
menuBar_height() - (TermWin.font->descent) + 1, title, len);
|
|
}
|
|
}
|
|
|
|
int
|
|
menubar_mapping(int map)
|
|
{
|
|
|
|
int change = 0;
|
|
|
|
if (map && !menubar_visible()) {
|
|
menuBar.state = 1;
|
|
XMapWindow(Xdisplay, menuBar.win);
|
|
change = 1;
|
|
} else if (!map && menubar_visible()) {
|
|
menubar_expose();
|
|
menuBar.state = 0;
|
|
XUnmapWindow(Xdisplay, menuBar.win);
|
|
change = 1;
|
|
} else
|
|
menubar_expose();
|
|
|
|
return change;
|
|
}
|
|
|
|
int
|
|
menu_select(XButtonEvent * ev)
|
|
{
|
|
|
|
menuitem_t *thisitem, *item = NULL;
|
|
int this_y = 0, y = 0;
|
|
|
|
Window unused_root, unused_child;
|
|
int unused_root_x, unused_root_y;
|
|
unsigned int unused_mask;
|
|
|
|
if (ActiveMenu == NULL)
|
|
return 0;
|
|
|
|
D_MENUBAR(("menu_select()\n"));
|
|
XQueryPointer(Xdisplay, ActiveMenu->win,
|
|
&unused_root, &unused_child,
|
|
&unused_root_x, &unused_root_y,
|
|
&(ev->x), &(ev->y),
|
|
&unused_mask);
|
|
|
|
if (ActiveMenu->parent != NULL && (ev->x < 0 || ev->y < 0 || (ev->y > menu_height() && ev->x < 0))) {
|
|
menu_hide();
|
|
return 1;
|
|
}
|
|
|
|
/* determine the menu item corresponding to the Y index */
|
|
if (ev->x >= 0 && ev->x <= (ActiveMenu->w - SHADOW)) {
|
|
for (y = 0, item = ActiveMenu->head; item != NULL; item = item->next) {
|
|
int h = menu_height();
|
|
|
|
if (isSeparator(item->name)) {
|
|
h = SEPARATOR_HEIGHT;
|
|
} else if (ev->y >= y && ev->y < (y + h)) {
|
|
break;
|
|
}
|
|
y += h;
|
|
}
|
|
}
|
|
if (item == NULL && ev->type == ButtonRelease) {
|
|
menu_hide_all();
|
|
return 0;
|
|
}
|
|
thisitem = item;
|
|
this_y = y;
|
|
|
|
/* erase the last item */
|
|
if (ActiveMenu->item != NULL) {
|
|
if (ActiveMenu->item != thisitem) {
|
|
for (y = 0, item = ActiveMenu->head; item != NULL; item = item->next) {
|
|
|
|
int h = menu_height();
|
|
|
|
if (isSeparator(item->name)) {
|
|
h = SEPARATOR_HEIGHT;
|
|
} else if (item == ActiveMenu->item) {
|
|
/* erase old menuitem */
|
|
drawbox_menuitem(y, 0); /* No Shadow */
|
|
if (item->entry.type == MenuSubMenu)
|
|
drawtriangle(ActiveMenu->w, y, +1);
|
|
break;
|
|
}
|
|
y += h;
|
|
}
|
|
} else {
|
|
if (ev->type == ButtonRelease) {
|
|
switch (item->entry.type) {
|
|
case MenuLabel:
|
|
case MenuSubMenu:
|
|
menu_hide_all();
|
|
break;
|
|
|
|
case MenuAction:
|
|
case MenuTerminalAction:
|
|
drawbox_menuitem(this_y, -1);
|
|
|
|
#if MENU_DELAY_USEC > 0
|
|
/* use select for timing */
|
|
{
|
|
struct itimerval tv;
|
|
|
|
tv.it_value.tv_sec = 0;
|
|
tv.it_value.tv_usec = MENU_DELAY_USEC;
|
|
|
|
select(0, NULL, NULL, NULL, &tv.it_value);
|
|
}
|
|
#endif
|
|
menu_hide_all(); /* remove menu before sending keys to the application */
|
|
D_MENUBAR(("%s: %s\n", item->name, item->entry.action.str));
|
|
action_dispatch(&(item->entry.action));
|
|
break;
|
|
}
|
|
return 0;
|
|
} else if (item->entry.type != MenuSubMenu) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
ActiveMenu->item = thisitem;
|
|
y = this_y;
|
|
if (thisitem != NULL) {
|
|
item = ActiveMenu->item;
|
|
if (item->entry.type != MenuLabel)
|
|
drawbox_menuitem(y, +1);
|
|
if (item->entry.type == MenuSubMenu) {
|
|
drawtriangle(ActiveMenu->w, y, -1);
|
|
if ((ev->x > ActiveMenu->w / 2) && (ev->y > 0) && (Menu_PixelWidth(item->entry.submenu.menu) + ev->x >= ActiveMenu->w)) {
|
|
ActiveMenu = item->entry.submenu.menu;
|
|
menu_show();
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
menubar_select(XButtonEvent * ev)
|
|
{
|
|
|
|
menu_t *menu = NULL;
|
|
static int last_mouse_x = 0, last_mouse_y = 0, last_win_x = 0, last_win_y = 0;
|
|
int mouse_x, mouse_y, win_x, win_y, dx, dy, unused;
|
|
Window unused_window;
|
|
|
|
/* determine the pulldown menu corresponding to the X index */
|
|
D_MENUBAR(("menubar_select():\n"));
|
|
if (ev->y >= 0 && ev->y <= menuBar_height() && CurrentBar != NULL) {
|
|
for (menu = CurrentBar->head; menu != NULL; menu = menu->next) {
|
|
int x = Width2Pixel(menu->x);
|
|
int w = Width2Pixel(menu->len + HSPACE);
|
|
|
|
if ((ev->x >= x && ev->x < x + w))
|
|
break;
|
|
}
|
|
}
|
|
switch (ev->type) {
|
|
case ButtonRelease:
|
|
D_MENUBAR((" menubar_select(ButtonRelease)\n"));
|
|
menu_hide_all();
|
|
break;
|
|
|
|
case ButtonPress:
|
|
D_MENUBAR((" menubar_select(ButtonPress)\n"));
|
|
if (menu == NULL && Arrows_x && ev->x >= Arrows_x) {
|
|
int i;
|
|
|
|
for (i = 0; i < NARROWS; i++) {
|
|
if ((ev->x >= (Arrows_x + (Width2Pixel(4 * i + i)) / 4)) && (ev->x < (Arrows_x + (Width2Pixel(4 * i + i + 4)) / 4))) {
|
|
|
|
draw_Arrows(Arrows[i].name, +1);
|
|
|
|
#if MENU_DELAY_USEC > 0
|
|
/*
|
|
* use select for timing
|
|
*/
|
|
{
|
|
struct itimerval tv;
|
|
|
|
tv.it_value.tv_sec = 0;
|
|
tv.it_value.tv_usec = MENU_DELAY_USEC;
|
|
|
|
select(0, NULL, NULL, NULL, &tv.it_value);
|
|
}
|
|
#endif
|
|
|
|
draw_Arrows(Arrows[i].name, -1);
|
|
#if DEBUG >= DEBUG_MENUARROWS
|
|
if (debug_level >= DEBUG_MENUARROWS) {
|
|
fprintf(stderr, "'%c': ", Arrows[i].name);
|
|
|
|
if (CurrentBar == NULL ||
|
|
(CurrentBar->arrows[i].type != MenuAction &&
|
|
CurrentBar->arrows[i].type != MenuTerminalAction)) {
|
|
if (Arrows[i].str != NULL && Arrows[i].str[0])
|
|
fprintf(stderr, "(default) \\033%s\n",
|
|
&(Arrows[i].str[2]));
|
|
} else {
|
|
fprintf(stderr, "%s\n", CurrentBar->arrows[i].str);
|
|
}
|
|
} else {
|
|
#endif
|
|
if (CurrentBar == NULL ||
|
|
action_dispatch(&(CurrentBar->arrows[i]))) {
|
|
if (Arrows[i].str != NULL &&
|
|
Arrows[i].str[0] != 0)
|
|
tt_write((Arrows[i].str + 1),
|
|
Arrows[i].str[0]);
|
|
}
|
|
#if DEBUG >= DEBUG_MENUARROWS
|
|
}
|
|
#endif /* DEBUG_MENUARROWS */
|
|
return;
|
|
}
|
|
}
|
|
} else if (menu == NULL && ActiveMenu == NULL && Options & Opt_menubar_move) {
|
|
XTranslateCoordinates(Xdisplay, TermWin.parent, Xroot,
|
|
0, 0, &last_win_x, &last_win_y, &unused_window);
|
|
XQueryPointer(Xdisplay, TermWin.parent, &unused_window, &unused_window, &unused, &unused,
|
|
&last_mouse_x, &last_mouse_y, &unused);
|
|
D_MENUBAR(("Initial data: last_mouse == %d,%d last_win == %d,%d\n",
|
|
last_mouse_x, last_mouse_y, last_win_x, last_win_y));
|
|
break;
|
|
}
|
|
/*drop */
|
|
case MotionNotify:
|
|
if (menu == NULL && ActiveMenu == NULL && Options & Opt_menubar_move) {
|
|
XQueryPointer(Xdisplay, TermWin.parent, &unused_window, &unused_window, &unused, &unused,
|
|
&mouse_x, &mouse_y, &unused);
|
|
if (mouse_x != last_mouse_x || mouse_y != last_mouse_y) {
|
|
dx = mouse_x - last_mouse_x;
|
|
dy = mouse_y - last_mouse_y;
|
|
D_MENUBAR((" -> last_mouse == %d,%d mouse == %d,%d rel == %d,%d move %d,%d to %d,%d\n",
|
|
last_mouse_x, last_mouse_y, mouse_x, mouse_y, dx, dy, last_win_x, last_win_y, last_win_x + dx, last_win_y + dy));
|
|
XMoveWindow(Xdisplay, TermWin.parent, last_win_x + dx, last_win_y + dy);
|
|
last_win_x += dx;
|
|
last_win_y += dy;
|
|
}
|
|
break;
|
|
}
|
|
/* drop */
|
|
default:
|
|
/*
|
|
* press menubar or move to a new entry
|
|
*/
|
|
D_MENUBAR((" menubar_select(default)\n"));
|
|
if (menu != NULL && menu != ActiveMenu) {
|
|
menu_hide_all(); /* pop down old menu */
|
|
ActiveMenu = menu;
|
|
menu_show(); /* pop up new menu */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* general dispatch routine,
|
|
* it would be nice to have `sticky' menus
|
|
*/
|
|
void
|
|
menubar_control(XButtonEvent * ev)
|
|
{
|
|
|
|
switch (ev->type) {
|
|
case ButtonPress:
|
|
D_MENUBAR(("menubar_control(ButtonPress)\n"));
|
|
if (ev->button == Button1)
|
|
menubar_select(ev);
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
D_MENUBAR(("menubar_control(ButtonRelease)\n"));
|
|
if (ev->button == Button1)
|
|
menu_select(ev);
|
|
break;
|
|
|
|
case MotionNotify:
|
|
D_MENUBAR(("menubar_control(MotionNotify)\n"));
|
|
while (XCheckTypedWindowEvent(Xdisplay, TermWin.parent, MotionNotify, (XEvent *) ev));
|
|
|
|
if (ActiveMenu)
|
|
while (menu_select(ev));
|
|
else
|
|
ev->y = -1;
|
|
if (ev->y < 0) {
|
|
|
|
Window unused_root, unused_child;
|
|
int unused_root_x, unused_root_y;
|
|
unsigned int unused_mask;
|
|
|
|
XQueryPointer(Xdisplay, menuBar.win, &unused_root, &unused_child, &unused_root_x, &unused_root_y,
|
|
&(ev->x), &(ev->y), &unused_mask);
|
|
menubar_select(ev);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#endif /* MENUBAR_MAX */
|