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