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