.
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 public class TabBar : GLib.Object {
20
21 public int width = 0;
22 public int height = 0;
23
24 public Gee.ArrayList<Tab> tabs;
25
26 static const int NO_TAB = -1;
27 static const int NEXT_TAB = -2;
28 static const int PREVIOUS_TAB = -3;
29 static const int PROGRESS_WHEEL = -3;
30 static const int SHOW_MENU = -4;
31
32 int first_tab = 0;
33 int selected = 0;
34 int over = NO_TAB;
35 int over_close_tab = NO_TAB;
36
37 public signal void signal_tab_selected (Tab selected_tab);
38 public signal void redraw_tab_bar (int x, int y, int w, int h);
39
40 Tab? previous_tab = null;
41 Tab? current_tab = null;
42
43 double scale = 1; // scale images in 320 dpi
44
45 bool processing = false;
46 double wheel_rotation = 0;
47
48 double background_r = 51 /255.0;
49 double background_g = 54 /255.0;
50 double background_b = 59 /255.0;
51
52 Text menu_icon;
53 Text progress_icon;
54 Text left_arrow;
55 Text right_arrow;
56
57 public TabBar () {
58 tabs = new Gee.ArrayList<Tab> ();
59
60 menu_icon = new Text ("menu_icon");
61 menu_icon.load_font (Theme.get_icon_file ());
62
63 progress_icon = new Text ("progress");
64 progress_icon.load_font (Theme.get_icon_file ());
65
66 left_arrow = new Text ("left_arrow");
67 left_arrow.load_font (Theme.get_icon_file ());
68
69 right_arrow = new Text ("right_arrow");
70 right_arrow.load_font (Theme.get_icon_file ());
71
72 start_wheel ();
73 }
74
75 public void redraw (int x, int y, int w, int h) {
76 redraw_tab_bar (x, y, w, h);
77 }
78
79 public void set_background_color (double r, double g, double b) {
80 background_r = r;
81 background_g = g;
82 background_b = r;
83 }
84
85 public void motion (double x, double y) {
86 MainWindow.set_cursor (NativeWindow.VISIBLE);
87
88 if (MenuTab.has_suppress_event ()) {
89 warn_if_test ("Event suppressed");
90 return;
91 }
92
93 over_close (x, y, out over, out over_close_tab);
94 }
95
96 private void over_close (double x, double y, out int over, out int over_close_tab) {
97 int i = 0;
98 double offset = 0;
99 bool close_y, close_x;
100
101 if (x < 24 && has_scroll ()) {
102 over_close_tab = NO_TAB;
103 over = PREVIOUS_TAB;
104 return;
105 }
106
107 if (!has_progress_wheel ()) {
108 if (x > width - 25) {
109 over_close_tab = NO_TAB;
110 over = SHOW_MENU;
111 return;
112 }
113 } else if (has_scroll () && has_progress_wheel ()) {
114 if (x > width - 19) {
115 over_close_tab = NO_TAB;
116 over = PROGRESS_WHEEL;
117 } else if (x > width - 2 * 19) {
118 over_close_tab = NO_TAB;
119 over = NEXT_TAB;
120 }
121 } else if (!has_scroll () && has_progress_wheel ()) {
122 if (x > width - 19) {
123 over_close_tab = NO_TAB;
124 over = PROGRESS_WHEEL;
125 }
126 } else if (has_scroll () && !has_progress_wheel ()) {
127 if (x > width - 19) {
128 over_close_tab = NO_TAB;
129 over = NEXT_TAB;
130 }
131 }
132
133 if (has_scroll ()) {
134 offset += 25;
135 }
136
137 foreach (Tab t in tabs) {
138 if (i < first_tab) {
139 i++;
140 continue;
141 }
142
143 if (offset < x < offset + t.get_width ()) {
144 over = i;
145
146 close_y = height / 2.0 - 4 < y < height / 2.0 + 4;
147 close_x = x > offset + t.get_width () - 16;
148
149 if (close_y && close_x) {
150 over_close_tab = i;
151 } else {
152 over_close_tab = NO_TAB;
153 }
154
155 return;
156 }
157
158 offset += t.get_width ();
159 i++;
160 }
161
162 over_close_tab = NO_TAB;
163 over = NO_TAB;
164 }
165
166
167 /** Select tab for a glyph by charcode or name.
168 * @return true if the tab was found
169 */
170 public bool select_char (string s) {
171 int i = 0;
172
173 if (MenuTab.has_suppress_event ()) {
174 warn_if_test ("Event suppressed");
175 return false;
176 }
177
178 foreach (Tab t in tabs) {
179 if (t.get_display ().get_name () == s) {
180 select_tab (i);
181 return true;
182 }
183 i++;
184 }
185
186 return false;
187 }
188
189 public bool select_tab_name (string s) {
190 if (MenuTab.has_suppress_event ()) {
191 warn_if_test ("Event suppressed");
192 return false;
193 }
194
195 return select_char (s);
196 }
197
198 public void select_overview () {
199 if (MenuTab.has_suppress_event ()) {
200 warn_if_test ("Event suppressed");
201 return;
202 }
203
204 select_tab_name ("Overview");
205 }
206
207 private void select_previous_tab () {
208 Tab t;
209 bool open;
210
211 if (MenuTab.has_suppress_event ()) {
212 warn_if_test ("Event suppressed");
213 return;
214 }
215
216 if (previous_tab == null) {
217 return;
218 }
219
220 t = (!) previous_tab;
221 open = selected_open_tab (t);
222
223 if (!open) {
224 select_tab ((int) tabs.size - 1);
225 }
226 }
227
228 public void close_display (FontDisplay f) {
229 int i = -1;
230
231 if (MenuTab.has_suppress_event ()) {
232 warn_if_test ("Event suppressed");
233 return;
234 }
235
236 if (tabs.size >= 1) {
237 foreach (Tab t in tabs) {
238 ++i;
239
240 if (t.get_display () == f) {
241 close_tab (i) ;
242 return;
243 }
244 }
245 }
246
247 return_if_fail (i != -1);
248 }
249
250 public void close_all_tabs () {
251 if (MenuTab.has_suppress_event ()) {
252 warn_if_test ("Event suppressed");
253 return;
254 }
255
256 for (int i = 0; i < get_length (); i++) {
257 if (close_tab (i, false, true)) {
258 close_all_tabs ();
259 }
260 }
261 }
262
263 public bool close_tab (int index, bool background_tab = false, bool select_new_tab = true) {
264 Tab t;
265 EmptyTab empty_tab_canvas;
266 Tab empty_tab;
267 GlyphCollection gc;
268
269 if (MenuTab.has_suppress_event ()) {
270 warn_if_test ("Event suppressed");
271 return false;
272 }
273
274 if (!(0 <= index < tabs.size)) {
275 return false;
276 }
277
278 if (tabs.size == 1) {
279 empty_tab_canvas = new EmptyTab ("", "");
280 gc = new GlyphCollection.with_glyph('\0', "");
281 GlyphCanvas.set_display (empty_tab_canvas);
282 MainWindow.get_glyph_canvas ().set_current_glyph_collection (gc);
283 empty_tab = new Tab (empty_tab_canvas, 0, false);
284 signal_tab_selected (empty_tab);
285 }
286
287 t = tabs.get (index);
288
289 if (first_tab > 0) {
290 first_tab--;
291 }
292
293 if (t.has_close_button ()) {
294 t.get_display ().close ();
295
296 tabs.remove_at (index);
297
298 if (!background_tab && select_new_tab) {
299 select_previous_tab ();
300 }
301
302 return true;
303 }
304
305 if (select_new_tab) {
306 select_tab (index);
307 }
308
309 return false;
310 }
311
312 public bool close_by_name (string name, bool background_tab = false) {
313 int i = 0;
314
315 foreach (var t in tabs) {
316 if (t.get_display ().get_name () == name) {
317 return close_tab (i, background_tab);
318 }
319
320 i++;
321 }
322
323 return false;
324 }
325
326 public void close_background_tab_by_name (string name) {
327 close_by_name (name, true);
328 }
329
330 /** Select a tab and return true if it is open. */
331 public bool selected_open_tab (Tab t) {
332 int i = 0;
333
334 if (MenuTab.has_suppress_event ()) {
335 warn_if_test ("Event suppressed");
336 return false;
337 }
338
339 foreach (var n in tabs) {
340 if (n == t) {
341 select_tab (i);
342 return true;
343 }
344
345 i++;
346 }
347
348 return false;
349 }
350
351 public Tab? get_nth (int i) {
352 if (!(0 <= i < get_length ())) {
353 return null;
354 }
355
356 return tabs.get (i);
357 }
358
359 public Tab? get_tab (string name) {
360 foreach (var n in tabs) {
361 if (n.get_display ().get_name () == name) {
362 return n;
363 }
364 }
365
366 return null;
367 }
368
369 public bool selected_open_tab_by_name (string t) {
370 int i = 0;
371
372 if (MenuTab.has_suppress_event ()) {
373 warn_if_test ("Event suppressed");
374 return false;
375 }
376
377 foreach (var n in tabs) {
378 if (n.get_display ().get_name () == t) {
379 select_tab (i);
380 return true;
381 }
382
383 i++;
384 }
385
386 return false;
387 }
388
389 public Tab get_selected_tab () {
390 int s = get_selected ();
391 if (0 <= s < tabs.size) {
392 return tabs.get (get_selected ());
393 }
394
395 warning ("No tab selected.");
396 return new Tab (new EmptyTab ("Error", "Error"), 30, false);
397 }
398
399 public uint get_length () {
400 return tabs.size;
401 }
402
403 public int get_selected () {
404 return selected;
405 }
406
407 public void select_tab (int index, bool signal_selected = true) {
408 Tab t;
409
410 if (MenuTab.has_suppress_event ()) {
411 warn_if_test ("Event suppressed");
412 return;
413 }
414
415 // always close any pending text input if the user switches tab
416 TabContent.hide_text_input ();
417
418 if (index == SHOW_MENU) {
419 MainWindow.get_menu ().show_menu = !MainWindow.get_menu ().show_menu;
420 GlyphCanvas.redraw ();
421 return;
422 }
423
424 if (index == NEXT_TAB) {
425 selected++;
426
427 if (selected >= tabs.size) {
428 selected = (int) tabs.size - 1;
429 }
430
431 scroll_to_tab (selected);
432 return;
433 }
434
435 if (index == PREVIOUS_TAB) {
436
437 if (selected > 0) {
438 selected--;
439 }
440
441 scroll_to_tab (selected);
442 return;
443 }
444
445 if (!(0 <= index < tabs.size)) {
446 return;
447 }
448
449 selected = index;
450
451 t = tabs.get (index);
452
453 previous_tab = current_tab;
454 current_tab = t;
455
456 scroll_to_tab (selected, signal_selected);
457 }
458
459 private bool has_scroll () {
460 int i = 0;
461 double offset = 19;
462 double end = (has_progress_wheel ()) ? width - 28 : width - 19;
463
464 if (first_tab > 0) {
465 return true;
466 }
467
468 foreach (Tab t in tabs) {
469 if (i < first_tab) {
470 i++;
471 continue;
472 }
473
474 if (offset + t.get_width () + 3 > end) {
475 return true;
476 }
477
478 offset += t.get_width ();
479 i++;
480 }
481
482 return false;
483 }
484
485 private void signal_selected (int index) {
486 Tab t;
487
488 t = tabs.get (index);
489
490 GlyphCanvas.set_display (t.get_display ());
491
492 MainWindow.get_glyph_canvas ()
493 .set_current_glyph_collection (t.get_glyph_collection ());
494
495 signal_tab_selected (t);
496 }
497
498 private void scroll_to_tab (int index, bool send_signal_selected = true) {
499 double offset = 19;
500 int i = 0;
501 double end = (has_progress_wheel ()) ? width - 68 : width - 40;
502
503 if (index < first_tab) {
504 first_tab = index;
505
506 if (send_signal_selected) {
507 signal_selected (index);
508 }
509 return;
510 }
511
512 foreach (Tab t in tabs) {
513
514 if (i < first_tab) {
515 i++;
516 continue;
517 }
518
519 // out of view
520 if (offset + t.get_width () + 3 > end) {
521 first_tab++;
522 scroll_to_tab (index);
523 return;
524 }
525
526 // in view
527 if (i == index) {
528
529 if (send_signal_selected) {
530 signal_selected (index);
531 }
532
533 return;
534 }
535
536 offset += t.get_width ();
537 i++;
538 }
539
540 warning ("");
541 }
542
543 public void select_tab_click (double x, double y, int width, int height) {
544 int over, close;
545
546 if (MainWindow.get_menu ().show_menu) {
547 MainWindow.get_menu ().show_menu = false;
548 GlyphCanvas.redraw ();
549 }
550
551 this.width = width;
552 this.height = height;
553 this.scale = height / 117.0;
554
555 over_close (x, y, out over, out close);
556
557 if (over_close_tab >= 0 && over == selected) {
558 close_tab (over_close_tab);
559 } else {
560 select_tab (over);
561 }
562 }
563
564 public void add_tab (FontDisplay display_item, bool signal_selected = true, GlyphCollection? gc = null) {
565 double tab_width = -1;
566 bool always_open = false;
567 int s = (tabs.size == 0) ? 0 : selected + 1;
568 Tab t;
569
570 if (MenuTab.has_suppress_event ()) {
571 warn_if_test ("Event suppressed");
572 return;
573 }
574
575 if (tab_width < 0) {
576 tab_width = 9 * display_item.get_label ().char_count ();
577 tab_width += 36;
578 }
579
580 t = new Tab (display_item, tab_width, always_open);
581 tabs.insert (s,t);
582
583 if (gc != null) {
584 t.set_glyph_collection ((!) gc);
585 }
586
587 GlyphCanvas.set_display (t.get_display ());
588
589 MainWindow.get_glyph_canvas ()
590 .set_current_glyph_collection (t.get_glyph_collection ());
591
592 select_tab (s, signal_selected);
593 }
594
595 /** Returns true if the new item was added to the bar. */
596 public bool add_unique_tab (FontDisplay display_item, bool signal_selected = true) {
597 bool i;
598
599 if (MenuTab.has_suppress_event ()) {
600 warn_if_test ("Event suppressed");
601 return false;
602 }
603
604 i = select_tab_name (display_item.get_name ());
605
606 if (!i) {
607 add_tab (display_item, signal_selected);
608 return true;
609 }
610
611 return false;
612 }
613
614 public void draw (Context cr, int width, int height) {
615 double next_tab_x;
616 double w, h;
617
618 this.width = width;
619 this.height = height;
620 this.scale = height / 117.0;
621
622 cr.save ();
623 cr.set_line_width (0);
624 Theme.color (cr, "Default Background");
625 cr.rectangle (0, 0, width, height);
626 cr.fill ();
627 cr.restore ();
628
629 cr.save ();
630 cr.scale (scale, scale);
631
632 w = width / scale;
633 h = height / scale;
634
635 if (has_scroll () && !has_progress_wheel ()) {
636 // left arrow
637 Theme.text_color (left_arrow, "Text Tab Bar");
638 left_arrow.set_font_size (40 / scale);
639 left_arrow.widget_x = 2 / scale;
640 left_arrow.widget_y = h / 2.0 - (40 / scale ) / 2;
641 left_arrow.draw (cr);
642
643 // right arrow
644 Theme.text_color (right_arrow, "Text Tab Bar");
645 next_tab_x = (has_progress_wheel ()) ? w - (2 * 19 + 3) / scale : w - 19 / scale;
646 next_tab_x-= 32 / scale;
647
648 right_arrow.set_font_size (40 / scale);
649 right_arrow.widget_x = next_tab_x;
650 right_arrow.widget_y = h / 2.0 - (40 / scale ) / 2;
651 right_arrow.draw (cr);
652 }
653
654 // progress wheel
655 if (has_progress_wheel ()) {
656 double progress_size = 40 / scale;
657
658 Theme.text_color (progress_icon, "Text Tab Bar");
659
660 double middley = h / 2;
661 double middlex = w - (progress_icon.get_sidebearing_extent () / 2) / scale;
662 progress_icon.set_font_size (progress_size);
663 progress_icon.widget_x = middlex;
664 progress_icon.widget_y = middley;
665
666 progress_icon.use_cache (false);
667
668 cr.save ();
669 cr.translate (middlex, middley);
670 cr.rotate (wheel_rotation);
671 cr.translate (-middlex, -middley);
672
673 progress_icon.draw_at_baseline (cr, progress_icon.widget_x, progress_icon.widget_y);
674 cr.restore ();
675 } else {
676 // menu icon
677 if (MainWindow.get_menu ().show_menu) {
678 Theme.color (cr, "Menu Background");
679 cr.rectangle (w - 40 / scale, 0, 40 / scale, h);
680 cr.fill ();
681 }
682
683 if (MainWindow.get_menu ().show_menu) {
684 Theme.text_color (menu_icon, "Foreground Inverted");
685 } else {
686 Theme.text_color (menu_icon, "Highlighted 1");
687 }
688
689 menu_icon.set_font_size (40 / scale);
690 menu_icon.widget_x = (int) (w - 27 / scale);
691 menu_icon.widget_y = (int) (((h - menu_icon.get_height ()) / 2) / scale);
692 menu_icon.draw (cr);
693 }
694
695 draw_tabs (cr);
696 cr.restore ();
697 }
698
699 private void draw_tabs (Context cr) {
700 double text_height, text_width, center_x, center_y;
701 double close_opacity;
702 double offset;
703 double tab_width;
704 double tabs_end = width / scale;
705 double h = height / scale;
706 double tab_height;
707 Tab t;
708 Text label;
709
710 if (has_progress_wheel ()) {
711 tabs_end -= 19 / scale;
712 }
713
714 if (has_scroll ()) {
715 tabs_end -= 60 / scale;
716 offset = 24 / scale;
717 } else {
718 offset = 0;
719 }
720
721 tab_height = this.height / scale;
722
723 for (int tab_index = first_tab; tab_index < tabs.size; tab_index++) {
724 t = tabs.get (tab_index);
725
726 cr.save ();
727 cr.translate (offset, 0);
728
729 tab_width = t.get_width () / scale;
730
731 if (offset + tab_width > tabs_end) {
732 cr.restore ();
733 break;
734 }
735
736 // background
737 if (tab_index == selected) {
738 cr.save ();
739 Theme.color (cr, "Highlighted 1");
740 cr.rectangle (0, 0, tab_width, h);
741 cr.fill ();
742 cr.restore ();
743 } else if (tab_index == over) {
744 cr.save ();
745 Theme.color (cr, "Default Background");
746 cr.rectangle (0, 0, tab_width, h);
747 cr.fill ();
748 cr.restore ();
749 } else {
750 cr.save ();
751 Theme.color (cr, "Default Background");
752 cr.rectangle (0, 0, tab_width, h);
753 cr.fill ();
754 cr.restore ();
755 }
756
757 // close (x)
758 if (t.has_close_button ()) {
759 cr.save ();
760 cr.new_path ();
761 cr.set_line_width (1 / scale);
762
763 close_opacity = (over_close_tab == tab_index) ? 1 : 0.2;
764
765 if (tab_index == selected) {
766 Theme.color_opacity (cr, "Selected Tab Foreground", close_opacity);
767 } else {
768 Theme.color_opacity (cr, "Text Foreground", close_opacity);
769 }
770
771 cr.move_to (tab_width - 7 / scale, h / 2.0 - 2.5 / scale);
772 cr.line_to (tab_width - 12 / scale, h / 2.0 + 2.5 / scale);
773
774 cr.move_to (tab_width - 12 / scale, h / 2.0 - 2.5 / scale);
775 cr.line_to (tab_width - 7 / scale, h / 2.0 + 2.5 / scale);
776
777 cr.stroke ();
778 cr.restore ();
779 }
780
781 // tab label
782 label = new Text ();
783 label.use_cache (false);
784 label.set_text (t.get_label ());
785 text_height = (int) (16 / scale);
786 label.set_font_size (text_height);
787 text_width = label.get_extent ();
788 center_x = tab_width / 2.0 - text_width / 2.0;
789 center_y = (int) (tab_height / 2.0 + 4 / scale);
790
791 if (tab_index == selected) {
792 Theme.text_color (label, "Selected Tab Foreground");
793 } else {
794 Theme.text_color (label, "Text Tab Bar");
795 }
796
797 label.set_font_size (text_height);
798 label.draw_at_baseline (cr, center_x, center_y);
799
800 // edges
801 if (tab_index != selected) { // don't draw edges for the selected tab
802 if (tab_index + 1 != selected) {
803 cr.save ();
804 Theme.color (cr, "Tab Separator");
805 cr.rectangle (tab_width - 1 / scale, 0, 1 / scale, h);
806 cr.fill ();
807 cr.restore ();
808 }
809
810 if (tab_index == first_tab) {
811 cr.save ();
812 Theme.color (cr, "Tab Separator");
813 cr.rectangle (0, 0, 1 / scale, h);
814 cr.fill ();
815 cr.restore ();
816 }
817 }
818
819 cr.restore ();
820
821 offset += tab_width;
822 }
823 }
824
825 public void add_empty_tab (string name, string label) {
826 add_tab (new EmptyTab (name, label));
827 }
828
829 bool has_progress_wheel () {
830 return processing;
831 }
832
833 public void set_progress (bool running) {
834 TimeoutSource timer;
835
836 if (unlikely (processing == running)) {
837 warning (@"Progress is already set to $running");
838 return;
839 }
840
841 processing = running;
842
843 if (processing) {
844 timer = new TimeoutSource (50);
845 timer.set_callback (() => {
846 wheel_rotation += 0.008 * 2 * Math.PI;
847
848 if (wheel_rotation > 2 * Math.PI) {
849 wheel_rotation -= 2 * Math.PI;
850 }
851
852 redraw_tab_bar (width - 40, 0, 40, height);
853 return processing;
854 });
855 timer.attach (null);
856 }
857 }
858
859 public static void start_wheel () {
860 TabBar t;
861 if (!is_null (MainWindow.get_tab_bar ())) {
862 t = MainWindow.get_tab_bar ();
863 t.set_progress (true);
864 }
865 }
866
867 public static void stop_wheel () {
868 if (!is_null (MainWindow.get_tab_bar ())) {
869 MainWindow.get_tab_bar ().set_progress (false);
870 }
871 }
872 }
873
874 }
875