The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

MoveTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/MoveTool.vala.
Use other types of graphical objects
1 /* 2 Copyright (C) 2012 2013 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 public class MoveTool : Tool { 21 22 static bool move_path = false; 23 static bool moved = false; 24 static double last_x = 0; 25 static double last_y = 0; 26 27 static double selection_x = 0; 28 static double selection_y = 0; 29 static bool group_selection= false; 30 31 public static static double selection_box_width = 0; 32 public static static double selection_box_height = 0; 33 public static static double selection_box_center_x = 0; 34 public static static double selection_box_center_y = 0; 35 36 public signal void selection_changed (); 37 public signal void objects_moved (); 38 public signal void objects_deselected (); 39 40 public MoveTool (string n) { 41 base (n, t_("Move paths")); 42 43 selection_changed.connect (() => { 44 update_selection_boundaries (); 45 redraw(); 46 }); 47 48 objects_deselected.connect (() => { 49 update_selection_boundaries (); 50 redraw(); 51 }); 52 53 select_action.connect((self) => { 54 MainWindow.get_current_glyph ().close_path (); 55 }); 56 57 deselect_action.connect((self) => { 58 }); 59 60 press_action.connect((self, b, x, y) => { 61 press (b, x, y); 62 }); 63 64 release_action.connect((self, b, x, y) => { 65 release (b, x, y); 66 }); 67 68 move_action.connect ((self, x, y) => { 69 move (x, y); 70 }); 71 72 key_press_action.connect ((self, keyval) => { 73 key_down (keyval); 74 }); 75 76 draw_action.connect ((self, cr, glyph) => { 77 draw_actions (cr); 78 }); 79 } 80 81 public static void draw_actions (Context cr) { 82 if (group_selection) { 83 draw_selection_box (cr); 84 } 85 } 86 87 public void key_down (uint32 keyval) { 88 Glyph g = MainWindow.get_current_glyph (); 89 90 // delete selected paths 91 if (keyval == Key.DEL || keyval == Key.BACK_SPACE) { 92 93 if (g.active_paths.size > 0) { 94 g.store_undo_state (); 95 } 96 97 foreach (Object p in g.active_paths) { 98 g.layers.remove (p); 99 g.update_view (); 100 } 101 102 g.active_paths.clear (); 103 } 104 105 if (is_arrow_key (keyval)) { 106 move_selected_paths (keyval); 107 } 108 } 109 110 public void move (int x, int y) { 111 Glyph glyph = MainWindow.get_current_glyph (); 112 double dx = last_x - x; 113 double dy = last_y - y; 114 double p = PenTool.precision; 115 double delta_x, delta_y; 116 117 if (!move_path) { 118 return; 119 } 120 121 if (move_path && (fabs(dx) > 0 || fabs (dy) > 0)) { 122 moved = true; 123 124 delta_x = Glyph.ivz () * -dx * p; 125 delta_y = Glyph.ivz () * dy * p; 126 127 if (glyph.color_svg_data != null) { 128 glyph.svg_x += delta_x; 129 glyph.svg_y += delta_y; 130 } else { 131 foreach (Layer group in glyph.selected_groups) { 132 if (group.gradient != null) { 133 Gradient g = (!) group.gradient; 134 g.x1 += delta_x; 135 g.x2 += delta_x; 136 g.y1 += delta_y; 137 g.y2 += delta_y; 138 } 139 } 140 141 foreach (Object path in glyph.active_paths) { 142 path.move (delta_x, delta_y); 143 } 144 } 145 } 146 147 last_x = x; 148 last_y = y; 149 150 update_selection_boundaries (); 151 152 if (glyph.active_paths.size > 0) { 153 objects_moved (); 154 } 155 156 BirdFont.get_current_font ().touch (); 157 158 GlyphCanvas.redraw (); 159 PenTool.reset_stroke (); 160 } 161 162 public void release (int b, int x, int y) { 163 Glyph glyph = MainWindow.get_current_glyph (); 164 165 move_path = false; 166 167 if (GridTool.is_visible () && moved) { 168 tie_paths_to_grid (glyph); 169 } else if (GridTool.has_ttf_grid ()) { 170 foreach (Object p in glyph.active_paths) { 171 tie_path_to_ttf_grid (p); 172 } 173 } 174 175 if (group_selection) { 176 select_group (); 177 } 178 179 group_selection = false; 180 moved = false; 181 182 if (glyph.active_paths.size > 0) { 183 selection_changed (); 184 objects_moved (); 185 DrawingTools.resize_tool.signal_objects_rotated (); 186 187 foreach (Object o in glyph.active_paths) { 188 if (o is FastPath) { 189 FastPath path = (FastPath) o; 190 path.get_path ().create_full_stroke (); 191 } 192 } 193 } else { 194 objects_deselected (); 195 } 196 } 197 198 public void press (int b, int x, int y) { 199 Glyph glyph = MainWindow.get_current_glyph (); 200 Object object; 201 bool selected = false; 202 Layer? group; 203 Layer g; 204 205 glyph.store_undo_state (); 206 group_selection = false; 207 208 if (glyph.color_svg_data == null) { 209 group = glyph.get_path_at (x, y); 210 211 if (group != null) { 212 g = (!) group; 213 return_if_fail (g.objects.objects.size > 0); 214 object = g.objects.objects.get (0); 215 selected = glyph.active_paths.contains (object); 216 217 if (!selected && !KeyBindings.has_shift ()) { 218 glyph.clear_active_paths (); 219 } 220 221 foreach (Object lp in g.objects) { 222 if (selected && KeyBindings.has_shift ()) { 223 glyph.selected_groups.remove ((!) group); 224 glyph.active_paths.remove (lp); 225 } else { 226 glyph.add_active_object ((!) group, lp); 227 } 228 } 229 } else if (!KeyBindings.has_shift ()) { 230 glyph.clear_active_paths (); 231 } 232 233 update_selection_boundaries (); 234 } 235 236 move_path = true; 237 238 last_x = x; 239 last_y = y; 240 241 if (glyph.active_paths.size == 0) { 242 group_selection = true; 243 selection_x = x; 244 selection_y = y; 245 } 246 247 update_boundaries_for_selection (); 248 selection_changed (); 249 GlyphCanvas.redraw (); 250 } 251 252 253 void select_group () { 254 double x1 = Glyph.path_coordinate_x (Math.fmin (selection_x, last_x)); 255 double y1 = Glyph.path_coordinate_y (Math.fmin (selection_y, last_y)); 256 double x2 = Glyph.path_coordinate_x (Math.fmax (selection_x, last_x)); 257 double y2 = Glyph.path_coordinate_y (Math.fmax (selection_y, last_y)); 258 Glyph glyph = MainWindow.get_current_glyph (); 259 260 glyph.clear_active_paths (); 261 262 foreach (Object p in glyph.get_objects_in_current_layer ()) { 263 if (p.xmin > x1 && p.xmax < x2 && p.ymin < y1 && p.ymax > y2) { 264 if (!p.is_empty ()) { 265 glyph.add_active_object (null, p); 266 } 267 } 268 } 269 270 selection_changed (); 271 } 272 273 public static void update_selection_boundaries () { 274 get_selection_box_boundaries (out selection_box_center_x, 275 out selection_box_center_y, out selection_box_width, 276 out selection_box_height); 277 } 278 279 public void move_to_baseline () { 280 Glyph glyph = MainWindow.get_current_glyph (); 281 Font font = BirdFont.get_current_font (); 282 double x, y, w, h; 283 284 get_selection_box_boundaries (out x, out y, out w, out h); 285 286 foreach (Object path in glyph.active_paths) { 287 path.move (glyph.left_limit - x + w / 2, font.base_line - y + h / 2); 288 } 289 290 update_selection_boundaries (); 291 objects_moved (); 292 GlyphCanvas.redraw (); 293 } 294 295 static void draw_selection_box (Context cr) { 296 double x = Math.fmin (selection_x, last_x); 297 double y = Math.fmin (selection_y, last_y); 298 299 double w = Math.fabs (selection_x - last_x); 300 double h = Math.fabs (selection_y - last_y); 301 302 Glyph glyph = MainWindow.get_current_glyph (); 303 304 if (glyph.color_svg_data == null) { 305 cr.save (); 306 Theme.color (cr, "Foreground 1"); 307 cr.set_line_width (2); 308 cr.rectangle (x, y, w, h); 309 cr.stroke (); 310 cr.restore (); 311 } 312 } 313 314 public static void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { 315 double px, py, px2, py2; 316 Glyph glyph = MainWindow.get_current_glyph (); 317 318 px = 10000; 319 py = 10000; 320 px2 = -10000; 321 py2 = -10000; 322 323 foreach (Object o in glyph.active_paths) { 324 if (o is FastPath) { 325 Path p = ((FastPath) o).get_path (); 326 p.update_region_boundaries (); 327 328 if (px > p.xmin) { 329 px = p.xmin; 330 } 331 332 if (py > p.ymin) { 333 py = p.ymin; 334 } 335 336 if (px2 < p.xmax) { 337 px2 = p.xmax; 338 } 339 340 if (py2 < p.ymax) { 341 py2 = p.ymax; 342 } 343 } 344 } 345 346 w = px2 - px; 347 h = py2 - py; 348 x = px + (w / 2); 349 y = py + (h / 2); 350 } 351 352 void move_selected_paths (uint key) { 353 Glyph glyph = MainWindow.get_current_glyph (); 354 double x, y; 355 356 x = 0; 357 y = 0; 358 359 switch (key) { 360 case Key.UP: 361 y = 1; 362 break; 363 case Key.DOWN: 364 y = -1; 365 break; 366 case Key.LEFT: 367 x = -1; 368 break; 369 case Key.RIGHT: 370 x = 1; 371 break; 372 default: 373 break; 374 } 375 376 foreach (Object path in glyph.active_paths) { 377 path.move (x * Glyph.ivz (), y * Glyph.ivz ()); 378 } 379 380 BirdFont.get_current_font ().touch (); 381 PenTool.reset_stroke (); 382 update_selection_boundaries (); 383 objects_moved (); 384 glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height); 385 } 386 387 static void tie_path_to_ttf_grid (Object p) { 388 double sx, sy, qx, qy; 389 390 sx = p.xmax; 391 sy = p.ymax; 392 qx = p.xmin; 393 qy = p.ymin; 394 395 GridTool.ttf_grid_coordinate (ref sx, ref sy); 396 GridTool.ttf_grid_coordinate (ref qx, ref qy); 397 398 if (Math.fabs (qy - p.ymin) < Math.fabs (sy - p.ymax)) { 399 p.move (0, qy - p.ymin); 400 } else { 401 p.move (0, sy - p.ymax); 402 } 403 404 if (Math.fabs (qx - p.xmin) < Math.fabs (sx - p.xmax)) { 405 p.move (qx - p.xmin, 0); 406 } else { 407 p.move (sx - p.xmax, 0); 408 } 409 } 410 411 static void tie_paths_to_grid (Glyph g) { 412 double sx, sy, qx, qy; 413 double dx_min, dx_max, dy_min, dy_max;; 414 double maxx, maxy, minx, miny; 415 416 update_selection_boundaries (); 417 418 // tie to grid 419 maxx = selection_box_center_x + selection_box_width / 2; 420 maxy = selection_box_center_y + selection_box_height / 2; 421 minx = selection_box_center_x - selection_box_width / 2; 422 miny = selection_box_center_y - selection_box_height / 2; 423 424 sx = maxx; 425 sy = maxy; 426 qx = minx; 427 qy = miny; 428 429 GridTool.tie_coordinate (ref sx, ref sy); 430 GridTool.tie_coordinate (ref qx, ref qy); 431 432 dy_min = Math.fabs (qy - miny); 433 dy_max = Math.fabs (sy - maxy); 434 dx_min = Math.fabs (qx - minx); 435 dx_max = Math.fabs (sx - maxx); 436 437 foreach (Object p in g.active_paths) { 438 if (dy_min < dy_max) { 439 p.move (0, qy - miny); 440 } else { 441 p.move (0, sy - maxy); 442 } 443 444 if (dx_min < dx_max) { 445 p.move (qx - minx, 0); 446 } else { 447 p.move (sx - maxx, 0); 448 } 449 } 450 451 update_selection_boundaries (); 452 } 453 454 public static void update_boundaries_for_selection () { 455 Glyph glyph = MainWindow.get_current_glyph (); 456 foreach (Object o in glyph.active_paths) { 457 if (o is FastPath) { 458 ((FastPath)o).get_path ().update_region_boundaries (); 459 } 460 } 461 } 462 463 public static void flip_vertical () { 464 flip (true); 465 } 466 467 public static void flip_horizontal () { 468 flip (false); 469 } 470 471 public static void flip (bool vertical) { 472 double xc, yc, xc2, yc2, w, h; 473 double dx, dy; 474 Glyph glyph = MainWindow.get_current_glyph (); 475 476 update_selection_boundaries (); 477 478 xc = selection_box_center_x; 479 yc = selection_box_center_y; 480 481 foreach (Object p in glyph.active_paths) { 482 if (p is FastPath) { 483 Path path = ((FastPath) p).get_path (); 484 485 // FIXME: move to object 486 if (vertical) { 487 path.flip_vertical (); 488 } else { 489 path.flip_horizontal (); 490 } 491 492 path.reverse (); 493 } 494 } 495 496 get_selection_box_boundaries (out xc2, out yc2, out w, out h); 497 498 dx = -(xc2 - xc); 499 dy = -(yc2 - yc); 500 501 foreach (Object p in glyph.active_paths) { 502 p.move (dx, dy); 503 } 504 505 update_selection_boundaries (); 506 PenTool.reset_stroke (); 507 508 BirdFont.get_current_font ().touch (); 509 } 510 511 public void select_all_paths () { 512 Glyph g = MainWindow.get_current_glyph (); 513 514 g.clear_active_paths (); 515 foreach (Object p in g.get_objects_in_current_layer ()) { 516 if (!p.is_empty ()) { 517 g.add_active_object (null, p); 518 } 519 } 520 521 g.update_view (); 522 523 update_selection_boundaries (); 524 objects_moved (); 525 } 526 } 527 528 } 529