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