1 /* edit_ob.c */
2 /* COPYRIGHT (C) 2000 THE VICTORIA UNIVERSITY OF MANCHESTER and John Levon
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
15 * Place - Suite 330, Boston, MA 02111-1307, USA.
16 */
17 /* for interactive object editing */
18 /*
19 * $Log: edit_ob.c,v $
20 * Revision 1.5 2000/12/17 00:57:42 moz
21 * examples, filled open splines, highlight_objects
22 *
23 * Revision 1.4 2000/12/06 20:56:01 moz
24 * GPL stuff.
25 *
26 * Revision 1.3 2000/08/21 01:32:56 moz
27 * Re-add changelog.
28 *
29 * Revision 1.2 2000/08/21 01:31:02 moz
30 * Stop infinitely thin objects from disappearing on resize.
31 *
32 * Revision 1.1.1.1 2000/07/19 22:45:30 moz
33 * CVS Import
34 *
35 * Revision 1.29 2000/03/15 00:15:32 moz
36 * Unset disable_motion for non-editing..
37 *
38 * Revision 1.28 2000/03/14 22:41:21 moz
39 * Very ugly hack to zoom-motion xor problem. Very tricky to solve,
40 * so it's a simple approach: ignore the first few edit_motion()s
41 * after a resize.
42 *
43 * Revision 1.27 2000/03/14 02:48:40 moz
44 * Slight xor improvement.
45 *
46 * Revision 1.26 2000/03/14 01:09:55 moz
47 * Make vars static.
48 *
49 * Revision 1.25 2000/03/06 02:28:10 moz
50 * Compile fix.
51 *
52 * Revision 1.24 2000/02/18 21:18:06 moz
53 * Compile fixes.
54 *
55 * Revision 1.23 2000/02/15 01:47:48 moz
56 * Hack to change editing_object.
57 *
58 * Revision 1.22 2000/02/04 01:52:53 moz
59 * switch_icons() needs view.
60 *
61 * Revision 1.21 2000/01/31 20:39:13 moz
62 * Removed unnecessary (and possibly broken) selected = highlighted.
63 *
64 * Revision 1.20 2000/01/31 01:17:09 moz
65 * Less flicker on highlighted object
66 *
67 * Revision 1.19 1999/11/15 02:11:27 moz
68 * Round angles to 90 degrees if nearby.
69 * Fixed logic when switching highlighted_object.
70 *
71 * Revision 1.18 1999/08/06 23:34:59 moz
72 * Don't allow rotation or scaling of a picture object.
73 *
74 * Revision 1.17 1999/07/29 20:45:21 moz
75 * Allow text to be resized.
76 *
77 * Revision 1.16 1999/06/16 00:55:25 moz
78 * Change to rotation to allow fine-tune (vertical).
79 *
80 * Revision 1.15 1999/05/31 23:05:43 moz
81 * Fix previous fix.
82 *
83 * Revision 1.14 1999/05/28 19:59:02 moz
84 * Need to xor off outline again when releasing outside window.
85 *
86 * Revision 1.13 1999/05/22 02:55:33 moz
87 * Code clear-up (partly).
88 *
89 * Revision 1.12 1999/05/19 17:10:58 moz
90 * 1.0 Checkin.
91 *
92 * Revision 1.11 1999/04/27 19:50:35 moz
93 * Fixed rubberbanding problems.
94 *
95 * Revision 1.10 1999/04/27 04:10:35 moz
96 * -Wall appeased.
97 *
98 * Revision 1.9 1999/04/26 19:59:34 moz
99 * Use move_object.
100 *
101 * Revision 1.8 1999/04/26 19:06:44 moz
102 * When cancelling with Button3, reset state Booleans.
103 *
104 * Revision 1.7 1999/04/25 19:44:45 moz
105 * Fixes for rotating text.
106 *
107 * Revision 1.6 1999/04/25 00:21:23 moz
108 * Rotation of text interface started.
109 *
110 * Revision 1.5 1999/04/23 00:38:59 moz
111 * redraw_view_window change.
112 *
113 * Revision 1.4 1999/04/22 22:15:50 moz
114 * Don't register undos for members of compound object in resize.
115 *
116 * Revision 1.3 1999/04/22 16:16:54 moz
117 * Fix for arc circles.
118 *
119 * Revision 1.2 1999/04/04 01:48:58 moz
120 * Now needs View as well.
121 *
122 * Revision 1.1 1999/03/30 00:04:48 moz
123 * Initial revision
124 *
125 */
126
127 #include "include/figurine.h"
128 #include "include/extern.h"
129
130
131 int disable_motion=0;
132
133 static Object *mob=NULL; /* moving object */
134 static long mx, my;
135 static long gox, goy;
136 static long ox, oy;
137 static long ngox, ngoy;
138 static double rx, ry;
139 static double angle=0.0; /* rotation angle */
140
141 static Boolean rotating_object=FALSE;
142 static Boolean resizing_object=FALSE;
143
144 extern Boolean just_resized;
145
146 void
toggle_object_outline(View * v)147 toggle_object_outline(View *v)
148 {
149 draw_object(mob,v,blackxorgc,ox,oy, rx, ry,angle,0,0,0,0);
150 }
151
152 /* we change object when we decide to */
153 Boolean
new_object(View * v,long x,long y)154 new_object(View *v,long x, long y)
155 {
156 if (v->selected_object!=NULL)
157 {
158 if (!is_in_bbox(x,y,
159 XD2P(v->selected_object->ob->bbox.x1,v),
160 YD2P(v->selected_object->ob->bbox.y1,v),
161 XD2P(v->selected_object->ob->bbox.x2,v),
162 YD2P(v->selected_object->ob->bbox.y2,v)))
163 return TRUE;
164 else
165 return FALSE; /* if we click in the selected object, stay with it */
166 }
167 else
168 return TRUE;
169 }
170
171 /* has user clicked on a rotate handle of an object ? */
172 Boolean
edit_on_rotate_handle(View * view,Object * ob,long x,long y)173 edit_on_rotate_handle(View *view, Object *ob, long x, long y)
174 {
175 return (((ob->type!=ARCELLIPSE && ob->type!=ELLIPSE && ob->type!=COMPOUND && ob->type!=TEXT &&
176 is_in_box(x,y,XD2P(ob->bbox.x2,view)-(2*HANDLE_PIXEL_SIZE),
177 YD2P(ob->bbox.y2,view)-(2*HANDLE_PIXEL_SIZE),
178 HANDLE_PIXEL_SIZE, HANDLE_PIXEL_SIZE)) ||
179 (ob->type==TEXT && !ob->ob.text.node &&
180 is_in_box(x,y,XD2P(ob->bbox.x2,view)-(2*HANDLE_PIXEL_SIZE),
181 YD2P(ob->bbox.y2,view)-(HANDLE_PIXEL_SIZE),
182 HANDLE_PIXEL_SIZE, HANDLE_PIXEL_SIZE)))
183 && !(ob->type==POLYGON && ob->ob.polyline.pic));
184 }
185
186 /* has user clicked on a scaling handle of an object ? */
187 /* if so record which corner should stay in place */
188 Boolean
edit_on_handle(View * view,Object * ob,long x,long y)189 edit_on_handle(View *view, Object *ob, long x, long y)
190 {
191 if (ob->type==POLYGON && ob->ob.polyline.pic)
192 return FALSE;
193
194 /* remember we can assume inside of box */
195 if (x >= (XD2P(ob->bbox.x2,view) - HANDLE_PIXEL_SIZE) && y >= (YD2P(ob->bbox.y2,view) - HANDLE_PIXEL_SIZE)
196 && (ob->type!=TEXT || !ob->ob.text.node))
197 {
198 state.tied_corner = TOPLEFT;
199 return TRUE;
200 };
201
202 if (ob->type==COMPOUND || ob->type==TEXT)
203 return FALSE;
204
205 if (x <= (HANDLE_PIXEL_SIZE + XD2P(ob->bbox.x1,view)) && y >= (YD2P(ob->bbox.y2,view) - HANDLE_PIXEL_SIZE))
206 {
207 state.tied_corner = TOPRIGHT;
208 return TRUE;
209 };
210
211 if (x >= (XD2P(ob->bbox.x2,view) - HANDLE_PIXEL_SIZE) && y <= (HANDLE_PIXEL_SIZE + YD2P(ob->bbox.y1,view)))
212 {
213 state.tied_corner = BOTTOMLEFT;
214 return TRUE;
215 };
216
217 if (x <= (HANDLE_PIXEL_SIZE + XD2P(ob->bbox.x1,view)) && y <= (HANDLE_PIXEL_SIZE + YD2P(ob->bbox.y1,view)))
218 {
219 state.tied_corner = BOTTOMRIGHT;
220 return TRUE;
221 };
222
223
224 return FALSE;
225 }
226
227 void
edit_button(BEvent * bev,View * v)228 edit_button(BEvent *bev, View *v)
229 {
230 Node *n;
231
232 if (v->edited_object!=NULL)
233 {
234 /* hook into point editing */
235 switch (v->edited_object->ob->type)
236 {
237 case SPLINE:
238 case ARC:
239 spline_point_button(bev,v);
240 break;
241
242 case POLYLINE:
243 case POLYGON:
244 polyline_point_button(bev,v);
245 break;
246
247 default:
248 break;
249 };
250 }
251 else if (bev->button == Button1)
252 {
253 n = v->highlighted_object;
254
255 switch (bev->type)
256 {
257 case BUTTON_HELD:
258 if (new_object(v,bev->x,bev->y))
259 {
260 /* get new object */
261 v->highlighted_object = object_at_point_p(v,v->doc->o,bev->x,bev->y);
262
263 v->selected_object = v->highlighted_object;
264 switch_icons(v);
265
266 if (n && (!v->highlight_objects || (n != v->highlighted_object)))
267 send_redraw_object(v,n->ob);
268 };
269
270 if (v->highlighted_object!=NULL)
271 {
272 v->selected_object = v->highlighted_object;
273 switch_icons(v);
274 state.editing_object = TRUE;
275
276 /* set start parameters for moving objects */
277 gox = XP2D(bev->x,v) - v->highlighted_object->ob->bbox.x1;
278 goy = YP2D(bev->y,v) - v->highlighted_object->ob->bbox.y1;
279
280 mx = XP2D(bev->x,v);
281 my = YP2D(bev->y,v);
282
283 mob = v->selected_object->ob;
284
285 if (edit_on_handle(v, mob, bev->x, bev->y))
286 resizing_object = TRUE;
287 else if (edit_on_rotate_handle(v,mob,bev->x,bev->y))
288 {
289 rotating_object = TRUE;
290 };
291
292 if (v->gridon)
293 {
294 ox = snap(v->grid_x,v->highlighted_object->ob->bbox.x1);
295 oy = snap(v->grid_y,v->highlighted_object->ob->bbox.y1);
296 }
297 else
298 {
299 ox = v->highlighted_object->ob->bbox.x1;
300 oy = v->highlighted_object->ob->bbox.y1;
301 };
302
303 ngox = v->highlighted_object->ob->bbox.x1;
304 ngoy = v->highlighted_object->ob->bbox.y1;
305
306 angle=0.0;
307 rx = 1; ry = 1;
308
309 state.rubberon = FALSE;
310
311 send_redraw_object(v, v->selected_object->ob);
312 };
313 break;
314
315 case BUTTON_CLICKED:
316 case BUTTON_RELEASED:
317
318 /* another unmaintainable hack */
319 if (state.editing_object && !v->selected_object)
320 {
321 v->highlighted_object=NULL;
322 state.editing_object=FALSE;
323 };
324
325 if (state.editing_object)
326 {
327 if (resizing_object == TRUE)
328 {
329 if (v->guide_lines && v->guide_lines_displayed)
330 {
331 toggle_guidelines(v);
332 v->guide_lines_displayed = FALSE;
333 };
334 /* scale the object as requested */
335 apply_scale_to_object(v,mob, rx, ry,TRUE);
336 state.tied_corner = NOTSCALING;
337 resizing_object = FALSE;
338 }
339 else if (rotating_object==TRUE)
340 {
341 if (v->guide_lines && v->guide_lines_displayed)
342 {
343 toggle_guidelines(v);
344 v->guide_lines_displayed = FALSE;
345 };
346 /* rotate the object as requested */
347 apply_rotate_to_object(v,mob,angle);
348 angle = 0.0;
349 rotating_object = FALSE;
350 }
351 else
352 {
353 /* see if moved */
354 if ((ngox != v->selected_object->ob->bbox.x1) ||
355 (ngoy != v->selected_object->ob->bbox.y1))
356 {
357 toggle_object_outline(v);
358 /* if in document, move object */
359 if (P_IN_DOC(bev->x, bev->y,v))
360 {
361 Object *ob = v->selected_object->ob;
362 register_undo(UNDO_OB_PROP,ob,v->doc);
363 store_redraw_object(ob);
364 move_object(v,ob, mx-ob->bbox.x1, my-ob->bbox.y1);
365 send_stored_redraw_object(v,ob);
366 };
367 };
368 };
369
370 state.editing_object = FALSE;
371
372 switch_icons(v);
373 }
374 else
375 {
376 v->selected_object = v->highlighted_object;
377 switch_icons(v);
378 };
379 break;
380 };
381 }
382 else if (bev->button == Button3)
383 {
384 if (state.editing_object)
385 {
386 draw_object(mob,v,blackxorgc,ox,oy, rx, ry,angle,0,0,0,0);
387
388 /* cancel it all */
389 state.editing_object = FALSE;
390 rotating_object = FALSE;
391 angle = 0;
392 resizing_object = FALSE;
393 }
394 else if (v->selected_object!=NULL)
395 {
396 n = v->selected_object;
397
398 v->selected_object = NULL;
399 switch_icons(v);
400 /* this is to provide visual feedback of selection cancellation when
401 pointer is still on object */
402 v->highlighted_object = NULL;
403
404 send_redraw_object(v,n->ob);
405 };
406 };
407 }
408
409 void
edit_motion(View * v,long x,long y)410 edit_motion(View *v, long x, long y)
411 {
412 if (v->edited_object!=NULL)
413 {
414 /* hook into point editing */
415 switch (v->edited_object->ob->type)
416 {
417 case SPLINE:
418 case ARC:
419 spline_point_motion(v,x,y);
420 break;
421
422 case POLYLINE:
423 case POLYGON:
424 polyline_point_motion(v,x,y);
425 break;
426
427 default:
428 break;
429 };
430 }
431 else if (state.editing_object)
432 {
433 int test=-1,test2=-1;
434
435 if (disable_motion)
436 {
437 disable_motion--;
438 return;
439 };
440
441 if (just_resized)
442 just_resized=FALSE;
443 else
444 toggle_object_outline(v);
445
446 /* if not rotating, mouse may be at document window border */
447 /* if so, scroll the view window along */
448 if (!rotating_object)
449 {
450 if (x < MOVE_BOUNDARY) /* move left */
451 test = nudge_ruler_x(v->ruler_x_window, v, LEFT);
452 else if (x >= ((signed long)(v->draw_window->w)) - MOVE_BOUNDARY) /* move right */
453 test = nudge_ruler_x(v->ruler_x_window, v, RIGHT);
454
455 if (y < MOVE_BOUNDARY) /* move up */
456 test2 = nudge_ruler_y(v->ruler_y_window, v, UP);
457 else if (y >= ((signed long)(v->draw_window->h)) - MOVE_BOUNDARY) /* move down */
458 test2 = nudge_ruler_y(v->ruler_y_window, v, DOWN);
459
460 if (test!=-1 || test2!=-1)
461 {
462 state.rubberon=FALSE;
463 redraw_view_window(v);
464 };
465 };
466
467 if (resizing_object)
468 {
469 unsigned int xdel,ydel;
470 xdel = (mob->bbox.x2 - mob->bbox.x1);
471 ydel = (mob->bbox.y2 - mob->bbox.y1);
472 if (!xdel)
473 xdel=XP2D(1,v);
474 if (!ydel)
475 ydel=YP2D(1,v);
476
477 /* calculate scaling factors */
478 switch (state.tied_corner)
479 {
480 case TOPLEFT:
481 if (v->gridon)
482 {
483 rx = ((double)GXP2D(x,v)-mob->bbox.x1) / xdel;
484 ry = ((double)GYP2D(y,v)-mob->bbox.y1) / ydel;
485 }
486 else
487 {
488 rx = ((double)XP2D(x,v)-mob->bbox.x1) / xdel;
489 ry = ((double)YP2D(y,v)-mob->bbox.y1) / ydel;
490 };
491 break;
492
493 case TOPRIGHT:
494 if (v->gridon)
495 {
496 rx = ((double)GXP2D(x,v)-mob->bbox.x2) / xdel;
497 ry = ((double)GYP2D(y,v)-mob->bbox.y1) / ydel;
498 }
499 else
500 {
501 rx = ((double)XP2D(x,v)-mob->bbox.x2) / xdel;
502 ry = ((double)YP2D(y,v)-mob->bbox.y1) / ydel;
503 };
504 break;
505
506 case BOTTOMLEFT:
507 if (v->gridon)
508 {
509 rx = ((double)GXP2D(x,v)-mob->bbox.x1) / xdel;
510 ry = ((double)GYP2D(y,v)-mob->bbox.y2) / ydel;
511 }
512 else
513 {
514 rx = ((double)XP2D(x,v)-mob->bbox.x1) / xdel;
515 ry = ((double)YP2D(y,v)-mob->bbox.y2) / ydel;
516 };
517 break;
518
519 case BOTTOMRIGHT:
520 if (v->gridon)
521 {
522 rx = ((double)GXP2D(x,v)-mob->bbox.x2) / xdel;
523 ry = ((double)GYP2D(y,v)-mob->bbox.y2) / ydel;
524 }
525 else
526 {
527 rx = ((double)XP2D(x,v)-mob->bbox.x2) / xdel;
528 ry = ((double)YP2D(y,v)-mob->bbox.y2) / ydel;
529 };
530 break;
531
532 default:
533 v_error(NOTSCALING);
534 break;
535 };
536
537 /* must only allow arcellipse to be of circular proportions */
538 /* text can reasonably be approximated by a proportional box */
539 /* other objects are resized proportionally with Control */
540 if (state.control_down || mob->type==ARCELLIPSE || mob->type==TEXT)
541 constrain_resize(&rx, &ry);
542 }
543 else if (rotating_object)
544 {
545 static Boolean last_shift=FALSE;
546 static double tangle;
547
548 if (state.shift_down)
549 {
550 double delta;
551
552 if (last_shift!=TRUE)
553 tangle=angle;
554
555 if (mob->type==TEXT)
556 delta = min(P2D(ROTATE_PIXEL_SIZE,v),max(P2D(-ROTATE_PIXEL_SIZE,v),YP2D(y, v)-(mob->bbox.y2-(P2D(HANDLE_PIXEL_SIZE,v)))));
557 else
558 delta = min(P2D(ROTATE_PIXEL_SIZE,v),max(P2D(-ROTATE_PIXEL_SIZE,v),YP2D(y, v)-(mob->bbox.y2-(2*P2D(HANDLE_PIXEL_SIZE,v)))));
559
560 delta /= 17*P2D(ROTATE_PIXEL_SIZE,v);
561 angle = tangle + delta;
562 }
563 else
564 {
565 /* calculate the rotation angle based
566 on mouse position from start */
567
568 if (mob->type==TEXT)
569 angle = min(P2D(ROTATE_PIXEL_SIZE,v),max(P2D(-ROTATE_PIXEL_SIZE,v),XP2D(x, v)-(mob->bbox.x2-(P2D(HANDLE_PIXEL_SIZE,v)))));
570 else
571 angle = min(P2D(ROTATE_PIXEL_SIZE,v),max(P2D(-ROTATE_PIXEL_SIZE,v),XP2D(x, v)-(mob->bbox.x2-(2*P2D(HANDLE_PIXEL_SIZE,v)))));
572
573 angle /= P2D(ROTATE_PIXEL_SIZE,v);
574 angle *= PI;
575
576 /* if near 90 degrees rotation, set to exactly 90 degrees */
577 if (abs(angle / (PI/2.0)) - ((long)(angle / (PI/2.0))) < ROTATE_RECTI_THRESHOLD)
578 angle = ((long)(angle / (PI/2.0))) * (PI/2.0);
579
580 };
581
582 last_shift = state.shift_down;
583 }
584 else
585 {
586 /* we're moving the object */
587 if (v->gridon)
588 {
589 ox = snap(v->grid_x,XP2D(x,v)-gox);
590 oy = snap(v->grid_y,YP2D(y,v)-goy);
591 }
592 else
593 {
594 ox = XP2D(x,v)-gox;
595 oy = YP2D(y,v)-goy;
596 };
597
598 ngox = XP2D(x,v)-gox;
599 ngoy = YP2D(y,v)-goy;
600 mx = ox;
601 my = oy;
602 };
603
604 toggle_object_outline(v);
605
606 state.rubberon = TRUE;
607 }
608 else if (v->selected_object==NULL)
609 {
610 if (disable_motion)
611 disable_motion=0;
612
613 /* we need to prevent hole in highlight */
614 if (v->highlighted_object!=NULL)
615 {
616 Object *ob = v->highlighted_object->ob;
617
618 if (!still_same_object_p(v,v->highlighted_object,x,y))
619 {
620 if ((v->highlighted_object = object_at_point_p(v,v->doc->o, x, y))==NULL) {
621 if (v->highlight_objects)
622 send_redraw_object(v,ob);
623 } else if (v->highlighted_object->ob != ob) {
624 if (v->highlight_objects) {
625 send_redraw_object(v,ob);
626 send_redraw_object(v,v->highlighted_object->ob);
627 };
628 };
629
630 };
631 } else
632 {
633 if ((v->highlighted_object = object_at_point_p(v,v->doc->o, x, y))!=NULL)
634 if (v->highlight_objects) {
635 send_redraw_object(v,v->highlighted_object->ob);
636 };
637 };
638 };
639 }
640