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