1 /*
2  * Copyright (c) 2003-2004 the xdvik development team
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17  * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
18  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20  * OTHER DEALINGS IN THE SOFTWARE.
21  *
22  */
23 
24 /*
25  * Common code for Xaw and Motif menu bar creation.
26  */
27 
28 #include "xdvi-config.h"
29 #include "xdvi.h"
30 #include "events.h"
31 #include "menu.h"
32 #include "util.h"
33 
34 #ifdef MOTIF
35 #  include <Xm/RowColumn.h>
36 #  include <Xm/ToggleB.h>
37 #else
38 #  include <X11/Intrinsic.h>
39 #  include <X11/Xatom.h>
40 #  include <X11/StringDefs.h>
41 #  include <X11/Xaw/Label.h>
42 #endif
43 
44 
45 /* translate string argument into corresponding buttonTypeT */
46 static buttonTypeT
get_type(const char * str)47 get_type(const char *str)
48 {
49     if (strcmp(str, "PUSH") == 0)
50 	return BT_PUSH;
51     else if (strcmp(str, "RADIO") == 0)
52 	return BT_RADIO;
53     else if (strcmp(str, "CHECK") == 0)
54 	return BT_CHECK;
55     else if (strcmp(str, "SEP") == 0)
56 	return BT_SEP;
57     else
58 	return BT_INVALID;
59 }
60 
61 static struct button_info *m_button_info = NULL; /* toplevel node of pulldown menu structure */
62 
63 
64 static void
set_menu_info(void * val,XtActionProc proc,Boolean (* cmp)(),struct button_info * item)65 set_menu_info(void *val, XtActionProc proc, Boolean (*cmp)(), struct button_info *item)
66 {
67     size_t i;
68     ASSERT(item != NULL, "item in set_menu_info musn't be NULL!");
69     for (i = 0; i < item->size; i++) {
70 	if ((item->elems[i].type == BT_RADIO || item->elems[i].type == BT_CHECK)
71 	    && item->elems[i].action != NULL
72 	    && item->elems[i].action->proc != NULL
73 	    && item->elems[i].action->proc == proc
74 	    && item->elems[i].action->num_params > 0
75 	    && item->elems[i].action->params[0] != NULL) {
76 	    Boolean state;
77 	    ASSERT(cmp != NULL, "comparison function musn't be NULL!");
78 	    state = cmp(val, item->elems[i].action->params[0]);
79 #ifdef MOTIF
80 	    ASSERT(item->elems[i].widget != 0, "Widget musn't be NULL!");
81 	    XmToggleButtonSetState(item->elems[i].widget, state, False);
82 #else
83 	    if (item->elems[i].widget == 0) {
84 		XDVI_WARNING((stderr, "Widget for menu `%s' is null!", item->elems[i].title));
85 		continue;
86 	    }
87 	    xaw_set_button_state(item->elems + i, state);
88 #endif
89 	}
90 	if (item->elems[i].submenu != NULL) { /* invoke it recursively */
91 	    set_menu_info(val, proc, cmp, item->elems[i].submenu);
92 	}
93     }
94 }
95 
96 
97 /* set a menu according to val and the compare function cmp */
98 void
set_menu(void * val,XtActionProc proc,Boolean (* cmp)())99 set_menu(void *val, XtActionProc proc, Boolean (*cmp)())
100 {
101     /* removed following since cast from function pointer to void pointer is not supported by ANSI C */
102     /* TRACE_GUI((stderr, "set_menu_info: %d, %p, %p", *(int *)val, (void *)proc, (void *)cmp)); */
103     set_menu_info(val, proc, cmp, m_button_info);
104 }
105 
106 static void
initialize_menus(void)107 initialize_menus(void)
108 {
109     int use_gs;
110     int shrinkval;
111 
112     /* initialize tickmarks for all possible actions */
113     use_gs = resource.postscript;
114 #ifdef PS_GS
115     if (!resource.useGS)
116 	use_gs = 0;
117 #endif
118 
119     set_menu(&use_gs, Act_set_ps, check_int);
120 #ifdef PS_GS
121     set_menu(&resource.gs_alpha, Act_set_gs_alpha, check_toggle);
122 #endif
123     set_menu(&resource.keep_flag, Act_set_keep_flag, check_toggle);
124     shrinkval = resource.pixels_per_inch / mane.shrinkfactor;
125     set_menu(&shrinkval, Act_shrink_to_dpi, check_int);
126     set_menu(&mane.shrinkfactor, Act_set_shrink_factor, check_int);
127     set_menu(&resource.use_tex_pages, Act_use_tex_pages, check_toggle);
128 #if 0
129     set_menu((char *)resource.paper,  Act_set_paper_landscape, check_paper_landscape);
130     set_menu((char *)resource.paper, Act_set_papersize, check_papersize);
131 #endif /* 0 */
132     set_menu(&resource.mouse_mode, Act_switch_mode, check_int);
133     set_menu(&resource.expert_mode, Act_set_expert_mode, check_resource_expert);
134 }
135 
136 static void
free_items(char ** items,size_t len)137 free_items(char **items, size_t len)
138 {
139     size_t curr = 0;
140     while(curr < len) {
141 	free(items[curr++]);
142     }
143     free(items);
144 }
145 
146 #if 0
147 static void
148 show_items(char *descr, char **items, size_t len)
149 {
150     size_t i;
151     for (i = 0; i < len; i++) {
152 	fprintf(stderr, "%s %d: |%s|\n", descr, i, items[i]);
153     }
154 }
155 #endif
156 
157 static void
add_info(struct button_info ** info,buttonTypeT bt_type,char mnemonic,const char * title,const char * accelerator,struct xdvi_action * action)158 add_info(struct button_info **info, buttonTypeT bt_type,
159 	 char mnemonic, const char *title,
160 	 const char *accelerator, struct xdvi_action *action)
161 {
162     size_t idx = (*info)->size++;
163     (*info)->elems = xrealloc((*info)->elems, (*info)->size * sizeof *((*info)->elems));
164     (*info)->elems[idx].title = xstrdup(title);
165     (*info)->elems[idx].type = bt_type;
166     if (accelerator == NULL || accelerator[0] == '\0')
167 	(*info)->elems[idx].accelerator = NULL;
168     else
169 	(*info)->elems[idx].accelerator = xstrdup(accelerator);
170     (*info)->elems[idx].mnemonic = mnemonic;
171     (*info)->elems[idx].action = action;
172     (*info)->elems[idx].widget = 0;
173     (*info)->elems[idx].submenu = NULL;
174 }
175 
176 static void
insert_items(struct button_info ** info,char ** items,size_t num_items,const char * button_type,const char * accelerator,const char * action)177 insert_items(struct button_info **info, char **items, size_t num_items,
178 	     const char *button_type, const char *accelerator, const char *action)
179 {
180     const char ENTRY_SEP = '|';
181     size_t i = 0;
182     size_t entry_len;
183     size_t entry_count = 0;
184     char **entry_items = NULL;
185     size_t idx;
186 
187     Boolean found = False;
188     Boolean have_error = False;
189 
190     if (*items == NULL) {
191 	/* should be a separator, which is treated as a special case since
192 	   there's no menu title: */
193 	if (strcmp (button_type, "SEP") == 0)
194 	    add_info(info, BT_SEP, '\0', "SEP", NULL, NULL);
195 	else
196 	    XDVI_WARNING((stderr, "Shouldn't happen: items == NULL!"));
197 	return;
198     }
199 
200     entry_len = strlen(items[0]);
201     entry_items = split_line(items[0], ENTRY_SEP, 0, entry_len, &entry_count);
202 
203     if (entry_count < 2) {
204 	XDVI_WARNING((stderr, "Missing Mnemonic in button info `%s'", items[0]));
205 	entry_count++;
206 	entry_items = xrealloc(entry_items, entry_count * sizeof *entry_items);
207 	entry_items[1] = xstrdup("");
208 	entry_items[2] = NULL;
209     }
210 
211     for (i = 0; i < (*info)->size; i++) {
212 	if (strcmp(entry_items[0], (*info)->elems[i].title) == 0) {
213 	    found = true;
214 	    break;
215 	}
216     }
217 
218     idx = i;
219 
220     if (!found) { /* new item, resize info and add this item */
221 	struct xdvi_action *my_action = NULL;
222 	buttonTypeT my_type = BT_NONE;
223 
224 	/* if it's a `leaf' in the menu hierarchy, compile the action and set the button type */
225 	if (num_items == 1) {
226 	    char *fmt = strchr(entry_items[0], '$');
227 	    if (fmt != NULL
228 		&& (fmt == entry_items[0] || (fmt > entry_items[0] && *(fmt - 1) != '\\'))
229 		&& (fmt[1] == '#' || fmt[1] == '%' ||fmt[1] == '_')) {
230 		XDVI_WARNING((stderr, "Xdvik doesn't support format characters in button labels; "
231 			      "skipping button \"%s\"", items[0]));
232 		have_error = True;
233 	    }
234 
235 	    if (strlen(action) == 0 || (!compile_action(action, &my_action))) {
236 		XDVI_WARNING((stderr, "Invalid action \"%s\" for button \"%s\" (skipping this line).",
237 			      action, items[0]));
238 		have_error = True;
239 	    }
240 
241 	    if ((my_type = get_type(button_type)) == BT_INVALID) {
242 		XDVI_WARNING((stderr, "Invalid type \"%s\" for button \"%s\" (skipping this line).",
243 			      button_type, items[0]));
244 		have_error = True;
245 	    }
246 	}
247 
248 	if (!have_error) {
249 	    add_info(info, my_type, entry_items[1][0], entry_items[0], accelerator, my_action);
250 	}
251     }
252     free_items(entry_items, entry_count);
253 
254     if (num_items > 1 || (num_items == 1 && strcmp(button_type, "SEP") == 0)) { /* not a leaf, invoke recursivly for next level */
255 	if ((*info)->elems[idx].submenu == NULL) { /* submenu didn't exist yet, create it */
256 	    struct button_info *new_submenu = xmalloc(sizeof *new_submenu);
257 	    new_submenu->elems = NULL;
258 	    new_submenu->size = 0;
259 	    (*info)->elems[idx].submenu = new_submenu;
260 	}
261 	insert_items(&((*info)->elems[idx].submenu), items + 1, num_items - 1, button_type, accelerator, action);
262     }
263 }
264 
265 static void
show_button_info(int depth,struct button_info * info)266 show_button_info(int depth, struct button_info *info)
267 {
268     size_t i;
269     for (i = 0; i < info->size; i++) {
270 	TRACE_GUI((stderr, "%*c-->%s; type=%d; mnemonic=%c; accel=%s; submenu=%p; w=%lu; action: %p",
271 		   depth, ' ',
272 		   info->elems[i].title,
273 		   info->elems[i].type,
274 		   info->elems[i].mnemonic,
275 		   info->elems[i].accelerator ? info->elems[i].accelerator : "<NULL>",
276 		   (void *)info->elems[i].submenu,
277 		   (unsigned long)info->elems[i].widget,
278 		   (void *)info->elems[i].action));
279 	if (info->elems[i].submenu != NULL) {
280 	    show_button_info(depth + 3, info->elems[i].submenu);
281 	}
282     }
283 }
284 
285 static void
parse_button_translations(struct button_info ** info)286 parse_button_translations(struct button_info **info)
287 {
288     const char *curr_p, *end_p;
289 
290     const char LINE_SEP = ':';
291     const char MENU_SEP = '>';
292 
293     for (curr_p = resource.menu_translations;
294 	 curr_p != NULL && *curr_p != '\0';
295 	 curr_p = end_p + 1) {
296 	end_p = strchr(curr_p, '\n');
297 	if (end_p != NULL) {
298 	    size_t line_len = end_p - curr_p;
299 	    size_t line_count = 0;
300 	    char **line_items;
301 	    line_items = split_line(curr_p, LINE_SEP, 0, line_len, &line_count);
302 	    /*  	    fprintf(stderr, "length of line: %d; %d items\n", line_len, line_count); */
303 	    /*  	    show_items("LINE", line_items, line_count); */
304 	    if (line_count != 4) { /* error */
305 		XDVI_WARNING((stderr, "Wrong number of items (%lu) in translations line:\n\"%.*s\" "
306 			      "(skipping this line).\n",
307 			      (unsigned long)line_count, (int)line_len, curr_p));
308 		free_items(line_items, line_count);
309 		continue;
310 	    }
311 	    else { /* split first elem into menu description */
312 		size_t menu_len = strlen(line_items[0]);
313 		size_t menu_count = 0;
314 		char **menu_items;
315 		if (menu_len == 0) { /* error */
316 		    XDVI_WARNING((stderr, "Menu description (first item) mustn't be empty:\n\"%.*s\" "
317 				  "(skipping this line).\n",
318 				  (int)line_len, curr_p));
319 		    free_items(line_items, line_count);
320 		    continue;
321 		}
322 		menu_items = split_line(line_items[0], MENU_SEP, 0, menu_len, &menu_count);
323 		/*  		show_items("   MENU", menu_items, menu_count); */
324 
325 		insert_items(info, menu_items, menu_count, line_items[1], line_items[2], line_items[3]);
326 		free_items(menu_items, menu_count);
327 	    }
328 	    free_items(line_items, line_count);
329 	}
330     }
331     show_button_info(0, *info);
332 }
333 
334 /*
335   Top-level routine: creates the pulldown menu buttons for Motif and Xaw.
336   For Motif, sets `*menu_bar' to the address of the new widget created,
337   for Xaw, sets *width to the width of the button panel created.
338 */
339 void
create_menu_buttons(Widget parent,Widget * menu_bar)340 create_menu_buttons(Widget parent,
341 #ifdef MOTIF
342 		    Widget *menu_bar
343 #else
344 		    int *width
345 #endif
346 		    )
347 {
348 #ifdef MOTIF
349     Widget menu = 0;
350     size_t i;
351 #else /* MOTIF */
352     Widget panel = 0;
353 #endif
354 
355     m_button_info = xmalloc(sizeof *m_button_info);
356     m_button_info->elems = NULL;
357     m_button_info->size = 0;
358 
359 #ifdef MOTIF
360     *menu_bar = XmCreateMenuBar(parent, "menuBar", NULL, 0);
361     parse_button_translations(&m_button_info);
362 
363     for (i = 0; i < m_button_info->size; i++) {
364 	menu = xm_create_menu(*menu_bar,
365 			      m_button_info->elems[i].title,
366 			      m_button_info->elems[i].mnemonic,
367 			      m_button_info->elems[i].submenu);
368     }
369     if (menu != 0) {
370 	XtVaSetValues(*menu_bar, XmNmenuHelpWidget, menu, NULL);
371     }
372 #else /* MOTIF */
373     xaw_initialize_menu_bitmaps();
374     panel = xaw_create_menu_widgets(parent);
375     parse_button_translations(&m_button_info);
376     xaw_create_menu(m_button_info, panel, width);
377 #endif /* MOTIF */
378     initialize_menus();
379 }
380 
381