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