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