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