.
1 /*
2 Copyright (C) 2012, 2013, 2014, 2015 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Math;
16 using Cairo;
17
18 namespace BirdFont {
19
20 /** Create new points. */
21 public class PenTool : Tool {
22
23 private static double contact_surface {
24 get {
25 return MainWindow.units * 20;
26 }
27 }
28
29 public static bool move_selected = false;
30 public static bool move_selected_handle = false;
31
32 public static bool move_point_on_path = false;
33
34 public static bool edit_active_corner = false;
35
36 public static bool move_point_independent_of_handle = false;
37
38 public static Gee.ArrayList<PointSelection> selected_points;
39
40 public static EditPointHandle active_handle;
41 public static EditPointHandle selected_handle;
42 public static PointSelection handle_selection;
43
44 public static EditPoint? active_edit_point;
45 public static Path active_path;
46
47 public static EditPoint selected_point;
48
49 public static double last_point_x = 0;
50 public static double last_point_y = 0;
51
52 public static bool show_selection_box = false;
53 private static double selection_box_x = 0;
54 private static double selection_box_y = 0;
55 private static double selection_box_last_x = 0;
56 private static double selection_box_last_y = 0;
57
58 private static bool point_selection_image = false;
59
60 public static double precision = 1;
61
62 // The pixel where the user pressed the mouse button
63 public static int begin_action_x = 0;
64 public static int begin_action_y = 0;
65
66 /* First move action must move the current point in to the grid. */
67 bool first_move_action = false;
68
69 /** Move curve handle instead of control point. */
70 private bool last_selected_is_handle = false;
71
72 static Gee.ArrayList<Path> clockwise;
73 static Gee.ArrayList<Path> counter_clockwise;
74
75 public static double path_stroke_width = 0;
76 public static double simplification_threshold = 0.5;
77
78 public static bool retain_angle = false;
79
80 public PenTool (string name) {
81 base (name, t_("Add new points"));
82
83 selected_points = new Gee.ArrayList<PointSelection> ();
84
85 active_handle = new EditPointHandle.empty ();
86 selected_handle = new EditPointHandle.empty ();
87 handle_selection = new PointSelection.empty ();
88
89 active_edit_point = new EditPoint ();
90 active_path = new Path ();
91
92 selected_point = new EditPoint ();
93 clockwise = new Gee.ArrayList<Path> ();
94 counter_clockwise = new Gee.ArrayList<Path> ();
95
96 select_action.connect ((self) => {
97 });
98
99 deselect_action.connect ((self) => {
100 force_direction ();
101 move_point_on_path = false;
102 });
103
104 press_action.connect ((self, b, x, y) => {
105 // retain path direction
106 Glyph glyph = MainWindow.get_current_glyph ();
107 clockwise = new Gee.ArrayList<Path> ();
108 counter_clockwise = new Gee.ArrayList<Path> ();
109
110 begin_action_x = x;
111 begin_action_y = y;
112
113 foreach (Path p in glyph.path_list) {
114 if (p.is_clockwise ()) {
115 clockwise.add (p);
116 } else {
117 counter_clockwise.add (p);
118 }
119 }
120
121 first_move_action = true;
122
123 last_point_x = Glyph.path_coordinate_x (x);
124 last_point_y = Glyph.path_coordinate_y (y);
125
126 move_action (this, x, y);
127 press (b, x, y, false);
128
129 if (BirdFont.android) {
130 point_selection_image = true;
131 }
132
133 selection_box_x = x;
134 selection_box_y = y;
135
136 last_point_x = Glyph.path_coordinate_x (x);
137 last_point_y = Glyph.path_coordinate_y (y);
138
139 BirdFont.get_current_font ().touch ();
140
141 // move new points on to grid
142 if (b == 1 && (GridTool.has_ttf_grid () || GridTool.is_visible ())) {
143 move (x, y);
144 }
145 });
146
147 double_click_action.connect ((self, b, x, y) => {
148 last_point_x = Glyph.path_coordinate_x (x);
149 last_point_y = Glyph.path_coordinate_y (y);
150
151 press (b, x, y, true);
152 });
153
154 release_action.connect ((self, b, ix, iy) => {
155 double x, y;
156
157 x = Glyph.path_coordinate_x (ix);
158 y = Glyph.path_coordinate_y (iy);
159
160 if (has_join_icon ()) {
161 join_paths (x, y);
162 }
163
164 active_handle = new EditPointHandle.empty ();
165
166 if (show_selection_box) {
167 select_points_in_box ();
168 }
169
170 move_selected = false;
171 move_selected_handle = false;
172 edit_active_corner = false;
173 show_selection_box = false;
174
175 // update path direction if it has changed
176 foreach (Path p in clockwise) {
177 if (!p.is_open () && !p.is_clockwise ()) {
178 p.reverse ();
179 update_selection ();
180 }
181 }
182
183 foreach (Path p in counter_clockwise) {
184 if (!p.is_open () && p.is_clockwise ()) {
185 p.reverse ();
186 update_selection ();
187 }
188 }
189
190 point_selection_image = false;
191 BirdFont.get_current_font ().touch ();
192 });
193
194 move_action.connect ((self, x, y) => {
195 selection_box_last_x = x;
196 selection_box_last_y = y;
197
198 if (Path.distance (begin_action_x, x, begin_action_y, y) > 10 * MainWindow.units) {
199 point_selection_image = false;
200 }
201
202 move (x, y);
203 });
204
205 key_press_action.connect ((self, keyval) => {
206 if (keyval == Key.DEL || keyval == Key.BACK_SPACE) {
207 if (KeyBindings.has_shift ()) {
208 delete_selected_points ();
209 } else {
210 delete_simplify ();
211 }
212 }
213
214 if (is_arrow_key (keyval)) {
215 if (KeyBindings.modifier != CTRL) {
216 move_selected_points (keyval);
217 active_edit_point = selected_point;
218 } else {
219 move_select_next_point (keyval);
220 }
221 }
222
223 if (KeyBindings.has_shift ()) {
224 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
225 last_point_x = selected_point.x;
226 last_point_y = selected_point.y;
227 }
228 }
229
230 GlyphCanvas.redraw ();
231 BirdFont.get_current_font ().touch ();
232 });
233
234 key_release_action.connect ((self, keyval) => {
235 double x, y;
236 if (is_arrow_key (keyval)) {
237 if (KeyBindings.modifier != CTRL) {
238 x = Glyph.reverse_path_coordinate_x (selected_point.x);
239 y = Glyph.reverse_path_coordinate_y (selected_point.y);
240 join_paths (x, y);
241 }
242 }
243 });
244
245 draw_action.connect ((tool, cairo_context, glyph) => {
246 draw_on_canvas (cairo_context, glyph);
247 });
248 }
249
250 public void set_stroke_width (double width) {
251 string w = SpinButton.convert_to_string (width);
252 Preferences.set ("pen_tool_stroke_width", w);
253 path_stroke_width = width;
254 }
255
256 public bool has_join_icon () {
257 double mx, my;
258 get_tie_position (out mx, out my);
259 return (mx > -10 * MainWindow.units && my > -10 * MainWindow.units);
260 }
261
262 public static void select_points_in_box () {
263 double x1, y1, x2, y2;
264 Glyph g;
265
266 g = MainWindow.get_current_glyph ();
267
268 x1 = Glyph.path_coordinate_x (fmin (selection_box_x, selection_box_last_x));
269 y1 = Glyph.path_coordinate_y (fmin (selection_box_y, selection_box_last_y));
270 x2 = Glyph.path_coordinate_x (fmax (selection_box_x, selection_box_last_x));
271 y2 = Glyph.path_coordinate_y (fmax (selection_box_y, selection_box_last_y));
272
273 remove_all_selected_points ();
274
275 foreach (Path p in g.path_list) {
276 // TODO: Select path only of bounding box is in selection box
277 foreach (EditPoint ep in p.points) {
278 if (x1 <= ep.x <= x2 && y2 <= ep.y <= y1) {
279 add_selected_point (ep, p);
280 ep.set_selected (true);
281 }
282 }
283 }
284 }
285
286 public static void delete_selected_points () {
287 Glyph g = MainWindow.get_current_glyph ();
288
289 foreach (PointSelection p in selected_points) {
290 p.point.deleted = true;
291 }
292
293 process_deleted ();
294
295 foreach (Path p in g.path_list) {
296 if (p.has_deleted_point ()) {
297 process_deleted ();
298 }
299 }
300
301 g.update_view ();
302
303 selected_points.clear ();
304 selected_handle.selected = false;
305
306 active_handle = new EditPointHandle.empty ();
307 selected_handle = new EditPointHandle.empty ();
308
309 active_edit_point = null;
310 selected_point = new EditPoint ();
311 }
312
313 static void get_closes_point_in_segment (EditPoint ep0, EditPoint ep1, EditPoint ep2,
314 double px, double py,
315 out double nx, out double ny) {
316 double npx0, npy0;
317 double npx1, npy1;
318
319 Path.find_closes_point_in_segment (ep0, ep1, px, py, out npx0, out npy0, 50);
320 Path.find_closes_point_in_segment (ep1, ep2, px, py, out npx1, out npy1, 50);
321
322 if (Path.distance (px, npx0, py, npy0) < Path.distance (px, npx1, py, npy1)) {
323 nx = npx0;
324 ny = npy0;
325 } else {
326 nx = npx1;
327 ny = npy1;
328 }
329 }
330
331 public static void get_path_distortion (EditPoint oe0, EditPoint oe1, EditPoint oe2,
332 EditPoint ep1, EditPoint ep2,
333 out double distortion_first, out double distortion_next) {
334 double nx, ny;
335 double df, dn;
336 int step;
337
338 df = 0;
339 dn = 0;
340 nx = 0;
341 ny = 0;
342
343 step = 4;
344
345 Path.all_of (ep1, ep2, (xa, ya, ta) => {
346 double f, n;
347
348 get_closes_point_in_segment (oe0, oe1, oe2, xa, ya, out nx, out ny);
349
350 if (ta < 0.5) {
351 f = Path.distance (nx, xa, ny, ya);
352 if (f > df) {
353 df += f;
354 }
355 } else {
356 n = Path.distance (nx, xa, ny, ya);
357 if (n > dn) {
358 dn += n;
359 }
360 }
361
362 return true;
363 }, step);
364
365 distortion_first = df;
366 distortion_next = dn;
367 }
368
369 public static void delete_simplify () {
370 Glyph g = MainWindow.get_current_glyph ();
371
372 foreach (PointSelection p in selected_points) {
373 remove_point_simplify (p);
374 }
375
376 g.update_view ();
377
378 selected_points.clear ();
379 selected_handle.selected = false;
380
381 active_handle = new EditPointHandle.empty ();
382 selected_handle = new EditPointHandle.empty ();
383
384 active_edit_point = null;
385 selected_point = new EditPoint ();
386 }
387
388 /** @return path distortion. */
389 public static double remove_point_simplify (PointSelection p, double tolerance = 0.6) {
390 double start_length, stop_length;
391 double start_distortion, start_min_distortion, start_previous_length;
392 double stop_distortion, stop_min_distortion, stop_previous_length;
393 double distortion, min_distortion;
394 double prev_length_adjustment, next_length_adjustment;
395 double prev_length_adjustment_reverse, next_length_adjustment_reverse;
396 EditPoint ep1, ep2;
397 EditPoint next, prev;
398 double step, distance;
399
400 return_if_fail (p.path.points.size > 0);
401
402 if (p.path.points.size <= 2) {
403 p.point.deleted = true;
404 p.path.remove_deleted_points ();
405 return 0;
406 }
407
408 p.point.deleted = true;
409
410 if (p.point.next != null) {
411 next = p.point.get_next ();
412 } else {
413 next = p.path.points.get (0);
414 }
415
416 if (p.point.prev != null) {
417 prev = p.point.get_prev ();
418 } else {
419 prev = p.path.points.get (p.path.points.size - 1);
420 }
421
422 prev.get_right_handle ().convert_to_curve ();
423 next.get_left_handle ().convert_to_curve ();
424
425 if (prev.get_right_handle ().type == PointType.QUADRATIC
426 && next.get_left_handle ().type != PointType.QUADRATIC) {
427 convert_point_type (prev, next.get_left_handle ().type);
428 }
429
430 if (prev.get_right_handle ().type != PointType.QUADRATIC
431 && next.get_left_handle ().type == PointType.QUADRATIC) {
432 convert_point_type (next, prev.get_right_handle ().type);
433 }
434
435 ep1 = prev.copy ();
436 ep2 = next.copy ();
437
438 start_length = ep1.get_right_handle ().length;
439 stop_length = ep2.get_left_handle ().length;
440
441 stop_previous_length = start_length;
442 start_previous_length = stop_length;
443
444 stop_min_distortion = double.MAX;
445 ep1.get_right_handle ().length = start_length;
446
447 start_min_distortion = double.MAX;
448 ep2.get_left_handle ().length = stop_length;
449
450 prev_length_adjustment = 0;
451 next_length_adjustment = 0;
452 prev_length_adjustment_reverse = 0;
453 next_length_adjustment_reverse = 0;
454
455 min_distortion = double.MAX;
456 distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y);
457
458 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) {
459 step = m / 10.0;
460 min_distortion = double.MAX;
461
462 double first = (m == 50.0) ? 0 : -m;
463 for (double a = first; a < m; a += step) {
464 for (double b = first; b < m; b += step) {
465
466 if (start_length + a + stop_length + b > distance) {
467 break;
468 }
469
470 ep1.get_right_handle ().length = start_length + a;
471 ep2.get_left_handle ().length = stop_length + b;
472
473 get_path_distortion (prev, p.point, next,
474 ep1, ep2,
475 out start_distortion, out stop_distortion);
476
477 distortion = Math.fmax (start_distortion, stop_distortion);
478
479 if (distortion < min_distortion
480 && start_length + a > 0
481 && stop_length + b > 0) {
482 min_distortion = distortion;
483
484 prev_length_adjustment_reverse = a;
485 next_length_adjustment = b;
486 }
487 }
488 }
489
490 start_length += prev_length_adjustment_reverse;
491 stop_length += next_length_adjustment;
492 }
493
494 prev.get_right_handle ().length = start_length;
495
496 if (prev.get_right_handle ().type != PointType.QUADRATIC) {
497 next.get_left_handle ().length = stop_length;
498 } else {
499 next.get_left_handle ().move_to_coordinate (
500 prev.get_right_handle ().x, prev.get_right_handle ().y);
501 }
502
503 p.point.deleted = true;
504 p.path.remove_deleted_points ();
505 p.path.update_region_boundaries ();
506
507 return min_distortion;
508 }
509
510 /** Retain selected points even if path is copied after running reverse. */
511 public static void update_selection () {
512 Glyph g = MainWindow.get_current_glyph ();
513
514 selected_points.clear ();
515
516 foreach (Path p in g.path_list) {
517 foreach (EditPoint e in p.points) {
518 if (e.is_selected ()) {
519 selected_points.add (new PointSelection (e, p));
520 }
521 }
522 }
523 }
524
525 static void process_deleted () {
526 Glyph g = MainWindow.get_current_glyph ();
527 while (g.process_deleted ());
528 }
529
530 public static void close_all_paths () {
531 Glyph g = MainWindow.get_current_glyph ();
532 foreach (Path p in g.path_list) {
533 p.close ();
534 }
535 g.close_path ();
536 g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
537 }
538
539 public void set_precision (double p) {
540 precision = p;
541 SettingsDisplay.precision.set_value_round (p, false, false);
542 }
543
544 public void move (int x, int y) {
545 Glyph glyph = MainWindow.get_current_glyph ();
546 double coordinate_x, coordinate_y;
547 double delta_coordinate_x, delta_coordinate_y;
548 double angle = 0;
549 bool tied;
550
551 control_point_event (x, y);
552 curve_active_corner_event (x, y);
553 set_default_handle_positions ();
554
555 if (move_selected_handle && move_selected) {
556 warning ("move_selected_handle && move_selected");
557 move_selected = false;
558 move_selected_handle = false;
559 }
560
561 // move control point handles
562 if (move_selected_handle) {
563 set_type_for_moving_handle ();
564
565 // don't update angle if the user is holding down shift
566 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) {
567 angle = selected_handle.angle;
568 }
569
570 if (GridTool.is_visible ()) {
571 coordinate_x = Glyph.path_coordinate_x (x);
572 coordinate_y = Glyph.path_coordinate_y (y);
573 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y);
574 delta_coordinate_x = coordinate_x - last_point_x;
575 delta_coordinate_y = coordinate_y - last_point_y;
576 selected_handle.move_to_coordinate (selected_handle.x + delta_coordinate_x, selected_handle.y + delta_coordinate_y);
577 } else if (GridTool.has_ttf_grid ()) {
578 coordinate_x = Glyph.path_coordinate_x (x);
579 coordinate_y = Glyph.path_coordinate_y (y);
580 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y);
581 delta_coordinate_x = coordinate_x - last_point_x;
582 delta_coordinate_y = coordinate_y - last_point_y;
583 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y);
584 } else {
585 coordinate_x = Glyph.path_coordinate_x (x);
586 coordinate_y = Glyph.path_coordinate_y (y);
587 delta_coordinate_x = coordinate_x - last_point_x;
588 delta_coordinate_y = coordinate_y - last_point_y;
589 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y);
590 }
591
592 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) {
593 selected_handle.angle = angle;
594 selected_handle.process_connected_handle ();
595
596 if (selected_handle.parent.tie_handles) {
597 if (selected_handle.is_left_handle ()) {
598 selected_handle.parent.get_right_handle ().angle = angle - PI;
599 } else {
600 selected_handle.parent.get_left_handle ().angle = angle + PI;
601 }
602 }
603 }
604
605 handle_selection.path.update_region_boundaries ();
606
607 // FIXME: redraw line only
608 glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height);
609
610 if (GridTool.is_visible ()) {
611 last_point_x = selected_handle.x;
612 last_point_y = selected_handle.y;
613 } else if (GridTool.has_ttf_grid ()) {
614 last_point_x = selected_handle.x;
615 last_point_y = selected_handle.y;
616 } else {
617 last_point_x = Glyph.path_coordinate_x (x);
618 last_point_y = Glyph.path_coordinate_y (y);
619 }
620 }
621
622 // move edit point
623 if (move_selected) {
624
625 if (GridTool.is_visible ()) {
626 coordinate_x = Glyph.path_coordinate_x (x);
627 coordinate_y = Glyph.path_coordinate_y (y);
628
629 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
630
631 if (first_move_action) {
632 last_point_x = selected_point.x;
633 last_point_y = selected_point.y;
634 }
635
636 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
637 } else {
638 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y);
639 }
640
641 delta_coordinate_x = coordinate_x - last_point_x;
642 delta_coordinate_y = coordinate_y - last_point_y;
643
644 foreach (PointSelection selected in selected_points) {
645 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
646 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
647 selected.point.y + delta_coordinate_y);
648 } else {
649 selected.point.set_position (selected.point.x + delta_coordinate_x,
650 selected.point.y + delta_coordinate_y);
651 }
652
653 selected.point.recalculate_linear_handles ();
654 selected.path.update_region_boundaries ();
655 }
656 } else if (GridTool.has_ttf_grid ()) {
657 coordinate_x = Glyph.path_coordinate_x (x);
658 coordinate_y = Glyph.path_coordinate_y (y);
659
660 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y);
661
662 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
663
664 if (first_move_action) {
665 last_point_x = selected_point.x;
666 last_point_y = selected_point.y;
667 }
668
669 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
670 }
671
672 delta_coordinate_x = coordinate_x - last_point_x;
673 delta_coordinate_y = coordinate_y - last_point_y;
674
675 foreach (PointSelection selected in selected_points) {
676 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
677 tied = selected.point.tie_handles;
678 selected.point.tie_handles = false;
679 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
680 selected.point.y + delta_coordinate_y);
681 selected.point.tie_handles = tied;
682 } else {
683 selected.point.set_position (selected.point.x + delta_coordinate_x,
684 selected.point.y + delta_coordinate_y);
685 }
686
687 selected.point.recalculate_linear_handles ();
688 selected.path.update_region_boundaries ();
689 }
690 } else {
691
692 coordinate_x = Glyph.path_coordinate_x (x);
693 coordinate_y = Glyph.path_coordinate_y (y);
694
695 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
696
697 if (first_move_action) {
698 last_point_x = selected_point.x;
699 last_point_y = selected_point.y;
700 }
701
702 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
703 delta_coordinate_x = coordinate_x - last_point_x;
704 delta_coordinate_y = coordinate_y - last_point_y;
705 } else {
706 delta_coordinate_x = coordinate_x - last_point_x;
707 delta_coordinate_y = coordinate_y - last_point_y;
708 }
709
710 foreach (PointSelection selected in selected_points) {
711
712 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
713 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
714 selected.point.y + delta_coordinate_y);
715 } else {
716 selected.point.set_position (selected.point.x + delta_coordinate_x,
717 selected.point.y + delta_coordinate_y);
718 }
719
720 selected.point.recalculate_linear_handles ();
721 selected.path.update_region_boundaries ();
722 }
723 }
724
725 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
726 last_point_x = selected_point.x;
727 last_point_y = selected_point.y;
728 } else if (GridTool.is_visible ()) {
729 last_point_x = selected_point.x;
730 last_point_y = selected_point.y;
731 } else if (GridTool.has_ttf_grid ()) {
732 last_point_x = selected_point.x;
733 last_point_y = selected_point.y;
734 } else {
735 last_point_x = Glyph.path_coordinate_x (x);
736 last_point_y = Glyph.path_coordinate_y (y);
737 }
738 }
739
740 if (show_selection_box) {
741 GlyphCanvas.redraw ();
742 }
743 }
744
745 private void move_point_on_handles (double px, double py, out double cx, out double cy) {
746 EditPoint ep;
747 ep = selected_point.copy ();
748 ep.tie_handles = false;
749 ep.reflective_point = false;
750 ep.get_right_handle ().angle += PI / 2;
751 ep.x = px;
752 ep.y = py;
753
754 Path.find_intersection_handle (ep.get_right_handle (), selected_point.get_right_handle (), out cx, out cy);
755 }
756
757 public void press (int button, int x, int y, bool double_click) {
758 Glyph? g = MainWindow.get_current_glyph ();
759 Glyph glyph = (!) g;
760
761 return_if_fail (g != null);
762
763 if ((double_click && !BirdFont.android)
764 || Toolbox.drawing_tools.insert_point_on_path_tool.is_selected ()) {
765 glyph.insert_new_point_on_path (x, y);
766 return;
767 }
768
769 if (button == 1) {
770 add_point_event (x, y);
771 return;
772 }
773
774 if (button == 2) {
775 if (glyph.is_open ()) {
776 force_direction ();
777 glyph.close_path ();
778 } else {
779 glyph.open_path ();
780 }
781 return;
782 }
783
784 if (button == 3) {
785 move_point_event (x, y);
786
787 // alt+click on a handle ends the symmetrical editing
788 if ((KeyBindings.has_alt () || KeyBindings.has_ctrl ())
789 && is_over_handle (x, y)) {
790
791 selected_handle.parent.set_reflective_handles (false);
792 selected_handle.parent.set_tie_handle (false);
793 GlyphCanvas.redraw ();
794 }
795
796 return;
797 }
798 }
799
800 public void add_point_event (int x, int y) {
801 Glyph? g = MainWindow.get_current_glyph ();
802 Glyph glyph = (!) g;
803
804 return_if_fail (g != null);
805
806 remove_all_selected_points ();
807 new_point_action (x, y);
808 glyph.store_undo_state ();
809 }
810
811 public void move_point_event (int x, int y) {
812 Glyph? g = MainWindow.get_current_glyph ();
813 Glyph glyph = (!) g;
814
815 return_if_fail (g != null);
816
817 control_point_event (x, y);
818 curve_corner_event (x, y);
819
820 if (!move_selected_handle) {
821 select_active_point (x, y);
822 last_selected_is_handle = false;
823 }
824
825 if (!KeyBindings.has_shift ()
826 && selected_points.size == 0
827 && !active_handle.active) {
828 show_selection_box = true;
829 }
830
831 glyph.store_undo_state ();
832 }
833
834 void set_type_for_moving_handle () {
835 if (selected_handle.type == PointType.LINE_CUBIC) {
836 selected_handle.set_point_type (PointType.CUBIC);
837 }
838
839 if (selected_handle.type == PointType.LINE_QUADRATIC) {
840 selected_handle.set_point_type (PointType.QUADRATIC);
841 }
842
843 if (selected_handle.type == PointType.LINE_DOUBLE_CURVE) {
844 selected_handle.set_point_type (PointType.DOUBLE_CURVE);
845 }
846 }
847
848 /** Set fill property to transparend for counter paths. */
849 public static void force_direction () {
850 Glyph g = MainWindow.get_current_glyph ();
851
852 clear_directions ();
853
854 foreach (Path p in g.path_list) {
855 if (!p.has_direction ()) {
856 if (is_counter_path (p)) {
857 p.force_direction (Direction.COUNTER_CLOCKWISE);
858 } else {
859 p.force_direction (Direction.CLOCKWISE);
860 }
861 }
862 }
863
864 update_selected_points ();
865 }
866
867 public static void clear_directions () {
868 clockwise.clear ();
869 counter_clockwise.clear ();
870 }
871
872 public static bool is_counter_path (Path path) {
873 Glyph g = MainWindow.get_current_glyph ();
874 PathList pl = new PathList ();
875
876 foreach (Path p in g.path_list) {
877 pl.add (p);
878 }
879
880 return Path.is_counter (pl, path);
881 }
882
883 public void remove_from_selected (EditPoint ep)
884 requires (selected_points.size > 0) {
885
886 Gee.ArrayList<PointSelection> remove = new Gee.ArrayList<PointSelection> ();
887
888 foreach (PointSelection e in selected_points) {
889 if (e.point.equals (e.point)) {
890 remove.add (e);
891 }
892 }
893
894 foreach (PointSelection e in remove) {
895 selected_points.remove (e);
896 }
897 }
898
899 public void select_active_point (double x, double y) {
900 Glyph? g = MainWindow.get_current_glyph ();
901 Glyph glyph = (!) g;
902 bool reverse;
903
904 control_point_event (x, y);
905
906 // continue adding points from the other end of the selected path
907 reverse = false;
908
909 foreach (Path p in glyph.path_list) {
910
911 if (p.is_open () && p.points.size >= 1
912 && (active_edit_point == p.points.get (0)
913 || active_edit_point == p.points.get (p.points.size - 1))) {
914 active_path = p;
915 glyph.set_active_path (p);
916
917 update_selection ();
918 reverse = true;
919 control_point_event (x, y);
920 break;
921 }
922 }
923
924 foreach (Path p in glyph.path_list) {
925 if (p.is_open () && p.points.size > 1 && active_edit_point == p.points.get (0)) {
926 p.reverse ();
927 update_selection ();
928 reverse = true;
929 control_point_event (x, y);
930 break;
931 }
932 }
933
934 if (active_edit_point == null) {
935 if (KeyBindings.modifier != SHIFT) {
936 remove_all_selected_points ();
937 return;
938 }
939 }
940
941 move_selected = true;
942 move_point_on_path = true;
943
944 if (active_edit_point != null) {
945 if (KeyBindings.modifier == SHIFT) {
946 if (((!)active_edit_point).is_selected () && selected_points.size > 1) {
947 ((!)active_edit_point).set_selected (false);
948 remove_from_selected ((!)active_edit_point);
949 selected_point = new EditPoint ();
950 last_selected_is_handle = false;
951 } else {
952 ((!)active_edit_point).set_selected (true);
953 selected_point = (!)active_edit_point;
954 add_selected_point (selected_point, active_path);
955 last_selected_is_handle = false;
956 }
957 } else {
958 selected_point = (!)active_edit_point;
959
960 if (!((!)active_edit_point).is_selected ()) {
961 remove_all_selected_points ();
962 ((!)active_edit_point).set_selected (true);
963 selected_point = (!)active_edit_point;
964 add_selected_point (selected_point, active_path); // FIXME: double check active path
965 last_selected_is_handle = false;
966 }
967
968 // alt+click creates a point with symmetrical handles
969 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) {
970 selected_point.set_reflective_handles (true);
971 selected_point.process_symmetrical_handles ();
972 GlyphCanvas.redraw ();
973 }
974 }
975 }
976
977 if (reverse) {
978 clockwise.clear ();
979 counter_clockwise.clear ();
980 }
981 }
982
983 private static Path? find_path_to_join () {
984 Path? m = null;
985 Glyph glyph = MainWindow.get_current_glyph ();
986 EditPoint ep_last, ep_first;
987
988 foreach (Path path in glyph.path_list) {
989 if (path.points.size == 0) {
990 continue;
991 }
992
993 ep_last = path.points.get (path.points.size - 1);
994 ep_first = path.points.get (0);
995
996 if (active_edit_point == ep_last) {
997 m = path;
998 break;
999 }
1000
1001 if (active_edit_point == ep_first) {
1002 m = path;
1003 break;
1004 }
1005 }
1006
1007 return m;
1008 }
1009
1010 public static Path merge_open_paths (Path path1, Path path2) {
1011 Path union, merge;
1012 EditPoint first_point;
1013
1014 return_if_fail (path1.points.size > 1);
1015 return_if_fail (path2.points.size > 1);
1016
1017 union = path1.copy ();
1018 merge = path2.copy ();
1019
1020 merge.points.get (0).set_tie_handle (false);
1021 merge.points.get (0).set_reflective_handles (false);
1022
1023 merge.points.get (merge.points.size - 1).set_tie_handle (false);
1024 merge.points.get (merge.points.size - 1).set_reflective_handles (false);
1025
1026 union.points.get (union.points.size - 1).set_tie_handle (false);
1027 union.points.get (union.points.size - 1).set_reflective_handles (false);
1028
1029 union.points.get (0).set_tie_handle (false);
1030 union.points.get (0).set_reflective_handles (false);
1031
1032 first_point = merge.get_first_point ();
1033
1034 if (union.get_last_point ().get_left_handle ().is_curve ()) {
1035 first_point.get_left_handle ().convert_to_curve ();
1036 } else {
1037 first_point.get_left_handle ().convert_to_line ();
1038 }
1039
1040 first_point.get_left_handle ().move_to_coordinate_internal (
1041 union.get_last_point ().get_left_handle ().x,
1042 union.get_last_point ().get_left_handle ().y);
1043
1044 union.delete_last_point ();
1045
1046 union.append_path (merge);
1047
1048 return union;
1049 }
1050
1051 public static void close_path (Path path) {
1052 bool last_segment_is_line;
1053 bool first_segment_is_line;
1054
1055 return_if_fail (path.points.size > 1);
1056
1057 last_segment_is_line = path.get_last_point ().get_left_handle ().is_line ();
1058 first_segment_is_line = path.get_first_point ().get_right_handle ().is_line ();
1059
1060 // TODO: set point type
1061 path.points.get (0).left_handle.move_to_coordinate (
1062 path.points.get (path.points.size - 1).left_handle.x,
1063 path.points.get (path.points.size - 1).left_handle.y);
1064
1065 path.points.get (0).left_handle.type =
1066 path.points.get (path.points.size - 1).left_handle.type;
1067
1068 path.points.get (0).recalculate_linear_handles ();
1069 path.points.get (path.points.size - 1).recalculate_linear_handles ();
1070
1071 // force the connected handle to move
1072 path.points.get (0).set_position (
1073 path.points.get (0).x, path.points.get (0).y);
1074
1075 path.points.remove_at (path.points.size - 1);
1076
1077 path.close ();
1078
1079 if (last_segment_is_line) {
1080 path.get_first_point ().get_left_handle ().convert_to_line ();
1081 path.get_first_point ().recalculate_linear_handles ();
1082 }
1083
1084 if (first_segment_is_line) {
1085 path.get_first_point ().get_right_handle ().convert_to_line ();
1086 path.get_first_point ().recalculate_linear_handles ();
1087 }
1088 }
1089
1090 private static void join_paths (double x, double y) {
1091 Glyph glyph = MainWindow.get_current_glyph ();
1092 Path? p;
1093 Path path;
1094 Path union;
1095 bool direction_changed = false;
1096 int px, py;
1097
1098 if (glyph.path_list.size == 0) {
1099 return;
1100 }
1101
1102 p = find_path_to_join ();
1103 if (p == null) {
1104 warning ("No path to join.");
1105 return;
1106 }
1107
1108 path = (!) p;
1109 if (!path.is_open ()) {
1110 warning ("Path is closed.");
1111 return;
1112 }
1113
1114 return_if_fail (path.points.size > 1);
1115
1116 if (active_edit_point == path.points.get (0)) {
1117 path.reverse ();
1118 update_selection ();
1119 path.recalculate_linear_handles ();
1120 direction_changed = true;
1121 active_edit_point = path.points.get (path.points.size - 1);
1122 active_path = path;
1123 }
1124
1125 if (path.points.get (0) == active_edit_point) {
1126 warning ("Wrong direction.");
1127 return;
1128 }
1129
1130 // join path with it self
1131 px = Glyph.reverse_path_coordinate_x (((!) active_edit_point).x);
1132 py = Glyph.reverse_path_coordinate_y (((!) active_edit_point).y);
1133 if (is_endpoint ((!) active_edit_point)
1134 && is_close_to_point (path.points.get (0), px, py)) {
1135
1136 close_path (path);
1137 glyph.close_path ();
1138 force_direction ();
1139
1140 if (direction_changed) {
1141 path.reverse ();
1142 update_selection ();
1143 }
1144
1145 remove_all_selected_points ();
1146 return;
1147 }
1148
1149 foreach (Path merge in glyph.path_list) {
1150 // don't join path with itself here
1151 if (path == merge) {
1152 continue;
1153 }
1154
1155 // we need both start and end points
1156 if (merge.points.size < 1 || path.points.size < 1) {
1157 continue;
1158 }
1159
1160 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) {
1161 merge.reverse ();
1162 update_selection ();
1163 direction_changed = !direction_changed;
1164 }
1165
1166 return_if_fail (merge.points.size > 0);
1167
1168 if (is_close_to_point (merge.points.get (0), px, py)) {
1169 union = merge_open_paths (path, merge);
1170
1171 glyph.add_path (union);
1172 glyph.delete_path (path);
1173 glyph.delete_path (merge);
1174 glyph.clear_active_paths ();
1175
1176 union.reopen ();
1177 union.create_list ();
1178
1179 force_direction ();
1180
1181 if (direction_changed) {
1182 path.reverse ();
1183 update_selection ();
1184 }
1185
1186 union.update_region_boundaries ();
1187
1188 return;
1189 }
1190 }
1191
1192 if (direction_changed) {
1193 path.reverse ();
1194 update_selection ();
1195 }
1196 }
1197
1198 /** Merge paths if ends are close. */
1199 public static bool is_close_to_point (EditPoint ep, double x, double y) {
1200 double px, py, distance;
1201
1202 px = Glyph.reverse_path_coordinate_x (ep.x);
1203 py = Glyph.reverse_path_coordinate_y (ep.y);
1204
1205 distance = sqrt (fabs (pow (px - x, 2)) + fabs (pow (py - y, 2)));
1206
1207 return (distance < 8 * MainWindow.units);
1208 }
1209
1210 /** Show the user that curves will be merged on release. */
1211 public void draw_on_canvas (Context cr, Glyph glyph) {
1212 if (show_selection_box) {
1213 draw_selection_box (cr);
1214 }
1215
1216 if (point_selection_image) {
1217 draw_point_selection_circle (cr);
1218 }
1219
1220 draw_merge_icon (cr);
1221 }
1222
1223 /** Higlight the selected point on Android. */
1224 void draw_point_selection_circle (Context cr) {
1225 PointSelection ps;
1226
1227 if (active_handle.active) {
1228 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1229 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Control Point Handle"));
1230 } else if (selected_points.size > 0) {
1231 ps = selected_points.get (selected_points.size - 1);
1232
1233 if (ps.point.type == PointType.CUBIC) {
1234 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1235 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point"));
1236 } else {
1237 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1238 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point"));
1239 }
1240 }
1241 }
1242
1243 void draw_selection_box (Context cr) {
1244 double x, y, w, h;
1245
1246 x = fmin (selection_box_x, selection_box_last_x);
1247 y = fmin (selection_box_y, selection_box_last_y);
1248 w = fmax (selection_box_x, selection_box_last_x) - x;
1249 h = fmax (selection_box_y, selection_box_last_y) - y;
1250
1251 cr.save ();
1252 Theme.color (cr, "Foreground 1");
1253 cr.set_line_width (2);
1254 cr.rectangle (x, y, w, h);
1255 cr.stroke ();
1256 cr.restore ();
1257 }
1258
1259 public static void draw_join_icon (Context cr, double x, double y) {
1260 cr.save ();
1261 Theme.color (cr, "Merge");
1262 cr.move_to (x, y);
1263 cr.arc (x, y, 15, 0, 2 * Math.PI);
1264 cr.close_path ();
1265 cr.fill ();
1266 cr.restore ();
1267 }
1268
1269 void draw_merge_icon (Context cr) {
1270 double x, y;
1271 get_tie_position (out x, out y);
1272 draw_join_icon (cr, x, y);
1273 }
1274
1275 /** Obtain the position where to ends meet. */
1276 void get_tie_position (out double x, out double y) {
1277 Glyph glyph;
1278 EditPoint active;
1279 double px, py;
1280
1281 x = -100;
1282 y = -100;
1283
1284 if (active_edit_point == null) {
1285 return;
1286 }
1287
1288 if (!is_endpoint ((!) active_edit_point)) {
1289 return;
1290 }
1291
1292 glyph = MainWindow.get_current_glyph ();
1293 active = (!) active_edit_point;
1294
1295 return_if_fail (!is_null (glyph));
1296
1297 px = Glyph.reverse_path_coordinate_x (active.x);
1298 py = Glyph.reverse_path_coordinate_y (active.y);
1299
1300 foreach (Path path in glyph.path_list) {
1301
1302 if (!path.is_open ()) {
1303 continue;
1304 }
1305
1306 if (path.points.size == 0) {
1307 continue;
1308 }
1309
1310 foreach (EditPoint ep in path.points) {
1311 if (ep == active || !is_endpoint (ep)) {
1312 continue;
1313 }
1314
1315 if (is_close_to_point (ep, px, py)) {
1316 x = Glyph.reverse_path_coordinate_x (ep.x);
1317 y = Glyph.reverse_path_coordinate_y (ep.y);
1318 return;
1319 }
1320 }
1321 }
1322 }
1323
1324 public static bool is_endpoint (EditPoint ep) {
1325 EditPoint start;
1326 EditPoint end;
1327 Glyph glyph = MainWindow.get_current_glyph ();
1328
1329 foreach (Path path in glyph.path_list) {
1330 if (path.points.size < 1) {
1331 continue;
1332 }
1333
1334 start = path.points.get (0);
1335 end = path.points.get (path.points.size - 1);
1336
1337 if (ep == start || ep == end) {
1338 return true;
1339 }
1340 }
1341
1342 return false;
1343 }
1344
1345 public static void set_active_edit_point (EditPoint? e, Path path) {
1346 bool redraw;
1347 Glyph g = MainWindow.get_current_glyph ();
1348
1349 foreach (var p in g.path_list) {
1350 foreach (var ep in p.points) {
1351 ep.set_active (false);
1352 }
1353 }
1354
1355 redraw = active_edit_point != e;
1356 active_edit_point = e;
1357 active_path = path;
1358
1359 if (e != null) {
1360 ((!)e).set_active (true);
1361 }
1362
1363 if (redraw) {
1364 g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
1365 }
1366 }
1367
1368 PointSelection? get_closest_point (double ex, double ey, out Path? path) {
1369 double x = Glyph.path_coordinate_x (ex);
1370 double y = Glyph.path_coordinate_y (ey);
1371 double d = double.MAX;
1372 double nd;
1373 PointSelection? ep = null;
1374 Glyph g = MainWindow.get_current_glyph ();
1375
1376 path = null;
1377
1378 foreach (Path current_path in g.path_list) {
1379 if (is_close_to_path (current_path, ex, ey)) {
1380 foreach (EditPoint e in current_path.points) {
1381 nd = e.get_distance (x, y);
1382
1383 if (nd < d) {
1384 d = nd;
1385 ep = new PointSelection (e, current_path);
1386 path = current_path;
1387 }
1388 }
1389 }
1390 }
1391
1392 return ep;
1393 }
1394
1395 public double get_distance_to_closest_edit_point (double event_x, double event_y) {
1396 Path? p;
1397 PointSelection e;
1398 PointSelection? ep = get_closest_point (event_x, event_y, out p);
1399
1400 double x = Glyph.path_coordinate_x (event_x);
1401 double y = Glyph.path_coordinate_y (event_y);
1402
1403 if (ep == null) {
1404 return double.MAX;
1405 }
1406
1407 e = (!) ep;
1408
1409 return e.point.get_distance (x, y);
1410 }
1411
1412 public void control_point_event (double event_x, double event_y) {
1413 Path? p;
1414 PointSelection? ep = get_closest_point (event_x, event_y, out p);
1415 Glyph g = MainWindow.get_current_glyph ();
1416 double x = Glyph.path_coordinate_x (event_x);
1417 double y = Glyph.path_coordinate_y (event_y);
1418 double distance;
1419 PointSelection e;
1420 set_active_edit_point (null, new Path ());
1421
1422 if (ep == null) {
1423 return;
1424 }
1425
1426 e = (!) ep;
1427 distance = e.point.get_distance (x, y) * g.view_zoom;
1428
1429 if (distance < contact_surface) {
1430 set_active_edit_point (e.point, e.path);
1431 }
1432 }
1433
1434 public PointSelection new_point_action (int x, int y) {
1435 Glyph glyph;
1436 PointSelection new_point;
1437 glyph = MainWindow.get_current_glyph ();
1438 glyph.open_path ();
1439
1440 remove_all_selected_points ();
1441
1442 new_point = add_new_edit_point (x, y);
1443 new_point.point.set_selected (true);
1444
1445 selected_point = new_point.point;
1446 active_edit_point = new_point.point;
1447
1448 return_if_fail (glyph.active_paths.size > 0);
1449 add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1));
1450
1451 move_selected = true;
1452
1453 return new_point;
1454 }
1455
1456 public static PointSelection add_new_edit_point (int x, int y) {
1457 Glyph glyph;
1458 PointSelection new_point;
1459
1460 glyph = MainWindow.get_current_glyph ();
1461
1462 new_point = glyph.add_new_edit_point (x, y);
1463 new_point.path.update_region_boundaries ();
1464
1465 selected_point = new_point.point;
1466 active_edit_point = new_point.point;
1467
1468 set_point_type (selected_point);
1469 set_default_handle_positions ();
1470
1471 selected_points.clear ();
1472 add_selected_point (new_point.point, new_point.path);
1473
1474 return new_point;
1475 }
1476
1477 static void set_point_type (EditPoint p) {
1478 if (p.prev != null && p.get_prev ().right_handle.type == PointType.QUADRATIC) {
1479 p.left_handle.type = PointType.QUADRATIC;
1480 p.right_handle.type = PointType.LINE_QUADRATIC;
1481 p.type = PointType.QUADRATIC;
1482 } else if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) {
1483 p.left_handle.type = PointType.LINE_QUADRATIC;
1484 p.right_handle.type = PointType.LINE_QUADRATIC;
1485 p.type = PointType.LINE_QUADRATIC;
1486 } else if (DrawingTools.get_selected_point_type () == PointType.DOUBLE_CURVE) {
1487 p.left_handle.type = PointType.LINE_DOUBLE_CURVE;
1488 p.right_handle.type = PointType.LINE_DOUBLE_CURVE;
1489 p.type = PointType.DOUBLE_CURVE;
1490 } else {
1491 p.left_handle.type = PointType.LINE_CUBIC;
1492 p.right_handle.type = PointType.LINE_CUBIC;
1493 p.type = PointType.CUBIC;
1494 }
1495 }
1496
1497 public static void set_default_handle_positions () {
1498 Glyph g = MainWindow.get_current_glyph ();
1499 foreach (var p in g.path_list) {
1500 if (p.is_editable ()) {
1501 p.create_list ();
1502 set_default_handle_positions_on_path (p);
1503 }
1504 }
1505 }
1506
1507 static void set_default_handle_positions_on_path (Path path) {
1508 foreach (EditPoint e in path.points) {
1509 if (!e.tie_handles && !e.reflective_point) {
1510 e.recalculate_linear_handles ();
1511 }
1512 }
1513 }
1514
1515 private bool is_over_handle (double event_x, double event_y) {
1516 Glyph g = MainWindow.get_current_glyph ();
1517 double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y);
1518
1519 if (!Path.show_all_line_handles) {
1520 foreach (PointSelection selected_corner in selected_points) {
1521 if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) {
1522 return true;
1523 }
1524 }
1525 } else {
1526 foreach (Path p in g.path_list) {
1527 if (is_close_to_path (p, event_x, event_y)) {
1528 foreach (EditPoint ep in p.points) {
1529 if (is_close_to_handle (ep, event_x, event_y, distance_to_edit_point)) {
1530 return true;
1531 }
1532 }
1533 }
1534 }
1535 }
1536
1537 return false;
1538 }
1539
1540 bool is_close_to_path (Path p, double event_x, double event_y) {
1541 double c = contact_surface * Glyph.ivz ();
1542 double x = Glyph.path_coordinate_x (event_x);
1543 double y = Glyph.path_coordinate_y (event_y);
1544
1545 if (unlikely (!p.has_region_boundaries ())) {
1546 if (p.points.size > 0) {
1547 warning (@"No bounding box. $(p.points.size)");
1548 p.update_region_boundaries ();
1549 }
1550 }
1551
1552 return p.xmin - c <= x <= p.xmax + c && p.ymin - c <= y <= p.ymax + c;
1553 }
1554
1555 private bool is_close_to_handle (EditPoint selected_corner, double event_x, double event_y, double distance_to_edit_point) {
1556 double x = Glyph.path_coordinate_x (event_x);
1557 double y = Glyph.path_coordinate_y (event_y);
1558 Glyph g = MainWindow.get_current_glyph ();
1559 double d_point = distance_to_edit_point;
1560 double dl, dr;
1561
1562 dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y);
1563 dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y);
1564
1565 if (dl < contact_surface && dl < d_point) {
1566 return true;
1567 }
1568
1569 if (dr < contact_surface && dr < d_point) {
1570 return true;
1571 }
1572
1573 return false;
1574 }
1575
1576 PointSelection get_closest_handle (double event_x, double event_y) {
1577 EditPointHandle left, right;
1578 double x = Glyph.path_coordinate_x (event_x);
1579 double y = Glyph.path_coordinate_y (event_y);
1580 EditPointHandle eh = new EditPointHandle.empty();
1581 Glyph g = MainWindow.get_current_glyph ();
1582 double d = double.MAX;
1583 double dn;
1584 Path path = new Path ();
1585 bool left_handle = false;
1586 EditPoint parent_point;
1587 EditPoint tied_point;
1588
1589 foreach (Path p in g.path_list) {
1590 if (is_close_to_path (p, event_x, event_y) || p == active_path) {
1591 foreach (EditPoint ep in p.points) {
1592 if (ep.is_selected () || Path.show_all_line_handles) {
1593 left = ep.get_left_handle ();
1594 right = ep.get_right_handle ();
1595
1596 dn = left.get_point ().get_distance (x, y);
1597
1598 if (dn < d) {
1599 eh = left;
1600 d = dn;
1601 path = p;
1602 left_handle = true;
1603 }
1604
1605 dn = right.get_point ().get_distance (x, y);
1606
1607 if (dn < d) {
1608 eh = right;
1609 d = dn;
1610 path = p;
1611 left_handle = false;
1612 }
1613 }
1614 }
1615 }
1616 }
1617
1618 // Make sure the selected handle belongs to the selected point if
1619 // the current segment is quadratic.
1620 if (eh.type == PointType.QUADRATIC) {
1621 parent_point = eh.get_parent ();
1622
1623 if (left_handle) {
1624 if (parent_point.prev != null) {
1625 tied_point = parent_point.get_prev ();
1626 if (tied_point.selected_point) {
1627 eh = tied_point.get_right_handle ();
1628 }
1629 }
1630 } else {
1631 if (parent_point.next != null) {
1632 tied_point = parent_point.get_next ();
1633 if (tied_point.selected_point) {
1634 eh = tied_point.get_left_handle ();
1635 }
1636 }
1637 }
1638 }
1639
1640 return new PointSelection.handle_selection (eh, path);
1641 }
1642
1643 private void curve_active_corner_event (double event_x, double event_y) {
1644 PointSelection eh;
1645
1646 active_handle.active = false;
1647
1648 if (!is_over_handle (event_x, event_y)) {
1649 return;
1650 }
1651
1652 eh = get_closest_handle (event_x, event_y);
1653 eh.handle.active = true;
1654 active_handle = eh.handle;
1655 active_path = eh.path;
1656 }
1657
1658 private void curve_corner_event (double event_x, double event_y) {
1659 MainWindow.get_current_glyph ().open_path ();
1660 PointSelection p;
1661
1662 if (!is_over_handle (event_x, event_y)) {
1663 return;
1664 }
1665
1666 move_selected_handle = true;
1667 last_selected_is_handle = true;
1668 selected_handle.selected = false;
1669 p = get_closest_handle (event_x, event_y);
1670 selected_handle = p.handle;
1671 handle_selection = p;
1672 active_path = p.path;
1673 selected_handle.selected = true;
1674 }
1675
1676 public static void add_selected_point (EditPoint p, Path path) {
1677 foreach (PointSelection ep in selected_points) {
1678 if (p == ep.point) {
1679 return;
1680 }
1681 }
1682
1683 selected_points.add (new PointSelection (p, path));
1684 }
1685
1686 public static void remove_all_selected_points () {
1687 Glyph g = MainWindow.get_current_glyph ();
1688
1689 foreach (PointSelection ep in selected_points) {
1690 ep.point.set_active (false);
1691 ep.point.set_selected (false);
1692 }
1693
1694 selected_points.clear ();
1695
1696 foreach (Path p in g.path_list) {
1697 foreach (EditPoint e in p.points) {
1698 e.set_active (false);
1699 e.set_selected (false);
1700 }
1701 }
1702 }
1703
1704 static void move_select_next_point (uint keyval) {
1705 PointSelection next = new PointSelection.empty ();
1706 Glyph g = MainWindow.get_current_glyph();
1707
1708 if (selected_points.size == 0) {
1709 return;
1710 }
1711
1712 switch (keyval) {
1713 case Key.UP:
1714 next = get_next_point_up ();
1715 break;
1716 case Key.DOWN:
1717 next = get_next_point_down ();
1718 break;
1719 case Key.LEFT:
1720 next = get_next_point_left ();
1721 break;
1722 case Key.RIGHT:
1723 next = get_next_point_right ();
1724 break;
1725 default:
1726 break;
1727 }
1728
1729 set_selected_point (next.point, next.path);
1730 g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
1731 }
1732
1733 private static PointSelection get_next_point (double angle)
1734 requires (selected_points.size != 0) {
1735 PointSelection e = selected_points.get (selected_points.size - 1);
1736 double right_angle = e.point.right_handle.angle;
1737 double left_angle = e.point.left_handle.angle;
1738 double min_right, min_left;
1739 double min;
1740
1741 return_val_if_fail (e.point.next != null, new EditPoint ());
1742 return_val_if_fail (e.point.prev != null, new EditPoint ());
1743
1744 // angle might be greater than 2 PI or less than 0
1745 min_right = double.MAX;
1746 min_left = double.MAX;
1747 for (double i = -2 * PI; i <= 2 * PI; i += 2 * PI) {
1748 min = fabs (right_angle - (angle + i));
1749 if (min < min_right) {
1750 min_right = min;
1751 }
1752
1753 min = fabs (left_angle - (angle + i));
1754 if (min < min_left) {
1755 min_left = min;
1756 }
1757 }
1758
1759 if (min_right < min_left) {
1760 return new PointSelection (e.point.get_next (), e.path);
1761 }
1762
1763 return new PointSelection (e.point.get_prev (), e.path);
1764 }
1765
1766 private static PointSelection get_next_point_up () {
1767 return get_next_point (PI / 2);
1768 }
1769
1770 private static PointSelection get_next_point_down () {
1771 return get_next_point (PI + PI / 2);
1772 }
1773
1774 private static PointSelection get_next_point_left () {
1775 return get_next_point (PI);
1776 }
1777
1778 private static PointSelection get_next_point_right () {
1779 return get_next_point (0);
1780 }
1781
1782 private static void set_selected_point (EditPoint ep, Path p) {
1783 remove_all_selected_points ();
1784 add_selected_point (ep, p);
1785 set_active_edit_point (ep, p);
1786 edit_active_corner = true;
1787 ep.set_selected (true);
1788 set_default_handle_positions ();
1789 }
1790
1791 public static void select_point_up () {
1792 move_select_next_point (Key.UP);
1793 }
1794
1795 public static void select_point_down () {
1796 move_select_next_point (Key.DOWN);
1797 }
1798
1799 public static void select_point_right () {
1800 move_select_next_point (Key.RIGHT);
1801 }
1802
1803 public static void select_point_left () {
1804 move_select_next_point (Key.LEFT);
1805 }
1806
1807 /**
1808 * Move the selected editpoint one pixel with keyboard irrespectivly of
1809 * current zoom.
1810 */
1811 void move_selected_points (uint keyval) {
1812 Glyph g = MainWindow.get_current_glyph();
1813 Path? last_path = null;
1814
1815 if (!last_selected_is_handle) {
1816 if (keyval == Key.UP) {
1817 foreach (PointSelection e in selected_points) {
1818 e.point.set_position (e.point.x, e.point.y + Glyph.ivz ());
1819 e.point.recalculate_linear_handles ();
1820 }
1821 }
1822
1823 if (keyval == Key.DOWN) {
1824 foreach (PointSelection e in selected_points) {
1825 e.point.set_position (e.point.x, e.point.y - Glyph.ivz ());
1826 e.point.recalculate_linear_handles ();
1827 }
1828 }
1829
1830 if (keyval == Key.LEFT) {
1831 foreach (PointSelection e in selected_points) {
1832 e.point.set_position (e.point.x - Glyph.ivz (), e.point.y);
1833 e.point.recalculate_linear_handles ();
1834 }
1835 }
1836
1837 if (keyval == Key.RIGHT) {
1838 foreach (PointSelection e in selected_points) {
1839 e.point.set_position (e.point.x + Glyph.ivz (), e.point.y);
1840 e.point.recalculate_linear_handles ();
1841 }
1842 }
1843
1844 last_path = null;
1845 foreach (PointSelection e in selected_points) {
1846 if (e.path != last_path) {
1847 e.path.update_region_boundaries ();
1848 last_path = e.path;
1849 }
1850 }
1851
1852 } else {
1853 set_type_for_moving_handle ();
1854 active_handle.active = false;
1855 active_handle = new EditPointHandle.empty ();
1856
1857 if (keyval == Key.UP) {
1858 selected_handle.move_delta_coordinate (0, 1 * Glyph.ivz ());
1859 }
1860
1861 if (keyval == Key.DOWN) {
1862 selected_handle.move_delta_coordinate (0, -1 * Glyph.ivz ());
1863 }
1864
1865 if (keyval == Key.LEFT) {
1866 selected_handle.move_delta_coordinate (-1 * Glyph.ivz (), 0);
1867 }
1868
1869 if (keyval == Key.RIGHT) {
1870 selected_handle.move_delta_coordinate (1 * Glyph.ivz (), 0);
1871 }
1872 }
1873
1874 // TODO: redraw only the relevant parts
1875 g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
1876 }
1877
1878 public static void convert_point_to_line (EditPoint ep, bool both) {
1879 ep.set_tie_handle (false);
1880 ep.set_reflective_handles (false);
1881
1882 if (ep.next == null) {
1883 // FIXME: write a new function for this case
1884 // warning ("Next is null.");
1885 }
1886
1887 if (ep.prev == null) {
1888 warning ("Prev is null.");
1889 }
1890
1891 if (ep.type == PointType.CUBIC || ep.type == PointType.LINE_CUBIC) {
1892 ep.type = PointType.LINE_CUBIC;
1893
1894 if (both) {
1895 ep.get_left_handle ().type = PointType.LINE_CUBIC;
1896 ep.get_right_handle ().type = PointType.LINE_CUBIC;
1897 }
1898
1899 if (ep.next != null && ep.get_next ().is_selected ()) {
1900 ep.get_right_handle ().type = PointType.LINE_CUBIC;
1901 }
1902
1903 if (ep.prev != null && ep.get_prev ().is_selected ()) {
1904 ep.get_left_handle ().type = PointType.LINE_CUBIC;
1905 }
1906
1907 }
1908
1909 if (ep.type == PointType.DOUBLE_CURVE| ep.type == PointType.LINE_DOUBLE_CURVE) {
1910 ep.type = PointType.LINE_DOUBLE_CURVE;
1911 if (both) {
1912 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
1913 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
1914 }
1915
1916 if (ep.next != null && ep.get_next ().is_selected ()) {
1917 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
1918 }
1919
1920 if (ep.prev != null && ep.get_prev ().is_selected ()) {
1921 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
1922 }
1923 }
1924
1925 if (ep.type == PointType.QUADRATIC || ep.type == PointType.LINE_QUADRATIC) {
1926 ep.type = PointType.LINE_QUADRATIC;
1927
1928 if (both) {
1929 ep.get_left_handle ().type = PointType.LINE_QUADRATIC;
1930 ep.get_right_handle ().type = PointType.LINE_QUADRATIC;
1931
1932 if (ep.next != null) {
1933 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC;
1934 }
1935
1936 if (ep.prev != null) {
1937 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC;
1938 }
1939 }
1940
1941 if (ep.next != null && ep.get_next ().is_selected ()) {
1942 ep.get_right_handle ().type = PointType.LINE_QUADRATIC;
1943 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC;
1944 }
1945
1946 if (ep.prev != null && ep.get_prev ().is_selected ()) {
1947 ep.get_left_handle ().type = PointType.LINE_QUADRATIC;
1948 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC;
1949 }
1950
1951 }
1952
1953 ep.recalculate_linear_handles ();
1954 }
1955
1956 public static void convert_segment_to_line () {
1957 if (selected_points.size == 0) {
1958 return;
1959 }
1960
1961 if (selected_points.size == 1) {
1962 convert_point_to_line (selected_points.get (0).point, true);
1963 } else {
1964 foreach (PointSelection p in selected_points) {
1965 convert_point_to_line (p.point, false);
1966 }
1967 }
1968 }
1969
1970 public static bool is_line (PointType t) {
1971 return t == PointType.LINE_QUADRATIC
1972 || t == PointType.LINE_DOUBLE_CURVE
1973 || t == PointType.LINE_CUBIC;
1974 }
1975
1976 public static PointType to_line (PointType t) {
1977 switch (t) {
1978 case PointType.QUADRATIC:
1979 return PointType.LINE_QUADRATIC;
1980 case PointType.DOUBLE_CURVE:
1981 return PointType.LINE_DOUBLE_CURVE;
1982 case PointType.CUBIC:
1983 return PointType.LINE_CUBIC;
1984 default:
1985 break;
1986 }
1987 return t;
1988 }
1989
1990 public static PointType to_curve (PointType t) {
1991 switch (t) {
1992 case PointType.LINE_QUADRATIC:
1993 return PointType.QUADRATIC;
1994 case PointType.LINE_DOUBLE_CURVE:
1995 return PointType.DOUBLE_CURVE;
1996 case PointType.LINE_CUBIC:
1997 return PointType.CUBIC;
1998 default:
1999 break;
2000 }
2001
2002 if (unlikely (t == PointType.NONE)) {
2003 warning ("Type is not set.");
2004 }
2005
2006 return t;
2007 }
2008
2009 public static void set_converted_handle_length (EditPointHandle e, PointType pt) {
2010
2011 if (e.type == PointType.QUADRATIC && pt == PointType.DOUBLE_CURVE) {
2012 e.length *= 2;
2013 e.length /= 4;
2014 }
2015
2016 if (e.type == PointType.QUADRATIC && pt == PointType.CUBIC) {
2017 e.length *= 2;
2018 e.length /= 3;
2019 }
2020
2021 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.QUADRATIC) {
2022 e.length *= 4;
2023 e.length /= 2;
2024 }
2025
2026 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.CUBIC) {
2027 e.length *= 4;
2028 e.length /= 3;
2029 }
2030
2031 if (e.type == PointType.CUBIC && pt == PointType.QUADRATIC) {
2032 e.length *= 3;
2033 e.length /= 2;
2034 }
2035
2036 if (e.type == PointType.CUBIC && pt == PointType.DOUBLE_CURVE) {
2037 e.length *= 3;
2038 e.length /= 4;
2039 }
2040 }
2041
2042 public static void convert_point_segment_type (EditPoint first, EditPoint next, PointType point_type) {
2043 bool line;
2044
2045 set_converted_handle_length (first.get_right_handle (), point_type);
2046 set_converted_handle_length (next.get_left_handle (), point_type);
2047
2048 line = is_line (first.type)
2049 && is_line (first.get_right_handle ().type)
2050 && is_line (next.get_left_handle ().type);
2051
2052 if (!line) {
2053 first.type = point_type;
2054 } else {
2055 first.type = to_line (point_type);
2056 }
2057
2058 if (!line) {
2059 first.get_right_handle ().type = point_type;
2060 } else {
2061 first.get_right_handle ().type = to_line (point_type);
2062 }
2063
2064 if (!line) {
2065 next.get_left_handle ().type = point_type;
2066 } else {
2067 next.get_left_handle ().type = to_line (point_type);
2068 }
2069
2070 // process connected handle
2071 if (point_type == PointType.QUADRATIC) {
2072 first.set_position (first.x, first.y);
2073 first.recalculate_linear_handles ();
2074 }
2075 }
2076
2077 public static void convert_point_type (EditPoint first, PointType point_type) {
2078 convert_point_segment_type (first, first.get_next (), point_type);
2079 }
2080
2081 public static void convert_point_types () {
2082 Glyph glyph = MainWindow.get_current_glyph ();
2083 glyph.store_undo_state ();
2084 PointSelection selected = new PointSelection.empty ();
2085 bool reset_selected = false;
2086 EditPoint e;
2087
2088 if (selected_points.size == 1) {
2089 selected = selected_points.get (0);
2090 if (selected.point.next != null) {
2091 selected_points.add (new PointSelection (selected.point.get_next (), selected.path));
2092 selected.point.get_next ().set_selected (true);
2093 }
2094
2095 if (selected.point.prev != null) {
2096 selected_points.add (new PointSelection (selected.point.get_prev (), selected.path));
2097 selected.point.get_next ().set_selected (true);
2098 }
2099
2100 reset_selected = true;
2101 }
2102
2103 foreach (PointSelection ps in selected_points) {
2104 e = ps.point;
2105 // convert segments not control points
2106 if (e.next == null || !e.get_next ().is_selected ()) {
2107 continue;
2108 }
2109
2110 convert_point_type (e, DrawingTools.point_type);
2111 }
2112
2113 if (reset_selected) {
2114 remove_all_selected_points ();
2115 selected_points.add (selected);
2116 selected.point.set_selected (true);
2117 }
2118 }
2119
2120 public static void update_selected_points () {
2121 Glyph g = MainWindow.get_current_glyph ();
2122 selected_points.clear ();
2123
2124 foreach (Path p in g.path_list) {
2125 foreach (EditPoint ep in p.points) {
2126 if (ep.is_selected ()) {
2127 selected_points.add (new PointSelection (ep, p));
2128 }
2129 }
2130 }
2131 }
2132
2133 public void select_all_points () {
2134 Glyph g = MainWindow.get_current_glyph ();
2135
2136 foreach (Path p in g.path_list) {
2137 foreach (EditPoint ep in p.points) {
2138 ep.set_selected (true);
2139 add_selected_point (ep, p);
2140 }
2141 }
2142 }
2143
2144 public static Path simplify (Path path, bool selected_segments = false, double threshold = 0.3) {
2145 PointSelection ps;
2146 EditPoint ep;
2147 Path p1, new_path;
2148 double d, sumd;
2149 int i;
2150
2151 p1 = path.copy ();
2152 new_path = p1.copy ();
2153 i = 0;
2154 sumd = 0;
2155 while (i < new_path.points.size) {
2156 ep = new_path.points.get (i);
2157 ps = new PointSelection (ep, new_path);
2158 d = PenTool.remove_point_simplify (ps);
2159 sumd += d;
2160
2161 if (sumd < threshold) {
2162 p1 = new_path.copy ();
2163 } else {
2164 new_path = p1.copy ();
2165 sumd = 0;
2166 i++;
2167 }
2168 }
2169
2170 new_path.update_region_boundaries ();
2171
2172 return new_path;
2173 }
2174
2175 public void set_simplification_threshold (double t) {
2176 simplification_threshold = t;
2177 }
2178 }
2179
2180 }
2181