org.controlsfx.control.action.ActionUtils Maven / Gradle / Ivy
Show all versions of controlsfx Show documentation
/**
* Copyright (c) 2013, 2015 ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.controlsfx.control.action;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.When;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.css.Styleable;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.tools.Duplicatable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* Convenience class for users of the {@link Action} API. Primarily this class
* is used to conveniently create UI controls from a given Action (this is
* necessary for now as there is no built-in support for Action in JavaFX
* UI controls at present).
*
* Some of the methods in this class take a {@link Collection} of
* {@link Action actions}. In these cases, it is likely they are designed to
* work with {@link ActionGroup action groups}. For examples on how to work with
* these methods, refer to the {@link ActionGroup} class documentation.
*
* @see Action
* @see ActionGroup
*/
@SuppressWarnings("deprecation")
public class ActionUtils {
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
private ActionUtils() {
// no-op
}
/***************************************************************************
* *
* Action API *
* *
**************************************************************************/
/**
* Action text behavior.
* Defines uniform action's text behavior for multi-action controls such as toolbars and menus
*/
public enum ActionTextBehavior {
/**
* Text is shown as usual on related control
*/
SHOW,
/**
* Text is not shown on the related control
*/
HIDE,
}
/**
* Takes the provided {@link Action} and returns a {@link Button} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link Button} should bind to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return A {@link Button} that is bound to the state of the provided
* {@link Action}
*/
public static Button createButton(final Action action, final ActionTextBehavior textBehavior) {
return configure(new Button(), action, textBehavior);
}
/**
* Takes the provided {@link Action} and returns a {@link Button} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link Button} should bind to.
* @return A {@link Button} that is bound to the state of the provided
* {@link Action}
*/
public static Button createButton(final Action action) {
return configure(new Button(), action, ActionTextBehavior.SHOW);
}
/**
* Takes the provided {@link Action} and binds the relevant properties to
* the supplied {@link Button}. This allows for the use of Actions
* within custom Button subclasses.
*
* @param action The {@link Action} that the {@link Button} should bind to.
* @param button The {@link ButtonBase} that the {@link Action} should be bound to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return The {@link ButtonBase} that was bound to the {@link Action}.
*/
public static ButtonBase configureButton(final Action action, ButtonBase button, final ActionTextBehavior textBehavior) {
return configure(button, action, textBehavior);
}
/**
* Takes the provided {@link Action} and binds the relevant properties to
* the supplied {@link Button}. This allows for the use of Actions
* within custom Button subclasses.
*
* @param action The {@link Action} that the {@link Button} should bind to.
* @param button The {@link ButtonBase} that the {@link Action} should be bound to.
* @return The {@link ButtonBase} that was bound to the {@link Action}.
*/
public static ButtonBase configureButton(final Action action, ButtonBase button) {
return configureButton(action, button, ActionTextBehavior.SHOW);
}
/**
* Removes all bindings and listeners which were added when the supplied
* {@link ButtonBase} was bound to an {@link Action} via one of the methods
* of this class.
*
* @param button a {@link ButtonBase} that was bound to an {@link Action}
*/
public static void unconfigureButton(ButtonBase button) {
unconfigure(button);
}
/**
* Takes the provided {@link Action} and returns a {@link MenuButton} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link MenuButton} should bind to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return A {@link MenuButton} that is bound to the state of the provided
* {@link Action}
*/
public static MenuButton createMenuButton(final Action action, final ActionTextBehavior textBehavior) {
return configure(new MenuButton(), action, textBehavior);
}
/**
* Takes the provided {@link Action} and returns a {@link MenuButton} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link MenuButton} should bind to.
* @return A {@link MenuButton} that is bound to the state of the provided
* {@link Action}
*/
public static MenuButton createMenuButton(final Action action) {
return configure(new MenuButton(), action, ActionTextBehavior.SHOW);
}
/**
* Takes the provided {@link Action} and returns a {@link Hyperlink} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link Hyperlink} should bind to.
* @return A {@link Hyperlink} that is bound to the state of the provided
* {@link Action}
*/
public static Hyperlink createHyperlink(final Action action) {
return configure(new Hyperlink(), action, ActionTextBehavior.SHOW);
}
/**
* Takes the provided {@link Action} and returns a {@link ToggleButton} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link ToggleButton} should bind to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return A {@link ToggleButton} that is bound to the state of the provided
* {@link Action}
*/
public static ToggleButton createToggleButton(final Action action, final ActionTextBehavior textBehavior ) {
return configure(new ToggleButton(), action, textBehavior);
}
/**
* Takes the provided {@link Action} and returns a {@link ToggleButton} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link ToggleButton} should bind to.
* @return A {@link ToggleButton} that is bound to the state of the provided
* {@link Action}
*/
public static ToggleButton createToggleButton( final Action action ) {
return createToggleButton( action, ActionTextBehavior.SHOW );
}
/**
* Takes the provided {@link Collection} of {@link Action} and returns a {@link SegmentedButton} instance
* with all relevant properties bound to the properties of the actions.
*
* @param actions The {@link Collection} of {@link Action} that the {@link SegmentedButton} should bind to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s
*/
public static SegmentedButton createSegmentedButton(final ActionTextBehavior textBehavior, Collection extends Action> actions) {
ObservableList buttons = FXCollections.observableArrayList();
for( Action a: actions ) {
buttons.add( createToggleButton(a,textBehavior));
}
return new SegmentedButton( buttons );
}
/**
* Takes the provided {@link Collection} of {@link Action} and returns a {@link SegmentedButton} instance
* with all relevant properties bound to the properties of the actions.
*
* @param actions The {@link Collection} of {@link Action} that the {@link SegmentedButton} should bind to.
* @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s
*/
public static SegmentedButton createSegmentedButton(Collection extends Action> actions) {
return createSegmentedButton( ActionTextBehavior.SHOW, actions);
}
/**
* Takes the provided varargs array of {@link Action} and returns a {@link SegmentedButton} instance
* with all relevant properties bound to the properties of the actions.
*
* @param actions A varargs array of {@link Action} that the {@link SegmentedButton} should bind to.
* @param textBehavior Defines {@link ActionTextBehavior}
* @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s
*/
public static SegmentedButton createSegmentedButton(ActionTextBehavior textBehavior, Action... actions) {
return createSegmentedButton(textBehavior, Arrays.asList(actions));
}
/**
* Takes the provided varargs array of {@link Action} and returns a {@link SegmentedButton} instance
* with all relevant properties bound to the properties of the actions.
*
* @param actions A varargs array of {@link Action} that the {@link SegmentedButton} should bind to.
* @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s
*/
public static SegmentedButton createSegmentedButton(Action... actions) {
return createSegmentedButton(ActionTextBehavior.SHOW, Arrays.asList(actions));
}
/**
* Takes the provided {@link Action} and returns a {@link CheckBox} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link CheckBox} should bind to.
* @return A {@link CheckBox} that is bound to the state of the provided
* {@link Action}
*/
public static CheckBox createCheckBox(final Action action) {
return configure(new CheckBox(), action, ActionTextBehavior.SHOW);
}
/**
* Takes the provided {@link Action} and returns a {@link RadioButton} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link RadioButton} should bind to.
* @return A {@link RadioButton} that is bound to the state of the provided
* {@link Action}
*/
public static RadioButton createRadioButton(final Action action) {
return configure(new RadioButton(), action, ActionTextBehavior.SHOW);
}
/**
* Takes the provided {@link Action} and returns a {@link MenuItem} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link MenuItem} should bind to.
* @return A {@link MenuItem} that is bound to the state of the provided
* {@link Action}
*/
public static MenuItem createMenuItem(final Action action) {
MenuItem menuItem =
action.getClass().isAnnotationPresent(ActionCheck.class)? new CheckMenuItem(): new MenuItem();
return configure( menuItem, action);
}
public static MenuItem configureMenuItem(final Action action, MenuItem menuItem) {
return configure(menuItem, action);
}
/**
* Removes all bindings and listeners which were added when the supplied
* {@link MenuItem} was bound to an {@link Action} via one of the methods
* of this class.
*
* @param menuItem a {@link MenuItem} that was bound to an {@link Action}
*/
public static void unconfigureMenuItem(final MenuItem menuItem) {
unconfigure(menuItem);
}
/**
* Takes the provided {@link Action} and returns a {@link Menu} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link Menu} should bind to.
* @return A {@link Menu} that is bound to the state of the provided
* {@link Action}
*/
public static Menu createMenu(final Action action) {
return configure(new Menu(), action);
}
/**
* Takes the provided {@link Action} and returns a {@link CheckMenuItem} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link CheckMenuItem} should bind to.
* @return A {@link CheckMenuItem} that is bound to the state of the provided
* {@link Action}
*/
public static CheckMenuItem createCheckMenuItem(final Action action) {
return configure(new CheckMenuItem(), action);
}
/**
* Takes the provided {@link Action} and returns a {@link RadioMenuItem} instance
* with all relevant properties bound to the properties of the Action.
*
* @param action The {@link Action} that the {@link RadioMenuItem} should bind to.
* @return A {@link RadioMenuItem} that is bound to the state of the provided
* {@link Action}
*/
public static RadioMenuItem createRadioMenuItem(final Action action) {
return configure(new RadioMenuItem(action.textProperty().get()), action);
}
/***************************************************************************
* *
* ActionGroup API *
* *
**************************************************************************/
/**
* Action representation of the generic separator. Adding this action anywhere in the
* action tree serves as indication that separator has be created in its place.
* See {@link ActionGroup} for example of action tree creation
*/
public static Action ACTION_SEPARATOR = new Action(null, null) {
@Override public String toString() {
return "Separator"; //$NON-NLS-1$
}
};
public static Action ACTION_SPAN = new Action(null, null) {
@Override public String toString() {
return "Span"; //$NON-NLS-1$
}
};
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and returns a {@link ToolBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}.
*
* @param actions The {@link Action actions} to place on the {@link ToolBar}.
* @param textBehavior defines {@link ActionTextBehavior}
* @return A {@link ToolBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ToolBar createToolBar(Collection extends Action> actions, ActionTextBehavior textBehavior) {
return updateToolBar( new ToolBar(), actions, textBehavior );
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and returns provided {@link ToolBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}. Previous toolbar content is removed
*
* @param toolbar The {@link ToolBar toolbar} to update
* @param actions The {@link Action actions} to place on the {@link ToolBar}.
* @param textBehavior defines {@link ActionTextBehavior}
* @return A {@link ToolBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ToolBar updateToolBar( ToolBar toolbar, Collection extends Action> actions, ActionTextBehavior textBehavior) {
toolbar.getItems().clear();
for (Action action : actions) {
if ( action instanceof ActionGroup ) {
MenuButton menu = createMenuButton( action, textBehavior );
menu.setFocusTraversable(false);
menu.getItems().addAll( toMenuItems( ((ActionGroup)action).getActions()));
toolbar.getItems().add(menu);
} else if ( action == ACTION_SEPARATOR ) {
toolbar.getItems().add( new Separator());
} else if ( action == ACTION_SPAN ) {
Pane span = new Pane();
HBox.setHgrow(span, Priority.ALWAYS);
VBox.setVgrow(span, Priority.ALWAYS);
toolbar.getItems().add(span);
} else if ( action == null ) {
//no-op
} else {
ButtonBase button;
if ( action.getClass().getAnnotation(ActionCheck.class) != null ) {
button = createToggleButton(action, textBehavior);
} else {
button = createButton(action, textBehavior);
}
button.setFocusTraversable(false);
toolbar.getItems().add(button);
}
}
return toolbar;
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and returns a {@link MenuBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}.
*
* @param actions The {@link Action actions} to place on the {@link MenuBar}.
* @return A {@link MenuBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static MenuBar createMenuBar(Collection extends Action> actions) {
return updateMenuBar(new MenuBar(), actions);
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and updates a {@link MenuBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}. Previous MenuBar content is removed.
*
* @param menuBar The {@link MenuBar menuBar} to update
* @param actions The {@link Action actions} to place on the {@link MenuBar}.
* @return A {@link MenuBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static MenuBar updateMenuBar( MenuBar menuBar, Collection extends Action> actions) {
menuBar.getMenus().clear();
for (Action action : actions) {
if ( action == ACTION_SEPARATOR || action == ACTION_SPAN ) continue;
Menu menu = createMenu( action );
if ( action instanceof ActionGroup ) {
menu.getItems().addAll( toMenuItems( ((ActionGroup)action).getActions()));
} else if ( action == null ) {
//no-op
}
menuBar.getMenus().add(menu);
}
return menuBar;
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and returns a {@link ButtonBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}.
*
* @param actions The {@link Action actions} to place on the {@link ButtonBar}.
* @return A {@link ButtonBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ButtonBar createButtonBar(Collection extends Action> actions) {
return updateButtonBar( new ButtonBar(), actions);
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and updates a {@link ButtonBar}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}. Previous content of button bar is removed
*
* @param buttonBar The {@link ButtonBar buttonBar} to update
* @param actions The {@link Action actions} to place on the {@link ButtonBar}.
* @return A {@link ButtonBar} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ButtonBar updateButtonBar( ButtonBar buttonBar, Collection extends Action> actions) {
buttonBar.getButtons().clear();
for (Action action : actions) {
if ( action instanceof ActionGroup ) {
// no-op
} else if ( action == ACTION_SPAN || action == ACTION_SEPARATOR || action == null ) {
// no-op
} else {
buttonBar.getButtons().add(createButton(action, ActionTextBehavior.SHOW));
}
}
return buttonBar;
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and returns a {@link ContextMenu}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}.
*
* @param actions The {@link Action actions} to place on the {@link ContextMenu}.
* @return A {@link ContextMenu} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ContextMenu createContextMenu(Collection extends Action> actions) {
return updateContextMenu(new ContextMenu(), actions);
}
/**
* Takes the provided {@link Collection} of {@link Action} (or subclasses,
* such as {@link ActionGroup}) instances and updates a {@link ContextMenu}
* populated with appropriate {@link Node nodes} bound to the provided
* {@link Action actions}. Previous content of context menu is removed
*
* @param menu The {@link ContextMenu menu} to update
* @param actions The {@link Action actions} to place on the {@link ContextMenu}.
* @return A {@link ContextMenu} that contains {@link Node nodes} which are bound
* to the state of the provided {@link Action}
*/
public static ContextMenu updateContextMenu(ContextMenu menu, Collection extends Action> actions) {
menu.getItems().clear();
menu.getItems().addAll(toMenuItems(actions));
return menu;
}
/***************************************************************************
* *
* Private implementation *
* *
**************************************************************************/
private static Collection