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