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