The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

VersionList.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/VersionList.vala.
Fix merging of 2x2 paths
1 /* 2 Copyright (C) 2012, 2014 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 Cairo; 16 using Math; 17 18 public enum MenuDirection { 19 DROP_DOWN, 20 POP_UP; 21 } 22 23 namespace BirdFont { 24 25 public class VersionList : GLib.Object { 26 public int current_version_id = -1; 27 GlyphCollection glyph_collection; 28 29 public Gee.ArrayList<Glyph> glyphs; 30 31 public delegate void Selected (MenuAction self); 32 public signal void selected (VersionList self); 33 34 double x = -1; 35 double y = -1; 36 double width = 0; 37 38 double menu_x = -1; 39 public bool menu_visible = false; 40 Gee.ArrayList <MenuAction> actions = new Gee.ArrayList <MenuAction> (); 41 const int item_height = 25; 42 MenuDirection direction = MenuDirection.DROP_DOWN; 43 44 // Glyphs gets added to and removed from the glyph collection in 45 // these signal. 46 public signal void signal_delete_item (int item_index); 47 public signal void add_glyph_item (Glyph item); 48 49 public VersionList (GlyphCollection gc) { 50 MenuAction ma = add_item (t_("New version")); 51 ma.has_delete_button = false; 52 ma.action.connect ((self) => { 53 return_if_fail (glyphs.size > 0); 54 55 BirdFont.get_current_font ().touch (); 56 57 add_new_version (); 58 current_version_id = glyphs.get (glyphs.size - 1).version_id; 59 }); 60 61 // delete one version 62 signal_delete_item.connect ((index) => { 63 delete_item (index); 64 }); 65 66 this.glyph_collection = gc; 67 glyphs = new Gee.ArrayList<Glyph> (); 68 set_direction (MenuDirection.POP_UP); 69 70 glyphs = new Gee.ArrayList<Glyph> (); 71 72 foreach (Glyph g in gc.glyphs) { 73 add_glyph (g, false); 74 } 75 76 if (gc.length () > 0) { 77 set_selected_version (gc.get_current ().version_id, false); 78 } 79 } 80 81 private void delete_item (int index) { 82 int current_version; 83 Font font = BirdFont.get_current_font (); 84 OverView over_view = MainWindow.get_overview (); 85 86 font.touch (); 87 88 index--; // first item adds new glyphs to the list 89 90 // delete the entire glyph if the last remaining version is removed 91 if (glyphs.size == 1) { 92 over_view.store_undo_state (glyph_collection.copy ()); 93 font.delete_glyph (glyph_collection); 94 return; 95 } 96 97 return_if_fail (0 <= index < glyphs.size); 98 99 font.deleted_glyphs.add (glyph_collection.get_current ()); 100 over_view.store_undo_state (glyph_collection.copy ()); 101 102 current_version = get_current_version_index (); 103 104 glyphs.remove_at (index); 105 glyph_collection.remove (index); 106 107 recreate_index (); 108 109 if (index == current_version) { 110 set_selected_item (get_action_no2 ()); // select the first glyph if the current glyph is deleted 111 } else if (index < current_version) { 112 return_if_fail (0 <= current_version - 1 < glyphs.size); 113 current_version_id = glyphs.get (current_version - 1).version_id; 114 int i = get_current_version_index (); 115 set_selected_item (get_action_index (i)); 116 } 117 } 118 119 private int get_current_version_index () { 120 int i = 0; 121 foreach (Glyph g in glyphs) { 122 if (g.version_id == current_version_id) { 123 return i; 124 } 125 i++; 126 } 127 128 warning ("No index for menu item."); 129 return 0; 130 } 131 132 public void set_selected_version (int version_id, bool update_loaded_glyph) { 133 current_version_id = version_id; 134 update_selection (update_loaded_glyph); 135 } 136 137 public Glyph get_current () { 138 Glyph? gl = null; 139 140 foreach (Glyph g in glyphs) { 141 if (g.version_id == current_version_id) { 142 return g; 143 } 144 } 145 146 if (unlikely (glyphs.size > 0)) { 147 warning (@"Can not find current glyph for id $current_version_id"); 148 gl = glyphs.get (glyphs.size - 1); 149 set_selected_version (((!) gl).version_id, false); 150 return (!) gl; 151 } 152 153 if (unlikely (glyphs.size == 0 && current_version_id == -1)) { 154 warning (@"No glyphs added to collection"); 155 gl = new Glyph.no_lines ("", '\0'); 156 } 157 158 return (!) gl; 159 } 160 161 public void add_new_version () { 162 Glyph g = get_current (); 163 Glyph new_version = g.copy (); 164 new_version.version_id = get_last_id () + 1; 165 166 // send signal back to the collection 167 add_glyph_item (new_version); 168 169 // add the item to the menu 170 add_glyph (new_version); 171 } 172 173 public int get_last_id () { 174 return_val_if_fail (glyphs.size > 0, 1); 175 return glyphs.get (glyphs.size - 1).version_id; 176 } 177 178 private void set_selected_item (MenuAction ma, bool update_loaded_glyph = true) { 179 int i = ma.index; 180 Glyph current_glyph; 181 Glyph g; 182 183 return_if_fail (0 <= i < glyphs.size); 184 g = glyphs.get (i); 185 186 current_version_id = g.version_id; 187 deselect_all (); 188 ma.set_selected (true); 189 190 glyph_collection.set_selected (g); 191 192 reload_all_open_glyphs (); 193 194 if (update_loaded_glyph && !is_null (BirdFont.current_glyph_collection)) { 195 current_glyph = MainWindow.get_current_glyph (); 196 g.set_allocation (current_glyph.allocation); 197 g.close_path (); 198 g.reset_zoom (); 199 } 200 } 201 202 /** Reload a glyph when a new version is selected. Updates the path 203 * in glyph view, not from disk but from the glyph table. 204 */ 205 void reload_all_open_glyphs () { 206 TabBar b; 207 Tab tab; 208 Tab? tn; 209 Glyph glyph; 210 Glyph updated_glyph; 211 Glyph? ug; 212 Font font = BirdFont.get_current_font (); 213 StringBuilder uni = new StringBuilder (); 214 215 if (is_null (MainWindow.get_tab_bar ())) { 216 return; 217 } 218 219 b = MainWindow.get_tab_bar (); 220 221 for (int i = 0; i < b.get_length (); i++) { 222 tn = b.get_nth (i); 223 224 if (tn == null) { 225 warning ("tab is null"); 226 return; 227 } 228 229 tab = (!) tn; 230 231 if (! (tab.get_display () is Glyph)) { 232 continue; 233 } 234 235 glyph = (Glyph) tab.get_display (); 236 uni.truncate (0); 237 uni.append_unichar (glyph.unichar_code); 238 ug = font.get_glyph (uni.str); 239 240 if (ug == null) { 241 return; 242 } 243 244 updated_glyph = (!) ug; 245 tab.set_display (updated_glyph); 246 updated_glyph.view_zoom = glyph.view_zoom; 247 updated_glyph.view_offset_x = glyph.view_offset_x; 248 updated_glyph.view_offset_y = glyph.view_offset_y; 249 } 250 } 251 252 public void add_glyph (Glyph new_version, bool selected = true) { 253 MenuAction ma; 254 int v; 255 256 v = new_version.version_id; 257 glyphs.add (new_version); 258 259 ma = add_item (t_("Version") + @" $(v + 1)"); 260 ma.index = (int) glyphs.size - 1; 261 262 ma.action.connect ((self) => { 263 Font font = BirdFont.get_current_font (); 264 set_selected_item (self); 265 font.touch (); 266 }); 267 268 if (selected) { 269 set_selected_item (ma); 270 } 271 272 if (selected) { 273 update_selection (); 274 } 275 } 276 277 bool has_version (int id) { 278 foreach (Glyph g in glyphs) { 279 if (g.version_id == id) { 280 return true; 281 } 282 } 283 return false; 284 } 285 286 void update_selection (bool update_loaded_glyph = true) { 287 int index; 288 289 if (has_version (current_version_id)) { 290 index = get_current_version_index (); 291 set_selected_item (get_action_index (index + 1), update_loaded_glyph); // the first item is the "new version" 292 } 293 } 294 295 public MenuAction get_action_index (int index) { 296 if (!(0 <= index < actions.size)) { 297 warning (@"No action for index $index. (actions.size: $(actions.size))"); 298 return new MenuAction ("None"); 299 } 300 return actions.get (index); 301 } 302 303 public void recreate_index () { 304 int i = -1; 305 foreach (MenuAction a in actions) { 306 a.index = i; 307 i++; 308 } 309 } 310 311 public MenuAction get_action_no2 () { 312 if (actions.size < 2) { 313 warning ("No such action"); 314 return new MenuAction ("None"); 315 } 316 317 return actions.get (1); 318 } 319 320 public void deselect_all () { 321 foreach (MenuAction m in actions) { 322 m.set_selected (false); 323 } 324 } 325 326 public void set_direction (MenuDirection d) { 327 direction = d; 328 } 329 330 public void close () { 331 menu_visible = false; 332 } 333 334 public MenuAction add_item (string label) { 335 MenuAction m = new MenuAction (label); 336 add_menu_item (m); 337 return m; 338 } 339 340 public void add_menu_item (MenuAction m) { 341 actions.add (m); 342 } 343 344 public bool is_over_icon (double px, double py) { 345 if (x == -1 || y == -1) { 346 return false; 347 } 348 349 return x - 12 < px <= x && y - 5 < py < y + 12 + 5; 350 } 351 352 public bool menu_item_action (double px, double py) { 353 MenuAction? action; 354 MenuAction a; 355 MenuAction ma; 356 int index; 357 358 if (menu_visible) { 359 action = get_menu_action_at (px, py); 360 361 if (action != null) { 362 a = (!) action; 363 364 // action for the delete button 365 if (a.has_delete_button && menu_x + width - 13 < px <= menu_x + width) { 366 index = 0; 367 ma = actions.get (0); 368 while (true) { 369 if (a == ma) { 370 actions.remove_at (index); 371 signal_delete_item (index); 372 break; 373 } 374 375 if (ma == actions.get (actions.size - 1)) { 376 break; 377 } else { 378 ma = actions.get (index + 1); 379 index++; 380 } 381 } 382 return false; 383 } else { 384 a.action (a); 385 selected (this); 386 menu_visible = false; 387 } 388 389 return true; 390 } 391 } 392 393 return false; 394 } 395 396 public bool menu_icon_action (double px, double py) { 397 menu_visible = is_over_icon (px, py); 398 return menu_visible; 399 } 400 401 MenuAction? get_menu_action_at (double px, double py) { 402 double n = 0; 403 double ix, iy; 404 405 foreach (MenuAction item in actions) { 406 ix = menu_x - 6; 407 408 if (direction == MenuDirection.DROP_DOWN) { 409 iy = y + 12 + n * item_height; 410 } else { 411 iy = y - 24 - n * item_height; 412 } 413 414 if (ix <= px <= ix + width && iy <= py <= iy + item_height) { 415 return item; 416 } 417 418 n++; 419 } 420 421 return null; 422 } 423 424 public void set_position (double px, double py) { 425 x = px; 426 y = py; 427 428 foreach (MenuAction item in actions) { 429 item.text = new Text (item.label); 430 if (item.text.get_sidebearing_extent () + 25 > width) { 431 width = item.text.get_sidebearing_extent () + 25; 432 } 433 } 434 435 if (x - width + 19 < 0) { 436 menu_x = 30; 437 } else { 438 menu_x = x - width; 439 } 440 } 441 442 public void draw_menu (Context cr) { 443 double ix, iy; 444 int n; 445 446 if (likely (!menu_visible)) { 447 return; 448 } 449 450 cr.save (); 451 Theme.color (cr, "Default Background"); 452 cr.rectangle (menu_x, y - actions.size * item_height, width, actions.size * item_height); 453 454 cr.fill_preserve (); 455 cr.stroke (); 456 cr.restore (); 457 458 cr.save (); 459 460 n = 0; 461 foreach (MenuAction item in actions) { 462 item.width = width; 463 464 iy = y - 8 - n * item_height; 465 ix = menu_x + 2; 466 467 item.draw (ix, iy, cr); 468 n++; 469 } 470 471 cr.restore (); 472 } 473 } 474 475 } 476