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