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