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 memory leak
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 get_current ().selected_canvas (); 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) { 141 current_version_id = version_id; 142 update_selection (); 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); 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 add_glyph (new_version); 174 add_glyph_item (new_version); 175 } 176 177 public int get_last_id () { 178 return_val_if_fail (glyphs.size > 0, 1); 179 return glyphs.get (glyphs.size - 1).version_id; 180 } 181 182 private void set_selected_item (MenuAction ma) { 183 int i = ma.index; 184 Glyph current_glyph; 185 Glyph g; 186 187 return_if_fail (0 <= i < glyphs.size); 188 g = glyphs.get (i); 189 190 current_version_id = g.version_id; 191 deselect_all (); 192 ma.set_selected (true); 193 194 reload_all_open_glyphs (); 195 196 glyph_collection.set_selected (g); 197 198 /* 199 if (!is_null (BirdFont.current_glyph_collection)) { 200 current_glyph = MainWindow.get_current_glyph (); 201 g.set_allocation (current_glyph.allocation); 202 g.set_default_zoom (); 203 } 204 */ 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"); 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 () { 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)); // 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 + 19 < 0) { 442 menu_x = 30; 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