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