ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/rxvt-unicode/src/menubar.C
Revision: 1.4
Committed: Sat Jan 31 00:20:21 2004 UTC (20 years, 3 months ago) by pcg
Content type: text/plain
Branch: MAIN
Changes since 1.3: +311 -348 lines
Log Message:
*** empty log message ***

File Contents

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