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