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 return; 815 816 case Key.RIGHT: 817 key_right (); 818 selected_item = get_selected_item (); 819 820 selected_items.clear (); 821 if (selected_item.glyphs != null) { 822 selected_items.add ((!) selected_item.glyphs); 823 } 824 return; 825 826 case Key.LEFT: 827 key_left (); 828 selected_item = get_selected_item (); 829 830 selected_items.clear (); 831 if (selected_item.glyphs != null) { 832 selected_items.add ((!) selected_item.glyphs); 833 } 834 return; 835 836 case Key.DOWN: 837 key_down (); 838 selected_item = get_selected_item (); 839 840 selected_items.clear (); 841 if (selected_item.glyphs != null) { 842 selected_items.add ((!) selected_item.glyphs); 843 } 844 return; 845 846 case Key.PG_UP: 847 for (int i = 0; i < rows; i++) { 848 key_up (); 849 } 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 return; 857 858 case Key.PG_DOWN: 859 for (int i = 0; i < rows; i++) { 860 key_down (); 861 } 862 selected_item = get_selected_item (); 863 864 selected_items.clear (); 865 if (selected_item.glyphs != null) { 866 selected_items.add ((!) selected_item.glyphs); 867 } 868 return; 869 870 case Key.DEL: 871 delete_selected_glyph (); 872 selected_item = get_selected_item (); 873 return; 874 875 case Key.BACK_SPACE: 876 delete_selected_glyph (); 877 selected_item = get_selected_item (); 878 return; 879 } 880 881 if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { 882 scroll_to_char (keyval); 883 } 884 885 selected_item = get_selected_item (); 886 887 selected_items.clear (); 888 if (selected_item.glyphs != null) { 889 selected_items.add ((!) selected_item.glyphs); 890 } 891 892 update_item_list (); 893 GlyphCanvas.redraw (); 894 } 895 896 public void delete_selected_glyph () { 897 Font font = BirdFont.get_current_font (); 898 OverViewUndoItem undo_item = new OverViewUndoItem (); 899 900 foreach (GlyphCollection g in selected_items) { 901 undo_item.glyphs.add (g.copy ()); 902 } 903 store_undo_items (undo_item); 904 905 foreach (GlyphCollection gc in selected_items) { 906 font.delete_glyph (gc); 907 } 908 909 update_item_list (); 910 GlyphCanvas.redraw (); 911 } 912 913 public override void undo () { 914 Font font = BirdFont.get_current_font (); 915 OverViewUndoItem previous_collection; 916 917 if (undo_items.size == 0) { 918 return; 919 } 920 921 previous_collection = undo_items.get (undo_items.size - 1); 922 redo_items.add (get_current_state (previous_collection)); 923 924 // remove the old glyph and add the new one 925 foreach (GlyphCollection g in previous_collection.glyphs) { 926 font.delete_glyph (g); 927 928 if (g.length () > 0) { 929 font.add_glyph_collection (g); 930 } 931 } 932 933 undo_items.remove_at (undo_items.size - 1); 934 GlyphCanvas.redraw (); 935 } 936 937 public override void redo () { 938 Font font = BirdFont.get_current_font (); 939 OverViewUndoItem previous_collection; 940 941 if (redo_items.size == 0) { 942 return; 943 } 944 945 previous_collection = redo_items.get (redo_items.size - 1); 946 undo_items.add (get_current_state (previous_collection)); 947 948 // remove the old glyph and add the new one 949 foreach (GlyphCollection g in previous_collection.glyphs) { 950 font.delete_glyph (g); 951 font.add_glyph_collection (g); 952 } 953 954 redo_items.remove_at (redo_items.size - 1); 955 GlyphCanvas.redraw (); 956 } 957 958 public OverViewUndoItem get_current_state (OverViewUndoItem previous_collection) { 959 GlyphCollection? gc; 960 OverViewUndoItem ui = new OverViewUndoItem (); 961 Font font = BirdFont.get_current_font (); 962 963 foreach (GlyphCollection g in previous_collection.glyphs) { 964 gc = font.get_glyph_collection (g.get_name ()); 965 966 if (gc != null) { 967 ui.glyphs.add (((!) gc).copy ()); 968 } else { 969 ui.glyphs.add (new GlyphCollection (g.get_unicode_character (), g.get_name ())); 970 } 971 } 972 973 return ui; 974 } 975 976 public void store_undo_state (GlyphCollection gc) { 977 OverViewUndoItem i = new OverViewUndoItem (); 978 i.glyphs.add (gc); 979 store_undo_items (i); 980 } 981 982 public void store_undo_items (OverViewUndoItem i) { 983 undo_items.add (i); 984 redo_items.clear (); 985 } 986 987 bool select_visible_glyph (string name) { 988 int i = 0; 989 990 foreach (OverViewItem o in visible_items) { 991 if (o.get_name () == name) { 992 selected = i; 993 selected_item = get_selected_item (); 994 return true; 995 } 996 997 if (i > 1000) { 998 warning ("selected character not found"); 999 return true; 1000 } 1001 1002 i++; 1003 } 1004 1005 return false; 1006 } 1007 1008 public void scroll_to_char (unichar c) { 1009 StringBuilder s = new StringBuilder (); 1010 1011 if (is_modifier_key (c)) { 1012 return; 1013 } 1014 1015 s.append_unichar (c); 1016 scroll_to_glyph (s.str); 1017 } 1018 1019 public void scroll_to_glyph (string name) { 1020 GlyphRange gr = glyph_range; 1021 int i, r, index; 1022 string ch; 1023 Font font = BirdFont.get_current_font (); 1024 GlyphCollection? glyphs = null; 1025 Glyph glyph; 1026 1027 index = -1; 1028 1029 if (items_per_row <= 0) { 1030 return; 1031 } 1032 1033 ch = name; 1034 1035 // selected char is visible 1036 if (select_visible_glyph (ch)) { 1037 return; 1038 } 1039 1040 // scroll to char 1041 if (all_available) { 1042 1043 // don't search for glyphs in huge CJK fonts 1044 if (font.length () > 300) { 1045 r = 0; 1046 } else { 1047 // FIXME: too slow 1048 for (r = 0; r < font.length (); r += items_per_row) { 1049 for (i = 0; i < items_per_row; i++) { 1050 glyphs = font.get_glyph_collection_indice ((uint32) r + i); 1051 return_if_fail (glyphs != null); 1052 glyph = ((!) glyphs).get_current (); 1053 1054 if (glyph.name == ch) { 1055 index = i; 1056 } 1057 } 1058 1059 if (index > -1) { 1060 break; 1061 } 1062 } 1063 } 1064 } else { 1065 1066 if (ch.char_count () > 1) { 1067 warning ("Can't scroll to ligature in this view"); 1068 return; 1069 } 1070 1071 for (r = 0; r < gr.length (); r += items_per_row) { 1072 for (i = 0; i < items_per_row; i++) { 1073 if (gr.get_char (r + i) == ch) { 1074 index = i; 1075 } 1076 } 1077 1078 if (index > -1) { 1079 break; 1080 } 1081 } 1082 } 1083 1084 if (index > -1) { 1085 first_visible = r; 1086 update_item_list (); 1087 select_visible_glyph (ch); 1088 } 1089 } 1090 1091 public override void double_click (uint button, double ex, double ey) 1092 requires (!is_null (visible_items) && !is_null (allocation)) { 1093 1094 return_if_fail (!is_null (this)); 1095 1096 foreach (OverViewItem i in visible_items) { 1097 if (i.double_click (button, ex, ey)) { 1098 open_overview_item (i); 1099 } 1100 } 1101 1102 GlyphCanvas.redraw (); 1103 } 1104 1105 public void open_overview_item (OverViewItem i) { 1106 if (i.glyphs != null) { 1107 open_glyph_signal ((!) i.glyphs); 1108 ((!) i.glyphs).get_current ().close_path (); 1109 } else { 1110 open_new_glyph_signal (i.character); 1111 } 1112 } 1113 1114 public void set_character_info (CharacterInfo i) { 1115 character_info = i; 1116 } 1117 1118 public int get_selected_index () { 1119 GlyphCollection gc; 1120 int index = 0; 1121 1122 if (selected_items.size == 0) { 1123 return 0; 1124 } 1125 1126 gc = selected_items.get (0); 1127 1128 foreach (OverViewItem i in visible_items) { 1129 if (i.glyphs != null && gc == ((!) i.glyphs)) { 1130 break; 1131 } 1132 1133 index++; 1134 } 1135 1136 return index; 1137 } 1138 1139 public void hide_menu () { 1140 foreach (OverViewItem i in visible_items) { 1141 i.hide_menu (); 1142 } 1143 } 1144 1145 public override void button_press (uint button, double x, double y) { 1146 OverViewItem i; 1147 int index = 0; 1148 int selected_index = -1; 1149 bool update = false; 1150 1151 if (character_info != null) { 1152 character_info = null; 1153 GlyphCanvas.redraw (); 1154 return; 1155 } 1156 1157 for (int j = 0; j < visible_items.size; j++) { 1158 i = visible_items.get (j); 1159 1160 if (i.click (button, x, y)) { 1161 selected = index; 1162 selected_item = get_selected_item (); 1163 1164 if (KeyBindings.has_shift ()) { 1165 if (selected_item.glyphs != null) { 1166 1167 selected_index = selected_items.index_of ((!) selected_item.glyphs); 1168 if (selected_index == -1) { 1169 selected_items.add ((!) selected_item.glyphs); 1170 } else { 1171 return_if_fail (0 <= selected_index < selected_items.size); 1172 selected_items.remove_at (selected_index); 1173 selected = get_selected_index (); 1174 selected_item = get_selected_item (); 1175 } 1176 } 1177 } else { 1178 selected_items.clear (); 1179 if (selected_item.glyphs != null) { 1180 selected_items.add ((!) selected_item.glyphs); 1181 } 1182 } 1183 1184 update = !i.version_menu.menu_visible; 1185 } 1186 index++; 1187 } 1188 1189 if (update) { 1190 update_item_list (); 1191 } 1192 1193 // FIXME: update_item_list (); 1194 GlyphCanvas.redraw (); 1195 } 1196 1197 /** Returns true if overview shows the last character. */ 1198 private bool at_bottom () { 1199 Font f; 1200 double t = rows * items_per_row + first_visible; 1201 1202 if (all_available) { 1203 f = BirdFont.get_current_font (); 1204 return t >= f.length (); 1205 } 1206 1207 return t >= glyph_range.length (); 1208 } 1209 1210 public void set_current_glyph_range (GlyphRange range) { 1211 GlyphRange? current = glyph_range; 1212 string c; 1213 1214 if (current != null) { 1215 c = glyph_range.get_char (selected); 1216 } 1217 1218 all_available = false; 1219 1220 glyph_range = range; 1221 scroll_top (); 1222 1223 // TODO: scroll down to c 1224 update_item_list (); 1225 selected_item = get_selected_item (); 1226 1227 GlyphCanvas.redraw (); 1228 } 1229 1230 public void select_next_glyph () { 1231 key_right (); 1232 } 1233 1234 public void open_current_glyph () { 1235 open_overview_item (selected_item); 1236 } 1237 1238 public override void update_scrollbar () { 1239 Font f; 1240 double nrows = 0; 1241 double pos = 0; 1242 double size; 1243 double visible_rows; 1244 1245 if (rows == 0) { 1246 MainWindow.set_scrollbar_size (0); 1247 MainWindow.set_scrollbar_position (0); 1248 } else { 1249 if (all_available) { 1250 f = BirdFont.get_current_font (); 1251 nrows = Math.floor ((f.length ()) / rows); 1252 size = f.length (); 1253 } else { 1254 nrows = Math.floor ((glyph_range.length ()) / rows); 1255 size = glyph_range.length (); 1256 } 1257 1258 if (nrows <= 0) { 1259 nrows = 1; 1260 } 1261 1262 // FIXME: this is not correct 1263 visible_rows = allocation.height / OverViewItem.height; 1264 scroll_size = visible_rows / nrows; 1265 MainWindow.set_scrollbar_size (scroll_size); 1266 pos = first_visible / (nrows * items_per_row - visible_rows * items_per_row); 1267 MainWindow.set_scrollbar_position (pos); 1268 } 1269 } 1270 1271 /** Display one entry from the Unicode Character Database. */ 1272 void draw_character_info (Context cr) 1273 requires (character_info != null) { 1274 double x, y, w, h; 1275 int i; 1276 string unicode_value, unicode_description; 1277 string[] column; 1278 string entry; 1279 int len = 0; 1280 int length = 0; 1281 bool see_also = false; 1282 WidgetAllocation allocation = MainWindow.get_overview ().allocation; 1283 string name; 1284 string[] lines; 1285 double character_start; 1286 double character_height; 1287 1288 entry = ((!)character_info).get_entry (); 1289 lines = entry.split ("\n"); 1290 1291 foreach (string line in entry.split ("\n")) { 1292 len = line.char_count (); 1293 if (len > length) { 1294 length = len; 1295 } 1296 } 1297 1298 x = allocation.width * 0.1; 1299 y = allocation.height * 0.1; 1300 w = allocation.width * 0.9 - x; 1301 h = allocation.height * 0.9 - y; 1302 1303 if (w < 8 * length) { 1304 w = 8 * length; 1305 x = (allocation.width - w) / 2.0; 1306 } 1307 1308 if (x < 0) { 1309 x = 2; 1310 } 1311 1312 // background 1313 cr.save (); 1314 Theme.color_opacity (cr, "Background 1", 0.98); 1315 cr.rectangle (x, y, w, h); 1316 cr.fill (); 1317 cr.restore (); 1318 1319 cr.save (); 1320 Theme.color_opacity (cr, "Foreground 1", 0.98); 1321 cr.set_line_width (2); 1322 cr.rectangle (x, y, w, h); 1323 cr.stroke (); 1324 cr.restore (); 1325 1326 // database entry 1327 1328 if (((!)character_info).is_ligature ()) { 1329 name = ((!)character_info).get_name (); 1330 draw_info_line (t_("Ligature") + ": " + name, cr, x, y, 0); 1331 } else { 1332 i = 0; 1333 foreach (string line in lines) { 1334 if (i == 0) { 1335 column = line.split ("\t"); 1336 return_if_fail (column.length == 2); 1337 unicode_value = "U+" + column[0]; 1338 unicode_description = column[1]; 1339 1340 draw_info_line (unicode_description, cr, x, y, i); 1341 i++; 1342 1343 draw_info_line (unicode_value, cr, x, y, i); 1344 i++; 1345 } else { 1346 1347 if (line.has_prefix ("\t*")) { 1348 draw_info_line (line.replace ("\t*", "•"), cr, x, y, i); 1349 i++; 1350 } else if (line.has_prefix ("\tx (")) { 1351 if (!see_also) { 1352 i++; 1353 draw_info_line (t_("See also:"), cr, x, y, i); 1354 i++; 1355 see_also = true; 1356 } 1357 1358 draw_info_line (line.replace ("\tx (", "•").replace (")", ""), cr, x, y, i); 1359 i++; 1360 } else { 1361 i++; 1362 } 1363 } 1364 } 1365 1366 character_start = y + 10 + i * UCD_LINE_HEIGHT; 1367 character_height = h - character_start; 1368 draw_fallback_character (cr, x, character_start, character_height); 1369 } 1370 } 1371 1372 /** Fallback character in UCD info. */ 1373 void draw_fallback_character (Context cr, double x, double y, double height) 1374 requires (character_info != null) { 1375 unichar c = ((!)character_info).unicode; 1376 1377 cr.save (); 1378 Text character = new Text (); 1379 Theme.text_color (character, "Foreground 1"); 1380 character.set_text ((!) c.to_string ()); 1381 character.set_font_size (height); 1382 character.draw_at_top (cr, x + 10, y); 1383 cr.restore (); 1384 } 1385 1386 void draw_info_line (string line, Context cr, double x, double y, int row) { 1387 Text ucd_entry = new Text (line); 1388 cr.save (); 1389 Theme.text_color (ucd_entry, "Foreground 1"); 1390 ucd_entry.widget_x = 10 + x; 1391 ucd_entry.widget_y = 10 + y + row * UCD_LINE_HEIGHT; 1392 ucd_entry.draw (cr); 1393 cr.restore (); 1394 } 1395 1396 public void paste () { 1397 GlyphCollection gc = new GlyphCollection ('\0', ""); 1398 GlyphCollection? c; 1399 Glyph glyph; 1400 uint32 index; 1401 int i; 1402 int skip = 0; 1403 int s; 1404 string character_string; 1405 Gee.ArrayList<GlyphCollection> glyps = new Gee.ArrayList<GlyphCollection> (); 1406 Font f = BirdFont.get_current_font (); 1407 OverViewUndoItem undo_item; 1408 1409 copied_glyphs.sort ((a, b) => { 1410 return (int) ((GlyphCollection) a).get_unicode_character () 1411 - (int) ((GlyphCollection) b).get_unicode_character (); 1412 }); 1413 1414 index = (uint32) first_visible + selected; 1415 for (i = 0; i < copied_glyphs.size; i++) { 1416 if (all_available) { 1417 if (f.length () == 0) { 1418 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1419 copied_glyphs.get (i).is_unassigned (), 1420 copied_glyphs.get (i).get_name ()); 1421 } else if (index >= f.length ()) { 1422 // FIXME: duplicated unicodes? 1423 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1424 copied_glyphs.get (i).is_unassigned (), 1425 copied_glyphs.get (i).get_name ()); 1426 } else { 1427 c = f.get_glyph_collection_indice ((uint32) index); 1428 } 1429 1430 if (c == null) { 1431 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (), 1432 copied_glyphs.get (i).is_unassigned (), 1433 copied_glyphs.get (i).get_name ()); 1434 } 1435 1436 return_if_fail (c != null); 1437 gc = (!) c; 1438 } else { 1439 if (i != 0) { 1440 s = (int) copied_glyphs.get (i).get_unicode_character (); 1441 s -= (int) copied_glyphs.get (i - 1).get_unicode_character (); 1442 s -= 1; 1443 skip += s; 1444 } 1445 1446 character_string = glyph_range.get_char ((uint32) (index + skip)); 1447 c = f.get_glyph_collection_by_name (character_string); 1448 1449 if (c == null) { 1450 gc = add_empty_character_to_font (character_string.get_char (), 1451 copied_glyphs.get (i).is_unassigned (), 1452 copied_glyphs.get (i).get_name ()); 1453 } else { 1454 gc = (!) c; 1455 } 1456 } 1457 1458 glyps.add (gc); 1459 index++; 1460 } 1461 1462 undo_item = new OverViewUndoItem (); 1463 foreach (GlyphCollection g in glyps) { 1464 undo_item.glyphs.add (g.copy ()); 1465 } 1466 store_undo_items (undo_item); 1467 1468 if (glyps.size != copied_glyphs.size) { 1469 warning ("glyps.size != copied_glyphs.size"); 1470 return; 1471 } 1472 1473 if (copied_glyphs.size < i) { 1474 warning ("Array index out of bounds."); 1475 return; 1476 } 1477 1478 i = 0; 1479 foreach (GlyphCollection g in glyps) { 1480 glyph = copied_glyphs.get (i).get_current ().copy (); 1481 glyph.version_id = (glyph.version_id == -1 || g.length () == 0) ? 1 : g.get_last_id () + 1; 1482 glyph.unichar_code = g.get_unicode_character (); 1483 1484 if (!g.is_unassigned ()) { 1485 glyph.name = (!) glyph.unichar_code.to_string (); 1486 } else { 1487 glyph.name = g.get_name (); 1488 } 1489 1490 g.insert_glyph (glyph, true); 1491 i++; 1492 } 1493 1494 f.touch (); 1495 } 1496 1497 public class OverViewUndoItem { 1498 public Gee.ArrayList<GlyphCollection> glyphs = new Gee.ArrayList<GlyphCollection> (); 1499 } 1500 } 1501 1502 } 1503