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