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