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