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