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