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