c67389dbb97798e1908488d2b241e93ba587c648
[dana/urxvt.git] / src / menubar.C
1 /*--------------------------------*-C-*---------------------------------*
2  * File:        menubar.c
3  *----------------------------------------------------------------------*
4  *
5  * Copyright (c) 1997,1998  mj olesen <olesen@me.QueensU.CA>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *----------------------------------------------------------------------*
21  * refer.html (or refer.txt) contains up-to-date documentation.  The
22  * summary that appears at the end of this file was taken from there.
23  *----------------------------------------------------------------------*/
24
25 #include "../config.h"          /* NECESSARY */
26
27 #include <stdlib.h>
28 #include <wchar.h>
29
30 #include "rxvt.h"               /* NECESSARY */
31 #ifdef MENUBAR
32 #include "version.h"
33 #include "menubar.h"
34 #include "menubar.intpro"       /* PROTOS for internal routines */
35
36 #define Menu_PixelWidth(menu)                                   \
37     (2 * SHADOW + Width2Pixel ((menu)->width + 3 * HSPACE))
38
39 static const struct
40   {
41     const char      name;       /* (l)eft, (u)p, (d)own, (r)ight */
42     const unsigned char str[5]; /* str[0] = STRLEN (str+1) */
43   }
44 Arrows[NARROWS] = {
45                     { 'l', "\003\033[D" },
46                     { 'u', "\003\033[A" },
47                     { 'd', "\003\033[B" },
48                     { 'r', "\003\033[C" }
49                   };
50
51 /*}}} */
52
53 static void
54 draw_string (rxvt_drawable &d, GC gc, rxvt_fontset *fs, int x, int y, char *str, int len)
55 {
56   mbstate mbs;
57
58   while (len)
59     {
60       wchar_t w;
61       int l = mbrtowc (&w, str, len, mbs);
62
63       if (l <= 0)
64         break;
65
66       len -= l;
67       str += l;
68
69       rxvt_font *font = (*fs)[fs->find_font (w)];
70       text_t ch = w;
71       font->draw (d, x, y, &ch, 1, Color_bg, Color_scroll);
72
73       x += font->width * wcwidth (w);
74     }
75 }
76
77 /*
78  * find an item called NAME in MENU
79  */
80 menuitem_t     *
81 rxvt_menuitem_find (const menu_t *menu, const char *name)
82 {
83   menuitem_t     *item;
84
85 #ifdef DEBUG_STRICT
86   assert (name != NULL);
87   assert (menu != NULL);
88 #endif
89
90   /* find the last item in the menu, this is good for separators */
91   for (item = menu->tail; item != NULL; item = item->prev)
92     {
93       if (item->entry.type == MenuSubMenu)
94         {
95           if (!STRCMP (name, (item->entry.submenu.menu)->name))
96             break;
97         }
98       else if ((isSeparator (name) && isSeparator (item->name))
99                || !STRCMP (name, item->name))
100         break;
101     }
102   return item;
103 }
104
105 /*
106  * unlink ITEM from its MENU and free its memory
107  */
108 void
109 rxvt_term::menuitem_free (menu_t *menu, menuitem_t *item)
110 {
111   /* disconnect */
112   menuitem_t     *prev, *next;
113
114 #ifdef DEBUG_STRICT
115   assert (menu != NULL);
116 #endif
117
118   prev = item->prev;
119   next = item->next;
120   if (prev != NULL)
121     prev->next = next;
122   if (next != NULL)
123     next->prev = prev;
124
125   /* new head, tail */
126   if (menu->tail == item)
127     menu->tail = prev;
128   if (menu->head == item)
129     menu->head = next;
130
131   switch (item->entry.type)
132     {
133       case MenuAction:
134       case MenuTerminalAction:
135         free (item->entry.action.str);
136         break;
137       case MenuSubMenu:
138         menu_delete (item->entry.submenu.menu);
139         break;
140     }
141   if (item->name != NULL)
142     free (item->name);
143   if (item->name2 != NULL)
144     free (item->name2);
145   free (item);
146 }
147
148 /*
149  * sort command vs. terminal actions and
150  * remove the first character of STR if it's '\0'
151  */
152 int
153 rxvt_action_type (action_t *action, unsigned char *str)
154 {
155   unsigned int    len;
156
157 #if defined (DEBUG_MENU) || defined (DEBUG_MENUARROWS)
158   len = STRLEN (str);
159   fprintf (stderr, " (len %d) = %s\n", len, str);
160 #else
161   len = rxvt_Str_escaped ((char *)str);
162 #endif
163
164   if (!len)
165     return -1;
166
167   /* sort command vs. terminal actions */
168   action->type = MenuAction;
169   if (str[0] == '\0')
170     {
171       /* the functional equivalent: memmove (str, str+1, len); */
172       unsigned char  *dst = (str);
173       unsigned char  *src = (str + 1);
174       unsigned char  *end = (str + len);
175
176       while (src <= end)
177         *dst++ = *src++;
178
179       len--;                    /* decrement length */
180       if (str[0] != '\0')
181         action->type = MenuTerminalAction;
182     }
183   action->str = str;
184   action->len = len;
185
186   return 0;
187 }
188
189 int
190 rxvt_term::action_dispatch (action_t *action)
191 {
192   switch (action->type)
193     {
194       case MenuTerminalAction:
195         cmd_write (action->str, action->len);
196         break;
197
198       case MenuAction:
199         tt_write (action->str, action->len);
200         break;
201
202       default:
203         return -1;
204         break;
205     }
206   return 0;
207 }
208
209 /* return the arrow index corresponding to NAME */
210 int
211 rxvt_menuarrow_find (char name)
212 {
213   int             i;
214
215   for (i = 0; i < NARROWS; i++)
216     if (name == Arrows[i].name)
217       return i;
218   return -1;
219 }
220
221 /* free the memory associated with arrow NAME of the current menubar */
222 void
223 rxvt_term::menuarrow_free (char name)
224 {
225   int             i;
226
227   if (name)
228     {
229       i = rxvt_menuarrow_find (name);
230       if (i >= 0)
231         {
232           action_t       *act = & (CurrentBar->arrows[i]);
233
234           switch (act->type)
235             {
236               case MenuAction:
237               case MenuTerminalAction:
238                 free (act->str);
239                 act->str = NULL;
240                 act->len = 0;
241                 break;
242             }
243           act->type = MenuLabel;
244         }
245     }
246   else
247     {
248       for (i = 0; i < NARROWS; i++)
249         menuarrow_free (Arrows[i].name);
250     }
251 }
252
253 void
254 rxvt_term::menuarrow_add (char *string)
255 {
256   int             i;
257   unsigned        xtra_len;
258   char           *p;
259   struct
260     {
261       char           *str;
262       int             len;
263     }
264   beg = { NULL, 0 },
265         end = { NULL, 0 },
266               *cur,
267               parse[NARROWS];
268
269   MEMSET (parse, 0, sizeof (parse));
270
271   /* fprintf (stderr, "add arrows = `%s'\n", string); */
272   for (p = string; p != NULL && *p; string = p)
273     {
274       p = (string + 3);
275       /* fprintf (stderr, "parsing at %s\n", string); */
276       switch (string[1])
277         {
278           case 'b':
279             cur = &beg;
280             break;
281           case 'e':
282             cur = &end;
283             break;
284
285           default:
286             i = rxvt_menuarrow_find (string[1]);
287             if (i >= 0)
288               cur = & (parse[i]);
289             else
290               continue; /* not found */
291             break;
292         }
293
294       string = p;
295       cur->str = string;
296       cur->len = 0;
297
298       if (cur == &end)
299         {
300           p = STRCHR (string, '\0');
301         }
302       else
303         {
304           char           *next = string;
305
306           while (1)
307             {
308               p = STRCHR (next, '<');
309               if (p != NULL)
310                 {
311                   if (p[1] && p[2] == '>')
312                     break;
313                   /* parsed */
314                 }
315               else
316                 {
317                   if (beg.str == NULL)  /* no end needed */
318                     p = STRCHR (next, '\0');
319                   break;
320                 }
321               next = (p + 1);
322             }
323         }
324
325       if (p == NULL)
326         return;
327       cur->len = (p - string);
328     }
329
330 #ifdef DEBUG_MENUARROWS
331   cur = &beg;
332   fprintf (stderr, "<b> (len %d) = %.*s\n",
333           cur->len, cur->len, (cur->str ? cur->str : ""));
334   for (i = 0; i < NARROWS; i++)
335     {
336       cur = & (parse[i]);
337       fprintf (stderr, "<%c> (len %d) = %.*s\n",
338               Arrows[i].name,
339               cur->len, cur->len, (cur->str ? cur->str : ""));
340     }
341   cur = &end;
342   fprintf (stderr, "<e> (len %d) = %.*s\n",
343           cur->len, cur->len, (cur->str ? cur->str : ""));
344 #endif
345
346   xtra_len = (beg.len + end.len);
347   for (i = 0; i < NARROWS; i++)
348     {
349       if (xtra_len || parse[i].len)
350         menuarrow_free (Arrows[i].name);
351     }
352
353   for (i = 0; i < NARROWS; i++)
354     {
355       unsigned char  *str;
356       unsigned int    len;
357
358       if (!parse[i].len)
359         continue;
360
361       str = (unsigned char *) rxvt_malloc (parse[i].len + xtra_len + 1);
362
363       len = 0;
364       if (beg.len)
365         {
366           STRNCPY (str + len, beg.str, beg.len);
367           len += beg.len;
368         }
369       STRNCPY (str + len, parse[i].str, parse[i].len);
370       len += parse[i].len;
371
372       if (end.len)
373         {
374           STRNCPY (str + len, end.str, end.len);
375           len += end.len;
376         }
377       str[len] = '\0';
378
379 #ifdef DEBUG_MENUARROWS
380       fprintf (stderr, "<%c> (len %d) = %s\n", Arrows[i].name, len, str);
381 #endif
382       if (rxvt_action_type (& (CurrentBar->arrows[i]), str) < 0)
383         free (str);
384     }
385 }
386
387 menuitem_t     *
388 rxvt_menuitem_add (menu_t *menu, const char *name, const char *name2, const char *action)
389 {
390   menuitem_t     *item;
391   unsigned int    len;
392
393 #ifdef DEBUG_STRICT
394   assert (name != NULL);
395   assert (action != NULL);
396 #endif
397
398   if (menu == NULL)
399     return NULL;
400
401   if (isSeparator (name))
402     {
403       /* add separator, no action */
404       name = "";
405       action = "";
406     }
407   else
408     {
409       /*
410        * add/replace existing menu item
411        */
412       item = rxvt_menuitem_find (menu, name);
413       if (item != NULL)
414         {
415           if (item->name2 != NULL && name2 != NULL)
416             {
417               free (item->name2);
418               item->len2 = 0;
419               item->name2 = NULL;
420             }
421           switch (item->entry.type)
422             {
423               case MenuAction:
424               case MenuTerminalAction:
425                 free (item->entry.action.str);
426                 item->entry.action.str = NULL;
427                 break;
428             }
429           goto Item_Found;
430         }
431     }
432   /* allocate a new itemect */
433   item = (menuitem_t *) rxvt_malloc (sizeof (menuitem_t));
434
435   item->len2 = 0;
436   item->name2 = NULL;
437
438   len = STRLEN (name);
439   item->name = (char *)rxvt_malloc (len + 1);
440   STRCPY (item->name, name);
441   if (name[0] == '.' && name[1] != '.')
442     len = 0;            /* hidden menu name */
443   item->len = len;
444
445   /* add to tail of list */
446   item->prev = menu->tail;
447   item->next = NULL;
448
449   if (menu->tail != NULL)
450     (menu->tail)->next = item;
451   menu->tail = item;
452   /* fix head */
453   if (menu->head == NULL)
454     menu->head = item;
455
456   /*
457    * add action
458    */
459 Item_Found:
460   if (name2 != NULL && item->name2 == NULL)
461     {
462       len = STRLEN (name2);
463       if (len == 0)
464         item->name2 = NULL;
465       else
466         {
467           item->name2 = (char *)rxvt_malloc (len + 1);
468           STRCPY (item->name2, name2);
469         }
470       item->len2 = len;
471     }
472   item->entry.type = MenuLabel;
473   len = STRLEN (action);
474
475   if (len == 0 && item->name2 != NULL)
476     {
477       action = item->name2;
478       len = item->len2;
479     }
480   if (len)
481     {
482       unsigned char *str = (unsigned char *)rxvt_malloc (len + 1);
483
484       STRCPY (str, action);
485
486       if (rxvt_action_type (& (item->entry.action), str) < 0)
487         free (str);
488     }
489   /* new item and a possible increase in width */
490   if (menu->width < (item->len + item->len2))
491     menu->width = (item->len + item->len2);
492
493   return item;
494 }
495
496 /*
497  * search for the base starting menu for NAME.
498  * return a pointer to the portion of NAME that remains
499  */
500 char           *
501 rxvt_term::menu_find_base (menu_t **menu, char *path)
502 {
503   menu_t         *m = NULL;
504   menuitem_t     *item;
505
506 #ifdef DEBUG_STRICT
507   assert (menu != NULL);
508   assert (CurrentBar != NULL);
509 #endif
510
511   if (path[0] == '\0')
512     return path;
513
514   if (STRCHR (path, '/') != NULL)
515     {
516       char           *p = path;
517
518       while ((p = STRCHR (p, '/')) != NULL)
519         {
520           p++;
521           if (*p == '/')
522             path = p;
523         }
524
525       if (path[0] == '/')
526         {
527           path++;
528           *menu = NULL;
529         }
530
531       while ((p = STRCHR (path, '/')) != NULL)
532         {
533           p[0] = '\0';
534           if (path[0] == '\0')
535             return NULL;
536
537           if (!STRCMP (path, DOT))
538             {
539               /* nothing to do */
540             }
541           else if (!STRCMP (path, DOTS))
542             {
543               if (*menu != NULL)
544                 *menu = (*menu)->parent;
545             }
546           else
547             {
548               path = menu_find_base (menu, path);
549               if (path[0] != '\0')
550                 {       /* not found */
551                   p[0] = '/';   /* fix-up name again */
552                   return path;
553                 }
554             }
555
556           path = (p + 1);
557         }
558     }
559
560   if (!STRCMP (path, DOTS))
561     {
562       path += STRLEN (DOTS);
563       if (*menu != NULL)
564         *menu = (*menu)->parent;
565       return path;
566     }
567
568   /* find this menu */
569   if (*menu == NULL)
570     {
571       for (m = CurrentBar->tail; m != NULL; m = m->prev)
572         if (!STRCMP (path, m->name))
573           break;
574     }
575   else
576     {
577       /* find this menu */
578       for (item = (*menu)->tail; item != NULL; item = item->prev)
579         {
580           if (item->entry.type == MenuSubMenu
581               && !STRCMP (path, (item->entry.submenu.menu)->name))
582             {
583               m = (item->entry.submenu.menu);
584               break;
585             }
586         }
587     }
588
589   if (m != NULL)
590     {
591       *menu = m;
592       path += STRLEN (path);
593     }
594
595   return path;
596 }
597
598 /*
599  * delete this entire menu
600  */
601 menu_t         *
602 rxvt_term::menu_delete (menu_t *menu)
603 {
604   menu_t         *parent = NULL, *prev, *next;
605   menuitem_t     *item;
606
607 #ifdef DEBUG_STRICT
608   assert (CurrentBar != NULL);
609 #endif
610
611   /* delete the entire menu */
612   if (menu == NULL)
613     return NULL;
614
615   parent = menu->parent;
616
617   /* unlink MENU */
618   prev = menu->prev;
619   next = menu->next;
620   if (prev != NULL)
621     prev->next = next;
622   if (next != NULL)
623     next->prev = prev;
624
625   /* fix the index */
626   if (parent == NULL)
627     {
628       const int       len = (menu->len + HSPACE);
629
630       if (CurrentBar->tail == menu)
631         CurrentBar->tail = prev;
632       if (CurrentBar->head == menu)
633         CurrentBar->head = next;
634
635       for (next = menu->next; next != NULL; next = next->next)
636         next->x -= len;
637     }
638   else
639     {
640       for (item = parent->tail; item != NULL; item = item->prev)
641         {
642           if (item->entry.type == MenuSubMenu
643               && item->entry.submenu.menu == menu)
644             {
645               item->entry.submenu.menu = NULL;
646               menuitem_free (menu->parent, item);
647               break;
648             }
649         }
650     }
651
652   item = menu->tail;
653   while (item != NULL)
654     {
655       menuitem_t *p = item->prev;
656
657       menuitem_free (menu, item);
658       item = p;
659     }
660
661   free (menu->name);
662   free (menu);
663
664   return parent;
665 }
666
667 menu_t         *
668 rxvt_term::menu_add (menu_t *parent, char *path)
669 {
670   menu_t *menu;
671
672 #ifdef DEBUG_STRICT
673   assert (CurrentBar != NULL);
674 #endif
675
676   if (STRCHR (path, '/') != NULL)
677     {
678       char *p;
679
680       if (path[0] == '/')
681         {
682           /* shouldn't happen */
683           path++;
684           parent = NULL;
685         }
686       while ((p = STRCHR (path, '/')) != NULL)
687         {
688           p[0] = '\0';
689           if (path[0] == '\0')
690             return NULL;
691
692           parent = menu_add (parent, path);
693           path = (p + 1);
694         }
695     }
696   if (!STRCMP (path, DOTS))
697     return (parent != NULL ? parent->parent : parent);
698
699   if (!STRCMP (path, DOT) || path[0] == '\0')
700     return parent;
701
702   /* allocate a new menu */
703   menu = (menu_t *) rxvt_malloc (sizeof (menu_t));
704
705   menu->width = 0;
706   menu->parent = parent;
707   menu->len = STRLEN (path);
708   menu->name = (char *)rxvt_malloc ((menu->len + 1));
709   STRCPY (menu->name, path);
710
711   /* initialize head/tail */
712   menu->head = menu->tail = NULL;
713   menu->prev = menu->next = NULL;
714
715   menu->win = None;
716   menu->drawable = 0;
717   menu->x = menu->y = menu->w = menu->h = 0;
718   menu->item = NULL;
719
720   /* add to tail of list */
721   if (parent == NULL)
722     {
723       menu->prev = CurrentBar->tail;
724       if (CurrentBar->tail != NULL)
725         CurrentBar->tail->next = menu;
726       CurrentBar->tail = menu;
727       if (CurrentBar->head == NULL)
728         CurrentBar->head = menu;        /* fix head */
729       if (menu->prev)
730         menu->x = (menu->prev->x + menu->prev->len + HSPACE);
731     }
732   else
733     {
734       menuitem_t     *item;
735
736       item = rxvt_menuitem_add (parent, path, "", "");
737       if (item == NULL)
738         {
739           free (menu);
740           return parent;
741         }
742 #ifdef DEBUG_STRICT
743       assert (item->entry.type == MenuLabel);
744 #endif
745       item->entry.type = MenuSubMenu;
746       item->entry.submenu.menu = menu;
747     }
748
749   return menu;
750 }
751
752 void
753 rxvt_term::drawbox_menubar (int x, int len, int state)
754 {
755   GC              top, bot;
756
757   x = Width2Pixel (x);
758   len = Width2Pixel (len + HSPACE);
759   if (x >= TermWin.width)
760     return;
761   else if (x + len >= TermWin.width)
762     len = (TermWin_TotalWidth () - x);
763
764 #ifdef MENUBAR_SHADOW_IN
765   state = -state;
766 #endif
767   switch (state)
768     {
769       case +1:
770         top = topShadowGC;
771         bot = botShadowGC;
772         break;                  /* SHADOW_OUT */
773       case -1:
774         top = botShadowGC;
775         bot = topShadowGC;
776         break;                  /* SHADOW_IN */
777       default:
778         top = bot = scrollbarGC;
779         break;                  /* neutral */
780     }
781
782   rxvt_Draw_Shadow (display->display, menuBar.win, top, bot,
783                    x, 0, len, menuBar_TotalHeight ());
784 }
785
786 void
787 rxvt_term::drawtriangle (int x, int y, int state)
788 {
789   GC              top, bot;
790   int             w;
791
792 #ifdef MENU_SHADOW_IN
793   state = -state;
794 #endif
795   switch (state)
796     {
797       case +1:
798         top = topShadowGC;
799         bot = botShadowGC;
800         break;                  /* SHADOW_OUT */
801       case -1:
802         top = botShadowGC;
803         bot = topShadowGC;
804         break;                  /* SHADOW_IN */
805       default:
806         top = bot = scrollbarGC;
807         break;                  /* neutral */
808     }
809
810   w = Height2Pixel (1) - 2 * SHADOW;
811
812   x -= SHADOW + (3 * w / 2);
813   y += SHADOW * 3;
814
815   rxvt_Draw_Triangle (display->display, ActiveMenu->win, top, bot, x, y, w, 'r');
816 }
817
818 void
819 rxvt_term::drawbox_menuitem (int y, int state)
820 {
821   GC              top, bot;
822
823 #ifdef MENU_SHADOW_IN
824   state = -state;
825 #endif
826   switch (state)
827     {
828       case +1:
829         top = topShadowGC;
830         bot = botShadowGC;
831         break;                  /* SHADOW_OUT */
832       case -1:
833         top = botShadowGC;
834         bot = topShadowGC;
835         break;                  /* SHADOW_IN */
836       default:
837         top = bot = scrollbarGC;
838         break;                  /* neutral */
839     }
840
841   rxvt_Draw_Shadow (display->display, ActiveMenu->win, top, bot,
842                    SHADOW + 0, SHADOW + y,
843                    ActiveMenu->w - 2 * (SHADOW),
844                    HEIGHT_TEXT + 2 * SHADOW);
845   XFlush (display->display);
846 }
847
848 #ifdef DEBUG_MENU_LAYOUT
849 void
850 rxvt_print_menu_ancestors (menu_t *menu)
851 {
852   if (menu == NULL)
853     {
854       fprintf (stderr, "Top Level menu\n");
855       return;
856     }
857
858   fprintf (stderr, "menu %s ", menu->name);
859   if (menu->parent != NULL)
860     {
861       menuitem_t     *item;
862
863       for (item = menu->parent->head; item != NULL; item = item->next)
864         {
865           if (item->entry.type == MenuSubMenu
866               && item->entry.submenu.menu == menu)
867             {
868               break;
869             }
870         }
871
872       if (item == NULL)
873         {
874           fprintf (stderr, "is an orphan!\n");
875           return;
876         }
877     }
878
879   fprintf (stderr, "\n");
880   rxvt_print_menu_ancestors (menu->parent);
881 }
882
883 void
884 rxvt_print_menu_descendants (menu_t *menu)
885 {
886   menuitem_t     *item;
887   menu_t         *parent;
888   int             i, level = 0;
889
890   parent = menu;
891   do
892     {
893       level++;
894       parent = parent->parent;
895     }
896   while (parent != NULL);
897
898   for (i = 0; i < level; i++)
899     fprintf (stderr, ">");
900   fprintf (stderr, "%s\n", menu->name);
901
902   for (item = menu->head; item != NULL; item = item->next)
903     {
904       if (item->entry.type == MenuSubMenu)
905         {
906           if (item->entry.submenu.menu == NULL)
907             fprintf (stderr, "> %s == NULL\n", item->name);
908           else
909             rxvt_print_menu_descendants (item->entry.submenu.menu);
910         }
911       else
912         {
913           for (i = 0; i < level; i++)
914             fprintf (stderr, "+");
915           if (item->entry.type == MenuLabel)
916             fprintf (stderr, "label: ");
917           fprintf (stderr, "%s\n", item->name);
918         }
919     }
920
921   for (i = 0; i < level; i++)
922     fprintf (stderr, "<");
923   fprintf (stderr, "\n");
924 }
925 #endif
926
927 /* pop up/down the current menu and redraw the menuBar button */
928 void
929 rxvt_term::menu_show ()
930 {
931   int x, y, xright;
932   menuitem_t *item;
933
934   if (ActiveMenu == NULL)
935     return;
936
937   x = ActiveMenu->x;
938   if (ActiveMenu->parent == NULL)
939     {
940       register int    h;
941
942       drawbox_menubar (x, ActiveMenu->len, -1);
943       x = Width2Pixel (x);
944
945       ActiveMenu->y = 1;
946       ActiveMenu->w = Menu_PixelWidth (ActiveMenu);
947
948       if ((x + ActiveMenu->w) >= TermWin.width)
949         x = (TermWin_TotalWidth () - ActiveMenu->w);
950
951       /* find the height */
952       for (h = 0, item = ActiveMenu->head; item != NULL; item = item->next)
953         h += isSeparator (item->name) ? HEIGHT_SEPARATOR
954              : HEIGHT_TEXT + 2 * SHADOW;
955       ActiveMenu->h = h + 2 * SHADOW;
956     }
957
958   if (ActiveMenu->win == None)
959     {
960       ActiveMenu->win = XCreateSimpleWindow (display->display, TermWin.vt,
961                                             x, ActiveMenu->y,
962                                             ActiveMenu->w, ActiveMenu->h,
963                                             0,
964                                             PixColors[Color_fg],
965                                             PixColors[Color_scroll]);
966       ActiveMenu->drawable = new rxvt_drawable (display, ActiveMenu->win);
967       XMapWindow (display->display, ActiveMenu->win);
968     }
969
970   rxvt_Draw_Shadow (display->display, ActiveMenu->win,
971                    topShadowGC, botShadowGC,
972                    0, 0, ActiveMenu->w, ActiveMenu->h);
973
974   /* determine the correct right-alignment */
975   for (xright = 0, item = ActiveMenu->head; item != NULL; item = item->next)
976     if (item->len2 > xright)
977       xright = item->len2;
978
979   for (y = 0, item = ActiveMenu->head; item != NULL; item = item->next)
980     {
981       const int xoff = (SHADOW + Width2Pixel (HSPACE) / 2);
982       register int h;
983       GC gc = menubarGC;
984
985       if (isSeparator (item->name))
986         {
987           rxvt_Draw_Shadow (display->display, ActiveMenu->win,
988                            topShadowGC, botShadowGC,
989                            SHADOW, y + SHADOW + 1,
990                            ActiveMenu->w - 2 * SHADOW, 0);
991           h = HEIGHT_SEPARATOR;
992         }
993       else
994         {
995           char           *name = item->name;
996           int             len = item->len;
997
998           if (item->entry.type == MenuLabel)
999             gc = botShadowGC;
1000           else if (item->entry.type == MenuSubMenu)
1001             {
1002               int             x1, y1;
1003               menuitem_t     *it;
1004               menu_t         *menu = item->entry.submenu.menu;
1005
1006               drawtriangle (ActiveMenu->w, y, +1);
1007
1008               name = menu->name;
1009               len = menu->len;
1010
1011               y1 = ActiveMenu->y + y;
1012
1013               menu->w = Menu_PixelWidth (menu);
1014
1015               /* place sub-menu at midpoint of parent menu */
1016               x1 = ActiveMenu->w / 2;
1017               if (x1 > menu->w) /* right-flush menu if too small */
1018                 x1 += (x1 - menu->w);
1019               x1 += x;
1020
1021               /* find the height of this submenu */
1022               for (h = 0, it = menu->head; it != NULL; it = it->next)
1023                 h += isSeparator (it->name) ? HEIGHT_SEPARATOR
1024                      : HEIGHT_TEXT + 2 * SHADOW;
1025               menu->h = h + 2 * SHADOW;
1026
1027               /* ensure menu is in window limits */
1028               if ((x1 + menu->w) >= TermWin.width)
1029                 x1 = (TermWin_TotalWidth () - menu->w);
1030
1031               if ((y1 + menu->h) >= TermWin.height)
1032                 y1 = (TermWin_TotalHeight () - menu->h);
1033
1034               menu->x = (x1 < 0 ? 0 : x1);
1035               menu->y = (y1 < 0 ? 0 : y1);
1036             }
1037           else if (item->name2 && !STRCMP (name, item->name2))
1038             name = NULL;
1039
1040           if (len && name)
1041             draw_string (*ActiveMenu->drawable, gc, TermWin.fontset,
1042                          xoff, 2 * SHADOW + y, name, len);
1043
1044           len = item->len2;
1045           name = item->name2;
1046
1047           if (len && name)
1048             draw_string (*ActiveMenu->drawable, gc, TermWin.fontset,
1049                          ActiveMenu->w - (xoff + Width2Pixel (xright)), 2 * SHADOW + y, name, len);
1050
1051           h = HEIGHT_TEXT + 2 * SHADOW;
1052         }
1053       y += h;
1054     }
1055 }
1056
1057 void
1058 rxvt_term::menu_display (void (rxvt_term::*update) ())
1059 {
1060   if (ActiveMenu == NULL)
1061     return;
1062
1063   delete ActiveMenu->drawable;
1064   if (ActiveMenu->win != None)
1065     XDestroyWindow (display->display, ActiveMenu->win);
1066   ActiveMenu->win = None;
1067   ActiveMenu->item = NULL;
1068
1069   if (ActiveMenu->parent == NULL)
1070     drawbox_menubar (ActiveMenu->x, ActiveMenu->len, +1);
1071
1072   ActiveMenu = ActiveMenu->parent;
1073   (this->*update) ();
1074 }
1075
1076 void
1077 rxvt_term::menu_hide_all ()
1078 {
1079   menu_display (&rxvt_term::menu_hide_all);
1080 }
1081
1082 void
1083 rxvt_term::menu_hide ()
1084 {
1085   menu_display (&rxvt_term::menu_show);
1086 }
1087
1088 void
1089 rxvt_term::menu_clear (menu_t *menu)
1090 {
1091   if (menu != NULL)
1092     {
1093       menuitem_t *item = menu->tail;
1094
1095       while (item != NULL)
1096         {
1097           menuitem_free (menu, item);
1098           /* it didn't get freed ... why? */
1099           if (item == menu->tail)
1100             return;
1101           item = menu->tail;
1102         }
1103       menu->width = 0;
1104     }
1105 }
1106
1107 void
1108 rxvt_term::menubar_clear ()
1109 {
1110   if (CurrentBar != NULL)
1111     {
1112       menu_t *menu = CurrentBar->tail;
1113
1114       while (menu != NULL)
1115         {
1116           menu_t *prev = menu->prev;
1117
1118           menu_delete (menu);
1119           menu = prev;
1120         }
1121       CurrentBar->head = CurrentBar->tail = NULL;
1122
1123       if (CurrentBar->title)
1124         {
1125           free (CurrentBar->title);
1126           CurrentBar->title = NULL;
1127         }
1128
1129       menuarrow_free (0);       /* remove all arrow functions */
1130     }
1131
1132   ActiveMenu = NULL;
1133 }
1134
1135 #if (MENUBAR_MAX > 1)
1136 /* find if menu already exists */
1137 bar_t          *
1138 rxvt_term::menubar_find (const char *name)
1139 {
1140   bar_t *bar = CurrentBar;
1141
1142 #ifdef DEBUG_MENUBAR_STACKING
1143   fprintf (stderr, "looking for [menu:%s] ...", name ? name : " (nil)");
1144 #endif
1145   if (bar == NULL || name == NULL)
1146     return NULL;
1147
1148   if (STRLEN (name) && STRCMP (name, "*"))
1149     {
1150       do
1151         {
1152           if (!STRCMP (bar->name, name))
1153             {
1154 #ifdef DEBUG_MENUBAR_STACKING
1155               fprintf (stderr, " found!\n");
1156 #endif
1157               return bar;
1158             }
1159           bar = bar->next;
1160         }
1161       while (bar != CurrentBar);
1162       bar = NULL;
1163     }
1164 #ifdef DEBUG_MENUBAR_STACKING
1165   fprintf (stderr, "%s found!\n", (bar ? "" : " NOT"));
1166 #endif
1167
1168   return bar;
1169 }
1170
1171 int
1172 rxvt_term::menubar_push (const char *name)
1173 {
1174   int ret = 1;
1175   bar_t *bar;
1176
1177   if (CurrentBar == NULL)
1178     {
1179       /* allocate first one */
1180       bar = (bar_t *) rxvt_malloc (sizeof (bar_t));
1181
1182       MEMSET (bar, 0, sizeof (bar_t));
1183       /* circular linked-list */
1184       bar->next = bar->prev = bar;
1185       bar->head = bar->tail = NULL;
1186       bar->title = NULL;
1187       CurrentBar = bar;
1188       Nbars++;
1189
1190       menubar_clear ();
1191     }
1192   else
1193     {
1194       /* find if menu already exists */
1195       bar = menubar_find (name);
1196       if (bar != NULL)
1197         {
1198           /* found it, use it */
1199           CurrentBar = bar;
1200         }
1201       else
1202         {
1203           /* create if needed, or reuse the existing empty menubar */
1204           if (CurrentBar->head != NULL)
1205             {
1206               /* need to malloc another one */
1207               if (Nbars < MENUBAR_MAX)
1208                 bar = (bar_t *) rxvt_malloc (sizeof (bar_t));
1209               else
1210                 bar = NULL;
1211
1212               /* malloc failed or too many menubars, reuse another */
1213               if (bar == NULL)
1214                 {
1215                   bar = CurrentBar->next;
1216                   ret = -1;
1217                 }
1218               else
1219                 {
1220                   bar->head = bar->tail = NULL;
1221                   bar->title = NULL;
1222
1223                   bar->next = CurrentBar->next;
1224                   CurrentBar->next = bar;
1225                   bar->prev = CurrentBar;
1226                   bar->next->prev = bar;
1227
1228                   Nbars++;
1229                 }
1230               CurrentBar = bar;
1231
1232             }
1233
1234           menubar_clear ();
1235         }
1236     }
1237
1238   /* give menubar this name */
1239   STRNCPY (CurrentBar->name, name, MAXNAME);
1240   CurrentBar->name[MAXNAME - 1] = '\0';
1241
1242   return ret;
1243 }
1244
1245 /* switch to a menu called NAME and remove it */
1246 void
1247 rxvt_term::menubar_remove (const char *name)
1248 {
1249   bar_t          *bar;
1250
1251   if ((bar = menubar_find (name)) == NULL)
1252     return;
1253   CurrentBar = bar;
1254
1255   do
1256     {
1257       menubar_clear ();
1258       /*
1259        * pop a menubar, clean it up first
1260        */
1261       if (CurrentBar != NULL)
1262         {
1263           bar_t          *prev = CurrentBar->prev;
1264           bar_t          *next = CurrentBar->next;
1265
1266           if (prev == next && prev == CurrentBar)
1267             {   /* only 1 left */
1268               prev = NULL;
1269               Nbars = 0;        /* safety */
1270             }
1271           else
1272             {
1273               next->prev = prev;
1274               prev->next = next;
1275               Nbars--;
1276             }
1277
1278           free (CurrentBar);
1279           CurrentBar = prev;
1280         }
1281     }
1282   while (CurrentBar && !STRCMP (name, "*"));
1283 }
1284
1285 void
1286 rxvt_action_decode (FILE *fp, action_t *act)
1287 {
1288   unsigned char *str;
1289   short len;
1290
1291   if (act == NULL || (len = act->len) == 0 || (str = act->str) == NULL)
1292     return;
1293
1294   if (act->type == MenuTerminalAction)
1295     {
1296       fprintf (fp, "^@");
1297       /* can strip trailing ^G from XTerm sequence */
1298       if (str[0] == C0_ESC && str[1] == ']' && str[len - 1] == C0_BEL)
1299         len--;
1300     }
1301   else if (str[0] == C0_ESC)
1302     {
1303       switch (str[1])
1304         {
1305           case '[':
1306           case ']':
1307             break;
1308
1309           case 'x':
1310             /* can strip trailing '\r' from M-x sequence */
1311             if (str[len - 1] == '\r')
1312               len--;
1313             /* FALLTHROUGH */
1314
1315           default:
1316             fprintf (fp, "M-"); /* meta prefix */
1317             str++;
1318             len--;
1319             break;
1320         }
1321     }
1322
1323   /*
1324    * control character form is preferred, since backslash-escaping
1325    * can be really ugly looking when the backslashes themselves also
1326    * have to be escaped to avoid Shell (or whatever scripting
1327    * language) interpretation
1328    */
1329   while (len > 0)
1330     {
1331       unsigned char   ch = *str++;
1332
1333       switch (ch)
1334         {
1335           case C0_ESC:
1336             fprintf (fp, "\\E");
1337             break;              /* escape */
1338           case '\r':
1339             fprintf (fp, "\\r");
1340             break;              /* carriage-return */
1341           case '\\':
1342             fprintf (fp, "\\\\");
1343             break;              /* backslash */
1344           case '^':
1345             fprintf (fp, "\\^");
1346             break;              /* caret */
1347           case 127:
1348             fprintf (fp, "^?");
1349           default:
1350             if (ch <= 31)
1351               fprintf (fp, "^%c", ('@' + ch));
1352             else if (ch > 127)
1353               fprintf (fp, "\\%o", ch);
1354             else
1355               fprintf (fp, "%c", ch);
1356             break;
1357         }
1358
1359       len--;
1360     }
1361
1362   fprintf (fp, "\n");
1363 }
1364
1365 void
1366 rxvt_menu_dump (FILE *fp, menu_t *menu)
1367 {
1368   menuitem_t     *item;
1369
1370   /* create a new menu and clear it */
1371   fprintf (fp, (menu->parent ? "./%s/*\n" : "/%s/*\n"), menu->name);
1372
1373   for (item = menu->head; item != NULL; item = item->next)
1374     {
1375       switch (item->entry.type)
1376         {
1377           case MenuSubMenu:
1378             if (item->entry.submenu.menu == NULL)
1379               fprintf (fp, "> %s == NULL\n", item->name);
1380             else
1381               rxvt_menu_dump (fp, item->entry.submenu.menu);
1382             break;
1383
1384           case MenuLabel:
1385             fprintf (fp, "{%s}\n", (STRLEN (item->name) ? item->name : "-"));
1386             break;
1387
1388           case MenuTerminalAction:
1389           case MenuAction:
1390             fprintf (fp, "{%s}", item->name);
1391             if (item->name2 != NULL && STRLEN (item->name2))
1392               fprintf (fp, "{%s}", item->name2);
1393             fprintf (fp, "\t");
1394             rxvt_action_decode (fp, & (item->entry.action));
1395             break;
1396         }
1397     }
1398
1399   fprintf (fp, (menu->parent ? "../\n" : "/\n\n"));
1400 }
1401
1402 void
1403 rxvt_term::menubar_dump (FILE *fp)
1404 {
1405   bar_t          *bar = CurrentBar;
1406   time_t          t;
1407
1408   if (bar == NULL || fp == NULL)
1409     return;
1410   time (&t);
1411
1412   fprintf (fp,
1413           "# " APL_SUBCLASS " (%s)  Pid: %u\n# Date: %s\n\n",
1414           rs[Rs_name], (unsigned int)getpid (), ctime (&t));
1415
1416   /* dump in reverse order */
1417   bar = CurrentBar->prev;
1418   do
1419     {
1420       menu_t         *menu;
1421       int             i;
1422
1423       fprintf (fp, "[menu:%s]\n", bar->name);
1424
1425       if (bar->title != NULL)
1426         fprintf (fp, "[title:%s]\n", bar->title);
1427
1428       for (i = 0; i < NARROWS; i++)
1429         {
1430           switch (bar->arrows[i].type)
1431             {
1432               case MenuTerminalAction:
1433               case MenuAction:
1434                 fprintf (fp, "<%c>", Arrows[i].name);
1435                 rxvt_action_decode (fp, & (bar->arrows[i]));
1436                 break;
1437             }
1438         }
1439       fprintf (fp, "\n");
1440
1441       for (menu = bar->head; menu != NULL; menu = menu->next)
1442         rxvt_menu_dump (fp, menu);
1443
1444       fprintf (fp, "\n[done:%s]\n\n", bar->name);
1445       bar = bar->prev;
1446     }
1447   while (bar != CurrentBar->prev);
1448 }
1449 #endif                          /* (MENUBAR_MAX > 1) */
1450
1451 /*
1452  * read in menubar commands from FILENAME
1453  * ignore all input before the tag line [menu] or [menu:???]
1454  *
1455  * Note that since File_find () is used, FILENAME can be semi-colon
1456  * delimited such that the second part can refer to a tag
1457  * so that a large `database' of menus can be collected together
1458  *
1459  * FILENAME = "file"
1460  * FILENAME = "file;"
1461  *      read `file' starting with first [menu] or [menu:???] line
1462  *
1463  * FILENAME = "file;tag"
1464  *      read `file' starting with [menu:tag]
1465  */
1466 void
1467 rxvt_term::menubar_read (const char *filename)
1468 {
1469   /* read in a menu from a file */
1470   FILE           *fp;
1471   char            buffer[256];
1472   char           *p, *file, *tag = NULL;
1473
1474   file = (char *)rxvt_File_find (filename, ".menu", rs[Rs_path]);
1475   if (file == NULL)
1476     return;
1477
1478   fp = fopen (file, "rb");
1479   free (file);
1480   if (fp == NULL)
1481     return;
1482
1483 #if (MENUBAR_MAX > 1)
1484   /* semi-colon delimited */
1485   if ((tag = STRCHR (filename, ';')) != NULL)
1486     {
1487       tag++;
1488       if (*tag == '\0')
1489         tag = NULL;
1490     }
1491 #endif                          /* (MENUBAR_MAX > 1) */
1492 #ifdef DEBUG_MENU
1493   fprintf (stderr, "[read:%s]\n", p);
1494   if (tag)
1495     fprintf (stderr, "looking for [menu:%s]\n", tag);
1496 #endif
1497
1498   while ((p = fgets (buffer, sizeof (buffer), fp)) != NULL)
1499     {
1500       int             n;
1501
1502       if ((n = rxvt_Str_match (p, "[menu")) != 0)
1503         {
1504           if (tag)
1505             {
1506               /* looking for [menu:tag] */
1507               if (p[n] == ':' && p[n + 1] != ']')
1508                 {
1509                   n++;
1510                   n += rxvt_Str_match (p + n, tag);
1511                   if (p[n] == ']')
1512                     {
1513 #ifdef DEBUG_MENU
1514                       fprintf (stderr, "[menu:%s]\n", tag);
1515 #endif
1516                       break;
1517                     }
1518                 }
1519             }
1520           else if (p[n] == ':' || p[n] == ']')
1521             break;
1522         }
1523     }
1524
1525   /* found [menu], [menu:???] tag */
1526   while (p != NULL)
1527     {
1528       int             n;
1529
1530 #ifdef DEBUG_MENU
1531       fprintf (stderr, "read line = %s\n", p);
1532 #endif
1533
1534       /* looking for [done:tag] or [done:] */
1535       if ((n = rxvt_Str_match (p, "[done")) != 0)
1536         {
1537           if (p[n] == ']')
1538             {
1539               menu_readonly = 1;
1540               break;
1541             }
1542           else if (p[n] == ':')
1543             {
1544               n++;
1545               if (p[n] == ']')
1546                 {
1547                   menu_readonly = 1;
1548                   break;
1549                 }
1550               else if (tag)
1551                 {
1552                   n += rxvt_Str_match (p + n, tag);
1553                   if (p[n] == ']')
1554                     {
1555 #ifdef DEBUG_MENU
1556                       fprintf (stderr, "[done:%s]\n", tag);
1557 #endif
1558                       menu_readonly = 1;
1559                       break;
1560                     }
1561                 }
1562               else
1563                 {
1564                   /* what? ... skip this line */
1565                   p[0] = COMMENT_CHAR;
1566                 }
1567             }
1568         }
1569       /*
1570        * remove leading/trailing space
1571        * and strip-off leading/trailing quotes
1572        * skip blank or comment lines
1573        */
1574       rxvt_Str_trim (p);
1575       if (*p && *p != '#')
1576         {
1577           menu_readonly = 0;    /* if case we read another file */
1578           menubar_dispatch (p);
1579         }
1580       /* get another line */
1581       p = fgets (buffer, sizeof (buffer), fp);
1582     }
1583
1584   fclose (fp);
1585 }
1586
1587 /*
1588  * user interface for building/deleting and otherwise managing menus
1589  */
1590 void
1591 rxvt_term::menubar_dispatch (char *str)
1592 {
1593   int n, cmd;
1594   char *path, *name, *name2;
1595
1596   if (menubar_visible () && ActiveMenu != NULL)
1597     menubar_expose ();
1598   else
1599     ActiveMenu = NULL;
1600
1601   cmd = *str;
1602   switch (cmd)
1603     {
1604       case '.':
1605       case '/':                 /* absolute & relative path */
1606       case MENUITEM_BEG:                /* menuitem */
1607         /* add `+' prefix for these cases */
1608         cmd = '+';
1609         break;
1610
1611       case '+':
1612       case '-':
1613         str++;                  /* skip cmd character */
1614         break;
1615
1616       case '<':
1617 #if (MENUBAR_MAX > 1)
1618         if (CurrentBar == NULL)
1619           break;
1620 #endif                          /* (MENUBAR_MAX > 1) */
1621         if (str[1] && str[2] == '>')    /* arrow commands */
1622           menuarrow_add (str);
1623         break;
1624
1625       case '[':                 /* extended command */
1626         while (str[0] == '[')
1627           {
1628             char           *next = (++str);     /* skip leading '[' */
1629
1630             if (str[0] == ':')
1631               { /* [:command:] */
1632                 do
1633                   {
1634                     next++;
1635                     if ((next = STRCHR (next, ':')) == NULL)
1636                       return;   /* parse error */
1637                   }
1638                 while (next[1] != ']');
1639                 /* remove and skip ':]' */
1640                 *next = '\0';
1641                 next += 2;
1642               }
1643             else
1644               {
1645                 if ((next = STRCHR (next, ']')) == NULL)
1646                   return;       /* parse error */
1647                 /* remove and skip ']' */
1648                 *next = '\0';
1649                 next++;
1650               }
1651
1652             if (str[0] == ':')
1653               {
1654                 int             saved;
1655
1656                 /* try and dispatch it, regardless of read/write status */
1657                 saved = menu_readonly;
1658                 menu_readonly = 0;
1659                 menubar_dispatch (str + 1);
1660                 menu_readonly = saved;
1661               }
1662             /* these ones don't require menu stacking */
1663             else if (!STRCMP (str, "clear"))
1664               {
1665                 menubar_clear ();
1666               }
1667             else if (!STRCMP (str, "done") || rxvt_Str_match (str, "done:"))
1668               {
1669                 menu_readonly = 1;
1670               }
1671             else if (!STRCMP (str, "show"))
1672               {
1673                 map_menuBar (1);
1674                 menu_readonly = 1;
1675               }
1676             else if (!STRCMP (str, "hide"))
1677               {
1678                 map_menuBar (0);
1679                 menu_readonly = 1;
1680               }
1681             else if ((n = rxvt_Str_match (str, "read:")) != 0)
1682               {
1683                 /* read in a menu from a file */
1684                 str += n;
1685                 menubar_read (str);
1686               }
1687             else if ((n = rxvt_Str_match (str, "title:")) != 0)
1688               {
1689                 str += n;
1690                 if (CurrentBar != NULL && !menu_readonly)
1691                   {
1692                     if (*str)
1693                       {
1694                         name = (char *)rxvt_realloc (CurrentBar->title, STRLEN (str) + 1);
1695                         if (name != NULL)
1696                           {
1697                             STRCPY (name, str);
1698                             CurrentBar->title = name;
1699                           }
1700                         menubar_expose ();
1701                       }
1702                     else
1703                       {
1704                         free (CurrentBar->title);
1705                         CurrentBar->title = NULL;
1706                       }
1707                   }
1708               }
1709             else if ((n = rxvt_Str_match (str, "pixmap:")) != 0)
1710               {
1711                 str += n;
1712                 xterm_seq (XTerm_Pixmap, str, CHAR_ST);
1713               }
1714 #if (MENUBAR_MAX > 1)
1715             else if ((n = rxvt_Str_match (str, "rm")) != 0)
1716               {
1717                 str += n;
1718                 switch (str[0])
1719                   {
1720                     case ':':
1721                       str++;
1722                       /* FALLTHROUGH */
1723                     case '\0':
1724                       /* FALLTHROUGH */
1725                     case '*':
1726                       menubar_remove (str);
1727                       break;
1728                   }
1729                 menu_readonly = 1;
1730               }
1731             else if ((n = rxvt_Str_match (str, "menu")) != 0)
1732               {
1733                 str += n;
1734                 switch (str[0])
1735                   {
1736                     case ':':
1737                       str++;
1738                       /* add/access menuBar */
1739                       if (*str != '\0' && *str != '*')
1740                         menubar_push (str);
1741                       break;
1742                     default:
1743                       if (CurrentBar == NULL)
1744                         {
1745                           menubar_push ("default");
1746                         }
1747                   }
1748
1749                 if (CurrentBar != NULL)
1750                   menu_readonly = 0;    /* allow menu build commands */
1751               }
1752             else if (!STRCMP (str, "dump"))
1753               {
1754                 /* dump current menubars to a file */
1755                 FILE           *fp;
1756
1757                 /* enough space to hold the results */
1758                 char            buffer[32];
1759
1760                 sprintf (buffer, "/tmp/" APL_SUBCLASS "-%u",
1761                         (unsigned int)getpid ());
1762
1763                 if ((fp = fopen (buffer, "wb")) != NULL)
1764                   {
1765                     xterm_seq (XTerm_title, buffer, CHAR_ST);
1766                     menubar_dump (fp);
1767                     fclose (fp);
1768                   }
1769               }
1770             else if (!STRCMP (str, "next"))
1771               {
1772                 if (CurrentBar)
1773                   {
1774                     CurrentBar = CurrentBar->next;
1775                     menu_readonly = 1;
1776                   }
1777               }
1778             else if (!STRCMP (str, "prev"))
1779               {
1780                 if (CurrentBar)
1781                   {
1782                     CurrentBar = CurrentBar->prev;
1783                     menu_readonly = 1;
1784                   }
1785               }
1786             else if (!STRCMP (str, "swap"))
1787               {
1788                 /* swap the top 2 menus */
1789                 if (CurrentBar)
1790                   {
1791                     bar_t          *cbprev = CurrentBar->prev;
1792                     bar_t          *cbnext = CurrentBar->next;
1793
1794                     cbprev->next = cbnext;
1795                     cbnext->prev = cbprev;
1796
1797                     CurrentBar->next = cbprev;
1798                     CurrentBar->prev = cbprev->prev;
1799
1800                     cbprev->prev->next = CurrentBar;
1801                     cbprev->prev = CurrentBar;
1802
1803                     CurrentBar = cbprev;
1804                     menu_readonly = 1;
1805                   }
1806               }
1807 #endif                          /* (MENUBAR_MAX > 1) */
1808             str = next;
1809
1810             BuildMenu = ActiveMenu = NULL;
1811             menubar_expose ();
1812 #ifdef DEBUG_MENUBAR_STACKING
1813             fprintf (stderr, "menus are read%s\n",
1814                     menu_readonly ? "only" : "/write");
1815 #endif
1816
1817           }
1818         return;
1819         break;
1820     }
1821
1822 #if (MENUBAR_MAX > 1)
1823   if (CurrentBar == NULL)
1824     return;
1825   if (menu_readonly)
1826     {
1827 #ifdef DEBUG_MENUBAR_STACKING
1828       fprintf (stderr, "menus are read%s\n",
1829               menu_readonly ? "only" : "/write");
1830 #endif
1831       return;
1832     }
1833 #endif                          /* (MENUBAR_MAX > 1) */
1834
1835   switch (cmd)
1836     {
1837       case '+':
1838       case '-':
1839         path = name = str;
1840
1841         name2 = NULL;
1842         /* parse STR, allow spaces inside (name)  */
1843         if (path[0] != '\0')
1844           {
1845             name = STRCHR (path, MENUITEM_BEG);
1846             str = STRCHR (path, MENUITEM_END);
1847             if (name != NULL || str != NULL)
1848               {
1849                 if (name == NULL || str == NULL || str <= (name + 1)
1850                     || (name > path && name[-1] != '/'))
1851                   {
1852                     rxvt_print_error ("menu error <%s>\n", path);
1853                     break;
1854                   }
1855                 if (str[1] == MENUITEM_BEG)
1856                   {
1857                     name2 = (str + 2);
1858                     str = STRCHR (name2, MENUITEM_END);
1859
1860                     if (str == NULL)
1861                       {
1862                         rxvt_print_error ("menu error <%s>\n", path);
1863                         break;
1864                       }
1865                     name2[-2] = '\0';   /* remove prev MENUITEM_END */
1866                   }
1867                 if (name > path && name[-1] == '/')
1868                   name[-1] = '\0';
1869
1870                 *name++ = '\0'; /* delimit */
1871                 *str++ = '\0';  /* delimit */
1872
1873                 while (isspace (*str))
1874                   str++;        /* skip space */
1875               }
1876 #ifdef DEBUG_MENU
1877             fprintf (stderr,
1878                     "`%c' path = <%s>, name = <%s>, name2 = <%s>, action = <%s>\n",
1879                     cmd, (path ? path : " (nil)"), (name ? name : " (nil)"),
1880                     (name2 ? name2 : " (nil)"), (str ? str : " (nil)")
1881                    );
1882 #endif
1883
1884           }
1885         /* process the different commands */
1886         switch (cmd)
1887           {
1888             case '+':           /* add/replace existing menu or menuitem */
1889               if (path[0] != '\0')
1890                 {
1891                   int             len;
1892
1893                   path = menu_find_base (& (BuildMenu), path);
1894                   len = STRLEN (path);
1895
1896                   /* don't allow menus called `*' */
1897                   if (path[0] == '*')
1898                     {
1899                       menu_clear (BuildMenu);
1900                       break;
1901                     }
1902                   else if (len >= 2 && !STRCMP ((path + len - 2), "/*"))
1903                     {
1904                       path[len - 2] = '\0';
1905                     }
1906                   if (path[0] != '\0')
1907                     BuildMenu = menu_add (BuildMenu, path);
1908                 }
1909               if (name != NULL && name[0] != '\0')
1910                 rxvt_menuitem_add (BuildMenu,
1911                                   (STRCMP (name, SEPARATOR_NAME) ? name : ""),
1912                                   name2, str);
1913               break;
1914
1915             case '-':           /* delete menu entry */
1916               if (!STRCMP (path, "/*") && (name == NULL || name[0] == '\0'))
1917                 {
1918                   menubar_clear ();
1919                   BuildMenu = NULL;
1920                   menubar_expose ();
1921                   break;
1922                 }
1923               else if (path[0] != '\0')
1924                 {
1925                   int             len;
1926                   menu_t         *menu = BuildMenu;
1927
1928                   path = menu_find_base (&menu, path);
1929                   len = STRLEN (path);
1930
1931                   /* submenu called `*' clears all menu items */
1932                   if (path[0] == '*')
1933                     {
1934                       menu_clear (menu);
1935                       break;    /* done */
1936                     }
1937                   else if (len >= 2 && !STRCMP (&path[len - 2], "/*"))
1938                     {
1939                       /* done */
1940                       break;
1941                     }
1942                   else if (path[0] != '\0')
1943                     {
1944                       BuildMenu = NULL;
1945                       break;
1946                     }
1947                   else
1948                     BuildMenu = menu;
1949                 }
1950               if (BuildMenu != NULL)
1951                 {
1952                   if (name == NULL || name[0] == '\0')
1953                     BuildMenu = menu_delete (BuildMenu);
1954                   else
1955                     {
1956                       const char     *n1;
1957                       menuitem_t     *item;
1958                       menu_t         *BuildMenu = BuildMenu;
1959
1960                       n1 = STRCMP (name, SEPARATOR_NAME) ? name : "";
1961                       item = rxvt_menuitem_find (BuildMenu, n1);
1962                       if (item != NULL && item->entry.type != MenuSubMenu)
1963                         {
1964                           menuitem_free (BuildMenu, item);
1965
1966                           /* fix up the width */
1967                           BuildMenu->width = 0;
1968                           for (item = BuildMenu->head; item != NULL;
1969                                item = item->next)
1970                             {
1971                               short           l = item->len + item->len2;
1972
1973                               MAX_IT (BuildMenu->width, l);
1974                             }
1975                         }
1976                     }
1977                   menubar_expose ();
1978                 }
1979               break;
1980           }
1981         break;
1982     }
1983 }
1984
1985 void
1986 rxvt_term::draw_Arrows (int name, int state)
1987 {
1988   GC              top, bot;
1989
1990   int             i;
1991
1992 #ifdef MENU_SHADOW_IN
1993   state = -state;
1994 #endif
1995   switch (state)
1996     {
1997       case +1:
1998         top = topShadowGC;
1999         bot = botShadowGC;
2000         break;                  /* SHADOW_OUT */
2001       case -1:
2002         top = botShadowGC;
2003         bot = topShadowGC;
2004         break;                  /* SHADOW_IN */
2005       default:
2006         top = bot = scrollbarGC;
2007         break;                  /* neutral */
2008     }
2009
2010   if (!Arrows_x)
2011     return;
2012
2013   for (i = 0; i < NARROWS; i++)
2014     {
2015       const int w = Width2Pixel (1);
2016       const int y = (menuBar_TotalHeight () - w) / 2;
2017       int       x = Arrows_x + (5 * Width2Pixel (i)) / 4;
2018
2019       if (!name || name == Arrows[i].name)
2020         rxvt_Draw_Triangle (display->display, menuBar.win, top, bot, x, y, w,
2021                            Arrows[i].name);
2022     }
2023   XFlush (display->display);
2024 }
2025
2026 void
2027 rxvt_term::menubar_expose ()
2028 {
2029   menu_t *menu;
2030   int x;
2031
2032   if (!menubar_visible () || menuBar.win == 0)
2033     return;
2034
2035   if (menubarGC == None)
2036     {
2037       /* Create the graphics context */
2038       XGCValues       gcvalue;
2039
2040       gcvalue.foreground = (XDEPTH <= 2 ? PixColors[Color_fg]
2041                             : PixColors[Color_Black]);
2042       menubarGC = XCreateGC (display->display, menuBar.win,
2043                             GCForeground, &gcvalue);
2044
2045     }
2046   /* make sure the font is correct */
2047   XClearWindow (display->display, menuBar.win);
2048
2049   menu_hide_all ();
2050
2051   x = 0;
2052   if (CurrentBar != NULL)
2053     {
2054       for (menu = CurrentBar->head; menu != NULL; menu = menu->next)
2055         {
2056           int             len = menu->len;
2057
2058           x = (menu->x + menu->len + HSPACE);
2059
2060 #ifdef DEBUG_MENU_LAYOUT
2061           rxvt_print_menu_descendants (menu);
2062 #endif
2063
2064           if (x >= TermWin.ncol)
2065             len = (TermWin.ncol - (menu->x + HSPACE));
2066
2067           drawbox_menubar (menu->x, len, +1);
2068           draw_string (*menuBar.drawable, menubarGC, TermWin.fontset,
2069                        (Width2Pixel (menu->x) + Width2Pixel (HSPACE) / 2),
2070                        SHADOW, menu->name, len);
2071
2072           if (x >= TermWin.ncol)
2073             break;
2074         }
2075     }
2076   drawbox_menubar (x, TermWin.ncol, (CurrentBar ? +1 : -1));
2077
2078   /* add the menuBar title, if it exists and there's plenty of room */
2079   Arrows_x = 0;
2080   if (x < TermWin.ncol)
2081     {
2082       const char     *str;
2083       int             ncol;
2084       unsigned int    len;
2085       char            title[256];
2086
2087       ncol = (int)TermWin.ncol;
2088       if (x < (ncol - (NARROWS + 1)))
2089         {
2090           ncol -= (NARROWS + 1);
2091           Arrows_x = Width2Pixel (ncol);
2092         }
2093       draw_Arrows (0, +1);
2094
2095       str = (CurrentBar
2096              && CurrentBar->title) ? CurrentBar->title : "%n-%v";
2097       for (len = 0; str[0] && len < sizeof (title) - 1; str++)
2098         {
2099           const char     *s = NULL;
2100
2101           switch (str[0])
2102             {
2103               case '%':
2104                 str++;
2105                 switch (str[0])
2106                   {
2107                     case 'n':
2108                       s = rs[Rs_name];
2109                       break;    /* resource name */
2110                     case 'v':
2111                       s = VERSION;
2112                       break;    /* version number */
2113                     case '%':
2114                       s = "%";
2115                       break;    /* literal '%' */
2116                   }
2117                 if (s != NULL)
2118                   while (*s && len < sizeof (title) - 1)
2119                     title[len++] = *s++;
2120                 break;
2121
2122               default:
2123                 title[len++] = str[0];
2124                 break;
2125             }
2126         }
2127       title[len] = '\0';
2128
2129       ncol -= (x + len + HSPACE);
2130       if (len > 0 && ncol >= 0)
2131         draw_string (*menuBar.drawable, menubarGC, TermWin.fontset,
2132                      Width2Pixel (x) + Width2Pixel (ncol + HSPACE) / 2,
2133                      SHADOW, title, len);
2134     }
2135 }
2136
2137 int
2138 rxvt_term::menubar_mapping (int map)
2139 {
2140   int             change = 0;
2141
2142   if (map && !menubar_visible ())
2143     {
2144       menuBar.state = 1;
2145       if (menuBar.win == 0)
2146         return 0;
2147       XMapWindow (display->display, menuBar.win);
2148       change = 1;
2149     }
2150   else if (!map && menubar_visible ())
2151     {
2152       menubar_expose ();
2153       menuBar.state = 0;
2154       XUnmapWindow (display->display, menuBar.win);
2155       change = 1;
2156     }
2157   else
2158     menubar_expose ();
2159
2160   return change;
2161 }
2162
2163 int
2164 rxvt_term::menu_select (XButtonEvent &ev)
2165 {
2166   menuitem_t *thisitem, *item = NULL;
2167   int this_y, y;
2168
2169   Window unused_root, unused_child;
2170   int unused_root_x, unused_root_y;
2171   unsigned int unused_mask;
2172
2173   if (ActiveMenu == NULL)
2174     return 0;
2175
2176   XQueryPointer (display->display, ActiveMenu->win,
2177                  &unused_root, &unused_child,
2178                  &unused_root_x, &unused_root_y,
2179                  &ev.x, &ev.y, &unused_mask);
2180
2181   if (ActiveMenu->parent != NULL && (ev.x < 0 || ev.y < 0))
2182     {
2183       menu_hide ();
2184       return 1;
2185     }
2186
2187   /* determine the menu item corresponding to the Y index */
2188   y = SHADOW;
2189   if (ev.x >= 0 && ev.x <= (ActiveMenu->w - SHADOW))
2190     {
2191       for (item = ActiveMenu->head; item != NULL; item = item->next)
2192         {
2193           int h = HEIGHT_TEXT + 2 * SHADOW;
2194
2195           if (isSeparator (item->name))
2196             h = HEIGHT_SEPARATOR;
2197           else if (ev.y >= y && ev.y < (y + h))
2198             break;
2199
2200           y += h;
2201         }
2202     }
2203
2204   if (item == NULL && ev.type == ButtonRelease)
2205     {
2206       menu_hide_all ();
2207       return 0;
2208     }
2209
2210   thisitem = item;
2211   this_y = y - SHADOW;
2212
2213   /* erase the last item */
2214   if (ActiveMenu->item != NULL)
2215     {
2216       if (ActiveMenu->item != thisitem)
2217         {
2218           for (y = 0, item = ActiveMenu->head; item != NULL; item = item->next)
2219             {
2220               int h;
2221
2222               if (isSeparator (item->name))
2223                 h = HEIGHT_SEPARATOR;
2224               else if (item == ActiveMenu->item)
2225                 {
2226                   /* erase old menuitem */
2227                   drawbox_menuitem (y, 0);      /* No Shadow */
2228                   if (item->entry.type == MenuSubMenu)
2229                     drawtriangle (ActiveMenu->w, y, +1);
2230
2231                   break;
2232                 }
2233               else
2234                 h = HEIGHT_TEXT + 2 * SHADOW;
2235
2236               y += h;
2237             }
2238         }
2239       else
2240         {
2241           switch (ev.type)
2242             {
2243               case ButtonRelease:
2244                 switch (item->entry.type)
2245                   {
2246                     case MenuLabel:
2247                     case MenuSubMenu:
2248                       menu_hide_all ();
2249                       break;
2250
2251                     case MenuAction:
2252                     case MenuTerminalAction:
2253                       drawbox_menuitem (this_y, -1);
2254 #ifdef HAVE_NANOSLEEP
2255                       struct timespec rqt;
2256
2257                       rqt.tv_sec = 0;
2258                       rqt.tv_nsec = MENU_DELAY_USEC * 1000;
2259                       nanosleep (&rqt, NULL);
2260 #else
2261                       /* use select for timing */
2262                       struct timeval  tv;
2263
2264                       tv.tv_sec = 0;
2265                       tv.tv_usec = MENU_DELAY_USEC;
2266                       select (0, NULL, NULL, NULL, &tv);
2267 #endif
2268                       /* remove menu before sending keys to the application */
2269                       menu_hide_all ();
2270 #ifndef DEBUG_MENU
2271                       action_dispatch (& (item->entry.action));
2272 #else                           /* DEBUG_MENU */
2273                       fprintf (stderr, "%s: %s\n", item->name,
2274                               item->entry.action.str);
2275 #endif                          /* DEBUG_MENU */
2276                       break;
2277                   }
2278                 break;
2279
2280               default:
2281                 if (item->entry.type == MenuSubMenu)
2282                   goto DoMenu;
2283                 break;
2284             }
2285           return 0;
2286         }
2287     }
2288
2289 DoMenu:
2290   ActiveMenu->item = thisitem;
2291   y = this_y;
2292
2293   if (thisitem != NULL)
2294     {
2295       item = ActiveMenu->item;
2296       if (item->entry.type != MenuLabel)
2297         drawbox_menuitem (y, +1);
2298
2299       if (item->entry.type == MenuSubMenu)
2300         {
2301           int x;
2302
2303           drawtriangle (ActiveMenu->w, y, -1);
2304
2305           x = ev.x + (ActiveMenu->parent
2306                        ? ActiveMenu->x
2307                        : Width2Pixel (ActiveMenu->x));
2308
2309           if (x >= item->entry.submenu.menu->x)
2310             {
2311               ActiveMenu = item->entry.submenu.menu;
2312               menu_show ();
2313               return 1;
2314             }
2315         }
2316     }
2317   return 0;
2318 }
2319
2320 void
2321 rxvt_term::menubar_select (XButtonEvent &ev)
2322 {
2323   menu_t *menu = NULL;
2324
2325   /* determine the pulldown menu corresponding to the X index */
2326   if (ev.y >= 0 && ev.y <= menuBar_height () && CurrentBar != NULL)
2327     {
2328       for (menu = CurrentBar->head; menu != NULL; menu = menu->next)
2329         {
2330           int x = Width2Pixel (menu->x);
2331           int w = Width2Pixel (menu->len + HSPACE);
2332
2333           if ((ev.x >= x && ev.x < x + w))
2334             break;
2335         }
2336     }
2337   switch (ev.type)
2338     {
2339       case ButtonRelease:
2340         menu_hide_all ();
2341         break;
2342
2343       case ButtonPress:
2344         if (menu == NULL && Arrows_x && ev.x >= Arrows_x)
2345           {
2346             int             i;
2347
2348             for (i = 0; i < NARROWS; i++)
2349               {
2350                 if (ev.x >= (Arrows_x + (Width2Pixel (4 * i + i)) / 4)
2351                     && ev.x < (Arrows_x
2352                                 + (Width2Pixel (4 * i + i + 4)) / 4))
2353                   {
2354                     draw_Arrows (Arrows[i].name, -1);
2355                     {
2356 #ifdef HAVE_NANOSLEEP
2357                       struct timespec rqt;
2358
2359                       rqt.tv_sec = 0;
2360                       rqt.tv_nsec = MENU_DELAY_USEC * 1000;
2361                       nanosleep (&rqt, NULL);
2362 #else
2363                       /* use select for timing */
2364                       struct timeval  tv;
2365
2366                       tv.tv_sec = 0;
2367                       tv.tv_usec = MENU_DELAY_USEC;
2368                       select (0, NULL, NULL, NULL, &tv);
2369 #endif
2370
2371                     }
2372                     draw_Arrows (Arrows[i].name, +1);
2373 #ifdef DEBUG_MENUARROWS
2374                     fprintf (stderr, "'%c': ", Arrows[i].name);
2375
2376                     if (CurrentBar == NULL
2377                         || (CurrentBar->arrows[i].type != MenuAction
2378                             && CurrentBar->arrows[i].type !=
2379                             MenuTerminalAction))
2380                       {
2381                         if (Arrows[i].str != NULL && Arrows[i].str[0])
2382                           fprintf (stderr, " (default) \\033%s\n",
2383                                   & (Arrows[i].str[2]));
2384                       }
2385                     else
2386                       {
2387                         fprintf (stderr, "%s\n",
2388                                 CurrentBar->arrows[i].str);
2389                       }
2390 #else                           /* DEBUG_MENUARROWS */
2391                     if (CurrentBar == NULL || action_dispatch (&CurrentBar->arrows[i]))
2392                       {
2393                         if (Arrows[i].str != NULL && Arrows[i].str[0] != 0)
2394                           tt_write ((Arrows[i].str + 1),
2395                                     Arrows[i].str[0]);
2396                       }
2397 #endif                          /* DEBUG_MENUARROWS */
2398                     return;
2399                   }
2400               }
2401           }
2402         /* FALLTHROUGH */
2403
2404       default:
2405         /*
2406          * press menubar or move to a new entry
2407          */
2408         if (menu != NULL && menu != ActiveMenu)
2409           {
2410             menu_hide_all ();   /* pop down old menu */
2411             ActiveMenu = menu;
2412             menu_show ();       /* pop up new menu */
2413           }
2414         break;
2415     }
2416 }
2417
2418 /*
2419  * general dispatch routine,
2420  * it would be nice to have `sticky' menus
2421  */
2422 void
2423 rxvt_term::menubar_control (XButtonEvent &ev)
2424 {
2425   switch (ev.type)
2426     {
2427       case ButtonPress:
2428         if (ev.button == Button1)
2429           menubar_select (ev);
2430         break;
2431
2432       case ButtonRelease:
2433         if (ev.button == Button1)
2434           menu_select (ev);
2435         break;
2436
2437       case MotionNotify:
2438         while (XCheckTypedWindowEvent (display->display, TermWin.parent[0],
2439                                        MotionNotify, (XEvent *)&ev)) ;
2440
2441         if (ActiveMenu)
2442           while (menu_select (ev)) ;
2443         else
2444           ev.y = -1;
2445         if (ev.y < 0)
2446           {
2447             Window          unused_root, unused_child;
2448             int             unused_root_x, unused_root_y;
2449             unsigned int    unused_mask;
2450
2451             XQueryPointer (display->display, menuBar.win,
2452                           &unused_root, &unused_child,
2453                           &unused_root_x, &unused_root_y,
2454                           &ev.x, &ev.y, &unused_mask);
2455             menubar_select (ev);
2456           }
2457         break;
2458     }
2459 }
2460
2461 void
2462 rxvt_term::map_menuBar (int map)
2463 {
2464   if (menubar_mapping (map))
2465     resize_all_windows (0, 0, 0);
2466 }
2467 #endif
2468 /*----------------------- end-of-file (C source) -----------------------*/