/*
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of Flamingo Kirill Grouchnikov 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 THE COPYRIGHT OWNER OR
* CONTRIBUTORS 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.pushingpixels.flamingo.api.common;
import java.awt.Component;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonStripUI;
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonStripUI;
/**
* Button strip component. Provides visual appearance of a strip. The buttons in
* the strip are either drawn horizontally with no horizontal space between them
* or drawn vertically with no vertical space between them.
*
* @author Kirill Grouchnikov
*/
public class JCommandButtonStrip extends JComponent {
/**
* The UI class ID string.
*/
public static final String uiClassID = "CommandButtonStripUI";
/**
* Element state for the buttons in this button strip. Default state is
* {@link CommandButtonDisplayState#SMALL}.
*/
protected CommandButtonDisplayState displayState;
/**
* Scale factor for horizontal gaps.
*
* @see #setVGapScaleFactor(double)
*/
protected double hgapScaleFactor;
/**
* Scale factor for vertical gaps.
*
* @see #setVGapScaleFactor(double)
*/
protected double vgapScaleFactor;
/**
* Button strip orientation.
*
* @author Kirill Grouchnikov
*/
public enum StripOrientation {
/**
* Horizontal strip orientation.
*/
HORIZONTAL,
/**
* Vertical strip orientation.
*/
VERTICAL
}
/**
* Orientation of <code>this</code> strip.
*
* @see #getOrientation()
*/
private StripOrientation orientation;
/**
* Creates an empty horizontally-oriented strip.
*/
public JCommandButtonStrip() {
this(StripOrientation.HORIZONTAL);
}
/**
* Creates an empty strip.
*
* @param orientation
* Orientation for this strip.
*/
public JCommandButtonStrip(StripOrientation orientation) {
this.orientation = orientation;
this.displayState = CommandButtonDisplayState.SMALL;
switch (orientation) {
case HORIZONTAL:
this.hgapScaleFactor = 0.75;
this.vgapScaleFactor = 1.0;
break;
case VERTICAL:
this.hgapScaleFactor = 1.0;
this.vgapScaleFactor = 0.75;
break;
}
this.setOpaque(false);
updateUI();
}
/**
* Sets the display state for the buttons in this button strip. This method
* must be called <em>before</em> adding the first command button. The
* default state is {@link CommandButtonDisplayState#SMALL}.
*
* @param elementState
* New element state for the buttons in this button strip.
*/
public void setDisplayState(CommandButtonDisplayState elementState) {
if (this.getComponentCount() > 0) {
throw new IllegalStateException(
"Can't call this method after buttons have been already added");
}
this.displayState = elementState;
}
/**
* Sets the horizontal gap scale factor for the buttons in this button
* strip. This method must be called <em>before</em> adding the first
* command button.
*
* <p>
* The default horizontal gap scale factor for horizontally oriented strips
* is 0.75. The default horizontal gap scale factor for vertically oriented
* strips is 1.0.
* </p>
*
* @param hgapScaleFactor
* New horizontal gap scale factor for the buttons in this button
* strip.
* @see #setVGapScaleFactor(double)
*/
public void setHGapScaleFactor(double hgapScaleFactor) {
if (this.getComponentCount() > 0) {
throw new IllegalStateException(
"Can't call this method after buttons have been already added");
}
this.hgapScaleFactor = hgapScaleFactor;
}
/**
* Sets the vertical gap scale factor for the buttons in this button strip.
* This method must be called <em>before</em> adding the first command
* button.
*
* <p>
* The default vertical gap scale factor for vertically oriented strips is
* 0.75. The default vertical gap scale factor for horizontally oriented
* strips is 1.0.
* </p>
*
* @param vgapScaleFactor
* New vertical gap scale factor for the buttons in this button
* strip.
* @see #setHGapScaleFactor(double)
*/
public void setVGapScaleFactor(double vgapScaleFactor) {
if (this.getComponentCount() > 0) {
throw new IllegalStateException(
"Can't call this method after buttons have been already added");
}
this.vgapScaleFactor = vgapScaleFactor;
}
/*
* (non-Javadoc)
*
* @see java.awt.Container#add(java.awt.Component, java.lang.Object, int)
*/
@Override
public void add(Component comp, Object constraints, int index) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see java.awt.Container#add(java.awt.Component, java.lang.Object)
*/
@Override
public void add(Component comp, Object constraints) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see java.awt.Container#add(java.awt.Component, int)
*/
@Override
public Component add(Component comp, int index) {
if (!(comp instanceof AbstractCommandButton))
throw new UnsupportedOperationException();
this.configureCommandButton((AbstractCommandButton) comp);
return super.add(comp, index);
}
/*
* (non-Javadoc)
*
* @see java.awt.Container#add(java.awt.Component)
*/
@Override
public Component add(Component comp) {
if (!(comp instanceof AbstractCommandButton))
throw new UnsupportedOperationException();
try {
this.configureCommandButton((AbstractCommandButton) comp);
Component result = super.add(comp);
return result;
} finally {
this.fireStateChanged();
}
}
/**
* Configures the specified command button.
*
* @param button
* Command button to configure.
*/
private void configureCommandButton(AbstractCommandButton button) {
button.setDisplayState(this.displayState);
button.setHGapScaleFactor(this.hgapScaleFactor);
button.setVGapScaleFactor(this.vgapScaleFactor);
button.setFlat(false);
}
/*
* (non-Javadoc)
*
* @see java.awt.Container#add(java.lang.String, java.awt.Component)
*/
@Override
public Component add(String name, Component comp) {
throw new UnsupportedOperationException();
}
/**
* Sets the new UI delegate.
*
* @param ui
* New UI delegate.
*/
public void setUI(CommandButtonStripUI ui) {
super.setUI(ui);
}
/**
* Resets the UI property to a value from the current look and feel.
*
* @see JComponent#updateUI
*/
@Override
public void updateUI() {
if (UIManager.get(getUIClassID()) != null) {
setUI((CommandButtonStripUI) UIManager.getUI(this));
} else {
setUI(BasicCommandButtonStripUI.createUI(this));
}
}
/**
* Returns the UI object which implements the L&F for this component.
*
* @return a <code>ButtonStripUI</code> object
* @see #setUI
*/
public CommandButtonStripUI getUI() {
return (CommandButtonStripUI) ui;
}
/**
* Returns the name of the UI class that implements the L&F for this
* component.
*
* @return the string "ButtonStripUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
@Override
public String getUIClassID() {
return uiClassID;
}
/**
* Returns the number of buttons in <code>this</code> strip.
*
* @return Number of buttons in <code>this</code> strip.
* @see #getButton(int)
*/
public int getButtonCount() {
return this.getComponentCount();
}
/**
* Returns the specified button component of <code>this</code> strip.
*
* @param index
* Button index.
* @return The matching button.
* @see #getButtonCount()
*/
public AbstractCommandButton getButton(int index) {
return (AbstractCommandButton) this.getComponent(index);
}
/**
* Checks whether the specified button is the first button in
* <code>this</code> strip.
*
* @param button
* Button to check.
* @return <code>true</code> if the specified button is the first button in
* <code>this</code> strip, <code>false</code> otherwise.
* @see #isLast(AbstractCommandButton)
*/
public boolean isFirst(AbstractCommandButton button) {
return (button == this.getButton(0));
}
/**
* Checks whether the specified button is the last button in
* <code>this</code> strip.
*
* @param button
* Button to check.
* @return <code>true</code> if the specified button is the last button in
* <code>this</code> strip, <code>false</code> otherwise.
* @see #isFirst(AbstractCommandButton)
*/
public boolean isLast(AbstractCommandButton button) {
return (button == this.getButton(this.getButtonCount() - 1));
}
/**
* Returns the orientation of <code>this</code> strip.
*
* @return Orientation of <code>this</code> strip.
*/
public StripOrientation getOrientation() {
return orientation;
}
/**
* Adds the specified change listener to track changes to this command
* button strip.
*
* @param l
* Change listener to add.
* @see #removeChangeListener(ChangeListener)
*/
public void addChangeListener(ChangeListener l) {
this.listenerList.add(ChangeListener.class, l);
}
/**
* Removes the specified change listener from tracking changes to this
* command button strip.
*
* @param l
* Change listener to remove.
* @see #addChangeListener(ChangeListener)
*/
public void removeChangeListener(ChangeListener l) {
this.listenerList.remove(ChangeListener.class, l);
}
/**
* Notifies all registered listener that the state of this command button
* strip has changed.
*/
protected void fireStateChanged() {
// Guaranteed to return a non-null array
Object[] listeners = this.listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
ChangeEvent event = new ChangeEvent(this);
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
((ChangeListener) listeners[i + 1]).stateChanged(event);
}
}
}
}