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