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