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