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