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