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