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