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