/*--------------------------------*-C-*---------------------------------* * File: menubar.c * * Copyright 1996,97 * mj olesen 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 #ifdef HAVE_SYS_SELECT_H # include #endif #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # 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 # else # include # 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(("(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(("(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 : ""), 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 : ""), 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 : ""))); 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 */