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