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