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