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