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