/*
* @(#)JideSplitButton.java 2/26/2005
*
* Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
*/
package com.jidesoft.swing;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.plaf.basic.ThemePainter;
import com.jidesoft.utils.SystemInfo;
import javax.swing.*;
import javax.swing.plaf.ButtonUI;
import java.awt.*;
import java.awt.event.ActionEvent;
/**
* <code>JideSplitButton</code> is a combination of button and menu. There is a line in the middle of the button that
* splits the button into two portions. The portion before the line is a button. User can click on it and trigger an
* action. The portion after the line is a menu. User can click on it to show a normal menu.
* <p/>
* Please be noted that, when you try to use JideSplitButton as a menu item, please make sure that you will re-configure
* its font with the following code. Otherwise, it may look different with the other JMenuItems.
* <code><pre>
* splitButton.setFont((Font) JideSwingUtilities.getMenuFont(Toolkit.getDefaultToolkit(), UIManager.getDefaults()));
* </pre></code>
*/
public class JideSplitButton extends JideMenu implements ButtonStyle, ComponentStateSupport {
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "JideSplitButtonUI";
private int _buttonStyle = TOOLBAR_STYLE;
private boolean _alwaysDropdown = false;
public static final String PROPERTY_ALWAYS_DROPDOWN = "alwaysDropdown";
public static final String ACTION_PROPERTY_SPLIT_BUTTON_ENABLED = "JideSplitButtonEnabled";
public JideSplitButton() {
initComponent();
}
public JideSplitButton(String s) {
super(s);
initComponent();
}
public JideSplitButton(String s, Icon icon) {
super(s);
setIcon(icon);
initComponent();
}
public JideSplitButton(Icon icon) {
super("");
setIcon(icon);
initComponent();
}
public JideSplitButton(Action a) {
super(a);
initComponent();
}
protected void initComponent() {
setModel(new DefaultSplitButtonModel());
if (getAction() != null) {
configurePropertiesFromAction(getAction());
}
setFocusable(true);
setRequestFocusEnabled(false);
}
/**
* Returns the split button 's current UI.
*
* @see #setUI
*/
@Override
public ButtonUI getUI() {
return (ButtonUI) ui;
}
/**
* Sets the L&F object that renders this component.
*
* @param ui the <code>JideSplitButtonUI</code> L&F object
* @see javax.swing.UIDefaults#getUI
*/
@Override
public void setUI(ButtonUI ui) {
super.setUI(ui);
}
/**
* Notification from the <code>UIFactory</code> that the L&F has changed. Called to replace the UI with the latest
* version from the <code>UIFactory</code>.
*
* @see javax.swing.JComponent#updateUI
*/
@Override
public void updateUI() {
if (UIDefaultsLookup.get(uiClassID) == null) {
LookAndFeelFactory.installJideExtension();
}
setUI(UIManager.getUI(this));
invalidate();
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "JideSplitButtonUI"
*
* @see javax.swing.JComponent#getUIClassID
* @see javax.swing.UIDefaults#getUI
*/
@Override
public String getUIClassID() {
return uiClassID;
}
/**
* Returns the state of the button part of the JideSplitButton. True if the toggle button is selected, false if it's
* not.
*
* @return true if the toggle button is selected, otherwise false
*/
public boolean isButtonSelected() {
return model instanceof SplitButtonModel && ((DefaultSplitButtonModel) model).isButtonSelected();
}
/**
* Sets the state of the button part of the JideSplitButton. Note that this method does not trigger an
* <code>actionEvent</code>. Call <code>doClick</code> to perform a programmatic action change.
*
* @param b true if the button is selected, otherwise false
*/
public void setButtonSelected(boolean b) {
if (model instanceof SplitButtonModel) {
((DefaultSplitButtonModel) model).setButtonSelected(b);
}
}
/**
* Returns the state of the button part of the JideSplitButton. True if the button is enabled, false if it's not.
*
* @return true if the button is enabled, otherwise false
*/
public boolean isButtonEnabled() {
return model instanceof SplitButtonModel && ((DefaultSplitButtonModel) model).isButtonEnabled();
}
/**
* Sets the state of the button part of the JideSplitButton.
*
* @param b true if the button is enabled, otherwise false
*/
public void setButtonEnabled(boolean b) {
if (model instanceof SplitButtonModel) {
((DefaultSplitButtonModel) model).setButtonEnabled(b);
}
}
/**
* Gets the button style.
*
* @return the button style.
*/
public int getButtonStyle() {
return _buttonStyle;
}
/**
* Sets the button style.
*
* @param buttonStyle the new button style.
*/
public void setButtonStyle(int buttonStyle) {
if (buttonStyle < 0 || buttonStyle > FLAT_STYLE) {
throw new IllegalArgumentException("Only TOOLBAR_STYLE, TOOLBOX_STYLE, and FLAT_STYLE are supported");
}
if (buttonStyle == _buttonStyle)
return;
int oldStyle = _buttonStyle;
_buttonStyle = buttonStyle;
firePropertyChange(BUTTON_STYLE_PROPERTY, oldStyle, _buttonStyle);
}
/**
* Checks the alwaysDropdown property value.
*
* @return true or false. If true, the split button doesn't have default action. It always drops down the menu when
* mouse clicks
*/
public boolean isAlwaysDropdown() {
return _alwaysDropdown;
}
/**
* If the property is true, the split button doesn't have default action. It always drops down the menu when mouse
* clicks. By default, this value is false.
*
* @param alwaysDropdown true or false.
*/
public void setAlwaysDropdown(boolean alwaysDropdown) {
if (_alwaysDropdown != alwaysDropdown) {
boolean old = _alwaysDropdown;
_alwaysDropdown = alwaysDropdown;
firePropertyChange(PROPERTY_ALWAYS_DROPDOWN, old, alwaysDropdown);
}
}
@Override
public void setText(String text) {
// this code is to fix a bug in JDK1.5's JMenuItem line 385. It should check hideActionText first
Boolean hide = (Boolean) getClientProperty("hideActionText");
if (hide == null || Boolean.FALSE.equals(hide)) {
super.setText(text);
}
}
private Color _defaultForeground;
private Color _rolloverBackground;
private Color _selectedBackground;
private Color _pressedBackground;
private Color _rolloverForeground;
private Color _selectedForeground;
private Color _pressedForeground;
public Color getDefaultForeground() {
return _defaultForeground;
}
public void setDefaultForeground(Color defaultForeground) {
_defaultForeground = defaultForeground;
}
private Color getRolloverBackground() {
return _rolloverBackground;
}
private void setRolloverBackground(Color rolloverBackground) {
_rolloverBackground = rolloverBackground;
}
private Color getSelectedBackground() {
return _selectedBackground;
}
private void setSelectedBackground(Color selectedBackground) {
_selectedBackground = selectedBackground;
}
private Color getPressedBackground() {
return _pressedBackground;
}
private void setPressedBackground(Color pressedBackground) {
_pressedBackground = pressedBackground;
}
private Color getRolloverForeground() {
return _rolloverForeground;
}
private void setRolloverForeground(Color rolloverForeground) {
_rolloverForeground = rolloverForeground;
}
private Color getSelectedForeground() {
return _selectedForeground;
}
private void setSelectedForeground(Color selectedForeground) {
_selectedForeground = selectedForeground;
}
private Color getPressedForeground() {
return _pressedForeground;
}
private void setPressedForeground(Color pressedForeground) {
_pressedForeground = pressedForeground;
}
/**
* Gets the background for different states. The states are defined in ThemePainter as constants. Not all states are
* supported by all components. If the state is not supported or background is never set, it will return null.
* <p/>
* Please note, each L&F will have its own way to paint the different backgrounds. This method allows you to
* customize it for each component to use a different background. So if you want the background to be used, don't
* use a ColorUIResource because UIResource is considered as a setting set by the L&F and any L&F can choose to
* ignore it.
*
* @param state the button state. Please refer to {@link com.jidesoft.plaf.basic.ThemePainter} to see the list of
* available states.
* @return the background for different states.
*/
public Color getBackgroundOfState(int state) {
switch (state) {
case ThemePainter.STATE_DEFAULT:
return getBackground();
case ThemePainter.STATE_ROLLOVER:
return getRolloverBackground();
case ThemePainter.STATE_SELECTED:
return getSelectedBackground();
case ThemePainter.STATE_PRESSED:
return getPressedBackground();
}
return null;
}
/**
* Sets the background for different states. The states are defined in ThemePainter as constants. Not all states
* are supported by all components. If the state is not supported or background is never set, it will return null.
* <p/>
* Please note, each L&F will have its own way to paint the different backgrounds. This method allows you to
* customize it for each component to use a different background. So if you want the background to be used, don't
* use a ColorUIResource because UIResource is considered as a setting set by the L&F and any L&F can choose to
* ignore it.
*
* @param state the button state. Please refer to {@link com.jidesoft.plaf.basic.ThemePainter} to see the list of
* available states.
* @param color the background color
*/
public void setBackgroundOfState(int state, Color color) {
switch (state) {
case ThemePainter.STATE_DEFAULT:
setBackground(color);
break;
case ThemePainter.STATE_ROLLOVER:
setRolloverBackground(color);
break;
case ThemePainter.STATE_SELECTED:
setSelectedBackground(color);
break;
case ThemePainter.STATE_PRESSED:
setPressedBackground(color);
break;
}
}
/**
* Gets the foreground for different states. The states are defined in ThemePainter as constants. Not all states are
* supported by all components. If the state is not supported or foreground is never set, it will return null.
* <p/>
* Please note, each L&F will have its own way to paint the different foregrounds. This method allows you to
* customize it for each component to use a different foreground. So if you want the foreground to be used, don't
* use a ColorUIResource because UIResource is considered as a setting set by the L&F and any L&F can choose to
* ignore it.
*
* @param state the button state. Please refer to {@link com.jidesoft.plaf.basic.ThemePainter} to see the list of
* available states.
* @return the foreground for different states.
*/
public Color getForegroundOfState(int state) {
switch (state) {
case ThemePainter.STATE_DEFAULT:
return getDefaultForeground();
case ThemePainter.STATE_ROLLOVER:
return getRolloverForeground();
case ThemePainter.STATE_SELECTED:
return getSelectedForeground();
case ThemePainter.STATE_PRESSED:
return getPressedForeground();
}
return null;
}
/**
* Sets the foreground for different states. The states are defined in ThemePainter as constants. Not all states
* are supported by all components. If the state is not supported or foreground is never set, it will return null.
* <p/>
* Please note, each L&F will have its own way to paint the different foregrounds. This method allows you to
* customize it for each component to use a different foreground. So if you want the foreground to be used, don't
* use a ColorUIResource because UIResource is considered as a setting set by the L&F and any L&F can choose to
* ignore it.
*
* @param state the button state. Please refer to {@link com.jidesoft.plaf.basic.ThemePainter} to see the list of
* available states.
* @param color the background color
*/
public void setForegroundOfState(int state, Color color) {
switch (state) {
case ThemePainter.STATE_DEFAULT:
setDefaultForeground(color);
break;
case ThemePainter.STATE_ROLLOVER:
setRolloverForeground(color);
break;
case ThemePainter.STATE_SELECTED:
setSelectedForeground(color);
break;
case ThemePainter.STATE_PRESSED:
setPressedForeground(color);
break;
}
}
/**
* Clicks on the button part of the <code>JideSplitButton</code>.
*/
@Override
public void doClick() {
Action action = getActionMap().get("pressed");
if (action != null) {
action.actionPerformed(new ActionEvent(this, 0, ""));
}
}
/**
* Clicks on the drop down menu part of the <code>JideSplitButton</code>.
*/
public void doClickOnMenu() {
Action action = getActionMap().get("downPressed");
if (action != null) {
action.actionPerformed(new ActionEvent(this, 0, ""));
}
}
@Override
protected void configurePropertiesFromAction(Action action) {
super.configurePropertiesFromAction(action);
setButtonEnabled(isSplitButtonEnabled(action));
setIconFromAction(action);
}
/**
* By default, we will use large icon instead of small icon in the JMenuItem. You could override this method to set
* your own icon size.
*
* @param action the action.
*/
protected void setIconFromAction(Action action) {
Icon icon = null;
if (action != null) {
icon = SystemInfo.isJdk6Above() ? (Icon) action.getValue(Action.LARGE_ICON_KEY) : null;
if (icon == null) {
icon = (Icon) action.getValue(Action.SMALL_ICON);
}
}
setIcon(icon);
}
@Override
protected void actionPropertyChanged(Action action, String propertyName) {
super.actionPropertyChanged(action, propertyName);
if (ACTION_PROPERTY_SPLIT_BUTTON_ENABLED.equals(propertyName) || "enabled".equals(propertyName)) {
setButtonEnabled(isSplitButtonEnabled(action));
}
else if (Action.SMALL_ICON.equals(propertyName)) {
if (!SystemInfo.isJdk6Above() || action.getValue(Action.LARGE_ICON_KEY) == null) {
setIconFromAction(action);
}
}
else if (SystemInfo.isJdk6Above() && Action.LARGE_ICON_KEY.equals(propertyName)) {
setIconFromAction(action);
}
}
/**
* Get if the split button is enable from the property stored inside the action.
*
* @param action the action
* @return true if the split button is enabled. Otherwise false.
*/
public static boolean isSplitButtonEnabled(Action action) {
if (action == null) {
return true;
}
else {
Object value = action.getValue(ACTION_PROPERTY_SPLIT_BUTTON_ENABLED);
if (value instanceof Boolean) {
return (Boolean) value;
}
else {
return action.isEnabled();
}
}
}
}