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