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