/*
* This Class was taken from the swing-bug project. https://swing-bug.dev.java.net/
*
* JMultiButton.java
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
* licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.rr.commons.swing.components.button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.ListModel;
/**
* A normal JButton which can have multiple actions added to it. When more than one action is added an arrow is added to the right hand side. If the user clicks
* on the left, the action is performed. If the user clicks on the right, a list of all of the actions is displayed.
*
* @author nigel
*/
public class JMenuButton extends JButton {
private static final long serialVersionUID = -2990339964071073830L;
private ListModel<Action> listModel;
private int width = -1;
private int height = -1;
/**
* Tracks if the mouse is inside the button or not.
*/
private boolean mouseInside = false;
/**
* True if all the actions are enabled, false if they are not.
*/
private boolean enabled = true;
private Color hoverColor = new Color(60, 60, 190, 164);
/**
* A flag that tells if a click to the button area should also toggle
* the popup menu.
*/
private boolean fullSizePopupActionArea = false;
public boolean isFullSizePopupActionArea() {
return fullSizePopupActionArea;
}
public void setFullSizePopupActionArea(boolean fullSizePopupActionArea) {
this.fullSizePopupActionArea = fullSizePopupActionArea;
}
/**
* Creates a new instance, no actions are added.
*/
public JMenuButton() {
initialize();
}
/**
* Sets the button up ready for use, must be called by any constructor.
*/
protected void initialize() {
setHorizontalAlignment(JButton.LEFT);
addMouseListener(new MouseAdapter() {
/**
* Records the mouse is inside the area
*
* @param mouseEvent The mouse event
*/
public void mouseEntered(MouseEvent mouseEvent) {
mouseInside = true;
repaint();
}
/**
* Records the mouse has moved outside
*
* @param mouseEvent The mouse event
*/
public void mouseExited(MouseEvent mouseEvent) {
mouseInside = false;
repaint();
}
});
}
/**
* Get the color of the rounded rectangle drawn when the mouse is hovering over the arrow.
*
* @return The color
*/
public Color getHoverColor() {
return hoverColor;
}
/**
* Change the color of the rounded rectangle drawn when the mouse is hovering over the arrow.
*
* @param hoverColor
* The chosen color
*/
public void setHoverColor(Color hoverColor) {
this.hoverColor = hoverColor;
}
/**
* Overridden so that a check can be made to see if the current action should be fired, or the list of available actions should be shown.
*
* @param actionEvent
* The action event
*/
protected void fireActionPerformed(ActionEvent actionEvent) {
Point p = getMousePosition();
if (p == null) {
return;
}
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
JPopupMenu popup = new JMediumWeightPopupMenu();
popup.setLightWeightPopupEnabled(false);
ListModel<Action> listModel = getListModel();
if(listModel!=null) {
int size = listModel.getSize();
for (int i = 0; i < size; i++) {
popup.add(new ActionOptionWrapper((Action) listModel.getElementAt(i)));
}
}
popup.setMinimumSize(new Dimension(getWidth(), getHeight()));
popup.show(this, 0, getHeight());
}
/**
* Enables the button
*
* @param enabled
* True if the button is enabled, false if it isn't
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
super.setEnabled(enabled);
}
/**
* Returns true if the button is enabled, false if it isn't
*
* @return See above
*/
public boolean isEnabled() {
return this.enabled;
}
/**
* Paints the component
*
* @param graphics
* The graphics context
*/
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g2d = (Graphics2D) graphics;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawArrow(g2d);
}
/**
* This function can be over-ridden to draw any kind of arrow (with any kind of hover effect) the over-riding class wants.
*
* @param graphics
* The graphics context
*/
protected void drawArrow(Graphics2D graphics) {
Rectangle arrowBounds = getArrowBounds();
int hShift = arrowBounds.width / 4;
int vShift = arrowBounds.height / 3;
if (!isEnabled()) {
graphics.setColor(new Color(0, 0, 0, 128));
} else if (mouseInside) {
graphics.setColor(hoverColor);
graphics.fillRoundRect(arrowBounds.x + 1, arrowBounds.y, arrowBounds.width, arrowBounds.height, hShift, vShift);
graphics.setColor(getForeground());
} else {
graphics.setColor(getForeground());
}
arrowBounds.x += hShift;
arrowBounds.width -= hShift * 2 - 1;
arrowBounds.y += vShift;
arrowBounds.height -= vShift * 2;
Polygon poly = new Polygon(new int[] { arrowBounds.x, arrowBounds.x + arrowBounds.width, arrowBounds.x + arrowBounds.width / 2 }, new int[] {
arrowBounds.y, arrowBounds.y, arrowBounds.y + arrowBounds.height }, 3);
graphics.fillPolygon(poly);
}
/**
* Gets the internal area of the button, useful for determining the draw size of the seperator etc.
*
* @return The internal bounds in component co-ordinates
*/
private Rectangle getInternalBounds() {
Insets insets = getInsets();
Rectangle rect = new Rectangle(insets.left, insets.top, getWidth() - (insets.left + insets.right + 1), getHeight() - (insets.top + insets.bottom + 1));
return rect;
}
/**
* Gets the area (in component co-ordinates) of the selection image used to bring up the choices for the options
*
* @return The area of the arrow bounds box
*/
private Rectangle getArrowBounds() {
Rectangle rect = getInternalBounds();
rect.x = rect.x + (rect.width - rect.height) + 1;
rect.width = rect.height;
return rect;
}
/**
* Returns a preferred size a bit bigger than it needs to be so there is room for the arrow.
*
* @return The minimum width and height of the button
*/
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
// preferredSize.width += getArrowBounds().width;
if(width >= 0) {
preferredSize.width = getWidth();
}
if(height >= 0) {
preferredSize.height = getHeight();
}
return preferredSize;
// return adjustDimension(super.getPreferredSize(), true);
}
public void setPreferredSize(Dimension dim) {
super.setPreferredSize(dim);
width = dim.width;
height = dim.height;
}
/**
* Utilitity method allowing the upsizing or downsizing of a dimension by the amount requied by the arrow
*
* @param current
* The dimension to adjust
* @param enlarge
* Upsize or downsize
*
* @return The adjusted dimension
*/
protected Dimension adjustDimension(Dimension current, boolean enlarge) {
if (enlarge) {
current.width += getInternalBounds().height + 1;
} else {
current.width -= getInternalBounds().height + 1;
}
return current;
}
/**
* Returns a minimum size a bit bigger than it needs to be so there is room for the arrow.
*
* @return The minimum width and height of the button
*/
public Dimension getMinimumSize() {
// return adjustDimension(super.getMinimumSize(), true);
return super.getMinimumSize();
}
/**
* A wrapper for the actions to be displayed when the pop-up is drawn.
*/
protected class ActionOptionWrapper extends AbstractAction {
private static final long serialVersionUID = -7161990723874074407L;
Action internalAction = null;
/**
* Creates a new instance of a wrapper action
*
* @param wrapAction
* The action to be wrapped
*/
public ActionOptionWrapper(Action wrapAction) {
internalAction = wrapAction;
this.setEnabled(wrapAction.isEnabled());
putValue(Action.NAME, wrapAction.getValue(Action.NAME));
putValue(Action.SMALL_ICON, wrapAction.getValue(Action.SMALL_ICON));
}
/**
* Fired when the action is performed
*
* @param actionEvent
* The action event
*/
public void actionPerformed(ActionEvent actionEvent) {
internalAction.actionPerformed(actionEvent);
}
}
public ListModel<Action> getListModel() {
return listModel;
}
/**
* Model which provides the Action entries.
* @param listModel The model to be set.
*/
public void setListModel(final ListModel<Action> listModel) {
this.listModel = listModel;
//disable the button if the model did not have any entries.
if(listModel.getSize() == 0) {
setEnabled(false);
} else {
setEnabled(true);
}
}
public int getWidth() {
return width;
}
public void setWidth(int with) {
this.width = with;
}
// public int getHeight() {
// return height;
// }
//
// public void setHeight(int height) {
// this.height = height;
// }
}