1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Set; 32 33 import com.sun.javafx.collections.UnmodifiableListSet; 34 import javafx.beans.property.BooleanProperty; 35 import javafx.beans.property.DoubleProperty; 36 import javafx.beans.property.ObjectProperty; 37 import javafx.beans.property.ObjectPropertyBase; 38 import javafx.beans.property.SimpleBooleanProperty; 39 import javafx.beans.property.SimpleObjectProperty; 40 import javafx.beans.value.WritableValue; 41 import javafx.collections.FXCollections; 42 import javafx.collections.ListChangeListener; 43 import javafx.collections.ObservableList; 44 import javafx.geometry.Side; 45 import javafx.scene.AccessibleAttribute; 46 import javafx.scene.AccessibleRole; 47 import javafx.css.StyleableDoubleProperty; 48 import javafx.css.CssMetaData; 49 import javafx.css.PseudoClass; 50 51 import javafx.css.converter.SizeConverter; 52 import javafx.scene.control.skin.TabPaneSkin; 53 54 import javafx.beans.DefaultProperty; 55 import javafx.css.Styleable; 56 import javafx.css.StyleableProperty; 57 import javafx.scene.Node; 58 59 /** 60 * <p>A control that allows switching between a group of {@link Tab Tabs}. Only one tab 61 * is visible at a time. Tabs are added to the TabPane by using the {@link #getTabs}.</p> 62 * 63 * <p>Tabs in a TabPane can be positioned at any of the four sides by specifying the 64 * {@link Side}. </p> 65 * 66 * <p>A TabPane has two modes floating or recessed. Applying the styleclass STYLE_CLASS_FLOATING 67 * will change the TabPane mode to floating.</p> 68 * 69 * <p>The tabs width and height can be set to a specific size by 70 * setting the min and max for height and width. TabPane default width will be 71 * determined by the largest content width in the TabPane. This is the same for the height. 72 * If a different size is desired the width and height of the TabPane can 73 * be overridden by setting the min, pref and max size.</p> 74 * 75 * <p>When the number of tabs do not fit the TabPane a menu button will appear on the right. 76 * The menu button is used to select the tabs that are currently not visible. 77 * </p> 78 * 79 * <p>Example:</p> 80 * <pre><code> 81 * TabPane tabPane = new TabPane(); 82 * Tab tab = new Tab(); 83 * tab.setText("new tab"); 84 * tab.setContent(new Rectangle(200,200, Color.LIGHTSTEELBLUE)); 85 * tabPane.getTabs().add(tab); 86 * </code></pre> 87 * 88 * @see Tab 89 * @since JavaFX 2.0 90 */ 91 @DefaultProperty("tabs") 92 public class TabPane extends Control { 93 private static final double DEFAULT_TAB_MIN_WIDTH = 0; 94 95 private static final double DEFAULT_TAB_MAX_WIDTH = Double.MAX_VALUE; 96 97 private static final double DEFAULT_TAB_MIN_HEIGHT = 0; 98 99 private static final double DEFAULT_TAB_MAX_HEIGHT = Double.MAX_VALUE; 100 101 /** 102 * TabPane mode will be changed to floating allowing the TabPane 103 * to be placed alongside other control. 104 */ 105 public static final String STYLE_CLASS_FLOATING = "floating"; 106 107 /** 108 * Constructs a new TabPane. 109 */ 110 public TabPane() { 111 this((Tab[])null); 112 } 113 114 /** 115 * Constructs a new TabPane with the given tabs set to show. 116 * 117 * @param tabs The {@link Tab tabs} to display inside the TabPane. 118 * @since JavaFX 8u40 119 */ 120 public TabPane(Tab... tabs) { 121 getStyleClass().setAll("tab-pane"); 122 setAccessibleRole(AccessibleRole.TAB_PANE); 123 setSelectionModel(new TabPaneSelectionModel(this)); 124 125 this.tabs.addListener((ListChangeListener<Tab>) c -> { 126 while (c.next()) { 127 for (Tab tab : c.getRemoved()) { 128 if (tab != null && !getTabs().contains(tab)) { 129 tab.setTabPane(null); 130 } 131 } 132 133 for (Tab tab : c.getAddedSubList()) { 134 if (tab != null) { 135 tab.setTabPane(TabPane.this); 136 } 137 } 138 } 139 }); 140 141 if (tabs != null) { 142 getTabs().addAll(tabs); 143 } 144 145 // initialize pseudo-class state 146 Side edge = getSide(); 147 pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (edge == Side.TOP)); 148 pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (edge == Side.RIGHT)); 149 pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (edge == Side.BOTTOM)); 150 pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (edge == Side.LEFT)); 151 152 } 153 154 private ObservableList<Tab> tabs = FXCollections.observableArrayList(); 155 156 /** 157 * <p>The tabs to display in this TabPane. Changing this ObservableList will 158 * immediately result in the TabPane updating to display the new contents 159 * of this ObservableList.</p> 160 * 161 * <p>If the tabs ObservableList changes, the selected tab will remain the previously 162 * selected tab, if it remains within this ObservableList. If the previously 163 * selected tab is no longer in the tabs ObservableList, the selected tab will 164 * become the first tab in the ObservableList.</p> 165 */ 166 public final ObservableList<Tab> getTabs() { 167 return tabs; 168 } 169 170 private ObjectProperty<SingleSelectionModel<Tab>> selectionModel = new SimpleObjectProperty<SingleSelectionModel<Tab>>(this, "selectionModel"); 171 172 /** 173 * <p>Sets the model used for tab selection. By changing the model you can alter 174 * how the tabs are selected and which tabs are first or last.</p> 175 */ 176 public final void setSelectionModel(SingleSelectionModel<Tab> value) { selectionModel.set(value); } 177 178 /** 179 * <p>Gets the model used for tab selection.</p> 180 */ 181 public final SingleSelectionModel<Tab> getSelectionModel() { return selectionModel.get(); } 182 183 /** 184 * The selection model used for selecting tabs. 185 */ 186 public final ObjectProperty<SingleSelectionModel<Tab>> selectionModelProperty() { return selectionModel; } 187 188 private ObjectProperty<Side> side; 189 190 /** 191 * <p>The position to place the tabs in this TabPane. Whenever this changes 192 * the TabPane will immediately update the location of the tabs to reflect 193 * this.</p> 194 * 195 */ 196 public final void setSide(Side value) { 197 sideProperty().set(value); 198 } 199 200 /** 201 * The current position of the tabs in the TabPane. The default position 202 * for the tabs is Side.Top. 203 * 204 * @return The current position of the tabs in the TabPane. 205 */ 206 public final Side getSide() { 207 return side == null ? Side.TOP : side.get(); 208 } 209 210 /** 211 * The position of the tabs in the TabPane. 212 */ 213 public final ObjectProperty<Side> sideProperty() { 214 if (side == null) { 215 side = new ObjectPropertyBase<Side>(Side.TOP) { 216 private Side oldSide; 217 @Override protected void invalidated() { 218 219 oldSide = get(); 220 221 pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (oldSide == Side.TOP || oldSide == null)); 222 pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (oldSide == Side.RIGHT)); 223 pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (oldSide == Side.BOTTOM)); 224 pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (oldSide == Side.LEFT)); 225 } 226 227 @Override 228 public Object getBean() { 229 return TabPane.this; 230 } 231 232 @Override 233 public String getName() { 234 return "side"; 235 } 236 }; 237 } 238 return side; 239 } 240 241 private ObjectProperty<TabClosingPolicy> tabClosingPolicy; 242 243 /** 244 * <p>Specifies how the TabPane handles tab closing from an end-users 245 * perspective. The options are:</p> 246 * 247 * <ul> 248 * <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user 249 * <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will 250 * have the option to be closed, with a graphic next to the tab 251 * text being shown. The graphic will disappear when a tab is no 252 * longer selected. 253 * <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be 254 * closed. 255 * </ul> 256 * 257 * <p>Refer to the {@link TabClosingPolicy} enumeration for further details.</p> 258 * 259 * The default closing policy is TabClosingPolicy.SELECTED_TAB 260 */ 261 public final void setTabClosingPolicy(TabClosingPolicy value) { 262 tabClosingPolicyProperty().set(value); 263 } 264 265 /** 266 * The closing policy for the tabs. 267 * 268 * @return The closing policy for the tabs. 269 */ 270 public final TabClosingPolicy getTabClosingPolicy() { 271 return tabClosingPolicy == null ? TabClosingPolicy.SELECTED_TAB : tabClosingPolicy.get(); 272 } 273 274 /** 275 * The closing policy for the tabs. 276 */ 277 public final ObjectProperty<TabClosingPolicy> tabClosingPolicyProperty() { 278 if (tabClosingPolicy == null) { 279 tabClosingPolicy = new SimpleObjectProperty<TabClosingPolicy>(this, "tabClosingPolicy", TabClosingPolicy.SELECTED_TAB); 280 } 281 return tabClosingPolicy; 282 } 283 284 private BooleanProperty rotateGraphic; 285 286 /** 287 * <p>Specifies whether the graphic inside a Tab is rotated or not, such 288 * that it is always upright, or rotated in the same way as the Tab text is.</p> 289 * 290 * <p>By default rotateGraphic is set to false, to represent the fact that 291 * the graphic isn't rotated, resulting in it always appearing upright. If 292 * rotateGraphic is set to {@code true}, the graphic will rotate such that it 293 * rotates with the tab text.</p> 294 * 295 */ 296 public final void setRotateGraphic(boolean value) { 297 rotateGraphicProperty().set(value); 298 } 299 300 /** 301 * Returns {@code true} if the graphic inside a Tab is rotated. The 302 * default is {@code false} 303 * 304 * @return the rotatedGraphic state. 305 */ 306 public final boolean isRotateGraphic() { 307 return rotateGraphic == null ? false : rotateGraphic.get(); 308 } 309 310 /** 311 * The rotatedGraphic state of the tabs in the TabPane. 312 */ 313 public final BooleanProperty rotateGraphicProperty() { 314 if (rotateGraphic == null) { 315 rotateGraphic = new SimpleBooleanProperty(this, "rotateGraphic", false); 316 } 317 return rotateGraphic; 318 } 319 320 private DoubleProperty tabMinWidth; 321 322 /** 323 * <p>The minimum width of the tabs in the TabPane. This can be used to limit 324 * the length of text in tabs to prevent truncation. Setting the min equal 325 * to the max will fix the width of the tab. By default the min equals to the max. 326 * 327 * This value can also be set via CSS using {@code -fx-tab-min-width} 328 * 329 * </p> 330 */ 331 public final void setTabMinWidth(double value) { 332 tabMinWidthProperty().setValue(value); 333 } 334 335 /** 336 * The minimum width of the tabs in the TabPane. 337 * 338 * @return The minimum width of the tabs. 339 */ 340 public final double getTabMinWidth() { 341 return tabMinWidth == null ? DEFAULT_TAB_MIN_WIDTH : tabMinWidth.getValue(); 342 } 343 344 /** 345 * The minimum width of the tabs in the TabPane. 346 */ 347 public final DoubleProperty tabMinWidthProperty() { 348 if (tabMinWidth == null) { 349 tabMinWidth = new StyleableDoubleProperty(DEFAULT_TAB_MIN_WIDTH) { 350 351 @Override 352 public CssMetaData<TabPane,Number> getCssMetaData() { 353 return StyleableProperties.TAB_MIN_WIDTH; 354 } 355 356 @Override 357 public Object getBean() { 358 return TabPane.this; 359 } 360 361 @Override 362 public String getName() { 363 return "tabMinWidth"; 364 } 365 }; 366 } 367 return tabMinWidth; 368 } 369 370 /** 371 * <p>Specifies the maximum width of a tab. This can be used to limit 372 * the length of text in tabs. If the tab text is longer than the maximum 373 * width the text will be truncated. Setting the max equal 374 * to the min will fix the width of the tab. By default the min equals to the max 375 * 376 * This value can also be set via CSS using {@code -fx-tab-max-width}.</p> 377 */ 378 private DoubleProperty tabMaxWidth; 379 public final void setTabMaxWidth(double value) { 380 tabMaxWidthProperty().setValue(value); 381 } 382 383 /** 384 * The maximum width of the tabs in the TabPane. 385 * 386 * @return The maximum width of the tabs. 387 */ 388 public final double getTabMaxWidth() { 389 return tabMaxWidth == null ? DEFAULT_TAB_MAX_WIDTH : tabMaxWidth.getValue(); 390 } 391 392 /** 393 * The maximum width of the tabs in the TabPane. 394 */ 395 public final DoubleProperty tabMaxWidthProperty() { 396 if (tabMaxWidth == null) { 397 tabMaxWidth = new StyleableDoubleProperty(DEFAULT_TAB_MAX_WIDTH) { 398 399 @Override 400 public CssMetaData<TabPane,Number> getCssMetaData() { 401 return StyleableProperties.TAB_MAX_WIDTH; 402 } 403 404 @Override 405 public Object getBean() { 406 return TabPane.this; 407 } 408 409 @Override 410 public String getName() { 411 return "tabMaxWidth"; 412 } 413 }; 414 } 415 return tabMaxWidth; 416 } 417 418 private DoubleProperty tabMinHeight; 419 420 /** 421 * <p>The minimum height of the tabs in the TabPane. This can be used to limit 422 * the height in tabs. Setting the min equal to the max will fix the height 423 * of the tab. By default the min equals to the max. 424 * 425 * This value can also be set via CSS using {@code -fx-tab-min-height} 426 * </p> 427 */ 428 public final void setTabMinHeight(double value) { 429 tabMinHeightProperty().setValue(value); 430 } 431 432 /** 433 * The minimum height of the tabs in the TabPane. 434 * 435 * @return The minimum height of the tabs. 436 */ 437 public final double getTabMinHeight() { 438 return tabMinHeight == null ? DEFAULT_TAB_MIN_HEIGHT : tabMinHeight.getValue(); 439 } 440 441 /** 442 * The minimum height of the tab. 443 */ 444 public final DoubleProperty tabMinHeightProperty() { 445 if (tabMinHeight == null) { 446 tabMinHeight = new StyleableDoubleProperty(DEFAULT_TAB_MIN_HEIGHT) { 447 448 @Override 449 public CssMetaData<TabPane,Number> getCssMetaData() { 450 return StyleableProperties.TAB_MIN_HEIGHT; 451 } 452 453 @Override 454 public Object getBean() { 455 return TabPane.this; 456 } 457 458 @Override 459 public String getName() { 460 return "tabMinHeight"; 461 } 462 }; 463 } 464 return tabMinHeight; 465 } 466 467 /** 468 * <p>The maximum height if the tabs in the TabPane. This can be used to limit 469 * the height in tabs. Setting the max equal to the min will fix the height 470 * of the tab. By default the min equals to the max. 471 * 472 * This value can also be set via CSS using -fx-tab-max-height 473 * </p> 474 */ 475 private DoubleProperty tabMaxHeight; 476 public final void setTabMaxHeight(double value) { 477 tabMaxHeightProperty().setValue(value); 478 } 479 480 /** 481 * The maximum height of the tabs in the TabPane. 482 * 483 * @return The maximum height of the tabs. 484 */ 485 public final double getTabMaxHeight() { 486 return tabMaxHeight == null ? DEFAULT_TAB_MAX_HEIGHT : tabMaxHeight.getValue(); 487 } 488 489 /** 490 * <p>The maximum height of the tabs in the TabPane.</p> 491 */ 492 public final DoubleProperty tabMaxHeightProperty() { 493 if (tabMaxHeight == null) { 494 tabMaxHeight = new StyleableDoubleProperty(DEFAULT_TAB_MAX_HEIGHT) { 495 496 @Override 497 public CssMetaData<TabPane,Number> getCssMetaData() { 498 return StyleableProperties.TAB_MAX_HEIGHT; 499 } 500 501 @Override 502 public Object getBean() { 503 return TabPane.this; 504 } 505 506 @Override 507 public String getName() { 508 return "tabMaxHeight"; 509 } 510 }; 511 } 512 return tabMaxHeight; 513 } 514 515 /** {@inheritDoc} */ 516 @Override protected Skin<?> createDefaultSkin() { 517 return new TabPaneSkin(this); 518 } 519 520 /** {@inheritDoc} */ 521 @Override public Node lookup(String selector) { 522 Node n = super.lookup(selector); 523 if (n == null) { 524 for(Tab tab : tabs) { 525 n = tab.lookup(selector); 526 if (n != null) break; 527 } 528 } 529 return n; 530 } 531 532 /** {@inheritDoc} */ 533 public Set<Node> lookupAll(String selector) { 534 535 if (selector == null) return null; 536 537 final List<Node> results = new ArrayList<>(); 538 539 results.addAll(super.lookupAll(selector)); 540 for(Tab tab : tabs) { 541 results.addAll(tab.lookupAll(selector)); 542 } 543 544 return new UnmodifiableListSet<Node>(results); 545 } 546 547 548 /*************************************************************************** 549 * * 550 * Stylesheet Handling * 551 * * 552 **************************************************************************/ 553 554 private static class StyleableProperties { 555 private static final CssMetaData<TabPane,Number> TAB_MIN_WIDTH = 556 new CssMetaData<TabPane,Number>("-fx-tab-min-width", 557 SizeConverter.getInstance(), DEFAULT_TAB_MIN_WIDTH) { 558 559 @Override 560 public boolean isSettable(TabPane n) { 561 return n.tabMinWidth == null || !n.tabMinWidth.isBound(); 562 } 563 564 @Override 565 public StyleableProperty<Number> getStyleableProperty(TabPane n) { 566 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMinWidthProperty(); 567 } 568 }; 569 570 private static final CssMetaData<TabPane,Number> TAB_MAX_WIDTH = 571 new CssMetaData<TabPane,Number>("-fx-tab-max-width", 572 SizeConverter.getInstance(), DEFAULT_TAB_MAX_WIDTH) { 573 574 @Override 575 public boolean isSettable(TabPane n) { 576 return n.tabMaxWidth == null || !n.tabMaxWidth.isBound(); 577 } 578 579 @Override 580 public StyleableProperty<Number> getStyleableProperty(TabPane n) { 581 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMaxWidthProperty(); 582 } 583 }; 584 585 private static final CssMetaData<TabPane,Number> TAB_MIN_HEIGHT = 586 new CssMetaData<TabPane,Number>("-fx-tab-min-height", 587 SizeConverter.getInstance(), DEFAULT_TAB_MIN_HEIGHT) { 588 589 @Override 590 public boolean isSettable(TabPane n) { 591 return n.tabMinHeight == null || !n.tabMinHeight.isBound(); 592 } 593 594 @Override 595 public StyleableProperty<Number> getStyleableProperty(TabPane n) { 596 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMinHeightProperty(); 597 } 598 }; 599 600 private static final CssMetaData<TabPane,Number> TAB_MAX_HEIGHT = 601 new CssMetaData<TabPane,Number>("-fx-tab-max-height", 602 SizeConverter.getInstance(), DEFAULT_TAB_MAX_HEIGHT) { 603 604 @Override 605 public boolean isSettable(TabPane n) { 606 return n.tabMaxHeight == null || !n.tabMaxHeight.isBound(); 607 } 608 609 @Override 610 public StyleableProperty<Number> getStyleableProperty(TabPane n) { 611 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMaxHeightProperty(); 612 } 613 }; 614 615 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 616 static { 617 final List<CssMetaData<? extends Styleable, ?>> styleables = 618 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); 619 styleables.add(TAB_MIN_WIDTH); 620 styleables.add(TAB_MAX_WIDTH); 621 styleables.add(TAB_MIN_HEIGHT); 622 styleables.add(TAB_MAX_HEIGHT); 623 STYLEABLES = Collections.unmodifiableList(styleables); 624 } 625 } 626 627 /** 628 * @return The CssMetaData associated with this class, which may include the 629 * CssMetaData of its superclasses. 630 * @since JavaFX 8.0 631 */ 632 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 633 return StyleableProperties.STYLEABLES; 634 } 635 636 /** 637 * {@inheritDoc} 638 * @since JavaFX 8.0 639 */ 640 @Override 641 public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 642 return getClassCssMetaData(); 643 } 644 645 private static final PseudoClass TOP_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("top"); 646 private static final PseudoClass BOTTOM_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("bottom"); 647 private static final PseudoClass LEFT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("left"); 648 private static final PseudoClass RIGHT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("right"); 649 650 /*************************************************************************** 651 * * 652 * Support classes * 653 * * 654 **************************************************************************/ 655 656 static class TabPaneSelectionModel extends SingleSelectionModel<Tab> { 657 private final TabPane tabPane; 658 659 public TabPaneSelectionModel(final TabPane t) { 660 if (t == null) { 661 throw new NullPointerException("TabPane can not be null"); 662 } 663 this.tabPane = t; 664 665 // watching for changes to the items list content 666 final ListChangeListener<Tab> itemsContentObserver = c -> { 667 while (c.next()) { 668 for (Tab tab : c.getRemoved()) { 669 if (tab != null && !tabPane.getTabs().contains(tab)) { 670 if (tab.isSelected()) { 671 tab.setSelected(false); 672 final int tabIndex = c.getFrom(); 673 674 // we always try to select the nearest, non-disabled 675 // tab from the position of the closed tab. 676 findNearestAvailableTab(tabIndex, true); 677 } 678 } 679 } 680 if (c.wasAdded() || c.wasRemoved()) { 681 // The selected tab index can be out of sync with the list of tab if 682 // we add or remove tabs before the selected tab. 683 if (getSelectedIndex() != tabPane.getTabs().indexOf(getSelectedItem())) { 684 clearAndSelect(tabPane.getTabs().indexOf(getSelectedItem())); 685 } 686 } 687 } 688 if (getSelectedIndex() == -1 && getSelectedItem() == null && tabPane.getTabs().size() > 0) { 689 // we go looking for the first non-disabled tab, as opposed to 690 // just selecting the first tab (fix for RT-36908) 691 findNearestAvailableTab(0, true); 692 } else if (tabPane.getTabs().isEmpty()) { 693 clearSelection(); 694 } 695 }; 696 if (this.tabPane.getTabs() != null) { 697 this.tabPane.getTabs().addListener(itemsContentObserver); 698 } 699 } 700 701 // API Implementation 702 @Override public void select(int index) { 703 if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) || 704 (index == getSelectedIndex() && getModelItem(index).isSelected())) { 705 return; 706 } 707 708 // Unselect the old tab 709 if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) { 710 tabPane.getTabs().get(getSelectedIndex()).setSelected(false); 711 } 712 713 setSelectedIndex(index); 714 715 Tab tab = getModelItem(index); 716 if (tab != null) { 717 setSelectedItem(tab); 718 } 719 720 // Select the new tab 721 if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) { 722 tabPane.getTabs().get(getSelectedIndex()).setSelected(true); 723 } 724 725 /* Does this get all the change events */ 726 tabPane.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM); 727 } 728 729 @Override public void select(Tab tab) { 730 final int itemCount = getItemCount(); 731 732 for (int i = 0; i < itemCount; i++) { 733 final Tab value = getModelItem(i); 734 if (value != null && value.equals(tab)) { 735 select(i); 736 return; 737 } 738 } 739 if (tab != null) { 740 setSelectedItem(tab); 741 } 742 } 743 744 @Override protected Tab getModelItem(int index) { 745 final ObservableList<Tab> items = tabPane.getTabs(); 746 if (items == null) return null; 747 if (index < 0 || index >= items.size()) return null; 748 return items.get(index); 749 } 750 751 @Override protected int getItemCount() { 752 final ObservableList<Tab> items = tabPane.getTabs(); 753 return items == null ? 0 : items.size(); 754 } 755 756 private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) { 757 // we always try to select the nearest, non-disabled 758 // tab from the position of the closed tab. 759 final int tabCount = getItemCount(); 760 int i = 1; 761 Tab bestTab = null; 762 while (true) { 763 // look leftwards 764 int downPos = tabIndex - i; 765 if (downPos >= 0) { 766 Tab _tab = getModelItem(downPos); 767 if (_tab != null && ! _tab.isDisable()) { 768 bestTab = _tab; 769 break; 770 } 771 } 772 773 // look rightwards. We subtract one as we need 774 // to take into account that a tab has been removed 775 // and if we don't do this we'll miss the tab 776 // to the right of the tab (as it has moved into 777 // the removed tabs position). 778 int upPos = tabIndex + i - 1; 779 if (upPos < tabCount) { 780 Tab _tab = getModelItem(upPos); 781 if (_tab != null && ! _tab.isDisable()) { 782 bestTab = _tab; 783 break; 784 } 785 } 786 787 if (downPos < 0 && upPos >= tabCount) { 788 break; 789 } 790 i++; 791 } 792 793 if (doSelect && bestTab != null) { 794 select(bestTab); 795 } 796 797 return bestTab; 798 } 799 } 800 801 /** 802 * <p>This specifies how the TabPane handles tab closing from an end-users 803 * perspective. The options are:</p> 804 * 805 * <ul> 806 * <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user 807 * <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will 808 * have the option to be closed, with a graphic next to the tab 809 * text being shown. The graphic will disappear when a tab is no 810 * longer selected. 811 * <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be 812 * closed. 813 * </ul> 814 * @since JavaFX 2.0 815 */ 816 public enum TabClosingPolicy { 817 818 /** 819 * Only the currently selected tab will have the option to be closed, with a 820 * graphic next to the tab text being shown. The graphic will disappear when 821 * a tab is no longer selected. 822 */ 823 SELECTED_TAB, 824 825 /** 826 * All tabs will have the option to be closed. 827 */ 828 ALL_TABS, 829 830 /** 831 * Tabs can not be closed by the user. 832 */ 833 UNAVAILABLE 834 } 835 }