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.
Parse more style attributes in SVG files
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