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