package de.unisiegen.tpml.graphics.components;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
public class MenuButton extends JComponent {
/**
*
*/
private static final long serialVersionUID = 3324315568161015639L;
/**
* A popup menu that will be displayed when the MenuButton is opend.
*/
private JPopupMenu menu = null;
/**
* The action listener.
*/
private ActionListener listener = null;
/**
* The text that will be displayed.
*/
private String text;
/**
* The color that will be used to render the text.
*/
private Color textColor;
/**
* The <i>dimension</i> is the width and height of the boxed arrow
* drawn by the MenuButton.
*/
private int dimension;
public MenuButton () {
// set the default font... it will be the font from a combo box
setFont (new JComboBox ().getFont ());
// the dimension of the drawn icon will be calculated by the height
// of the text, that would be used in an JComboBox
FontMetrics fm = getFontMetrics (getFont());
this.dimension = fm.getHeight();
// initially the text will be empty
this.text = "";
this.textColor = Color.BLACK;
/*
* create the listener that will be connected to every element
* that is in the menu item tree.
*/
this.listener = new ActionListener () {
public void actionPerformed (ActionEvent event) {
// just delegate the event to a method of the surrounding class.
MenuButton.this.fireMenuItemActivated ((JMenuItem)event.getSource());
}
};
this.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed (MouseEvent event) {
MenuButton.this.handleMousePressed (event);
}
});
}
public void addMenuButtonListener (MenuButtonListener listener) {
this.listenerList.add(MenuButtonListener.class, listener);
}
public void removeMenuButtonListener (MenuButtonListener listener) {
this.listenerList.remove(MenuButtonListener.class, listener);
}
/**
* Sets the Popupmenu for the MenuButton.
*
* @param menu
*/
public void setMenu (JPopupMenu menu) {
if (this.menu != null) {
uninstallMenuEventListener (this.menu);
}
this.menu = menu;
// the application needs to know when the menu is closed
this.menu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuCanceled (PopupMenuEvent event) { }
public void popupMenuWillBecomeVisible (PopupMenuEvent event) { }
public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
MenuButton.this.fireMenuClosed ();
}
});
installMenuEventListener (this.menu);
}
/**
* Sets the text that should be shown.
* @param text
*/
public void setText (String text) {
this.text = text;
}
/**
* Sets the color
* @param color
*/
public void setTextColor (Color color) {
this.textColor = color;
}
/**
* Calculates and returns the dimension this menu button needs
* to do a propper rendering.
*
* @return The needed size for this MenuButton
*/
public Dimension getNeededSize () {
Dimension result = new Dimension (0, 0);
// normaly there is no text in the button so only the
// sizes of the boxed arrow would be of interest.
result.width = this.dimension;
result.height = this.dimension;
if (this.text != null && this.text.length() != 0) {
FontMetrics fm = getFontMetrics (getFont ());
// when text should be visible, add the half dimension as
// an additional size and add the size of the text
result.width += this.dimension / 2;
result.width += fm.stringWidth(this.text);
// check whether the text of the dimension for the
// boxed arrow is bigger
result.height = Math.max(result.height, fm.getHeight());
}
return result;
}
/**
* Paints the component
*
* @param gc The graphic context
*/
@Override
protected void paintComponent (Graphics gc) {
// find the vertical centering position of menubutton
int centerV = getHeight () / 2;
int posX = 0;
int posY = centerV - this.dimension / 2;
// render the boxed around the arrow at the front of the component
gc.setColor (Color.LIGHT_GRAY);
gc.drawRect (posX, posY, this.dimension - 1, this.dimension - 1);
// draw the arrow within the box
Polygon polygon = new Polygon ();
polygon.addPoint(posX + this.dimension * 3 / 12, posY + this.dimension * 3 / 12);
polygon.addPoint(posX + this.dimension * 9 / 12, posY + this.dimension * 3 / 12);
polygon.addPoint(posX + this.dimension / 2, posY + this.dimension * 9 / 12);
gc.fillPolygon(polygon);
if (this.text != null && this.text.length() != 0) {
FontMetrics fm = getFontMetrics (getFont ());
posX += this.dimension + this.dimension / 2;
posY = centerV + fm.getAscent() / 3;
gc.setFont(getFont ());
gc.setColor(this.textColor);
gc.drawString(this.text, posX, posY);
}
}
/*
*
* Implementation of the event handling of the menu item tree
*/
/**
* Handle the mousePress event
* @param event
*/
private void handleMousePressed (MouseEvent event) {
if (this.menu == null) {
return;
}
this.menu.show (this, event.getX(), event.getY());
}
/**
* Fires the menuItemActivated function from MenuButtonListener.
*
* @param item the item that has been activated
*/
private void fireMenuItemActivated (JMenuItem item) {
Object[] listeners = this.listenerList.getListenerList();
for (int i=0; i<listeners.length; i+=2) {
// find the correct listener object
if (listeners [i] != MenuButtonListener.class) {
continue;
}
// perform the listener
((MenuButtonListener)listeners [i+1]).menuItemActivated(this, item);
}
}
/**
* Fires the menuClosed function from MenuButtonListener
*/
private void fireMenuClosed () {
Object[] listeners = this.listenerList.getListenerList();
for (int i=0; i<listeners.length; i+=2) {
// find the correct listener object
if (listeners [i] != MenuButtonListener.class) {
continue;
}
// perform the listener
((MenuButtonListener)listeners [i+1]).menuClosed(this);
}
}
/**
* Removes all previously install menu item listeners from every JMenuItem
* within the menu tree.
*
* @param element The element which listeners should be removed
*/
private void uninstallMenuEventListener (MenuElement element ) {
if (element instanceof JMenuItem) {
// if this is just a leaf object remove the listener
JMenuItem item = (JMenuItem)element;
item.removeActionListener(this.listener);
}
else {
// if this is a submenuitem proceed with all children elements
MenuElement[] subElements = element.getSubElements();
for (MenuElement e : subElements) {
uninstallMenuEventListener (e);
}
}
}
/**
* Installs a menu item listener to every JMenuItem within the menu tree.
*
* @param element The element where a listener should be installed.
*/
private void installMenuEventListener(MenuElement element) {
if (element instanceof JMenuItem) {
// if this is just a leaf object remove the listener
JMenuItem item = (JMenuItem)element;
item.addActionListener(this.listener);
}
else {
// if this is a submenuitem proceed with all children elements
MenuElement[] subElements = element.getSubElements();
for (MenuElement e : subElements) {
installMenuEventListener (e);
}
}
}
}