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