1 /*
2  * FIG : Facility for Interactive Generation of figures
3  * Copyright (c) 1985-1988 by Supoj Sutanthavibul
4  * Parts Copyright (c) 1989-2007 by Brian V. Smith
5  * Parts Copyright (c) 1991 by Paul King
6  * Parts Copyright (c) 2016-2020 by Thomas Loimer
7  *
8  * Any party obtaining a copy of these files is granted, free of charge, a
9  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
10  * nonexclusive right and license to deal in this software and documentation
11  * files (the "Software"), including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense and/or sell copies
13  * of the Software, and to permit persons who receive copies from any such
14  * party to do so, with the only requirement being that the above copyright
15  * and this permission notice remain intact.
16  *
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 #include "w_canvas.h"
23 
24 #include <limits.h>
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include <X11/Xlib.h>
31 #include <X11/Intrinsic.h>
32 #include <X11/Shell.h>
33 #include <X11/StringDefs.h>
34 #include <X11/Xatom.h>
35 #include <X11/Xutil.h>
36 #include <X11/keysym.h>
37 #include <X11/Xaw3d/Command.h>
38 #include <X11/Xaw3d/Form.h>
39 #include <X11/Xaw3d/Label.h>
40 
41 #include "resources.h"
42 #include "object.h"
43 #include "main.h"
44 #include "mode.h"
45 #include "paintop.h"
46 
47 #include "d_text.h"
48 #include "e_edit.h"
49 #include "u_pan.h"
50 #include "u_create.h"
51 #include "u_redraw.h"
52 #include "u_search.h"
53 #include "w_canvas.h"
54 #include "w_cmdpanel.h"
55 #include "w_cursor.h"
56 #include "w_grid.h"
57 #include "w_layers.h"
58 #include "w_modepanel.h"
59 #include "w_mousefun.h"
60 #include "w_msgpanel.h"
61 #include "w_rulers.h"
62 #include "w_setup.h"
63 #include "w_util.h"
64 #include "w_drawprim.h"
65 #include "w_keyboard.h"
66 #include "w_snap.h"
67 #include "w_zoom.h"
68 
69 
70 /*********************** EXPORTS ************************/
71 
72 void		(*canvas_kbd_proc) ();
73 void		(*canvas_locmove_proc) ();
74 void		(*canvas_ref_proc) ();
75 void		(*canvas_leftbut_proc) ();
76 void		(*canvas_middlebut_proc) ();
77 void		(*canvas_middlebut_save) ();
78 void		(*canvas_rightbut_proc) ();
79 void		(*return_proc) ();
80 
81 int		clip_xmin, clip_ymin, clip_xmax, clip_ymax;
82 int		clip_width, clip_height;
83 int		cur_x, cur_y;
84 int		fix_x, fix_y;
85 int		last_x, last_y;		/* last position of mouse */
86 int		shift;			/* global state of shift key */
87 #ifdef SEL_TEXT
88 int		pointer_click = 0;	/* for counting multiple clicks */
89 #endif /* SEL_TEXT */
90 int		ignore_exp_cnt = 1;	/* we get 2 expose events at startup */
91 String		local_translations = "";
92 
93 
94 /*********************** LOCAL ************************/
95 
96 #ifndef NO_COMPKEYDB
97 typedef struct _CompKey CompKey;
98 
99 struct _CompKey {
100     unsigned char   key;
101     unsigned char   first;
102     unsigned char   second;
103     CompKey	   *next;
104 };
105 
106 static CompKey *allCompKey = NULL;
107 static unsigned char	getComposeKey(char *buf);
108 static void		readComposeKey(void);
109 #endif /* NO_COMPKEYDB */
110 
111 #ifdef SEL_TEXT
112 /* for multiple click timer */
113 static XtIntervalId  click_id = (XtIntervalId) 0;
114 static void          reset_click_counter();
115 #endif /* SEL_TEXT */
116 
117 static void	canvas_paste(Widget w, XKeyEvent *paste_event);
118 static void	popup_mode_panel(Widget widget, XButtonEvent *event,
119 				String *params, Cardinal *num_params);
120 static void	popdown_mode_panel(void);
121 
122 void
null_proc(void)123 null_proc(void)
124 {
125     /* almost does nothing */
126     if (highlighting)
127 	erase_objecthighlight();
128 }
129 
130 static void
canvas_exposed(Widget tool,XEvent * event,String * params,Cardinal * nparams)131 canvas_exposed(Widget tool, XEvent *event, String *params, Cardinal *nparams)
132 {
133     (void)tool;
134     (void)params;
135     (void)nparams;
136 
137     static int	    xmin = 9999, xmax = -9999, ymin = 9999, ymax = -9999;
138     XExposeEvent   *xe = (XExposeEvent *) event;
139     register int    tmp;
140 
141     if (xe->x < xmin)
142 	xmin = xe->x;
143     if (xe->y < ymin)
144 	ymin = xe->y;
145     if ((tmp = xe->x + xe->width) > xmax)
146 	xmax = tmp;
147     if ((tmp = xe->y + xe->height) > ymax)
148 	ymax = tmp;
149     if (xe->count > 0)
150 	return;
151 
152     /* kludge to stop getting extra redraws at start up */
153     if (ignore_exp_cnt)
154 	ignore_exp_cnt--;
155     else
156 	redisplay_region(xmin, ymin, xmax, ymax);
157     xmin = 9999, xmax = -9999, ymin = 9999, ymax = -9999;
158 }
159 
160 
161 XtActionsRec	canvas_actions[] =
162 {
163     {"EventCanv", (XtActionProc) canvas_selected},
164     {"ExposeCanv", (XtActionProc) canvas_exposed},
165     {"EnterCanv", (XtActionProc) draw_mousefun_canvas},
166     {"PasteCanv", (XtActionProc) canvas_paste},
167     {"LeaveCanv", (XtActionProc) clear_mousefun},
168     {"EraseRulerMark", (XtActionProc) erase_rulermark},
169     {"Unzoom", (XtActionProc) unzoom},
170     {"PanOrigin", (XtActionProc) pan_origin},
171     {"ToggleShowDepths", (XtActionProc) toggle_show_depths},
172     {"ToggleShowBalloons", (XtActionProc) toggle_show_balloons},
173     {"ToggleShowLengths", (XtActionProc) toggle_show_lengths},
174     {"ToggleShowVertexnums", (XtActionProc) toggle_show_vertexnums},
175     {"ToggleShowBorders", (XtActionProc) toggle_show_borders},
176     {"ToggleAutoRefresh", (XtActionProc) toggle_refresh_mode},
177     {"PopupModePanel", (XtActionProc)popup_mode_panel},
178     {"PopdownModePanel", (XtActionProc)popdown_mode_panel},
179     {"PopupKeyboardPanel", (XtActionProc)popup_keyboard_panel},
180     {"PopdownKeyboardPanel", (XtActionProc)popdown_keyboard_panel},
181 };
182 
183 /* need the ~Meta for the EventCanv action so that the accelerators still work
184    during text input */
185 static String	canvas_translations =
186    "<Motion>:EventCanv()\n\
187     Any<BtnDown>:EventCanv()\n\
188     Any<BtnUp>:EventCanv()\n\
189     <Key>F18: PasteCanv()\n\
190     <Key>F20: PasteCanv()\n\
191     <EnterWindow>:EnterCanv()\n\
192     <LeaveWindow>:LeaveCanv()EraseRulerMark()\n\
193     <KeyUp>:EventCanv()\n\
194     ~Meta<Key>:EventCanv()\n\
195     <Expose>:ExposeCanv()\n";
196 
197 void
init_canvas(Widget tool)198 init_canvas(Widget tool)
199 {
200     DeclareArgs(12);
201 
202     FirstArg(XtNlabel, "");
203     NextArg(XtNwidth, CANVAS_WD);
204     NextArg(XtNheight, CANVAS_HT);
205     NextArg(XtNborderWidth, 0);
206     NextArg(XtNfromHoriz, mode_panel);
207     NextArg(XtNfromVert, topruler_sw);
208     NextArg(XtNtop, XtChainTop);
209     NextArg(XtNleft, XtChainLeft);
210 
211     canvas_sw = XtCreateWidget("canvas", labelWidgetClass, tool,
212 			       Args, ArgCount);
213     canvas_leftbut_proc = null_proc;
214     canvas_middlebut_proc = null_proc;
215     canvas_rightbut_proc = null_proc;
216     canvas_kbd_proc = canvas_locmove_proc = null_proc;
217     XtAugmentTranslations(canvas_sw,
218 			   XtParseTranslationTable(canvas_translations));
219 #ifndef NO_COMPKEYDB
220     readComposeKey();
221 #endif /* NO_COMPKEYDB */
222 }
223 
224 void
add_canvas_actions(void)225 add_canvas_actions(void)
226 {
227     XtAppAddActions(tool_app, canvas_actions, XtNumber(canvas_actions));
228 }
229 
230 /* at this point, the canvas widget is realized so we can get the window from it */
231 
setup_canvas(void)232 void setup_canvas(void)
233 {
234     init_grid();
235     reset_clip_window();
236 }
237 
238 void
canvas_selected(Widget tool,XButtonEvent * event,String * params,Cardinal * nparams)239 canvas_selected(Widget tool, XButtonEvent *event, String *params,
240 		Cardinal *nparams)
241 {
242     (void)tool;
243     (void)params;
244     (void)nparams;
245 
246     KeySym	    key;
247     static int	    sx = -10000, sy = -10000;
248     char	    buf[1];
249     XButtonPressedEvent *be = (XButtonPressedEvent *) event;
250     XKeyPressedEvent *kpe = (XKeyPressedEvent *) event;
251     Window	    rw, cw;
252     int		    rx, ry, cx, cy;
253     unsigned int    mask;
254     int    x, y;
255 
256 
257     static char	    compose_buf[2];
258 #ifdef NO_COMPKEYDB
259     static XComposeStatus compstat;
260 #define compose_key compstat.chars_matched
261 #else
262     static char	    compose_key = 0;
263 #endif /* NO_COMPKEYDB */
264     unsigned char   c;
265 
266     /* key on event type */
267     switch (event->type) {
268 
269       /****************/
270       case MotionNotify:
271 
272 #if defined(SMOOTHMOTION)
273 	/* translate from zoomed coords to object coords */
274 	x = BACKX(event->x);
275 	y = BACKY(event->y);
276 
277 	/* perform appropriate rounding if necessary */			// isometric grid
278 	round_coords(&x, &y);
279 
280 	if (x == sx && y == sy)
281 	    return;
282 	sx = x;
283 	sy = y;
284 #else
285 	XQueryPointer(event->display, event->window,
286 			  &rw, &cw,
287 			  &rx, &ry,
288 			  &cx, &cy,
289 			  &mask);
290 	cx = BACKX(cx);
291 	cy = BACKY(cy);
292 
293 	/* perform appropriate rounding if necessary */
294 	round_coords(&cx, &cy);						// isometric grid
295 
296 #ifdef REPORT_XY_ALWAYS
297 	put_msg("x = %.3f, y = %.3f %s",
298 	    (float) cx/(appres.INCHES? PPI: PPCM), (float) cy/(appres.INCHES? PPI: PPCM),
299 	    appres.INCHES? "in": "cm");
300 #endif
301 
302 	if (cx == sx && cy == sy)
303 		break;
304 
305 	/* draw crosshair cursor where pointer is */
306 	if (appres.crosshair && action_on) {
307 	    pw_vector(canvas_win, 0, last_y, (int)(CANVAS_WD*ZOOM_FACTOR), last_y,
308 					INV_PAINT, 1, RUBBER_LINE, 0.0, RED);
309 	    pw_vector(canvas_win, last_x, 0, last_x, (int)(CANVAS_HT*ZOOM_FACTOR),
310 					INV_PAINT, 1, RUBBER_LINE, 0.0, RED);
311 	    pw_vector(canvas_win, 0, cy, (int)(CANVAS_WD*ZOOM_FACTOR), cy,
312 					INV_PAINT, 1, RUBBER_LINE, 0.0, RED);
313 	    pw_vector(canvas_win, cx, 0, cx, (int)(CANVAS_HT*ZOOM_FACTOR),
314 					INV_PAINT, 1, RUBBER_LINE, 0.0, RED);
315 	}
316 
317 	last_x = x = sx = cx;	/* these are zoomed */
318 	last_y = y = sy = cy;	/* coordinates!	    */
319 
320 #endif /* SMOOTHMOTION */
321 
322 	set_rulermark(x, y);
323 	(*canvas_locmove_proc) (x, y);
324 	break;
325 
326       /*****************/
327       case ButtonRelease:
328 
329 #ifdef SEL_TEXT
330 	/* user was selecting text, and has released pointer button */
331 	if (action_on && cur_mode == F_TEXT) {
332 	    /* clear text selection flag since user released pointer button */
333 	    text_selection_active = False;
334 	    /* own the selection */
335 	    XtOwnSelection(tool, XA_PRIMARY, event->time, ConvertSelection,
336 			LoseSelection, TransferSelectionDone);
337 	}
338 #endif /* SEL_TEXT */
339 	break;
340 
341       /***************/
342       case ButtonPress:
343 
344 #ifdef SEL_TEXT
345 	/* increment click counter in case we're looking for double/triple click on text */
346 	pointer_click++;
347 	if (pointer_click > 2)
348 	    pointer_click = 1;
349 	/* add timer to reset the counter after n milliseconds */
350 	/* after first removing any previous timer */
351 	if (click_id)
352 	    XtRemoveTimeOut(click_id);
353 	click_id = XtAppAddTimeOut(tool_app, 300,
354 			(XtTimerCallbackProc) reset_click_counter, (XtPointer) NULL);
355 #endif /* SEL_TEXT */
356 
357 	/* translate from zoomed coords to object coords */
358 	x = BACKX(event->x);
359 	y = BACKY(event->y);
360 
361 	if ((SNAP_MODE_NONE != snap_mode) &&
362             (be->button != Button3)) {
363           int vx = x;   /* these asssigments are here because x and y are register vbls */
364           int vy = y;
365 
366 	  /* if snap fails, i'm opting to punt entirely and let the user try again.
367 	   * could also just revert to the usual round_coords(), but i think that
368 	   * might cause confusion.
369 	   */
370 
371           if (False == snap_process(&vx, &vy, be->state & ShiftMask)) break;
372           else {
373             x = vx;
374             y = vy;
375           }
376         }
377 	else {
378 	  /* if the point hasn't been snapped... */
379 	  /* perform appropriate rounding if necessary */
380 	  round_coords(&x, &y);				// isometric grid
381 	}
382 
383 	/* Convert Alt-Button3 to Button2 */
384 	if (be->button == Button3 && be->state & Mod1Mask) {
385 	    be->button = Button2;
386 	    be->state = be->state & ~Mod1Mask;
387 	}
388 
389 	/* call interactive zoom function when only control key pressed */
390 	if (!zoom_in_progress && ((be->state & ControlMask) && !(be->state & ShiftMask))) {
391 	    zoom_selected(x, y, be->button);
392 	    break;
393 	}
394 
395         /* edit shape factor when pressing control & shift keys in edit mode */
396 	if ((be->state & ControlMask && be->state & ShiftMask) &&
397 	    cur_mode >= FIRST_EDIT_MODE) {
398 		change_sfactor(x, y, be->button);
399 		break;
400 	}
401 
402 	if (be->button == Button1)
403 	    /* left button proc */
404 	    (*canvas_leftbut_proc) (x, y, be->state & ShiftMask);
405 
406 	else if (be->button == Button2)
407 	    /* middle button proc */
408 	    (*canvas_middlebut_proc) (x, y, be->state & ShiftMask);
409 
410 	else if (be->button == Button3)
411 	    /* right button proc */
412 	    (*canvas_rightbut_proc) (x, y, be->state & ShiftMask);
413 
414 	else if (be->button == Button4 && !shift)
415 	    /* pan down with wheelmouse */
416 	    pan_down(0);
417 
418 	else if (be->button == Button5 && !shift)
419 	    /* pan up with wheelmouse */
420 	    pan_up(0);
421 
422 	else if ((be->button == 6 /* Button6 */ && !shift) ||
423 		(be->button == Button4 && shift))
424 	    /* pan right with wheelmouse or touchpad */
425 	    pan_right(0);
426 
427 	else if ((be->button == 7 /* Button7 */ && !shift) ||
428 		(be->button == Button5 && shift))
429 	    /* pan left with wheelmouse or touchpad */
430 	    pan_left(0);
431 
432 	break;
433 
434       /**************/
435       case KeyPress:
436       case KeyRelease:
437 
438 	XQueryPointer(event->display, event->window,
439 			&rw, &cw, &rx, &ry, &cx, &cy, &mask);
440 	/* save global shift state */
441 	shift = mask & ShiftMask;
442 
443 	if (True == keyboard_input_available) {
444 	  XMotionEvent	me;
445 
446 	  if (keyboard_state & ShiftMask)
447 	    (*canvas_middlebut_proc) (keyboard_x, keyboard_y, 0);
448 	  else if (keyboard_state & ControlMask)
449 	    (*canvas_rightbut_proc) (keyboard_x, keyboard_y, 0);
450 	  else
451 	    (*canvas_leftbut_proc) (keyboard_x, keyboard_y, 0);
452 	  keyboard_input_available = False;
453 
454 	  /*
455 	   * get some sort of feedback, like a drawing rubberband, onto
456 	   * the canvas that the coord has been entered by faking up a
457 	   * motion event.
458 	   */
459 
460 	  last_x = x = sx = cx = keyboard_x;	/* these are zoomed */
461 	  last_y = y = sy = cy = keyboard_y;	/* coordinates!	    */
462 
463 	  me.type		= MotionNotify;
464 	  me.send_event		= 1;
465 	  me.display		= event->display;
466 	  me.window		= event->window;
467 	  me.root		= event->root;
468 	  me.subwindow		= event->subwindow;
469 	  me.x			= event->x + 10;
470 	  me.y			= event->y + 10;
471 	  me.x_root		= event->x_root;
472 	  me.y_root		= event->y_root;
473 	  me.state		= event->state;
474 	  me.is_hint		= 0;
475 	  me.same_screen=	 event->same_screen;;
476 	  XtDispatchEvent((XEvent *)(&me));
477 
478 	  break;
479 	}
480 
481 	/* we might want to check action_on */
482 	/* if arrow keys are pressed, pan */
483 	key = XLookupKeysym(kpe, 0);
484 
485 	/* do the mouse function stuff first */
486 	if (zoom_in_progress) {
487 	    set_temp_cursor(magnify_cursor);
488 	    draw_mousefun("final point", "", "cancel");
489 	} else if (mask & ControlMask) {
490 	    if (mask & ShiftMask) {
491 		reset_cursor();
492 		if (cur_mode >= FIRST_EDIT_MODE)
493 		    draw_mousefun("More approx", "Cycle shapes", "More interp");
494 		else
495 		    draw_shift_mousefun_canvas();
496 	    } else {  /* show control-key action */
497 		set_temp_cursor(magnify_cursor);
498 		draw_mousefun("Zoom area", "Pan to origin", "Unzoom");
499 	    }
500 	} else if (mask & ShiftMask) {
501 	    reset_cursor();
502 	    draw_shift_mousefun_canvas();
503 	} else {
504 	    reset_cursor();
505 	    draw_mousefun_canvas();
506 	}
507 
508 	if (event->type == KeyPress) {
509 	  if (key == XK_Up ||
510 	    key == XK_Down ||
511 	    ((key == XK_Left ||    /* don't process the following if in text input mode */
512 	      key == XK_Right ||
513 	      key == XK_Home) && (!action_on || cur_mode != F_TEXT))) {
514 	        switch (key) {
515 		    case XK_Left:
516 			pan_right(event->state&ShiftMask);
517 			break;
518 		    case XK_Right:
519 			pan_left(event->state&ShiftMask);
520 			break;
521 		    case XK_Up:
522 			pan_down(event->state&ShiftMask);
523 			break;
524 		    case XK_Down:
525 			pan_up(event->state&ShiftMask);
526 			break;
527 		    case XK_Home:
528 			pan_origin();
529 			break;
530 		} /* switch (key) */
531 	  } else if
532 #ifdef NO_COMPKEYDB
533 	     (key == XK_Multi_key && action_on && cur_mode == F_TEXT &&
534 		!XLookupString(kpe, buf, sizeof(buf), NULL, &compstat) &&
535 		compose_key) {
536 #else
537 	     ((key == XK_Multi_key || /* process the following *only* if in text input mode */
538 	      key == XK_Meta_L ||
539 	      key == XK_Meta_R ||
540 	      key == XK_Alt_L ||
541 	      key == XK_Alt_R ) && action_on && cur_mode == F_TEXT) {
542 			compose_key = 1;
543 #endif /* NO_COMPKEYDB */
544 			setCompLED(1);
545 			break;
546 	  } else {
547 	    if (canvas_kbd_proc != null_proc ) {
548 		if (key == XK_Left || key == XK_Right || key == XK_Home || key == XK_End) {
549 		    if (compose_key)
550 			setCompLED(0);
551 		    (*canvas_kbd_proc) (kpe, (unsigned char) 0, key);
552 		    compose_key = 0;	/* in case Meta was followed with cursor movement */
553 		} else {
554 #ifdef NO_COMPKEYDB
555 		    int oldstat = compose_key;
556 		    if (XLookupString(kpe, &compose_buf[0], 1, NULL, &compstat) > 0) {
557 			if (oldstat)
558 			    setCompLED(0);
559 			(*canvas_kbd_proc) (kpe, compose_buf[0], (KeySym) 0);
560 			compose_key = 0;
561 		    }
562 #else /* NO_COMPKEYDB */
563 		    switch (compose_key) {
564 			case 0:
565 #ifdef I18N
566 			    if (xim_ic != NULL) {
567 			      static int lbuf_size = 0;
568 			      static char *lbuf = NULL;
569 			      KeySym key_sym;
570 			      Status status;
571 			      int i, len;
572 
573 			       if (lbuf == NULL) {
574 				 lbuf_size = 100;
575 				 lbuf = new_string(lbuf_size);
576 			       }
577 			       len = XmbLookupString(xim_ic, kpe, lbuf, lbuf_size,
578 						     &key_sym, &status);
579 			       if (status == XBufferOverflow) {
580 				 lbuf_size = len;
581 				 lbuf = realloc(lbuf, lbuf_size + 1);
582 				 len = XmbLookupString(xim_ic, kpe, lbuf, lbuf_size,
583 						       &key_sym, &status);
584 			       }
585 			       if (status == XBufferOverflow) {
586 				 fprintf(stderr, "xfig: buffer overflow (XmbLookupString)\n");
587 			       }
588 			       lbuf[len] = '\0';
589 			       if (0 < len) {
590 				 if (2 <= len && canvas_kbd_proc == (void (*)())char_handler) {
591 				   i18n_char_handler((unsigned char *)lbuf);
592 				 } else {
593 				   for (i = 0; i < len; i++) {
594 				     (*canvas_kbd_proc) (kpe, lbuf[i], (KeySym) 0);
595 				   }
596 				 }
597 			       }
598 			    } else
599 #endif  /* I18N */
600 			    if (XLookupString(kpe, buf, sizeof(buf), NULL, NULL) > 0)
601 				(*canvas_kbd_proc) (kpe, buf[0], (KeySym) 0);
602 			    break;
603 			/* first char of multi-key sequence has been typed here */
604 			case 1:
605 			    if (XLookupString(kpe, &compose_buf[0], 1, NULL, NULL) > 0)
606 				compose_key = 2;	/* got first char, on to state 2 */
607 			    break;
608 			/* last char of multi-key sequence has been typed here */
609 			case 2:
610 			    if (XLookupString(kpe, &compose_buf[1], 1, NULL, NULL) > 0) {
611 				if ((c = getComposeKey(compose_buf)) != '\0') {
612 				    (*canvas_kbd_proc) (kpe, c, (KeySym) 0);
613 				} else {
614 				    (*canvas_kbd_proc) (kpe, compose_buf[0], (KeySym) 0);
615 				    (*canvas_kbd_proc) (kpe, compose_buf[1], (KeySym) 0);
616 				}
617 				setCompLED(0);	/* turn off the compose LED */
618 				compose_key = 0;	/* back to state 0 */
619 			    }
620 			    break;
621 		    } /* switch */
622 #endif /* NO_COMPKEYDB */
623 		}
624 	    } else {
625 		/* Be cheeky... we aren't going to do anything, so pass the
626 		 * key on to the mode_panel window by rescheduling the event
627 		 * The message window might treat it as a hotkey!
628 		 */
629 		kpe->window = XtWindow(mode_panel);
630 		kpe->subwindow = 0;
631 		XPutBackEvent(kpe->display,(XEvent *)kpe);
632 	    }
633 	  }
634 	  break;
635 	} /* event-type == KeyPress */
636     } /* switch(event->type) */
637 }
638 
639 #ifdef SEL_TEXT
640 /* come here if user doesn't press the pointer button within the click-time */
641 
642 static void
643 reset_click_counter(widget, closure, event, continue_to_dispatch)
644     Widget          widget;
645     XtPointer	    closure;
646     XEvent*	    event;
647     Boolean*	    continue_to_dispatch;
648 {
649     if (click_id)
650 	XtRemoveTimeOut(click_id);
651     pointer_click = 0;
652 }
653 #endif /* SEL_TEXT */
654 
655 /* clear the canvas - this can't be called to clear a pixmap, only a window */
656 
657 void
658 clear_canvas(void)
659 {
660     /* clear the splash graphic if it is still on the screen */
661     if (splash_onscreen) {
662 	splash_onscreen = False;
663 	XClearArea(tool_d, canvas_win, 0, 0, CANVAS_WD, CANVAS_HT, False);
664     } else {
665 	XClearArea(tool_d, canvas_win, clip_xmin, clip_ymin,
666 	       clip_width, clip_height, False);
667     }
668     /* redraw any page border */
669     redisplay_pageborder();
670 }
671 
672 void
673 clear_region(int xmin, int ymin, int xmax, int ymax)
674 {
675     XClearArea(tool_d, canvas_win, xmin, ymin,
676 	       xmax - xmin + 1, ymax - ymin + 1, False);
677 }
678 
679 static void
680 get_canvas_clipboard(Widget w, XtPointer client_data, Atom *selection,
681 		Atom *type, XtPointer buf, unsigned long *length, int *format)
682 {
683 	(void)client_data;
684 	(void)selection;
685 
686 	char *c;
687 	int i;
688 #ifdef I18N
689 	if (appres.international) {
690 	  Atom atom_compound_text = XInternAtom(XtDisplay(w), "COMPOUND_TEXT", False);
691 	  char **tmp;
692 	  XTextProperty prop;
693 	  int num_values;
694 	  int ret_status;
695 
696 	  if (*type == atom_compound_text) {
697 	    prop.value = buf;
698 	    prop.encoding = *type;
699 	    prop.format = *format;
700 	    prop.nitems = *length;
701 	    num_values = 0;
702 	    ret_status = XmbTextPropertyToTextList(XtDisplay(w), &prop,
703 				(char***) &tmp, &num_values);
704 	    if (ret_status == Success || 0 < num_values) {
705 	      for (i = 0; i < num_values; i++) {
706 		for (c = tmp[i]; *c; c++) {
707 		  if (canvas_kbd_proc == (void (*)())char_handler && ' ' <= *c && *(c + 1)) {
708 		    prefix_append_char(*c);
709 		  } else {
710 		    canvas_kbd_proc((XKeyEvent *) 0, *c, (KeySym) 0);
711 		  }
712 		}
713 	      }
714 	      XtFree(buf);
715 	      return;
716 	    }
717 	  }
718 	}
719 #endif  /* I18N */
720 
721 	c = (char *)buf;
722 	for (i = 0; (unsigned long)i < *length; ++i) {
723            canvas_kbd_proc((XKeyEvent *)0, *c, (KeySym)0);
724            ++c;
725 	}
726 	XtFree(buf);
727 }
728 
729 /* paste primary X selection to the canvas */
730 
731 void
732 paste_primary_selection(void)
733 {
734     /* turn off Compose key LED */
735     setCompLED(0);
736 
737     canvas_paste(canvas_sw, NULL);
738 }
739 
740 static void
741 canvas_paste(Widget w, XKeyEvent *paste_event)
742 {
743 	Time event_time;
744 
745 	if (canvas_kbd_proc != (void (*)())char_handler)
746 		return;
747 
748 	if (paste_event != NULL)
749 		event_time = paste_event->time;
750 	else
751 		event_time = CurrentTime;
752 
753 #ifdef I18N
754 	if (appres.international) {
755 	  Atom atom_compound_text = XInternAtom(XtDisplay(w), "COMPOUND_TEXT", False);
756 	  if (atom_compound_text) {
757 	    XtGetSelectionValue(w, XA_PRIMARY, atom_compound_text,
758 				get_canvas_clipboard, NULL, event_time);
759 	    return;
760 	  }
761 	}
762 #endif  /* I18N */
763 
764 	XtGetSelectionValue(w, XA_PRIMARY,
765 		XA_STRING, get_canvas_clipboard, NULL, event_time);
766 }
767 
768 #ifndef NO_COMPKEYDB
769 static unsigned char
770 getComposeKey(char *buf)
771 {
772     CompKey	   *compKeyPtr = allCompKey;
773 
774     while (compKeyPtr != NULL) {
775 	if (compKeyPtr->first == (unsigned char) (buf[0]) &&
776 	    compKeyPtr->second == (unsigned char) (buf[1]))
777 	    return (compKeyPtr->key);
778 	else
779 	    compKeyPtr = compKeyPtr->next;
780     }
781     return ('\0');
782 }
783 
784 static void
785 readComposeKey(void)
786 {
787     FILE	   *st;
788     CompKey	   *compKeyPtr;
789     char	    line[255];
790     char	   *p;
791     char	   *p1;
792     char	   *p2;
793     char	   *p3;
794     long	    size;
795     int		    charfrom;
796     int		    charinto;
797 
798 
799 /* Treat the compose key DB a different way.  In this order:
800  *
801  *  1.	If the resource contains no "/", prefix it with the name of
802  *	the wired-in directory and use that.
803  *
804  *  2.	Otherwise see if it begins with "~/", and if so use that,
805  *	with the leading "~" replaced by the name of this user's
806  *	$HOME directory.
807  *
808  * This way a user can have private compose key settings even when running
809  * xfig privately.
810  *
811  * Pete Kaiser
812  * 24 April 1992
813  */
814 
815      /* no / in name, make relative to XFIGLIBDIR */
816      if (strchr(appres.keyFile, '/') == NULL) {
817 	 strcpy(line, XFIGLIBDIR);
818 	 strcat(line, "/");
819 	 strcat(line, appres.keyFile);
820 	 }
821 
822      /* expand the ~ to the user's home directory */
823      else if (! strncmp(appres.keyFile, "~/", 2)) {
824 	 strcpy(line, getenv("HOME"));
825 	 for (charinto = strlen(line), charfrom = 1;
826 	      (line[charinto++] = appres.keyFile[charfrom++]); );
827        }
828     else
829 	strcpy(line, appres.keyFile);
830 
831     if ((st = fopen(line, "r")) == NULL) {
832 	allCompKey = NULL;
833 	fprintf(stderr,"%cCan't open compose key file '%s',\n",007,line);
834 	fprintf(stderr,"\tno multi-key sequences available\n");
835 	return;
836     }
837     fseek(st, 0, 2);
838     size = ftell(st);
839     fseek(st, 0, 0);
840 
841     local_translations = (String) new_string(size);
842 
843     strcpy(local_translations, "");
844     while (fgets(line, 250, st) != NULL) {
845 	if (line[0] != '#') {
846 	    strcat(local_translations, line);
847 	    if ((p = strstr(line, "Multi_key")) != NULL) {
848 		if (allCompKey == NULL) {
849 		    allCompKey = (CompKey *) malloc(sizeof(CompKey));
850 		    compKeyPtr = allCompKey;
851 		} else {
852 		    compKeyPtr->next = (CompKey *) malloc(sizeof(CompKey));
853 		    compKeyPtr = compKeyPtr->next;
854 		}
855 
856 		p1 = strstr(p, "<Key>") + strlen("<Key>");
857 		p = strstr(p1, ",");
858 		*p++ = '\0';
859 		p2 = strstr(p, "<Key>") + strlen("<Key>");
860 		p = strstr(p2, ":");
861 		*p++ = '\0';
862 		p3 = strstr(p, "insert-string(") + strlen("insert-string(");
863 		p = strstr(p3, ")");
864 		*p++ = '\0';
865 
866 		if (strlen(p3) == 1)
867 		    compKeyPtr->key = *p3;
868 		else {
869 		    int x;
870 		    sscanf(p3, "%i", &x);
871 		    compKeyPtr->key = (char) x;
872 		}
873 		compKeyPtr->first = XStringToKeysym(p1);
874 		compKeyPtr->second = XStringToKeysym(p2);
875 		compKeyPtr->next = NULL;
876 	    }
877 	}
878     }
879 
880     fclose(st);
881 
882 }
883 #endif /* !NO_COMPKEYDB */
884 
885 void
886 setCompLED(int on)
887 {
888 #ifdef COMP_LED
889 	XKeyboardControl values;
890 	values.led = COMP_LED;
891 	values.led_mode = on ? LedModeOn : LedModeOff;
892 	XChangeKeyboardControl(tool_d, KBLed|KBLedMode, &values);
893 #endif /* COMP_LED */
894 }
895 
896 /* toggle the length lines when drawing or moving points */
897 
898 void
899 toggle_show_lengths(void)
900 {
901 	appres.showlengths = !appres.showlengths;
902 	put_msg("%s lengths of lines in red ",appres.showlengths? "Show": "Don't show");
903 	refresh_view_menu();
904 }
905 
906 /* toggle the drawing of vertex numbers on objects */
907 
908 void
909 toggle_show_vertexnums(void)
910 {
911 	appres.shownums = !appres.shownums;
912 	put_msg("%s vertex numbers on objects",appres.shownums? "Show": "Don't show");
913 	refresh_view_menu();
914 
915 	/* if user just turned on vertex numbers, redraw only objects */
916 	if (appres.shownums) {
917 	    redisplay_canvas();
918 	} else {
919 	    /* otherwise redraw whole canvas */
920 	    clear_canvas();
921 	    redisplay_canvas();
922 	}
923 }
924 
925 /* toggle the drawing of page borders on the canvas */
926 
927 void
928 toggle_show_borders(void)
929 {
930 	appres.show_pageborder = !appres.show_pageborder;
931 	put_msg("%s page borders on canvas",
932 		appres.show_pageborder? "Show": "Don't show");
933 	refresh_view_menu();
934 
935 	/* if user just turned on the border, draw only it */
936 	if (appres.show_pageborder) {
937 	    redisplay_pageborder();
938 	} else {
939 	    /* otherwise redraw whole canvas */
940 	    clear_canvas();
941 	    redisplay_canvas();
942 	}
943 }
944 
945 /* toggle the information balloons */
946 
947 void
948 toggle_show_balloons(void)
949 {
950     appres.showballoons = !appres.showballoons;
951     put_msg("%s information balloons",
952 		appres.showballoons? "Show": "Don't show");
953     refresh_view_menu();
954 }
955 
956 
957 
958 /* popup drawing/editing mode panel on the canvas.
959   This can be useful especially if wheel-mouse is in use.  - T.Sato */
960 
961 static Widget draw_panel = None;
962 static Widget edit_panel = None;
963 static Widget active_mode_panel = None;
964 
965 extern mode_sw_info mode_switches[];
966 
967 void static popdown_mode_panel(void)
968 {
969   if (active_mode_panel != None) XtPopdown(active_mode_panel);
970   active_mode_panel = None;
971 }
972 
973 static void
974 mode_panel_button_selected(Widget w, icon_struct *icon, char *call_data)
975 {
976 	(void)w;
977 	(void)call_data;
978 
979 	change_mode(icon);
980 	popdown_mode_panel();
981 }
982 
983 static void
984 create_mode_panel(void)
985 {
986   Widget draw_form, edit_form;
987   Widget form, entry;
988   icon_struct *icon;
989   Widget up = None, left = None;
990   int max_wd = 150, wd = 0;
991   int i;
992 
993   draw_panel = XtVaCreatePopupShell("draw_menu", transientShellWidgetClass, tool,
994 				    XtNtitle, "Drawing Modes", NULL);
995   draw_form = XtVaCreateManagedWidget("form", formWidgetClass, draw_panel,
996 				    XtNdefaultDistance, 0, NULL);
997 
998   edit_panel = XtVaCreatePopupShell("edit_menu", transientShellWidgetClass, tool,
999 				    XtNtitle, "Editing Modes", NULL);
1000   edit_form = XtVaCreateManagedWidget("form", formWidgetClass, edit_panel,
1001 				    XtNdefaultDistance, 0, NULL);
1002 
1003   form = draw_form;
1004   for (i = 0; mode_switches[i].mode != F_NULL; i++) {
1005     if (form == draw_form && FIRST_EDIT_MODE <= mode_switches[i].mode) {
1006       form = edit_form;
1007       left = None;
1008       up = None;
1009       wd = 0;
1010     }
1011     icon = mode_switches[i].icon;
1012     wd = wd + icon->width;
1013     if (max_wd < wd) {
1014       up = left;
1015       left = None;
1016       wd = icon->width;
1017     }
1018     entry = XtVaCreateManagedWidget("button", commandWidgetClass, form,
1019 			    XtNlabel, "", XtNresizable, False,
1020 			    XtNtop, XawChainTop, XtNbottom, XawChainTop,
1021 			    XtNleft, XawChainLeft, XtNright, XawChainLeft,
1022 			    XtNwidth, icon->width, XtNheight, icon->height,
1023 			    XtNbackgroundPixmap, mode_switches[i].pixmap,
1024 			    NULL);
1025     if (up != None) XtVaSetValues(entry, XtNfromVert, up, NULL);
1026     if (left != None) XtVaSetValues(entry, XtNfromHoriz, left, NULL);
1027     XtAddCallback(entry, XtNcallback, (XtCallbackProc)mode_panel_button_selected,
1028 		  (XtPointer)mode_switches[i].icon);
1029     left = entry;
1030   }
1031 }
1032 
1033 static void
1034 popup_mode_panel(Widget widget, XButtonEvent *event, String *params,
1035 		Cardinal *num_params)
1036 {
1037   (void)widget;
1038   (void)num_params;
1039 
1040   Dimension wd, ht;
1041   Widget panel;
1042 
1043   if (draw_panel == None) {
1044     create_mode_panel();
1045     XtRealizeWidget(draw_panel);
1046     XtRealizeWidget(edit_panel);
1047     XSetWMProtocols(tool_d, XtWindow(draw_panel), &wm_delete_window, 1);
1048     XSetWMProtocols(tool_d, XtWindow(edit_panel), &wm_delete_window, 1);
1049   }
1050   panel = (strcmp(params[0], "edit") == 0) ? edit_panel : draw_panel;
1051   if (active_mode_panel != panel) popdown_mode_panel();
1052   XtVaGetValues(panel, XtNwidth, &wd, XtNheight, &ht, NULL);
1053   XtVaSetValues(panel, XtNx, event->x_root - wd / 2,
1054 		XtNy, event->y_root - ht * 2 / 3, NULL);
1055   XtPopup(panel, XtGrabNone);
1056   active_mode_panel = panel;
1057 }
1058 
1059 /* round coordinate on square grid */
1060 static void
1061 round_square(int *x, const int spacing, const int half)
1062 {
1063 	if (*x < INT_MIN + half + 1) {
1064 		*x = -(INT_MAX / spacing) * spacing;
1065 	} else if (*x >= INT_MAX - half) {
1066 		*x = (INT_MAX / spacing) * spacing;
1067 	} else {
1068 		int d = *x % spacing;
1069 
1070 		/*
1071 		 * The standard guarantees that
1072 		 *   (a/b)*b + a%b == a.
1073 		 * But, for a < 0, it is implementation defined whether, e.g.,
1074 		 *   -7 / 3 == -2  &&  -7 % 3 == -1, or
1075 		 *   -7 / 3 == -3  &&  -7 % 3 == 2.
1076 		 */
1077 		if (d > 0)
1078 			*x += d < half ? -d : -d + spacing;
1079 		else if (d < 0)
1080 			*x += d < -half ? -d - spacing : -d;
1081 	}
1082 }
1083 
1084 
1085 int
1086 point_spacing(void)
1087 {
1088 	int	grid_unit;
1089 	int	spacing;
1090 
1091 	grid_unit = (appres.userscale != 1.0 && appres.INCHES) ?
1092 		TENTH_UNIT : cur_gridunit;
1093 	spacing = (int)(posn_rnd[grid_unit][cur_pointposn] / appres.userscale);
1094 	return spacing;
1095 }
1096 
1097 /* macro which rounds coordinates depending on point positioning mode */		// isometric grid
1098 void
1099 round_coords(int *x, int *y)
1100 {
1101 	const int	spacing = point_spacing();
1102 	const int	half = spacing / 2;
1103 
1104 	/* make sure the cursor is on grid */
1105 	if (cur_pointposn == P_ANY || anypointposn)
1106 		return;
1107 
1108 	if (cur_gridtype == GRID_ISO) {
1109 		/* do all calculations in double */
1110 		double xd = *x;
1111 		double yd = *y;
1112 		const double c30 = sqrt(0.75);		/* cos(30) */
1113 		double txx;		/* position from nearest whole grid */
1114 		const double s = spacing;		/* whole grid interval */
1115 		const double h = half;			/* half grid interval */
1116 
1117 		/* determine x */
1118 		xd = ( fabs( txx = fmod( xd, s * c30 ) ) < ( h * c30 ) ) ?
1119 			xd - txx : xd + ( xd >= 0 ? s * c30 : -s * c30 ) - txx;
1120 
1121 		/* determine y depending on x */
1122 		if( fabs( fmod( xd / ( s * c30 ), 2.0 ) ) > 0.5 ) {
1123 			yd = ( fabs( txx = fmod( yd + h, s ) ) < h ) ?
1124 				yd - txx : yd + ( yd >= 0 ? s : -s ) - txx;
1125 		} else {
1126 			yd = ( fabs( txx = fmod( yd, s ) ) < h ) ?
1127 				yd - txx : yd + ( yd >= 0 ? s : -s ) - txx;
1128 		}
1129 
1130 		/* check for overflow */
1131 		if (xd < INT_MIN || xd > INT_MAX ||
1132 				yd < INT_MIN || yd > INT_MAX) {
1133 			if (xd < INT_MIN)
1134 				*x = xd + s * c30;
1135 			else if (xd > INT_MAX)
1136 				*x = xd - s * c30;
1137 			if (yd < INT_MIN)
1138 				*y = yd + s; /* h gave segfault */
1139 			else if  (yd > INT_MAX)
1140 				*y = yd - s; /* h gave segfault */
1141 			round_coords(x, y);
1142 		} else {
1143 			/* store (return) result as integer */
1144 			*x = xd;
1145 			*y = yd;
1146 		}
1147 	} else {	/* cur_gridtype == GRID_SQUARE */
1148 		round_square(x, spacing, half);
1149 		round_square(y, spacing, half);
1150 	}
1151 }
1152