.
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
444 cr.rectangle (0, 0, allocation.width, allocation.height);
445 cr.fill ();
446 cr.restore ();
447
448 foreach (OverViewItem i in visible_items) {
449 i.draw (allocation, cr);
450 }
451
452 if (visible_items.size == 0) {
453 draw_empty_canvas (allocation, cr);
454 }
455
456 if (character_info != null) {
457 draw_character_info (cr);
458 }
459 }
460
461 void draw_empty_canvas (WidgetAllocation allocation, Context cr) {
462 cr.save ();
463 Theme.color (cr, "Background 7");
464 cr.move_to (30, 40);
465 cr.set_font_size (18);
466 cr.show_text (t_("No glyphs in this view."));
467 cr.restore ();
468 }
469
470 public void scroll_rows (int row_adjustment) {
471 for (int i = 0; i < row_adjustment; i++) {
472 scroll (-OverViewItem.height);
473 }
474
475 for (int i = 0; i > row_adjustment; i--) {
476 scroll (OverViewItem.height);
477 }
478 }
479
480 public void scroll_adjustment (double pixel_adjustment) {
481 double l;
482 Font f;
483
484 if (all_available) {
485 f = BirdFont.get_current_font ();
486 l = f.length ();
487 } else {
488 l = glyph_range.length ();
489 }
490
491 if (first_visible <= 0) {
492 return;
493 }
494
495 if (first_visible + rows * items_per_row >= l) {
496 return;
497 }
498
499 scroll ((int64) pixel_adjustment);
500 }
501
502 void default_position () {
503 scroll_top ();
504 scroll_rows (1);
505 }
506
507 void scroll_to_position (int64 r) {
508 if (r < 0) {
509 scroll_top ();
510 return;
511 }
512
513 default_position ();
514
515 first_visible = (int) r;
516 }
517
518 public override void scroll_to (double position) requires (items_per_row > 0) {
519 double r;
520 int nrows;
521 Font f;
522
523 if (all_available) {
524 f = BirdFont.get_current_font ();
525 nrows = (int) (f.length () / items_per_row);
526 } else {
527 nrows = (int) (glyph_range.length () / items_per_row);
528 }
529
530 view_offset_y = 0;
531 r = (int64) (position * (nrows - rows + 3)); // 3 invisible rows
532 r *= items_per_row;
533
534 scroll_to_position ((int64) r);
535
536 GlyphCanvas.redraw ();
537 }
538
539 private void scroll (double pixel_adjustment) {
540 if (first_visible < 0 && pixel_adjustment < 0) {
541 scroll_top ();
542 return;
543 }
544
545 view_offset_y += pixel_adjustment;
546
547 if (view_offset_y >= 0) {
548 while (view_offset_y > OverViewItem.height) {
549 view_offset_y -= OverViewItem.height;
550 first_visible -= items_per_row;
551 }
552
553 first_visible -= items_per_row;
554 view_offset_y -= OverViewItem.height;
555 } else if (view_offset_y < -OverViewItem.height) {
556 view_offset_y = 0;
557 first_visible += items_per_row;
558 }
559 }
560
561 public void scroll_top () {
562 selected = 0;
563 first_visible = 0;
564
565 if (visible_items.size != 0) {
566 selected_item = get_selected_item ();
567 }
568 }
569
570 /** Returns true if the selected glyph is at the last row. */
571 private bool last_row () {
572 return visible_items.size - selected <= items_per_row;
573 }
574
575 public void key_down () {
576 Font f = BirdFont.get_current_font ();
577 int64 len = (all_available) ? f.length () : glyph_range.length ();
578
579 if (at_bottom () && last_row ()) {
580 return;
581 }
582
583 selected += items_per_row;
584
585 if (selected >= items_per_row * rows) {
586 first_visible += items_per_row;
587 selected -= items_per_row;
588 }
589
590 if (first_visible + selected >= len) {
591 selected = (int) (len - first_visible - 1);
592
593 if (selected < items_per_row * (rows - 1)) {
594 first_visible -= items_per_row;
595 selected += items_per_row;
596 }
597 }
598
599 if (selected >= visible_items.size) {
600 selected = (int) (visible_items.size - 1);
601 }
602
603 selected_item = get_selected_item ();
604 }
605
606 public void key_right () {
607 Font f = BirdFont.get_current_font ();
608 int64 len = (all_available) ? f.length () : glyph_range.length ();
609
610 if (at_bottom () && first_visible + selected + 1 >= len) {
611 selected = (int) (visible_items.size - 1);
612 selected_item = get_selected_item ();
613 return;
614 }
615
616 selected += 1;
617
618 if (selected >= items_per_row * rows) {
619 first_visible += items_per_row;
620 selected -= items_per_row;
621 selected -= 1;
622 }
623
624 if (first_visible + selected > len) {
625 first_visible -= items_per_row;
626 selected = (int) (len - first_visible - 1);
627 selected_item = get_selected_item ();
628 }
629 }
630
631 public void key_up () {
632 selected -= items_per_row;
633
634 if (selected < 0) {
635 first_visible -= items_per_row;
636 selected += items_per_row;
637 }
638
639 if (first_visible < 0) {
640 first_visible = 0;
641 }
642 }
643
644 public void key_left () {
645 selected -= 1;
646
647 if (selected < 0) {
648 first_visible -= items_per_row;
649 selected += items_per_row;
650 selected += 1;
651 }
652
653 if (first_visible < 0) {
654 scroll_top ();
655 }
656 }
657
658 public string get_selected_char () {
659 Font f;
660 Glyph? g;
661
662 if (all_available) {
663 f = BirdFont.get_current_font ();
664 g = f.get_glyph_indice (selected);
665 return_val_if_fail (g != null, "".dup ());
666 return ((!) g).get_name ();
667 }
668
669 return glyph_range.get_char (selected);
670 }
671
672 public override void key_press (uint keyval) {
673 GlyphCanvas.redraw ();
674
675 if (KeyBindings.modifier == CTRL) {
676 return;
677 }
678
679 switch (keyval) {
680 case Key.ENTER:
681 open_current_glyph ();
682 return;
683
684 case Key.UP:
685 key_up ();
686 selected_item = get_selected_item ();
687 return;
688
689 case Key.RIGHT:
690 key_right ();
691 selected_item = get_selected_item ();
692 return;
693
694 case Key.LEFT:
695 key_left ();
696 selected_item = get_selected_item ();
697 return;
698
699 case Key.DOWN:
700 key_down ();
701 selected_item = get_selected_item ();
702 return;
703
704 case Key.PG_UP:
705 for (int i = 0; i < rows; i++) {
706 key_up ();
707 }
708 selected_item = get_selected_item ();
709 return;
710
711 case Key.PG_DOWN:
712 for (int i = 0; i < rows; i++) {
713 key_down ();
714 }
715 selected_item = get_selected_item ();
716 return;
717
718 case Key.DEL:
719 delete_selected_glyph ();
720 selected_item = get_selected_item ();
721 return;
722
723 case Key.BACK_SPACE:
724 delete_selected_glyph ();
725 selected_item = get_selected_item ();
726 return;
727 }
728
729 scroll_to_char (keyval);
730 selected_item = get_selected_item ();
731 }
732
733 public void delete_selected_glyph () {
734 Font font = BirdFont.get_current_font ();
735 OverViewUndoItem undo_item = new OverViewUndoItem ();
736
737 foreach (GlyphCollection g in selected_items) {
738 undo_item.glyphs.add (g.copy ());
739 }
740 store_undo_items (undo_item);
741
742 foreach (GlyphCollection gc in selected_items) {
743 font.delete_glyph (gc);
744 }
745 }
746
747 public override void undo () {
748 Font font = BirdFont.get_current_font ();
749 OverViewUndoItem previous_collection;
750
751 if (undo_items.size == 0) {
752 return;
753 }
754
755 previous_collection = undo_items.get (undo_items.size - 1);
756 redo_items.add (get_current_state (previous_collection));
757
758 // remove the old glyph and add the new one
759 foreach (GlyphCollection g in previous_collection.glyphs) {
760 font.delete_glyph (g);
761
762 if (g.length () > 0) {
763 font.add_glyph_collection (g);
764 }
765 }
766
767 undo_items.remove_at (undo_items.size - 1);
768 GlyphCanvas.redraw ();
769 }
770
771 public override void redo () {
772 Font font = BirdFont.get_current_font ();
773 OverViewUndoItem previous_collection;
774
775 if (redo_items.size == 0) {
776 return;
777 }
778
779 previous_collection = redo_items.get (redo_items.size - 1);
780 undo_items.add (get_current_state (previous_collection));
781
782 // remove the old glyph and add the new one
783 foreach (GlyphCollection g in previous_collection.glyphs) {
784 font.delete_glyph (g);
785 font.add_glyph_collection (g);
786 }
787
788 redo_items.remove_at (redo_items.size - 1);
789 GlyphCanvas.redraw ();
790 }
791
792 public OverViewUndoItem get_current_state (OverViewUndoItem previous_collection) {
793 GlyphCollection? gc;
794 OverViewUndoItem ui = new OverViewUndoItem ();
795 Font font = BirdFont.get_current_font ();
796
797 foreach (GlyphCollection g in previous_collection.glyphs) {
798 gc = font.get_glyph_collection (g.get_name ());
799
800 if (gc != null) {
801 ui.glyphs.add (((!) gc).copy ());
802 } else {
803 ui.glyphs.add (new GlyphCollection (g.get_unicode_character (), g.get_name ()));
804 }
805 }
806
807 return ui;
808 }
809
810 public void store_undo_state (GlyphCollection gc) {
811 OverViewUndoItem i = new OverViewUndoItem ();
812 i.glyphs.add (gc);
813 store_undo_items (i);
814 }
815
816 public void store_undo_items (OverViewUndoItem i) {
817 undo_items.add (i);
818 redo_items.clear ();
819 }
820
821 bool select_visible_glyph (string name) {
822 int i = 0;
823
824 foreach (OverViewItem o in visible_items) {
825 if (o.get_name () == name) {
826 selected = i;
827 selected_item = get_selected_item ();
828 return true;
829 }
830
831 if (i > 1000) {
832 warning ("selected character not found");
833 return true;
834 }
835
836 i++;
837 }
838
839 return false;
840 }
841
842 public void scroll_to_char (unichar c) {
843 StringBuilder s = new StringBuilder ();
844
845 if (is_modifier_key (c)) {
846 return;
847 }
848
849 s.append_unichar (c);
850 scroll_to_glyph (s.str);
851 }
852
853 public void scroll_to_glyph (string name) {
854 GlyphRange gr = glyph_range;
855 int i, r, index;
856 string ch;
857 Font font = BirdFont.get_current_font ();
858 GlyphCollection? glyphs = null;
859 Glyph glyph;
860
861 index = -1;
862
863 if (items_per_row <= 0) {
864 return;
865 }
866
867 ch = name;
868
869 // selected char is visible
870 if (select_visible_glyph (ch)) {
871 return;
872 }
873
874 // scroll to char
875 if (all_available) {
876
877 // don't search for glyphs in huge CJK fonts
878 if (font.length () > 300) {
879 r = 0;
880 } else {
881 // FIXME: too slow
882 for (r = 0; r < font.length (); r += items_per_row) {
883 for (i = 0; i < items_per_row; i++) {
884 glyphs = font.get_glyph_collection_indice ((uint32) r + i);
885 return_if_fail (glyphs != null);
886 glyph = ((!) glyphs).get_current ();
887
888 if (glyph.name == ch) {
889 index = i;
890 }
891 }
892
893 if (index > -1) {
894 break;
895 }
896 }
897 }
898 } else {
899
900 if (ch.char_count () > 1) {
901 warning ("Can't scroll to ligature in this view");
902 return;
903 }
904
905 for (r = 0; r < gr.length (); r += items_per_row) {
906 for (i = 0; i < items_per_row; i++) {
907 if (gr.get_char (r + i) == ch) {
908 index = i;
909 }
910 }
911
912 if (index > -1) {
913 break;
914 }
915 }
916 }
917
918 if (index > -1) {
919 first_visible = r;
920 update_item_list ();
921 select_visible_glyph (ch);
922 }
923 }
924
925 public override void double_click (uint button, double ex, double ey)
926 requires (!is_null (visible_items) && !is_null (allocation)) {
927
928 return_if_fail (!is_null (this));
929
930 foreach (OverViewItem i in visible_items) {
931 if (i.double_click (button, ex, ey)) {
932 open_overview_item (i);
933 }
934 }
935
936 GlyphCanvas.redraw ();
937 }
938
939 public void open_overview_item (OverViewItem i) {
940 if (i.glyphs != null) {
941 open_glyph_signal ((!) i.glyphs);
942 ((!) i.glyphs).get_current ().close_path ();
943 } else {
944 open_new_glyph_signal (i.character);
945 }
946 }
947
948 public void set_character_info (CharacterInfo i) {
949 character_info = i;
950 }
951
952 public int get_selected_index () {
953 GlyphCollection gc;
954 int index = 0;
955
956 if (selected_items.size == 0) {
957 return 0;
958 }
959
960 gc = selected_items.get (0);
961
962 foreach (OverViewItem i in visible_items) {
963
964 if (i.glyphs != null && gc == ((!) i.glyphs)) {
965 break;
966 }
967
968 index++;
969 }
970
971 return index;
972 }
973
974 public override void button_press (uint button, double x, double y) {
975 int index = 0;
976 int selected_index = -1;
977
978 if (character_info != null) {
979 character_info = null;
980 GlyphCanvas.redraw ();
981 return;
982 }
983
984 foreach (OverViewItem i in visible_items) {
985 if (i.click (button, x, y)) {
986 selected = index;
987 selected_item = get_selected_item ();
988
989 if (KeyBindings.has_shift ()) {
990 if (selected_item.glyphs != null) {
991
992 selected_index = selected_items.index_of ((!) selected_item.glyphs);
993 if (selected_index == -1) {
994 selected_items.add ((!) selected_item.glyphs);
995 } else {
996 return_if_fail (0 <= selected_index < selected_items.size);
997 selected_items.remove_at (selected_index);
998 selected = get_selected_index ();
999 selected_item = get_selected_item ();
1000 }
1001 }
1002 } else {
1003 selected_items.clear ();
1004 if (selected_item.glyphs != null) {
1005 selected_items.add ((!) selected_item.glyphs);
1006 }
1007 }
1008 }
1009 index++;
1010 }
1011
1012 update_item_list ();
1013 GlyphCanvas.redraw ();
1014 }
1015
1016 /** Returns true if overview shows the last character. */
1017 private bool at_bottom () {
1018 Font f;
1019 double t = rows * items_per_row + first_visible;
1020
1021 if (all_available) {
1022 f = BirdFont.get_current_font ();
1023 return t >= f.length ();
1024 }
1025
1026 return t >= glyph_range.length ();
1027 }
1028
1029 public void set_glyph_range (GlyphRange range) {
1030 GlyphRange? current = glyph_range;
1031 string c;
1032
1033 if (current != null) {
1034 c = glyph_range.get_char (selected);
1035 }
1036
1037 all_available = false;
1038
1039 glyph_range = range;
1040 scroll_top ();
1041
1042 // TODO: scroll down to c
1043
1044 GlyphCanvas.redraw ();
1045 }
1046
1047 public void select_next_glyph () {
1048 key_right ();
1049 }
1050
1051 public void open_current_glyph () {
1052 open_overview_item (selected_item);
1053 }
1054
1055 public override void update_scrollbar () {
1056 Font f;
1057 double nrows = 0;
1058 double pos = 0;
1059 double size;
1060 double visible_rows;
1061
1062 if (rows == 0) {
1063 MainWindow.set_scrollbar_size (0);
1064 MainWindow.set_scrollbar_position (0);
1065 } else {
1066 if (all_available) {
1067 f = BirdFont.get_current_font ();
1068 nrows = Math.floor ((f.length ()) / rows);
1069 size = f.length ();
1070 } else {
1071 nrows = Math.floor ((glyph_range.length ()) / rows);
1072 size = glyph_range.length ();
1073 }
1074
1075 if (nrows <= 0) {
1076 nrows = 1;
1077 }
1078
1079 // FIXME: this is not correct
1080 visible_rows = allocation.height / OverViewItem.height;
1081 scroll_size = visible_rows / nrows;
1082 MainWindow.set_scrollbar_size (scroll_size);
1083 pos = first_visible / (nrows * items_per_row - visible_rows * items_per_row);
1084 MainWindow.set_scrollbar_position (pos);
1085 }
1086 }
1087
1088 /** Display one entry from the Unicode Character Database. */
1089 void draw_character_info (Context cr)
1090 requires (character_info != null) {
1091 double x, y, w, h;
1092 int i;
1093 string unicode_value, unicode_description;
1094 string[] column;
1095 string entry;
1096 int len = 0;
1097 int length = 0;
1098 bool see_also = false;
1099 WidgetAllocation allocation = MainWindow.get_overview ().allocation;
1100 string name;
1101
1102 entry = ((!)character_info).get_entry ();
1103
1104 foreach (string line in entry.split ("\n")) {
1105 len = line.char_count ();
1106 if (len > length) {
1107 length = len;
1108 }
1109 }
1110
1111 x = allocation.width * 0.1;
1112 y = allocation.height * 0.1;
1113 w = allocation.width * 0.9 - x;
1114 h = allocation.height * 0.9 - y;
1115
1116 if (w < 8 * length) {
1117 w = 8 * length;
1118 x = (allocation.width - w) / 2.0;
1119 }
1120
1121 if (x < 0) {
1122 x = 2;
1123 }
1124
1125 // background
1126 cr.save ();
1127 Theme.color_opacity (cr, "Background 1", 0.98);
1128 cr.rectangle (x, y, w, h);
1129 cr.fill ();
1130 cr.restore ();
1131
1132 cr.save ();
1133 Theme.color_opacity (cr, "Foreground 1", 0.98);
1134 cr.set_line_width (2);
1135 cr.rectangle (x, y, w, h);
1136 cr.stroke ();
1137 cr.restore ();
1138
1139 // database entry
1140
1141 if (((!)character_info).is_ligature ()) {
1142 name = ((!)character_info).get_name ();
1143 draw_info_line (t_("Ligature") + ": " + name, cr, x, y, 0);
1144 } else {
1145 i = 0;
1146 foreach (string line in entry.split ("\n")) {
1147 if (i == 0) {
1148 column = line.split ("\t");
1149 return_if_fail (column.length == 2);
1150 unicode_value = "U+" + column[0];
1151 unicode_description = column[1];
1152
1153 draw_info_line (unicode_description, cr, x, y, i);
1154 i++;
1155
1156 draw_info_line (unicode_value, cr, x, y, i);
1157 i++;
1158 } else {
1159
1160 if (line.has_prefix ("\t*")) {
1161 draw_info_line (line.replace ("\t*", "•"), cr, x, y, i);
1162 i++;
1163 } else if (line.has_prefix ("\tx (")) {
1164 if (!see_also) {
1165 i++;
1166 draw_info_line (t_("See also:"), cr, x, y, i);
1167 i++;
1168 see_also = true;
1169 }
1170
1171 draw_info_line (line.replace ("\tx (", "•").replace (")", ""), cr, x, y, i);
1172 i++;
1173 } else {
1174
1175 i++;
1176 }
1177 }
1178 }
1179 }
1180 }
1181
1182 void draw_info_line (string line, Context cr, double x, double y, int row) {
1183 cr.save ();
1184 cr.set_font_size (12);
1185 Theme.color (cr, "Foreground 1");
1186 cr.move_to (x + 10, y + 28 + row * 18 * 1.2);
1187 cr.show_text (line);
1188 cr.restore ();
1189 }
1190
1191 public void paste () {
1192 GlyphCollection gc = new GlyphCollection ('\0', "");
1193 GlyphCollection? c;
1194 Glyph glyph;
1195 uint32 index;
1196 int i;
1197 int skip = 0;
1198 int s;
1199 string character_string;
1200 Gee.ArrayList<GlyphCollection> glyps = new Gee.ArrayList<GlyphCollection> ();
1201 Font f = BirdFont.get_current_font ();
1202 OverViewUndoItem undo_item;
1203
1204 copied_glyphs.sort ((a, b) => {
1205 return (int) ((GlyphCollection) a).get_unicode_character ()
1206 - (int) ((GlyphCollection) b).get_unicode_character ();
1207 });
1208
1209 index = (uint32) first_visible + selected;
1210 for (i = 0; i < copied_glyphs.size; i++) {
1211 if (all_available) {
1212 if (f.length () == 0) {
1213 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (),
1214 copied_glyphs.get (i).is_unassigned (),
1215 copied_glyphs.get (i).get_name ());
1216 } else if (index >= f.length ()) {
1217 // FIXME: duplicated unicodes?
1218 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (),
1219 copied_glyphs.get (i).is_unassigned (),
1220 copied_glyphs.get (i).get_name ());
1221 } else {
1222 c = f.get_glyph_collection_indice ((uint32) index);
1223 }
1224
1225 if (c == null) {
1226 c = add_empty_character_to_font (copied_glyphs.get (i).get_unicode_character (),
1227 copied_glyphs.get (i).is_unassigned (),
1228 copied_glyphs.get (i).get_name ());
1229 }
1230
1231 return_if_fail (c != null);
1232 gc = (!) c;
1233 } else {
1234 if (i != 0) {
1235 s = (int) copied_glyphs.get (i).get_unicode_character ();
1236 s -= (int) copied_glyphs.get (i - 1).get_unicode_character ();
1237 s -= 1;
1238 skip += s;
1239 }
1240
1241 character_string = glyph_range.get_char ((uint32) (index + skip));
1242 c = f.get_glyph_collection_by_name (character_string);
1243
1244 if (c == null) {
1245 gc = add_empty_character_to_font (character_string.get_char (),
1246 copied_glyphs.get (i).is_unassigned (),
1247 copied_glyphs.get (i).get_name ());
1248 } else {
1249 gc = (!) c;
1250 }
1251 }
1252
1253 glyps.add (gc);
1254 index++;
1255 }
1256
1257 undo_item = new OverViewUndoItem ();
1258 foreach (GlyphCollection g in glyps) {
1259 undo_item.glyphs.add (g.copy ());
1260 }
1261 store_undo_items (undo_item);
1262
1263 if (glyps.size != copied_glyphs.size) {
1264 warning ("glyps.size != copied_glyphs.size");
1265 return;
1266 }
1267
1268 i = 0;
1269 foreach (GlyphCollection g in glyps) {
1270 glyph = copied_glyphs.get (i).get_current ().copy ();
1271 glyph.version_id = (glyph.version_id == -1 || g.length () == 0) ? 1 : g.get_last_id () + 1;
1272 glyph.unichar_code = g.get_unicode_character ();
1273
1274 if (!g.is_unassigned ()) {
1275 glyph.name = (!) glyph.unichar_code.to_string ();
1276 } else {
1277 glyph.name = g.get_name ();
1278 }
1279
1280 g.insert_glyph (glyph, true);
1281 i++;
1282 }
1283
1284 f.touch ();
1285 }
1286
1287 public class OverViewUndoItem {
1288 public Gee.ArrayList<GlyphCollection> glyphs = new Gee.ArrayList<GlyphCollection> ();
1289 }
1290 }
1291
1292 }
1293