The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

OverView.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/OverView.vala.
Merge ../birdfont-2.x
1 /* 2 Copyright (C) 2012 - 2016 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 SvgBird; 17 18 namespace BirdFont { 19 20 /** A display with all glyphs present in this font. */ 21 public class OverView : FontDisplay { 22 public WidgetAllocation allocation = new WidgetAllocation (); 23 24 public OverViewItem selected_item = new OverViewItem (); 25 26 public Gee.ArrayList<GlyphCollection> copied_glyphs = new Gee.ArrayList<GlyphCollection> (); 27 public Gee.ArrayList<GlyphCollection> selected_items = new Gee.ArrayList<GlyphCollection> (); 28 29 int selected = 0; 30 int first_visible = 0; 31 int rows = 0; 32 int items_per_row = 0; 33 34 double view_offset_y = 0; 35 double view_offset_x = 0; 36 37 public signal void open_new_glyph_signal (unichar c); 38 public signal void open_glyph_signal (GlyphCollection c); 39 40 public GlyphRange glyph_range { 41 get { 42 return _glyph_range; 43 } 44 45 set { 46 _glyph_range = value; 47 } 48 } 49 50 GlyphRange _glyph_range; 51 52 string search_query = ""; 53 54 public Gee.ArrayList<OverViewItem> visible_items = new Gee.ArrayList<OverViewItem> (); 55 56 /** List of undo commands. */ 57 public Gee.ArrayList<OverViewUndoItem> undo_items = new Gee.ArrayList<OverViewUndoItem> (); 58 public Gee.ArrayList<OverViewUndoItem> redo_items = new Gee.ArrayList<OverViewUndoItem> (); 59 60 /** Show all characters that has been drawn. */ 61 public bool all_available { 62 set { 63 _all_available = value; 64 update_item_list (); 65 } 66 67 get { 68 return _all_available; 69 } 70 } 71 72 bool _all_available = false; 73 74 /** Show unicode database info. */ 75 CharacterInfo? character_info = null; 76 77 double scroll_size = 1; 78 const double UCD_LINE_HEIGHT = 17 * 1.3; 79 80 private bool update_scheduled = true; 81 82 public OverView (GlyphRange? range = null, 83 bool open_selected = true, bool default_character_set = true) { 84 85 GlyphRange gr; 86 87 if (range == null) { 88 gr = new GlyphRange (); 89 set_current_glyph_range (gr); 90 } 91 92 if (open_selected) { 93 this.open_glyph_signal.connect ((glyph_collection) => { 94 TabBar tabs = MainWindow.get_tab_bar (); 95 string n = glyph_collection.get_current ().name; 96 bool selected = tabs.select_char (n); 97 98 GlyphTab glyph_tab; 99 100 if (!selected) { 101 glyph_tab = new GlyphTab (glyph_collection); 102 tabs.add_tab (glyph_tab, true, glyph_collection); 103 set_glyph_zoom (glyph_collection); 104 PenTool.update_orientation (); 105 } 106 107 if (glyph_collection.get_name () == ".notdef") { 108 Font font = BirdFont.get_current_font (); 109 if (!font.has_glyph (".notdef")) { 110 font.add_glyph_collection (glyph_collection); 111 } 112 } 113 }); 114 115 this.open_new_glyph_signal.connect ((character) => { 116 // ignore control characters 117 if (character <= 0x1F) { 118 return; 119 } 120 121 create_new_glyph (character); 122 }); 123 } 124 125 if (default_character_set) { 126 IdleSource idle = new IdleSource (); 127 128 idle.set_callback (() => { 129 use_default_character_set (); 130 selected_canvas (); 131 return false; 132 }); 133 134 idle.attach (null); 135 } 136 137 update_item_list (); 138 update_scrollbar (); 139 reset_zoom (); 140 } 141 142 public Glyph? get_selected_glyph () { 143 if (selected_items.size == 0) { 144 return null; 145 } 146 147 return selected_items.get (0).get_current (); 148 } 149 150 public void select_all_glyphs () { 151 Font f; 152 GlyphCollection? glyphs; 153 154 f = BirdFont.get_current_font (); 155 156 for (int index = 0; index < f.length (); index++) { 157 glyphs = f.get_glyph_collection_index ((uint32) index); 158 return_if_fail (glyphs != null); 159 160 selected_items.add ((!) glyphs); 161 } 162 163 foreach (OverViewItem item in visible_items) { 164 item.selected = item.glyphs != null; 165 } 166 167 GlyphCanvas.redraw (); 168 } 169 170 public void use_default_character_set () { 171 GlyphRange gr = new GlyphRange (); 172 all_available = false; 173 DefaultCharacterSet.use_default_range (gr); 174 set_current_glyph_range (gr); 175 OverviewTools.update_overview_characterset (); 176 FontDisplay.dirty_scrollbar = true; 177 } 178 179 public GlyphCollection create_new_glyph (unichar character) { 180 StringBuilder name = new StringBuilder (); 181 TabBar tabs = MainWindow.get_tab_bar (); 182 bool selected; 183 Glyph glyph; 184 GlyphCollection glyph_collection; 185 GlyphCanvas canvas; 186 GlyphTab glyph_tab; 187 188 glyph_collection = MainWindow.get_current_glyph_collection (); 189 name.append_unichar (character); 190 selected = tabs.select_char (name.str); 191 192 if (!selected) { 193 glyph_collection = add_character_to_font (character); 194 glyph_tab = new GlyphTab (glyph_collection); 195 glyph = glyph_collection.get_current (); 196 glyph.layers.add_layer (new Layer ()); 197 tabs.add_tab (glyph_tab, true, glyph_collection); 198 199 selected_items.add (glyph_collection); 200 201 canvas = MainWindow.get_glyph_canvas (); 202 canvas.set_current_glyph_collection (glyph_collection); 203 204 set_glyph_zoom (glyph_collection); 205 } else { 206 warning ("Glyph is already open"); 207 } 208 209 OverviewTools.update_overview_characterset (); 210 return glyph_collection; 211 } 212 213 public GlyphCollection add_empty_character_to_font (unichar character, bool unassigned, string name) { 214 return add_character_to_font (character, true, unassigned); 215 } 216 217 public GlyphCollection add_character_to_font (unichar character, bool empty = false, 218 bool unassigned = false, string glyph_name = "") { 219 StringBuilder name = new StringBuilder (); 220 Font font = BirdFont.get_current_font (); 221 GlyphCollection? fg; 222 Glyph glyph; 223 GlyphCollection glyph_collection; 224 225 if (glyph_name == "") { 226 name.append_unichar (character); 227 } else { 228 name.append (glyph_name); 229 } 230 231 if (all_available) { 232 fg = font.get_glyph_collection_by_name (name.str); 233 } else { 234 fg = font.get_glyph_collection (name.str); 235 } 236 237 if (fg != null) { 238 glyph_collection = (!) fg; 239 } else { 240 glyph_collection = new GlyphCollection (character, name.str); 241 242 if (!empty) { 243 glyph = new Glyph (name.str, !unassigned ? character : '\0'); 244 glyph_collection.add_master (new GlyphMaster ()); 245 glyph_collection.insert_glyph (glyph, true); 246 } 247 248 font.add_glyph_collection (glyph_collection); 249 } 250 251 glyph_collection.set_unassigned (unassigned); 252 253 return glyph_collection; 254 } 255 256 public static void search () { 257 OverView ow = MainWindow.get_overview (); 258 TextListener listener = new TextListener (t_("Search"), ow.search_query, t_("Filter")); 259 260 listener.signal_text_input.connect ((text) => { 261 OverView o = MainWindow.get_overview (); 262 o.search_query = text; 263 }); 264 265 listener.signal_submit.connect (() => { 266 OverView o = MainWindow.get_overview (); 267 string q = o.search_query; 268 269 if (q.char_count () > 1) { 270 q = q.down (); 271 } 272 273 GlyphRange r = CharDatabase.search (q); 274 o.set_current_glyph_range (r); 275 MainWindow.get_tab_bar ().select_tab_name ("Overview"); 276 277 TextListener tl = new TextListener (t_("Search"), o.search_query, t_("Filter")); 278 TabContent.show_text_input (tl); 279 }); 280 281 TabContent.show_text_input (listener); 282 } 283 284 public Glyph? get_current_glyph () { 285 OverViewItem oi = selected_item; 286 if (oi.glyphs != null) { 287 return ((!) oi.glyphs).get_current (); 288 } 289 return null; 290 } 291 292 public void set_glyph_zoom (GlyphCollection glyphs) { 293 GlyphCanvas canvas; 294 canvas = MainWindow.get_glyph_canvas (); 295 canvas.set_current_glyph_collection (glyphs); 296 Toolbox tools = MainWindow.get_toolbox (); 297 ZoomTool z = (ZoomTool) tools.get_tool ("zoom_tool"); 298 z.store_current_view (); 299 glyphs.get_current ().default_zoom (); 300 z.store_current_view (); 301 OverViewItem.reset_label (); 302 } 303 304 public double get_height () { 305 double l; 306 Font f; 307 308 if (rows == 0) { 309 return 0; 310 } 311 312 if (all_available) { 313 f = BirdFont.get_current_font (); 314 l = f.length (); 315 } else { 316 l = glyph_range.length (); 317 } 318 319 return 2.0 * OverViewItem.height * (l / rows); 320 } 321 322 public bool selected_char_is_visible () { 323 return first_visible <= selected <= first_visible + items_per_row * rows; 324 } 325 326 public override bool has_scrollbar () { 327 return true; 328 } 329 330 public bool all_characters_in_view () { 331 double length; 332 Font f; 333 334 if (all_available) { 335 f = BirdFont.get_current_font (); 336 length = f.length (); 337 } else { 338 length = glyph_range.length (); 339 } 340 341 return length < items_per_row * rows; 342 } 343 344 public void move_up () { 345 first_visible -= items_per_row; 346 selected += items_per_row; 347 348 if (first_visible < 0) { 349 selected -= items_per_row; 350 first_visible = 0; 351 view_offset_y = 0; 352 } 353 } 354 355 public void move_down () { 356 if (!at_bottom ()) { 357 first_visible += items_per_row; 358 selected -= items_per_row; 359 } 360 } 361 362 public override void scroll_wheel (double x, double y, double dx, double dy) { 363 double pixel_delta = 3 * dy; 364 365 if (dy > 0) { 366 view_offset_y += pixel_delta; 367 368 while (view_offset_y > 0) { 369 view_offset_y -= OverViewItem.height; 370 move_up (); 371 } 372 } else { 373 if (at_bottom ()) { 374 if (view_offset_y > -2 * OverViewItem.height && !all_characters_in_view ()) { 375 view_offset_y += pixel_delta; 376 } 377 } else { 378 view_offset_y += pixel_delta; 379 while (view_offset_y < -OverViewItem.height) { 380 view_offset_y += OverViewItem.height; 381 move_down (); 382 } 383 } 384 } 385 386 if (view_offset_y < -2 * OverViewItem.height) { 387 view_offset_y = -2 * OverViewItem.height; 388 } 389 390 update_item_list (); 391 update_scrollbar (); 392 hide_menu (); 393 GlyphCanvas.redraw (); 394 } 395 396 public override void selected_canvas () { 397 OverviewTools.update_overview_characterset (); 398 KeyBindings.set_require_modifier (true); 399 update_scrollbar (); 400 update_zoom_bar (); 401 update_item_list (); 402 selected_item = get_selected_item (); 403 GlyphCanvas.redraw (); 404 } 405 406 public void update_zoom_bar () { 407 double z = OverViewItem.width / OverViewItem.DEFAULT_WIDTH - 0.5; 408 Toolbox.overview_tools.zoom_bar.set_zoom (z); 409 Toolbox.redraw_tool_box (); 410 update_item_list (); 411 } 412 413 public void set_zoom (double zoom) { 414 double z = zoom + 0.5; 415 OverViewItem.glyph_scale = 1; 416 OverViewItem.width = OverViewItem.DEFAULT_WIDTH * z; 417 OverViewItem.height = OverViewItem.DEFAULT_HEIGHT * z; 418 OverViewItem.margin = OverViewItem.DEFAULT_MARGIN * z; 419 update_item_list (); 420 OverViewItem.reset_label (); 421 GlyphCanvas.redraw (); 422 } 423 424 public override void zoom_min () { 425 OverViewItem.width = OverViewItem.DEFAULT_WIDTH * 0.5; 426 OverViewItem.height = OverViewItem.DEFAULT_HEIGHT * 0.5; 427 OverViewItem.margin = OverViewItem.DEFAULT_MARGIN * 0.5; 428 update_item_list (); 429 OverViewItem.reset_label (); 430 GlyphCanvas.redraw (); 431 update_zoom_bar (); 432 } 433 434 public override void reset_zoom () { 435 OverViewItem.width = OverViewItem.DEFAULT_WIDTH; 436 OverViewItem.height = OverViewItem.DEFAULT_HEIGHT; 437 OverViewItem.margin = OverViewItem.DEFAULT_MARGIN; 438 update_item_list (); 439 OverViewItem.reset_label (); 440 GlyphCanvas.redraw (); 441 update_zoom_bar (); 442 } 443 444 public override void zoom_max () { 445 OverViewItem.width = allocation.width; 446 OverViewItem.height = allocation.height; 447 update_item_list (); 448 OverViewItem.reset_label (); 449 GlyphCanvas.redraw (); 450 } 451 452 public override void zoom_in () { 453 OverViewItem.width *= 1.1; 454 OverViewItem.height *= 1.1; 455 OverViewItem.margin *= 1.1; 456 update_item_list (); 457 OverViewItem.reset_label (); 458 GlyphCanvas.redraw (); 459 update_zoom_bar (); 460 } 461 462 public override void zoom_out () { 463 OverViewItem.width *= 0.9; 464 OverViewItem.height *= 0.9; 465 OverViewItem.margin *= 0.9; 466 update_item_list (); 467 OverViewItem.reset_label (); 468 GlyphCanvas.redraw (); 469 update_zoom_bar (); 470 } 471 472 public override void store_current_view () { 473 } 474 475 public override void restore_last_view () { 476 } 477 478 public override void next_view () { 479 } 480 481 public override string get_label () { 482 return t_("Overview"); 483 } 484 485 public override string get_name () { 486 return "Overview"; 487 } 488 489 public void display_all_available_glyphs () { 490 all_available = true; 491 492 view_offset_y = 0; 493 first_visible = 0; 494 selected = 0; 495 496 update_item_list (); 497 selected_item = get_selected_item (); 498 GlyphCanvas.redraw (); 499 } 500 501 OverViewItem get_selected_item () { 502 if (visible_items.size == 0) { 503 return new OverViewItem (); 504 } 505 506 if (!(0 <= selected < visible_items.size)) { 507 return selected_item; 508 } 509 510 OverViewItem item = visible_items.get (selected); 511 item.selected = true; 512 return item; 513 } 514 515 int get_items_per_row () { 516 int i = 1; 517 double tab_with = allocation.width - 30; // 30 px for the scroll bar 518 OverViewItem.margin = OverViewItem.width * 0.1; 519 double l = OverViewItem.margin + OverViewItem.full_width (); 520 521 while (l <= tab_with) { 522 l += OverViewItem.full_width (); 523 i++; 524 } 525 526 return i - 1; 527 } 528 529 public void update_item_list () { 530 update_scheduled = true; 531 } 532 533 public void process_item_list_update () { 534 string character_string; 535 Font font = BirdFont.get_current_font (); 536 GlyphCollection? glyphs = null; 537 uint32 index; 538 OverViewItem item; 539 double x, y; 540 unichar character; 541 Glyph glyph; 542 double tab_with; 543 int item_list_length; 544 int visible_size; 545 546 tab_with = allocation.width - 30; // scrollbar 547 548 items_per_row = get_items_per_row (); 549 rows = (int) (allocation.height / OverViewItem.full_height ()) + 2; 550 551 item_list_length = items_per_row * rows; 552 visible_items.clear (); 553 554 index = (uint32) first_visible; 555 x = OverViewItem.margin; 556 y = OverViewItem.margin; 557 558 if (all_available) { 559 uint font_length = font.length (); 560 int i; 561 562 for (i = 0; i < item_list_length && index < font_length; i++) { 563 glyphs = font.get_glyph_collection_index ((uint32) index); 564 return_if_fail (glyphs != null); 565 566 glyph = ((!) glyphs).get_current (); 567 character_string = glyph.name; 568 character = glyph.unichar_code; 569 570 item = new OverViewItem (); 571 item.set_character (character); 572 item.set_glyphs (glyphs); 573 item.generate_graphics (); 574 item.selected = (selected_items.index_of ((!) glyphs) != -1); 575 576 visible_items.add (item); 577 index++; 578 } 579 580 if (i < item_list_length && !font.has_glyph (".notdef")) { 581 item = new OverViewItem (); 582 item.set_glyphs (font.get_notdef_character ()); 583 item.generate_graphics (false); 584 item.version_menu = null; 585 item.selected = (selected_items.index_of ((!) glyphs) != -1); 586 visible_items.add (item); 587 } 588 } else { 589 uint32 glyph_range_size = glyph_range.get_length (); 590 591 uint max_length = glyph_range.length () - first_visible; 592 593 if (item_list_length > max_length) { 594 item_list_length = (int) max_length; 595 } 596 597 for (int i = 0; i < item_list_length && index < glyph_range_size; i++) { 598 item = new OverViewItem (); 599 visible_items.add (item); 600 } 601 602 index = (uint32) first_visible; 603 visible_size = visible_items.size; 604 for (int i = 0; i < visible_size; i++) { 605 item = visible_items.get (i); 606 character = glyph_range.get_character ((uint32) index); 607 item.set_character (character); 608 index++; 609 } 610 611 visible_size = visible_items.size; 612 for (int i = 0; i < visible_size; i++) { 613 item = visible_items.get (i); 614 glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); 615 item.set_glyphs (glyphs); 616 617 if (glyphs != null) { 618 item.selected = (selected_items.index_of ((!) glyphs) != -1); 619 } 620 } 621 622 for (int i = 0; i < visible_size; i++) { 623 item = visible_items.get (i); 624 item.generate_graphics (); 625 } 626 } 627 628 x = OverViewItem.margin; 629 y = OverViewItem.margin; 630 631 visible_size = visible_items.size; 632 double full_width = OverViewItem.full_width (); 633 634 for (int i = 0; i < visible_size; i++) { 635 item = visible_items.get (i); 636 637 if (all_available) { 638 glyphs = font.get_glyph_collection_index ((uint32) i); 639 } else { 640 glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); 641 } 642 643 if (glyphs != null) { 644 int selected_index = selected_items.index_of ((!) glyphs); 645 item.selected = (selected_index != -1); 646 } 647 648 item.adjust_scale (); 649 650 item.x = x + view_offset_x; 651 item.y = y + view_offset_y; 652 653 x += full_width; 654 655 if (x + full_width >= tab_with) { 656 x = OverViewItem.margin; 657 y += OverViewItem.full_height (); 658 } 659 } 660 661 update_scheduled = false; 662 } 663 664 public override void draw (WidgetAllocation allocation, Context cr) { 665 if (update_scheduled 666 || this.allocation.width != allocation.width 667 || this.allocation.height != allocation.height 668 || this.allocation.width == 0) { 669 this.allocation = allocation; 670 process_item_list_update (); 671 } 672 673 this.allocation = allocation; 674 675 // clear canvas 676 cr.save (); 677 Theme.color (cr, "Background 1"); 678 cr.rectangle (0, 0, allocation.width, allocation.height); 679 cr.fill (); 680 cr.restore (); 681 682 foreach (OverViewItem i in visible_items) { 683 i.draw (allocation, cr); 684 } 685 686 if (unlikely (visible_items.size == 0)) { 687 draw_empty_canvas (allocation, cr); 688 } 689 690 if (unlikely (character_info != null)) { 691 draw_character_info (cr); 692 } 693 } 694 695 void draw_empty_canvas (WidgetAllocation allocation, Context cr) { 696 Text t; 697 cr.save (); 698 t = new Text ("No glyphs in this view.", 24); 699 Theme.text_color (t, "Text Foreground"); 700 t.widget_x = 40; 701 t.widget_y = 30; 702 t.draw (cr); 703 cr.restore (); 704 } 705 706 public void scroll_rows (int row_adjustment) { 707 for (int i = 0; i < row_adjustment; i++) { 708 scroll (-OverViewItem.height); 709 } 710 711 for (int i = 0; i > row_adjustment; i--) { 712 scroll (OverViewItem.height); 713 } 714 } 715 716 public void scroll_adjustment (double pixel_adjustment) { 717 double l; 718 Font f; 719 720 if (all_available) { 721 f = BirdFont.get_current_font (); 722 l = f.length (); 723 } else { 724 l = glyph_range.length (); 725 } 726 727 if (first_visible <= 0) { 728 return; 729 } 730 731 if (first_visible + rows * items_per_row >= l) { 732 return; 733 } 734 735 scroll ((int64) pixel_adjustment); 736 } 737 738 void scroll_to_position (int64 r) { 739 if (r < 0) { 740 scroll_top (); 741 return; 742 } 743 744 selected -= (int) (r - first_visible); 745 first_visible = (int) r; 746 update_item_list (); 747 } 748 749 public override void scroll_to (double position) requires (items_per_row > 0) { 750 double r; 751 int nrows; 752 Font f; 753 754 if (all_available) { 755 f = BirdFont.get_current_font (); 756 nrows = (int) (f.length () / items_per_row); 757 } else { 758 nrows = (int) (glyph_range.length () / items_per_row); 759 } 760 761 view_offset_y = 0; 762 r = (int64) (position * (nrows - rows + 3)); // 3 invisible rows 763 r *= items_per_row; 764 765 scroll_to_position ((int64) r); 766 update_item_list (); 767 GlyphCanvas.redraw (); 768 } 769 770 private void scroll (double pixel_adjustment) { 771 if (first_visible < 0 && pixel_adjustment < 0) { 772 scroll_top (); 773 return; 774 } 775 776 view_offset_y += pixel_adjustment; 777 778 if (view_offset_y >= 0) { 779 while (view_offset_y > OverViewItem.height) { 780 view_offset_y -= OverViewItem.height; 781 first_visible -= items_per_row; 782 } 783 784 first_visible -= items_per_row; 785 view_offset_y -= OverViewItem.height; 786 } else if (view_offset_y < -OverViewItem.height) { 787 view_offset_y = 0; 788 first_visible += items_per_row; 789 } 790 791 update_item_list (); 792 } 793 794 public void scroll_top () { 795 first_visible = 0; 796 view_offset_y = 0; 797 798 update_item_list (); 799 800 if (visible_items.size != 0) { 801 selected_item = get_selected_item (); 802 } 803 } 804 805 /** Returns true if the selected glyph is at the last row. */ 806 private bool last_row () { 807 return visible_items.size - selected <= items_per_row; 808 } 809 810 public void key_down () { 811 Font f = BirdFont.get_current_font (); 812 int64 len = (all_available) ? f.length () : glyph_range.length (); 813 814 if (at_bottom () && last_row ()) { 815 return; 816 } 817 818 selected += items_per_row; 819 820 if (selected >= items_per_row * rows) { 821 first_visible += items_per_row; 822 selected -= items_per_row; 823 } 824 825 if (first_visible + selected >= len) { 826 selected = (int) (len - first_visible - 1); 827 828 if (selected < items_per_row * (rows - 1)) { 829 first_visible -= items_per_row; 830 selected += items_per_row; 831 } 832 } 833 834 if (selected >= visible_items.size) { 835 selected = (int) (visible_items.size - 1); 836 } 837 838 selected_item = get_selected_item (); 839 update_item_list (); 840 } 841 842 public void key_right () { 843 Font f = BirdFont.get_current_font (); 844 int64 len = (all_available) ? f.length () : glyph_range.length (); 845 846 if (at_bottom () && first_visible + selected + 1 >= len) { 847 selected = (int) (visible_items.size - 1); 848 selected_item = get_selected_item (); 849 return; 850 } 851 852 selected += 1; 853 854 if (selected >= items_per_row * rows) { 855 first_visible += items_per_row; 856 selected -= items_per_row; 857 selected -= 1; 858 } 859 860 if (first_visible + selected > len) { 861 first_visible -= items_per_row; 862 selected = (int) (len - first_visible - 1); 863 selected_item = get_selected_item (); 864 } 865 update_item_list (); 866 } 867 868 public void key_up () { 869 selected -= items_per_row; 870 871 if (selected < 0) { 872 first_visible -= items_per_row; 873 selected += items_per_row; 874 } 875 876 if (first_visible < 0) { 877 first_visible = 0; 878 } 879 update_item_list (); 880 } 881 882 public void key_left () { 883 selected -= 1; 884 885 if (selected < 0) { 886 first_visible -= items_per_row; 887 selected += items_per_row; 888 selected += 1; 889 } 890 891 if (first_visible < 0) { 892 scroll_top (); 893 } 894 update_item_list (); 895 } 896 897 public string get_selected_char () { 898 Font f; 899 Glyph? g; 900 901 if (all_available) { 902 f = BirdFont.get_current_font (); 903 g = f.get_glyph_index (selected); 904 return_val_if_fail (g != null, "".dup ()); 905 return ((!) g).get_name (); 906 } 907 908 return glyph_range.get_char (selected); 909 } 910 911 public override void key_press (uint keyval) { 912 hide_menu (); 913 update_item_list (); 914 GlyphCanvas.redraw (); 915 916 if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { 917 return; 918 } 919 920 switch (keyval) { 921 case Key.ENTER: 922 open_current_glyph (); 923 return; 924 925 case Key.UP: 926 get_selected_item ().selected = false; 927 928 key_up (); 929 selected_item = get_selected_item (); 930 931 selected_items.clear (); 932 if (selected_item.glyphs != null) { 933 selected_items.add ((!) selected_item.glyphs); 934 } 935 936 update_scrollbar (); 937 return; 938 939 case Key.RIGHT: 940 get_selected_item ().selected = false; 941 942 key_right (); 943 selected_item = get_selected_item (); 944 945 selected_items.clear (); 946 if (selected_item.glyphs != null) { 947 selected_items.add ((!) selected_item.glyphs); 948 } 949 950 update_scrollbar (); 951 return; 952 953 case Key.LEFT: 954 get_selected_item ().selected = false; 955 956 key_left (); 957 selected_item = get_selected_item (); 958 959 selected_items.clear (); 960 if (selected_item.glyphs != null) { 961 selected_items.add ((!) selected_item.glyphs); 962 } 963 964 update_scrollbar(); 965 return; 966 967 case Key.DOWN: 968 get_selected_item ().selected = false; 969 970 key_down (); 971 selected_item = get_selected_item (); 972 973 selected_items.clear (); 974 if (selected_item.glyphs != null) { 975 selected_items.add ((!) selected_item.glyphs); 976 } 977 978 update_scrollbar (); 979 return; 980 981 case Key.PG_UP: 982 get_selected_item ().selected = false; 983 984 for (int i = 0; i < rows; i++) { 985 key_up (); 986 } 987 selected_item = get_selected_item (); 988 989 selected_items.clear (); 990 if (selected_item.glyphs != null) { 991 selected_items.add ((!) selected_item.glyphs); 992 } 993 994 update_scrollbar (); 995 return; 996 997 case Key.PG_DOWN: 998 get_selected_item ().selected = false; 999 1000 for (int i = 0; i < rows; i++) { 1001 key_down (); 1002 } 1003 selected_item = get_selected_item (); 1004 1005 selected_items.clear (); 1006 if (selected_item.glyphs != null) { 1007 selected_items.add ((!) selected_item.glyphs); 1008 } 1009 1010 update_scrollbar (); 1011 return; 1012 1013 case Key.DEL: 1014 delete_selected_glyph (); 1015 selected_item = get_selected_item (); 1016 return; 1017 1018 case Key.BACK_SPACE: 1019 delete_selected_glyph (); 1020 selected_item = get_selected_item (); 1021 return; 1022 } 1023 1024 if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { 1025 scroll_to_char (keyval); 1026 } 1027 1028 selected_item = get_selected_item (); 1029 1030 selected_items.clear (); 1031 if (selected_item.glyphs != null) { 1032 selected_items.add ((!) selected_item.glyphs); 1033 } 1034 1035 update_item_list (); 1036 GlyphCanvas.redraw (); 1037 } 1038 1039 public void delete_selected_glyph () { 1040 Font font = BirdFont.get_current_font (); 1041 OverViewUndoItem undo_item = new OverViewUndoItem (); 1042 1043 undo_item.alternate_sets = font.alternates.copy (); 1044 1045 foreach (GlyphCollection g in selected_items) { 1046 undo_item.glyphs.add (g.copy ()); 1047 } 1048 store_undo_items (undo_item); 1049 1050 foreach (GlyphCollection glyph_collection in selected_items) { 1051 font.delete_glyph (glyph_collection); 1052 string name = glyph_collection.get_name (); 1053 MainWindow.get_tab_bar ().close_background_tab_by_name (name); 1054 } 1055 1056 update_item_list (); 1057 GlyphCanvas.redraw (); 1058 } 1059 1060 public override void undo () { 1061 Font font = BirdFont.get_current_font (); 1062 OverViewUndoItem previous_collection; 1063 1064 if (undo_items.size == 0) { 1065 return; 1066 } 1067 1068 previous_collection = undo_items.get (undo_items.size - 1); 1069 redo_items.add (get_current_state (previous_collection)); 1070 1071 // remove the old glyph and add the new one 1072 foreach (GlyphCollection g in previous_collection.glyphs) { 1073 font.delete_glyph (g); 1074 1075 if (g.length () > 0) { 1076 font.add_glyph_collection (g); 1077 } 1078 1079 TabBar tabs = MainWindow.get_tab_bar (); 1080 Tab? tab = tabs.get_tab (g.get_name ()); 1081 1082 if (tab != null) { 1083 Tab t = (!) tab; 1084 set_glyph_zoom (g); 1085 t.set_glyph_collection (g); 1086 t.set_display (new GlyphTab (g)); 1087 } 1088 } 1089 1090 Font f = BirdFont.get_current_font (); 1091 f.alternates = previous_collection.alternate_sets.copy (); 1092 1093 undo_items.remove_at (undo_items.size - 1); 1094 GlyphCanvas.redraw (); 1095 } 1096 1097 public override void redo () { 1098 Font font = BirdFont.get_current_font (); 1099 OverViewUndoItem previous_collection; 1100 1101 if (redo_items.size == 0) { 1102 return; 1103 } 1104 1105 previous_collection = redo_items.get (redo_items.size - 1); 1106 undo_items.add (get_current_state (previous_collection)); 1107 1108 // remove the old glyph and add the new one 1109 foreach (GlyphCollection g in previous_collection.glyphs) { 1110 font.delete_glyph (g); 1111 font.add_glyph_collection (g); 1112 1113 TabBar tabs = MainWindow.get_tab_bar (); 1114 Tab? tab = tabs.get_tab (g.get_name ()); 1115 1116 if (tab != null) { 1117 Tab t = (!) tab; 1118 set_glyph_zoom (g); 1119 t.set_glyph_collection (g); 1120 t.set_display (new GlyphTab (g)); 1121 } 1122 } 1123 font.alternates = previous_collection.alternate_sets.copy (); 1124 1125 redo_items.remove_at (redo_items.size - 1); 1126 GlyphCanvas.redraw (); 1127 } 1128 1129 public OverViewUndoItem get_current_state (OverViewUndoItem previous_collection) { 1130 GlyphCollection? gc; 1131 OverViewUndoItem ui = new OverViewUndoItem (); 1132 Font font = BirdFont.get_current_font (); 1133 1134 ui.alternate_sets = font.alternates.copy (); 1135 1136 foreach (GlyphCollection g in previous_collection.glyphs) { 1137 gc = font.get_glyph_collection (g.get_name ()); 1138 1139 if (gc != null) { 1140 ui.glyphs.add (((!) gc).copy ()); 1141 } else { 1142 ui.glyphs.add (new GlyphCollection (g.get_unicode_character (), g.get_name ())); 1143 } 1144 } 1145 1146 return ui; 1147 } 1148 1149 public void store_undo_state (GlyphCollection gc) { 1150 OverViewUndoItem i = new OverViewUndoItem (); 1151 Font f = BirdFont.get_current_font (); 1152 i.alternate_sets = f.alternates.copy (); 1153 i.glyphs.add (gc); 1154 store_undo_items (i); 1155 } 1156 1157 public void store_undo_items (OverViewUndoItem i) { 1158 undo_items.add (i); 1159 redo_items.clear (); 1160 } 1161 1162 bool select_visible_glyph (string name) { 1163 int i = 0; 1164 1165 foreach (OverViewItem o in visible_items) { 1166 if (o.get_name () == name) { 1167 selected = i; 1168 selected_item = get_selected_item (); 1169 1170 if (selected_item.y > allocation.height - OverViewItem.height) { 1171 view_offset_y -= (selected_item.y + OverViewItem.height) - allocation.height; 1172 } 1173 1174 if (selected_item.y < 0) { 1175 view_offset_y = 0; 1176 } 1177 1178 return true; 1179 } 1180 1181 if (i > 1000) { 1182 warning ("selected character not found"); 1183 return true; 1184 } 1185 1186 i++; 1187 } 1188 1189 return false; 1190 } 1191 1192 public void scroll_to_char (unichar c) { 1193 StringBuilder s = new StringBuilder (); 1194 1195 if (is_modifier_key (c)) { 1196 return; 1197 } 1198 1199 s.append_unichar (c); 1200 scroll_to_glyph (s.str); 1201 } 1202 1203 public void scroll_to_glyph (string name) { 1204 GlyphRange gr = glyph_range; 1205 int i, r, index; 1206 string ch; 1207 Font font = BirdFont.get_current_font (); 1208 GlyphCollection? glyphs = null; 1209 Glyph glyph; 1210 1211 index = -1; 1212 1213 if (items_per_row <= 0) { 1214 return; 1215 } 1216 1217 ch = name; 1218 1219 // selected char is visible 1220 if (select_visible_glyph (ch)) { 1221 return; 1222 } 1223 1224 // scroll to char 1225 if (all_available) { 1226 1227 // don't search for glyphs in huge CJK fonts 1228 if (font.length () > 500) { 1229 r = 0; 1230 } else { 1231 // FIXME: too slow 1232 for (r = 0; r < font.length (); r += items_per_row) { 1233 for (i = 0; i < items_per_row && i < font.length (); i++) { 1234 glyphs = font.get_glyph_collection_index ((uint32) r + i); 1235 return_if_fail (glyphs != null); 1236 glyph = ((!) glyphs).get_current (); 1237 1238 if (glyph.name == ch) { 1239 index = i; 1240 } 1241 } 1242 1243 if (index > -1) { 1244 break; 1245 } 1246 } 1247 } 1248 } else { 1249 1250 if (ch.char_count () > 1) { 1251 warning ("Can't scroll to ligature in this view"); 1252 return; 1253 } 1254 1255 for (r = 0; r < gr.length (); r += items_per_row) { 1256 for (i = 0; i < items_per_row; i++) { 1257 if (gr.get_char (r + i) == ch) { 1258 index = i; 1259 } 1260 } 1261 1262 if (index > -1) { 1263 break; 1264 } 1265 } 1266 } 1267 1268 if (index > -1) { 1269 first_visible = r; 1270 process_item_list_update (); 1271 update_item_list (); 1272 select_visible_glyph (ch); 1273 } 1274 } 1275 1276 public override void double_click (uint button, double ex, double ey) 1277 requires (!is_null (visible_items) && !is_null (allocation)) { 1278 1279 return_if_fail (!is_null (this)); 1280 1281 foreach (OverViewItem i in visible_items) { 1282 if (i.double_click (button, ex, ey)) { 1283 open_overview_item (i); 1284 } 1285 } 1286 1287 GlyphCanvas.redraw (); 1288 } 1289 1290 public void open_overview_item (OverViewItem i) { 1291 if (i.glyphs != null) { 1292 open_glyph_signal ((!) i.glyphs); 1293 ((!) i.glyphs).get_current ().close_path (); 1294 } else { 1295 open_new_glyph_signal (i.character); 1296 } 1297 } 1298 1299 public void set_character_info (CharacterInfo i) { 1300 character_info = i; 1301 } 1302 1303 public int get_selected_index () { 1304 GlyphCollection gc; 1305 int index = 0; 1306 1307 if (selected_items.size == 0) { 1308 return 0; 1309 } 1310 1311 gc = selected_items.get (0); 1312 1313 foreach (OverViewItem i in visible_items) { 1314 if (i.glyphs != null && gc == ((!) i.glyphs)) { 1315 break; 1316 } 1317 1318 index++; 1319 } 1320 1321 return index; 1322 } 1323 1324 public void hide_menu () { 1325 foreach (OverViewItem i in visible_items) { 1326 i.hide_menu (); 1327 } 1328 } 1329 1330 public override void button_press (uint button, double x, double y) { 1331 OverViewItem selected_item; 1332 int index = 0; 1333 int selected_index = -1; 1334 bool update = false; 1335 1336 if (character_info != null) { 1337 character_info = null; 1338 GlyphCanvas.redraw (); 1339 return; 1340 } 1341 1342 if (!KeyBindings.has_shift ()) { 1343 selected_items.clear (); 1344 } 1345 1346 for (int j = 0; j < visible_items.size; j++) { 1347 selected_item = visible_items.get (j); 1348 1349 if (selected_item.click (button, x, y)) { 1350 selected = index; 1351 selected_item = get_selected_item (); 1352 1353 if (KeyBindings.has_shift ()) { 1354 if (selected_item.glyphs != null) { 1355 selected_index = selected_items.index_of ((!) selected_item.glyphs); 1356 if (selected_index == -1) { 1357 selected_items.add ((!) selected_item.glyphs); 1358 } else { 1359 return_if_fail (0 <= selected_index < selected_items.size); 1360 selected_items.remove_at (selected_index); 1361 selected = get_selected_index (); 1362 selected_item = get_selected_item (); 1363 } 1364 } 1365 1366 update = true; 1367 } else { 1368 selected_items.clear (); 1369 if (selected_item.glyphs != null) { 1370 selected_items.add ((!) selected_item.glyphs); 1371 } 1372 } 1373 1374 if (!is_null (selected_item.version_menu)) { 1375 update = !((!) selected_item.version_menu).menu_visible; 1376 } else { 1377 update = true; 1378 } 1379 } 1380 index++; 1381 } 1382 1383 if (update) { 1384 update_item_list (); 1385 } 1386 1387 GlyphCanvas.redraw (); 1388 } 1389 1390 /** Returns true if overview shows the last character. */ 1391 private bool at_bottom () { 1392 Font f; 1393 double t = rows * items_per_row + first_visible; 1394 1395 if (all_available) { 1396 f = BirdFont.get_current_font (); 1397 return t >= f.length (); 1398 } 1399 1400 return t >= glyph_range.length (); 1401 } 1402 1403 public void set_current_glyph_range (GlyphRange range) { 1404 GlyphRange? current = glyph_range; 1405 string c; 1406 1407 if (current != null) { 1408 c = glyph_range.get_char (selected); 1409 } 1410 1411 all_available = false; 1412 1413 glyph_range = range; 1414 scroll_top (); 1415 1416 // TODO: scroll down to c 1417 update_item_list (); 1418 selected_item = get_selected_item (); 1419 1420 GlyphCanvas.redraw (); 1421 } 1422 1423 public void select_next_glyph () { 1424 key_right (); 1425 } 1426 1427 public void open_current_glyph () { 1428 open_overview_item (selected_item); 1429 } 1430 1431 public override void update_scrollbar () { 1432 Font f; 1433 double nrows = 0; 1434 double pos = 0; 1435 double size; 1436 double visible_rows; 1437 1438 if (rows == 0) { 1439 MainWindow.set_scrollbar_size (0); 1440 MainWindow.set_scrollbar_position (0); 1441 } else { 1442 if (all_available) { 1443 f = BirdFont.get_current_font (); 1444 nrows = Math.floor ((f.length ()) / rows); 1445 size = f.length (); 1446 } else { 1447 nrows = Math.floor ((glyph_range.length ()) / rows); 1448 size = glyph_range.length (); 1449 } 1450 1451 if (nrows <= 0) { 1452 nrows = 1; 1453 } 1454 1455 visible_rows = allocation.height / OverViewItem.height; 1456 scroll_size = visible_rows / nrows; 1457 MainWindow.set_scrollbar_size (scroll_size); 1458 pos = first_visible / (nrows * items_per_row - visible_rows * items_per_row); 1459 MainWindow.set_scrollbar_position (pos); 1460 } 1461 } 1462 1463 /** Display one entry from the Unicode Character Database. */ 1464 void draw_character_info (Context cr) 1465 requires (character_info != null) { 1466 double x, y, w, h; 1467 int i; 1468 string unicode_value, unicode_description; 1469 string[] column; 1470 string entry; 1471 int len = 0; 1472 int length = 0; 1473 bool see_also = false; 1474 WidgetAllocation allocation = MainWindow.get_overview ().allocation; 1475 string name; 1476 string[] lines; 1477 double character_start; 1478 double character_height; 1479 1480 entry = ((!)character_info).get_entry (); 1481 lines = entry.split ("\n"); 1482 1483 foreach (string line in entry.split ("\n")) { 1484 len = line.char_count (); 1485 if (len > length) { 1486 length = len; 1487 } 1488 } 1489 1490 x = allocation.width * 0.1; 1491 y = allocation.height * 0.1; 1492 w = allocation.width * 0.9 - x; 1493 h = allocation.height * 0.9 - y; 1494 1495 if (w < 8 * length) { 1496 w = 8 * length; 1497 x = (allocation.width - w) / 2.0; 1498 } 1499 1500 if (x < 0) { 1501 x = 2; 1502 } 1503 1504 // background 1505 cr.save (); 1506 Theme.color_opacity (cr, "Background 1", 0.98); 1507 cr.rectangle (x, y, w, h); 1508 cr.fill (); 1509 cr.restore (); 1510 1511 cr.save (); 1512 Theme.color_opacity (cr, "Foreground 1", 0.98); 1513 cr.set_line_width (2); 1514 cr.rectangle (x, y, w, h); 1515 cr.stroke (); 1516 cr.restore (); 1517 1518 // database entry 1519 1520 if (((!)character_info).is_ligature ()) { 1521 name = ((!)character_info).get_name (); 1522 draw_info_line (name, cr, x, y, 0); 1523 } else { 1524 i = 0; 1525 foreach (string line in lines) { 1526 if (i == 0) { 1527 column = line.split ("\t"); 1528 return_if_fail (column.length == 2); 1529 unicode_value = "U+" + column[0]; 1530 unicode_description = column[1]; 1531 1532 draw_info_line (unicode_description, cr, x, y, i); 1533 i++; 1534 1535 draw_info_line (unicode_value, cr, x, y, i); 1536 i++; 1537 } else { 1538 1539 if (line.has_prefix ("\t*")) { 1540 draw_info_line (line.replace ("\t*", "•"), cr, x, y, i); 1541 i++; 1542 } else if (line.has_prefix ("\tx (")) { 1543 if (!see_also) { 1544 i++; 1545 draw_info_line (t_("See also:"), cr, x, y, i); 1546 i++; 1547 see_also = true; 1548 } 1549 1550 draw_info_line (line.replace ("\tx (", "•").replace (")", ""), cr, x, y, i); 1551 i++; 1552 } else { 1553 i++; 1554 } 1555 } 1556 } 1557 1558 character_start = y + 10 + i * UCD_LINE_HEIGHT; 1559 character_height = h - character_start; 1560 draw_fallback_character (cr, x, character_start, character_height); 1561 } 1562 } 1563 1564 /** Fallback character in UCD info. */ 1565 void draw_fallback_character (Context cr, double x, double y, double height) 1566 requires (character_info != null) { 1567 unichar c = ((!)character_info).unicode; 1568 1569 cr.save (); 1570 Text character = new Text (); 1571 character.set_use_cache (false); 1572 Theme.text_color (character, "Foreground 1"); 1573 character.set_text ((!) c.to_string ()); 1574 character.set_font_size (height); 1575 character.draw_at_top (cr, x + 10, y); 1576 cr.restore (); 1577 } 1578 1579 void draw_info_line (string line, Context cr, double x, double y, int row) { 1580 Text ucd_entry = new Text (line); 1581 cr.save (); 1582 Theme.text_color (ucd_entry, "Foreground 1"); 1583 ucd_entry.widget_x = 10 + x; 1584 ucd_entry.widget_y = 10 + y + row * UCD_LINE_HEIGHT; 1585 ucd_entry.draw (cr); 1586 cr.restore (); 1587 } 1588 1589 public void paste () { 1590 GlyphCollection gc; 1591 GlyphCollection? c; 1592 Glyph glyph; 1593 uint32 index; 1594 int i; 1595 int skip = 0; 1596 int s; 1597 string character_string; 1598 Gee.ArrayList<GlyphCollection> glyps; 1599 Font f; 1600 OverViewUndoItem undo_item; 1601 1602 f = BirdFont.get_current_font (); 1603 gc = new GlyphCollection ('\0', ""); 1604 glyps = new Gee.ArrayList<GlyphCollection> (); 1605 1606 copied_glyphs.sort ((a, b) => { 1607 return (int) ((GlyphCollection) a).get_unicode_character () 1608 - (int) ((GlyphCollection) b).get_unicode_character (); 1609 }); 1610 1611 index = (uint32) first_visible + selected; 1612 for (i = 0; i < copied_glyphs.size; i++) { 1613 if (all_available) { 1614 if (f.length () == 0) { 1615 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1616 copied_glyphs.get (i).is_unassigned (), 1617 copied_glyphs.get (i).get_name ()); 1618 } else if (index >= f.length ()) { 1619 // FIXME: duplicated unicodes? 1620 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1621 copied_glyphs.get (i).is_unassigned (), 1622 copied_glyphs.get (i).get_name ()); 1623 } else { 1624 c = f.get_glyph_collection_index ((uint32) index); 1625 } 1626 1627 if (c == null) { 1628 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1629 copied_glyphs.get (i).is_unassigned (), 1630 copied_glyphs.get (i).get_name ()); 1631 } 1632 1633 return_if_fail (c != null); 1634 gc = (!) c; 1635 } else { 1636 if (i != 0) { 1637 s = (int) copied_glyphs.get (i).get_unicode_character (); 1638 s -= (int) copied_glyphs.get (i - 1).get_unicode_character (); 1639 s -= 1; 1640 skip += s; 1641 } 1642 1643 character_string = glyph_range.get_char ((uint32) (index + skip)); 1644 c = f.get_glyph_collection_by_name (character_string); 1645 1646 if (c == null) { 1647 gc = add_empty_character_to_font (character_string.get_char (), 1648 copied_glyphs.get (i).is_unassigned (), 1649 copied_glyphs.get (i).get_name ()); 1650 } else { 1651 gc = (!) c; 1652 } 1653 } 1654 1655 glyps.add (gc); 1656 index++; 1657 } 1658 1659 undo_item = new OverViewUndoItem (); 1660 undo_item.alternate_sets = f.alternates.copy (); 1661 foreach (GlyphCollection g in glyps) { 1662 undo_item.glyphs.add (g.copy ()); 1663 } 1664 store_undo_items (undo_item); 1665 1666 if (glyps.size != copied_glyphs.size) { 1667 warning ("glyps.size != copied_glyphs.size"); 1668 return; 1669 } 1670 1671 if (copied_glyphs.size < i) { 1672 warning ("Array index out of bounds."); 1673 return; 1674 } 1675 1676 i = 0; 1677 foreach (GlyphCollection g in glyps) { 1678 glyph = copied_glyphs.get (i).get_current ().copy (); 1679 glyph.version_id = (glyph.version_id == -1 || g.length () == 0) ? 1 : g.get_last_id () + 1; 1680 glyph.unichar_code = g.get_unicode_character (); 1681 1682 if (!g.is_unassigned ()) { 1683 glyph.name = (!) glyph.unichar_code.to_string (); 1684 } else { 1685 glyph.name = g.get_name (); 1686 } 1687 1688 g.insert_glyph (glyph, true); 1689 i++; 1690 } 1691 1692 f.touch (); 1693 } 1694 1695 public class OverViewUndoItem { 1696 public AlternateSets alternate_sets = new AlternateSets (); 1697 public Gee.ArrayList<GlyphCollection> glyphs = new Gee.ArrayList<GlyphCollection> (); 1698 } 1699 } 1700 1701 } 1702