The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

TabBar.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/TabBar.vala.
Cache overview items
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