1 /*
   2  * Copyright (c) 2010, 2019, 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 javafx.beans.property.ObjectProperty;
  29 import javafx.beans.property.ObjectPropertyBase;
  30 import javafx.beans.property.SimpleObjectProperty;
  31 import javafx.beans.value.ChangeListener;
  32 import javafx.collections.FXCollections;
  33 import javafx.collections.ListChangeListener;
  34 import javafx.collections.ObservableList;
  35 import javafx.beans.property.ReadOnlyBooleanProperty;
  36 import javafx.beans.property.ReadOnlyBooleanWrapper;
  37 import javafx.event.ActionEvent;
  38 import javafx.event.Event;
  39 import javafx.event.EventHandler;
  40 import javafx.event.EventType;
  41 import javafx.scene.AccessibleAction;
  42 import javafx.scene.AccessibleAttribute;
  43 import javafx.scene.AccessibleRole;
  44 import javafx.util.StringConverter;
  45 import javafx.css.PseudoClass;
  46 
  47 import javafx.scene.control.skin.ChoiceBoxSkin;
  48 
  49 import javafx.beans.DefaultProperty;
  50 
  51 /**
  52  * The ChoiceBox is used for presenting the user with a relatively small set of
  53  * predefined choices from which they may choose. The ChoiceBox, when "showing",
  54  * will display to the user these choices and allow them to pick exactly one
  55  * choice. When not showing, the current choice is displayed.
  56  * <p>
  57  * By default, the ChoiceBox has no item selected unless otherwise specified.
  58  * Although the ChoiceBox will only allow a user to select from the predefined
  59  * list, it is possible for the developer to specify the selected item to be
  60  * something other than what is available in the predefined list. This is
  61  * required for several important use cases.
  62  * <p>
  63  * It means configuration of the ChoiceBox is order independent. You
  64  * may either specify the items and then the selected item, or you may
  65  * specify the selected item and then the items. Either way will function
  66  * correctly.
  67  * <p>
  68  * ChoiceBox item selection is handled by
  69  * {@link javafx.scene.control.SelectionModel SelectionModel}
  70  * As with ListView and ComboBox, it is possible to modify the
  71  * {@link javafx.scene.control.SelectionModel SelectionModel} that is used,
  72  * although this is likely to be rarely changed. ChoiceBox supports only a
  73  * single selection model, hence the default used is a {@link SingleSelectionModel}.
  74  *
  75  * <p>
  76  * Example:
  77  * <pre> ChoiceBox cb = new ChoiceBox();
  78  * cb.getItems().addAll("item1", "item2", "item3");</pre>
  79  *
  80  * <img src="doc-files/ChoiceBox.png" alt="Image of the ChoiceBox control">
  81  *
  82  * @since JavaFX 2.0
  83  */
  84 @DefaultProperty("items")
  85 public class ChoiceBox<T> extends Control {
  86 
  87     /***************************************************************************
  88      *                                                                         *
  89      * Static properties and methods                                           *
  90      *                                                                         *
  91      **************************************************************************/
  92 
  93     /**
  94      * Called prior to the ChoiceBox showing its popup after the user
  95      * has clicked or otherwise interacted with the ChoiceBox.
  96      * @since JavaFX 8u60
  97      */
  98     public static final EventType<Event> ON_SHOWING =
  99             new EventType<Event>(Event.ANY, "CHOICE_BOX_ON_SHOWING");
 100 
 101     /**
 102      * Called after the ChoiceBox has shown its popup.
 103      * @since JavaFX 8u60
 104      */
 105     public static final EventType<Event> ON_SHOWN =
 106             new EventType<Event>(Event.ANY, "CHOICE_BOX_ON_SHOWN");
 107 
 108     /**
 109      * Called when the ChoiceBox popup <b>will</b> be hidden.
 110      * @since JavaFX 8u60
 111      */
 112     public static final EventType<Event> ON_HIDING =
 113             new EventType<Event>(Event.ANY, "CHOICE_BOX_ON_HIDING");
 114 
 115     /**
 116      * Called when the ChoiceBox popup has been hidden.
 117      * @since JavaFX 8u60
 118      */
 119     public static final EventType<Event> ON_HIDDEN =
 120             new EventType<Event>(Event.ANY, "CHOICE_BOX_ON_HIDDEN");
 121 
 122 
 123 
 124     /***************************************************************************
 125      *                                                                         *
 126      * Constructors                                                            *
 127      *                                                                         *
 128      **************************************************************************/
 129 
 130     /**
 131      * Create a new ChoiceBox which has an empty list of items.
 132      */
 133     public ChoiceBox() {
 134         this(FXCollections.<T>observableArrayList());
 135     }
 136 
 137     /**
 138      * Create a new ChoiceBox with the given set of items. Since it is observable,
 139      * the content of this list may change over time and the ChoiceBox will
 140      * be updated accordingly.
 141      * @param items the set of items
 142      */
 143     public ChoiceBox(ObservableList<T> items) {
 144         getStyleClass().setAll("choice-box");
 145         setAccessibleRole(AccessibleRole.COMBO_BOX);
 146         setItems(items);
 147         setSelectionModel(new ChoiceBoxSelectionModel<T>(this));
 148 
 149         // listen to the value property, if the value is
 150         // set to something that exists in the items list, update the
 151         // selection model to indicate that this is the selected item
 152         valueProperty().addListener((ov, t, t1) -> {
 153             if (getItems() == null) return;
 154             int index = getItems().indexOf(t1);
 155             if (index > -1) {
 156                 getSelectionModel().select(index);
 157             }
 158         });
 159     }
 160 
 161     /***************************************************************************
 162      *                                                                         *
 163      * Properties                                                              *
 164      *                                                                         *
 165      **************************************************************************/
 166 
 167     /**
 168      * The selection model for the ChoiceBox. Only a single choice can be made,
 169      * hence, the ChoiceBox supports only a SingleSelectionModel. Generally, the
 170      * main interaction with the selection model is to explicitly set which item
 171      * in the items list should be selected, or to listen to changes in the
 172      * selection to know which item has been chosen.
 173      */
 174     private ObjectProperty<SingleSelectionModel<T>> selectionModel =
 175             new SimpleObjectProperty<SingleSelectionModel<T>>(this, "selectionModel") {
 176          private SelectionModel<T> oldSM = null;
 177         @Override protected void invalidated() {
 178             if (oldSM != null) {
 179                 oldSM.selectedItemProperty().removeListener(selectedItemListener);
 180             }
 181             SelectionModel<T> sm = get();
 182             oldSM = sm;
 183             if (sm != null) {
 184                 sm.selectedItemProperty().addListener(selectedItemListener);
 185                 if (sm.getSelectedItem() != null && ! valueProperty().isBound()) {
 186                     ChoiceBox.this.setValue(sm.getSelectedItem());
 187                 }
 188             }
 189         }
 190     };
 191 
 192     private ChangeListener<T> selectedItemListener = (ov, t, t1) -> {
 193         if (! valueProperty().isBound()) {
 194             setValue(t1);
 195         }
 196     };
 197 
 198 
 199     public final void setSelectionModel(SingleSelectionModel<T> value) { selectionModel.set(value); }
 200     public final SingleSelectionModel<T> getSelectionModel() { return selectionModel.get(); }
 201     public final ObjectProperty<SingleSelectionModel<T>> selectionModelProperty() { return selectionModel; }
 202 
 203 
 204     /**
 205      * Indicates whether the drop down is displaying the list of choices to the
 206      * user. This is a readonly property which should be manipulated by means of
 207      * the #show and #hide methods.
 208      */
 209     private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() {
 210         @Override protected void invalidated() {
 211             pseudoClassStateChanged(SHOWING_PSEUDOCLASS_STATE, get());
 212             notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
 213         }
 214 
 215         @Override
 216         public Object getBean() {
 217             return ChoiceBox.this;
 218         }
 219 
 220         @Override
 221         public String getName() {
 222             return "showing";
 223         }
 224     };
 225     public final boolean isShowing() { return showing.get(); }
 226     public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
 227     private void setShowing(boolean value) {
 228         // these events will not fire if the showing property is bound
 229         Event.fireEvent(this, value ? new Event(ON_SHOWING) :
 230                 new Event(ON_HIDING));
 231         showing.set(value);
 232         Event.fireEvent(this, value ? new Event(ON_SHOWN) :
 233                 new Event(ON_HIDDEN));
 234     }
 235 
 236     /**
 237      * The items to display in the choice box. The selected item (as indicated in the
 238      * selection model) must always be one of these items.
 239      */
 240     private ObjectProperty<ObservableList<T>> items = new ObjectPropertyBase<ObservableList<T>>() {
 241         ObservableList<T> old;
 242         @Override protected void invalidated() {
 243             final ObservableList<T> newItems = get();
 244             if (old != newItems) {
 245                 // Add and remove listeners
 246                 if (old != null) old.removeListener(itemsListener);
 247                 if (newItems != null) newItems.addListener(itemsListener);
 248                 // Clear the selection model
 249                 final SingleSelectionModel<T> sm = getSelectionModel();
 250                 if (sm != null) {
 251                     if (newItems != null && newItems.isEmpty()) {
 252                         // RT-29433 - clear selection.
 253                         sm.clearSelection();
 254                     } else if (sm.getSelectedIndex() == -1 && sm.getSelectedItem() != null) {
 255                         int newIndex = getItems().indexOf(sm.getSelectedItem());
 256                         if (newIndex != -1) {
 257                             sm.setSelectedIndex(newIndex);
 258                         }
 259                     } else sm.clearSelection();
 260                 }
 261 //                if (sm != null) sm.setSelectedIndex(-1);
 262                 // Save off the old items
 263                 old = newItems;
 264             }
 265         }
 266 
 267         @Override
 268         public Object getBean() {
 269             return ChoiceBox.this;
 270         }
 271 
 272         @Override
 273         public String getName() {
 274             return "items";
 275         }
 276     };
 277     public final void setItems(ObservableList<T> value) { items.set(value); }
 278     public final ObservableList<T> getItems() { return items.get(); }
 279     public final ObjectProperty<ObservableList<T>> itemsProperty() { return items; }
 280 
 281     private final ListChangeListener<T> itemsListener = c -> {
 282         final SingleSelectionModel<T> sm = getSelectionModel();
 283         if (sm!= null) {
 284             if (getItems() == null || getItems().isEmpty()) {
 285                 sm.clearSelection();
 286             } else {
 287                 int newIndex = getItems().indexOf(sm.getSelectedItem());
 288                 sm.setSelectedIndex(newIndex);
 289             }
 290         }
 291         if (sm != null) {
 292 
 293             // Look for the selected item as having been removed. If it has been,
 294             // then we need to clear the selection in the selection model.
 295             final T selectedItem = sm.getSelectedItem();
 296             while (c.next()) {
 297                 if (selectedItem != null && c.getRemoved().contains(selectedItem)) {
 298                     sm.clearSelection();
 299                     break;
 300                     }
 301             }
 302         }
 303     };
 304 
 305     /**
 306      * Allows a way to specify how to represent objects in the items list. When
 307      * a StringConverter is set, the object toString method is not called and
 308      * instead its toString(object T) is called, passing the objects in the items list.
 309      * This is useful when using domain objects in a ChoiceBox as this property
 310      * allows for customization of the representation. Also, any of the pre-built
 311      * Converters available in the {@link javafx.util.converter} package can be set.
 312      * @return the string converter property
 313      * @since JavaFX 2.1
 314      */
 315     public ObjectProperty<StringConverter<T>> converterProperty() { return converter; }
 316     private ObjectProperty<StringConverter<T>> converter =
 317             new SimpleObjectProperty<StringConverter<T>>(this, "converter", null);
 318     public final void setConverter(StringConverter<T> value) { converterProperty().set(value); }
 319     public final StringConverter<T> getConverter() {return converterProperty().get(); }
 320 
 321     /**
 322      * The value of this ChoiceBox is defined as the selected item in the ChoiceBox
 323      * selection model. The valueProperty is synchronized with the selectedItem.
 324      * This property allows for bi-directional binding of external properties to the
 325      * ChoiceBox and updates the selection model accordingly.
 326      * @return the value property
 327      * @since JavaFX 2.1
 328      */
 329     public ObjectProperty<T> valueProperty() { return value; }
 330     private ObjectProperty<T> value = new SimpleObjectProperty<T>(this, "value") {
 331         @Override protected void invalidated() {
 332             super.invalidated();
 333             fireEvent(new ActionEvent());
 334             // Update selection
 335             final SingleSelectionModel<T> sm = getSelectionModel();
 336             if (sm != null) {
 337                 sm.select(super.getValue());
 338             }
 339             notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
 340         }
 341     };
 342     public final void setValue(T value) { valueProperty().set(value); }
 343     public final T getValue() { return valueProperty().get(); }
 344 
 345 
 346     // --- On Action
 347     /**
 348      * The ChoiceBox action, which is invoked whenever the ChoiceBox
 349      * {@link #valueProperty() value} property is changed. This
 350      * may be due to the value property being programmatically changed or when the
 351      * user selects an item in a popup menu.
 352      *
 353      * @return the on action property
 354      * @since JavaFX 8u60
 355      */
 356     public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
 357     public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
 358     public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
 359     private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
 360         @Override protected void invalidated() {
 361             setEventHandler(ActionEvent.ACTION, get());
 362         }
 363 
 364         @Override
 365         public Object getBean() {
 366             return ChoiceBox.this;
 367         }
 368 
 369         @Override
 370         public String getName() {
 371             return "onAction";
 372         }
 373     };
 374 
 375 
 376     // --- On Showing
 377     /**
 378      * Called just prior to the {@code ChoiceBox} popup being shown.
 379      * @return the on showing property
 380      * @since JavaFX 8u60
 381      */
 382     public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; }
 383     public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); }
 384     public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); }
 385     private ObjectProperty<EventHandler<Event>> onShowing = new ObjectPropertyBase<EventHandler<Event>>() {
 386         @Override protected void invalidated() {
 387             setEventHandler(ON_SHOWING, get());
 388         }
 389 
 390         @Override public Object getBean() {
 391             return ChoiceBox.this;
 392         }
 393 
 394         @Override public String getName() {
 395             return "onShowing";
 396         }
 397     };
 398 
 399 
 400     // -- On Shown
 401     /**
 402      * Called just after the {@link ChoiceBox} popup is shown.
 403      * @return the on shown property
 404      * @since JavaFX 8u60
 405      */
 406     public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; }
 407     public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); }
 408     public final EventHandler<Event> getOnShown() { return onShownProperty().get(); }
 409     private ObjectProperty<EventHandler<Event>> onShown = new ObjectPropertyBase<EventHandler<Event>>() {
 410         @Override protected void invalidated() {
 411             setEventHandler(ON_SHOWN, get());
 412         }
 413 
 414         @Override public Object getBean() {
 415             return ChoiceBox.this;
 416         }
 417 
 418         @Override public String getName() {
 419             return "onShown";
 420         }
 421     };
 422 
 423 
 424     // --- On Hiding
 425     /**
 426      * Called just prior to the {@link ChoiceBox} popup being hidden.
 427      * @return the on hiding property
 428      * @since JavaFX 8u60
 429      */
 430     public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; }
 431     public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); }
 432     public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); }
 433     private ObjectProperty<EventHandler<Event>> onHiding = new ObjectPropertyBase<EventHandler<Event>>() {
 434         @Override protected void invalidated() {
 435             setEventHandler(ON_HIDING, get());
 436         }
 437 
 438         @Override public Object getBean() {
 439             return ChoiceBox.this;
 440         }
 441 
 442         @Override public String getName() {
 443             return "onHiding";
 444         }
 445     };
 446 
 447 
 448     // --- On Hidden
 449     /**
 450      * Called just after the {@link ChoiceBox} popup has been hidden.
 451      * @return the on hidden property
 452      * @since JavaFX 8u60
 453      */
 454     public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; }
 455     public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); }
 456     public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); }
 457     private ObjectProperty<EventHandler<Event>> onHidden = new ObjectPropertyBase<EventHandler<Event>>() {
 458         @Override protected void invalidated() {
 459             setEventHandler(ON_HIDDEN, get());
 460         }
 461 
 462         @Override public Object getBean() {
 463             return ChoiceBox.this;
 464         }
 465 
 466         @Override public String getName() {
 467             return "onHidden";
 468         }
 469     };
 470 
 471     /***************************************************************************
 472      *                                                                         *
 473      * Methods                                                                 *
 474      *                                                                         *
 475      **************************************************************************/
 476 
 477     /**
 478      * Opens the list of choices.
 479      */
 480     public void show() {
 481         if (!isDisabled()) setShowing(true);
 482     }
 483 
 484     /**
 485      * Closes the list of choices.
 486      */
 487     public void hide() {
 488         setShowing(false);
 489     }
 490 
 491     /** {@inheritDoc} */
 492     @Override protected Skin<?> createDefaultSkin() {
 493         return new ChoiceBoxSkin<T>(this);
 494     }
 495 
 496     /***************************************************************************
 497      *                                                                         *
 498      * Stylesheet Handling                                                     *
 499      *                                                                         *
 500      **************************************************************************/
 501 
 502     private static final PseudoClass SHOWING_PSEUDOCLASS_STATE =
 503             PseudoClass.getPseudoClass("showing");
 504 
 505     // package for testing
 506     static class ChoiceBoxSelectionModel<T> extends SingleSelectionModel<T> {
 507         private final ChoiceBox<T> choiceBox;
 508 
 509         public ChoiceBoxSelectionModel(final ChoiceBox<T> cb) {
 510             if (cb == null) {
 511                 throw new NullPointerException("ChoiceBox can not be null");
 512             }
 513             this.choiceBox = cb;
 514 
 515             /*
 516              * The following two listeners are used in conjunction with
 517              * SelectionModel.select(T obj) to allow for a developer to select
 518              * an item that is not actually in the data model. When this occurs,
 519              * we actively try to find an index that matches this object, going
 520              * so far as to actually watch for all changes to the items list,
 521              * rechecking each time.
 522              */
 523 
 524             // watching for changes to the items list content
 525             final ListChangeListener<T> itemsContentObserver = c -> {
 526                 if (choiceBox.getItems() == null || choiceBox.getItems().isEmpty()) {
 527                     setSelectedIndex(-1);
 528                 } else if (getSelectedIndex() == -1 && getSelectedItem() != null) {
 529                     int newIndex = choiceBox.getItems().indexOf(getSelectedItem());
 530                     if (newIndex != -1) {
 531                         setSelectedIndex(newIndex);
 532                     }
 533                 }
 534             };
 535             if (this.choiceBox.getItems() != null) {
 536                 this.choiceBox.getItems().addListener(itemsContentObserver);
 537             }
 538 
 539             // watching for changes to the items list
 540             ChangeListener<ObservableList<T>> itemsObserver = (valueModel, oldList, newList) -> {
 541                 if (oldList != null) {
 542                     oldList.removeListener(itemsContentObserver);
 543                 }
 544                 if (newList != null) {
 545                     newList.addListener(itemsContentObserver);
 546                 }
 547                 setSelectedIndex(-1);
 548                 if (getSelectedItem() != null) {
 549                     int newIndex = choiceBox.getItems().indexOf(getSelectedItem());
 550                     if (newIndex != -1) {
 551                         setSelectedIndex(newIndex);
 552                     }
 553                 }
 554             };
 555             this.choiceBox.itemsProperty().addListener(itemsObserver);
 556         }
 557 
 558         // API Implementation
 559         @Override protected T getModelItem(int index) {
 560             final ObservableList<T> items = choiceBox.getItems();
 561             if (items == null) return null;
 562             if (index < 0 || index >= items.size()) return null;
 563             return items.get(index);
 564         }
 565 
 566         @Override protected int getItemCount() {
 567             final ObservableList<T> items = choiceBox.getItems();
 568             return items == null ? 0 : items.size();
 569         }
 570 
 571         /**
 572          * Selects the given row. Since the SingleSelectionModel can only support having
 573          * a single row selected at a time, this also causes any previously selected
 574          * row to be unselected.
 575          * This method is overridden here so that we can move past a Separator
 576          * in a ChoiceBox and select the next valid menuitem.
 577          */
 578         @Override public void select(int index) {
 579             // this does not sound right, we should let the superclass handle it.
 580             super.select(index);
 581 
 582             if (choiceBox.isShowing()) {
 583                 choiceBox.hide();
 584             }
 585         }
 586 
 587         /** {@inheritDoc} */
 588         @Override public void selectPrevious() {
 589             // overridden to properly handle Separators
 590             int index = getSelectedIndex() - 1;
 591             while (index >= 0) {
 592                 final T value = getModelItem(index);
 593                 if (value instanceof Separator) {
 594                     index--;
 595                 } else {
 596                     select(index);
 597                     break;
 598                 }
 599             }
 600         }
 601 
 602         /** {@inheritDoc} */
 603         @Override public void selectNext() {
 604             // overridden to properly handle Separators
 605             int index = getSelectedIndex() + 1;
 606             while (index < getItemCount()) {
 607                 final T value = getModelItem(index);
 608                 if (value instanceof Separator) {
 609                     index++;
 610                 } else {
 611                     select(index);
 612                     break;
 613                 }
 614             }
 615         }
 616     }
 617 
 618     /***************************************************************************
 619      *                                                                         *
 620      * Accessibility handling                                                  *
 621      *                                                                         *
 622      **************************************************************************/
 623 
 624     /** {@inheritDoc} */
 625     @Override
 626     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 627         switch(attribute) {
 628             case TEXT:
 629                 String accText = getAccessibleText();
 630                 if (accText != null && !accText.isEmpty()) return accText;
 631 
 632                 //let the skin first.
 633                 Object title = super.queryAccessibleAttribute(attribute, parameters);
 634                 if (title != null) return title;
 635                 StringConverter<T> converter = getConverter();
 636                 if (converter == null) {
 637                     return getValue() != null ? getValue().toString() : "";
 638                 }
 639                 return converter.toString(getValue());
 640             case EXPANDED: return isShowing();
 641             default: return super.queryAccessibleAttribute(attribute, parameters);
 642         }
 643     }
 644 
 645     /** {@inheritDoc} */
 646     @Override
 647     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 648         switch (action) {
 649             case COLLAPSE: hide(); break;
 650             case EXPAND: show(); break;
 651             default: super.executeAccessibleAction(action); break;
 652         }
 653     }
 654 
 655 }