/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue.gui;
import tufts.vue.DEBUG;
import tufts.vue.LWPropertyChangeEvent;
import tufts.vue.RecentlyUsedColorsManager;
import tufts.vue.RichTextBox;
import tufts.vue.VueResources;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import java.awt.Window;
import javax.swing.SwingUtilities;
import edu.tufts.vue.preferences.implementations.ColorPreference;
//import com.sun.codemodel.JLabel;
/**
* ColorMenuButton
*
* This class provides a popup menu of items that supports named color values
* with a corresponding color swatch.
*
* @version $Revision: 1.33 $ / $Date: 2010-02-03 19:15:47 $ / $Author: mike $
* @author csb
* @author Scott Fraize
*/
public class ColorMenuButton extends JButton
implements ActionListener, tufts.vue.LWEditor<Color>
{
public static final String COLOR_POPUP_NAME = "ColorPopupMenu";
/** The currently selected Color item--if any **/
//protected Color mCurColor = new Color(0,0,0);
protected Color mCurColor = null;
private Icon mButtonIcon;
protected Object mPropertyKey;
protected Object mCurrentValue;
private Window popupWindow;
private final RecentlyUsedColorsManager colorManager = RecentlyUsedColorsManager.getInstance();
/**
* Creates a new ColorMenuButton with the passed array of items
* as it's palette menu.
*
* It will preselect the first item in the array as
* its default selection and use its images for its own view.
*
* @param pItems an array of ColorMenuButtonItems for the menu.
**/
private JPanel colorArrayPanel = null;
public ColorMenuButton(Color[] pColors, boolean pHasCustom) {
// create default color swatch icon: override with setButtonIcon if want different
setButtonIcon(new BlobIcon(16,16, true)); // can we live with no default? clean up init style...
colorArrayPanel = buildMenu(pColors, pHasCustom);
setBorder(null);
addActionListener(this);
// add components to main panel
// use frame
if (tufts.Util.isUnixPlatform())
popupWindow = new JWindow();
else
popupWindow = new JFrame();
if (tufts.Util.isWindowsPlatform())
popupWindow.setAlwaysOnTop(true);
popupWindow.setName(COLOR_POPUP_NAME);
if (!tufts.Util.isUnixPlatform())
((JFrame)popupWindow).setUndecorated(true);
// popupWindow.setAlwaysOnTop(true);
Component c = null;
if (!tufts.Util.isUnixPlatform())
c= ((JFrame)popupWindow).getGlassPane();
else
c = ((JWindow)popupWindow).getGlassPane();
c.setVisible(true);
c.addMouseListener(new MouseAdapter()
{
public void mousePressed(final MouseEvent e)
{
Component c = null;
if (!tufts.Util.isUnixPlatform())
c= ((JFrame)popupWindow).getGlassPane();
else
c = ((JWindow)popupWindow).getGlassPane();
java.awt.Container contentPane = null;
if (!tufts.Util.isUnixPlatform())
contentPane= ((JFrame)popupWindow).getContentPane();
else
contentPane = ((JWindow)popupWindow).getContentPane();
// get the mouse click point relative to the content pane
Point containerPoint = SwingUtilities.convertPoint(popupWindow,
e.getPoint(),contentPane);
// find the component that under this point
Component component = SwingUtilities.getDeepestComponentAt(
contentPane,
containerPoint.x,
containerPoint.y);
// return if nothing was found
if (component == null) {
return;
}
// convert point relative to the target component
Point componentPoint = SwingUtilities.convertPoint(
popupWindow,
e.getPoint(),
component);
// redispatch the event
component.dispatchEvent(new MouseEvent(component,
e.getID(),
e.getWhen(),
e.getModifiers(),
componentPoint.x,
componentPoint.y,
e.getClickCount(),
e.isPopupTrigger()));
}
public void mouseReleased(final MouseEvent e)
{
Component c = null;
if (!tufts.Util.isUnixPlatform())
c= ((JFrame)popupWindow).getGlassPane();
else
c = ((JWindow)popupWindow).getGlassPane();
java.awt.Container contentPane = null;
if (!tufts.Util.isUnixPlatform())
contentPane= ((JFrame)popupWindow).getContentPane();
else
contentPane = ((JWindow)popupWindow).getContentPane();
// get the mouse click point relative to the content pane
Point containerPoint = SwingUtilities.convertPoint(popupWindow,
e.getPoint(),contentPane);
// find the component that under this point
Component component = SwingUtilities.getDeepestComponentAt(
contentPane,
containerPoint.x,
containerPoint.y);
// return if nothing was found
if (component == null) {
return;
}
// convert point relative to the target component
Point componentPoint = SwingUtilities.convertPoint(
popupWindow,
e.getPoint(),
component);
// redispatch the event
component.dispatchEvent(new MouseEvent(component,
e.getID(),
e.getWhen(),
e.getModifiers(),
componentPoint.x,
componentPoint.y,
e.getClickCount(),
e.isPopupTrigger()));
}
public void mouseExited(final MouseEvent e)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (popupWindow.isVisible())
doClick();
}
});
}
});
popupWindow.addWindowFocusListener(new WindowFocusListener() {
public void windowGainedFocus(WindowEvent e) {
}
public void windowLostFocus(WindowEvent e) {
//System.out.println("Opposite component" + e.getOppositeWindow().getClass().toString());
if (e.getOppositeWindow() != null && ((e.getOppositeWindow().getClass() == VueFrame.class)))
{
return;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (popupWindow.isVisible())
doClick();
}
});
}
});
// add some components to window
java.awt.Container contentPane = null;
if (!tufts.Util.isUnixPlatform())
contentPane= ((JFrame)popupWindow).getContentPane();
else
contentPane = ((JWindow)popupWindow).getContentPane();
((JComponent)contentPane).setLayout(new BorderLayout());
((JComponent)contentPane).setBorder(BorderFactory.createEtchedBorder());
contentPane.add(colorArrayPanel);
popupWindow.pack();
}
private JPanel colorPanel = new ColorPanel();
public Window getPopupWindow()
{
return popupWindow;
}
public void rebuildMenu()
{
popupWindow.remove(colorArrayPanel);
colorArrayPanel = buildMenu(colorsToRebuild,customRebuild);
// add some components to window
java.awt.Container contentPane = null;
if (!tufts.Util.isUnixPlatform())
contentPane= ((JFrame)popupWindow).getContentPane();
else
contentPane = ((JWindow)popupWindow).getContentPane();
contentPane.add(colorArrayPanel);
popupWindow.pack();
}
private Color[] colorsToRebuild;
private boolean customRebuild;
public JPanel buildMenu(Color[] colors, boolean custom)
{
// 4 x 15
JPanel parent = new JPanel();
parent.setLayout(new BorderLayout());
colorsToRebuild =colors.clone();
customRebuild = custom;
colorPanel.removeAll();
colorPanel.setLayout(new GridLayout(0,4,2,2));
Color c = this.getColor();
for (int i = 0; i < colors.length ; i++)
{
JButton colorButton = new JButton();
colorButton.setFocusable(false);
colorButton.setBorderPainted(false);
colorButton.setContentAreaFilled(false);
Icon icon = makeIcon(colors[i]);
colorButton.setIcon(icon);
colorButton.setPreferredSize(new Dimension(20,20));
colorButton.addActionListener(this);
Color blobColor = colors[i];
if (c != null && blobColor != null && blobColor.getRed() == c.getRed() && blobColor.getBlue() == c.getBlue() && blobColor.getGreen() == c.getGreen() && blobColor.getAlpha() == c.getAlpha())
{
//System.out.println("MATCH");
colorButton.setBorderPainted(true);
colorButton.setBorder(BorderFactory.createLineBorder(Color.gray));
}
colorPanel.add(colorButton);
}
ColorPreference customColor = null;
List colorList = colorManager.getRecentlyUsedColors();
//for (int i=0; i < colorList.size(); i++};
for (int p = 0 ; p< colorList.size(); p++)
{
JButton colorButton = new JButton();
colorButton.setFocusable(false);
colorButton.setBorderPainted(false);
colorButton.setContentAreaFilled(false);
//System.out.println(color);
StringTokenizer tokens = new StringTokenizer((String)colorList.get(p),",");
Color color = new Color(Integer.parseInt((String)tokens.nextElement()),Integer.parseInt((String)tokens.nextElement()),Integer.parseInt((String)tokens.nextElement()));
Icon icon = makeIcon(color);
colorButton.setIcon(icon);
colorButton.setPreferredSize(new Dimension(20,20));
colorButton.addActionListener(this);
Color blobColor = color;
if (c != null && blobColor != null && blobColor.getRed() == c.getRed() && blobColor.getBlue() == c.getBlue() && blobColor.getGreen() == c.getGreen() && blobColor.getAlpha() == c.getAlpha())
{
//System.out.println("MATCH");
colorButton.setBorderPainted(true);
colorButton.setBorder(BorderFactory.createLineBorder(Color.gray));
}
colorPanel.add(colorButton);
}
JButton item = new JButton(VueResources.getString("button.label.other"));
item.setFocusable(false);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
Color c =(Color)runCustomChooser();
colorManager.updateRecentlyUsedColors(c.getRed()+","+c.getGreen()+","+c.getBlue());
handleValueSelection(c);
doClick();
if (tufts.Util.isWindowsPlatform())
popupWindow.setAlwaysOnTop(true);
// rebuildMenu();
}
});
item.setBorderPainted(false);
item.setContentAreaFilled(false);
parent.add(colorPanel,BorderLayout.CENTER);
parent.add(item,BorderLayout.SOUTH);
return parent;
}
public ColorMenuButton(Color[] pColors) {
this(pColors, false);
}
/**
* Sets a the current color.
*/
public void setColor(Color c) {
if (DEBUG.TOOL) System.out.println(this + " setColor " + c);
if (c == null && mPropertyKey == null)
return;
if (c == null || c.getAlpha() == 0)
setToolTipText("No Fill");
else if (c.getAlpha() == 255)
setToolTipText(String.format("RGB: %d,%d,%d", c.getRed(), c.getGreen(), c.getBlue()));
else
setToolTipText(String.format("RGB: %d,%d,%d @ %.0f%%", c.getRed(), c.getGreen(), c.getBlue(),
100f * ((float)c.getAlpha()) / 255f));
mCurColor = c;
Icon i = getButtonIcon();
if (i instanceof BlobIcon)
((BlobIcon)i).setColor(c);
// if (c == null)
// mPopup.setSelected(super.mEmptySelection);
int count = colorPanel.getComponentCount();
for (int p = 0 ; p< count; p++)
{
Component comp = colorPanel.getComponent(p);
if (comp instanceof JButton)
{
JButton b = ((JButton)comp);
if (b.getIcon() instanceof BlobIcon)
{
BlobIcon blob = ((BlobIcon)b.getIcon());
//System.out.println(String.format("RGB: %d,%d,%d @ %.0f%%", c.getRed(), c.getGreen(), c.getBlue(),
// 100f * ((float)c.getAlpha()) / 255f));
//System.out.println(String.format("RGB BLOB: %d,%d,%d @ %.0f%%", blob.getColor().getRed(), blob.getColor().getGreen(), blob.getColor().getBlue(),
// 100f * ((float) blob.getColor().getAlpha()) / 255f));
Color blobColor = blob.getColor();
if (c != null && blobColor != null && blobColor.getRed() == c.getRed() && blobColor.getBlue() == c.getBlue() && blobColor.getGreen() == c.getGreen() && blobColor.getAlpha() == c.getAlpha())
{
//System.out.println("MATCH");
b.setBorderPainted(true);
b.setBorder(BorderFactory.createLineBorder(Color.gray));
}
else
{ //System.out.println(" NO MATCH");
b.setBorderPainted(false);
}
}
}
}
repaint();
}
public void setButtonIcon(Icon i) {
if (DEBUG.BOXES||DEBUG.TOOL) System.out.println(this + " setButtonIcon " + i);
//if (DEBUG.Enabled) new Throwable().printStackTrace();
_setIcon(mButtonIcon = i);
}
/** return the default button size for this type of button: subclasses can override */
protected Dimension getButtonSize() {
return new Dimension(32,22); // better at 22, but get clipped 1 pix at top in VueToolbarController! todo: BUG
}
private void _setIcon(Icon i) {
/*
super.setIcon(i);
super.setRolloverIcon(new VueButtonIcon(i, VueButtonIcon.ROLLOVER));
*/
/*
final int pad = 7;
Dimension d = new Dimension(i.getIconWidth()+pad, i.getIconHeight()+pad);
if (d.width < 21) d.width = 21; // todo: config
if (d.height < 21) d.height = 21; // todo: config
*/
//if (DEBUG.BOXES||DEBUG.TOOL) System.out.println(this + " _setIcon " + i);
Dimension d = getButtonSize();
if (true || !GUI.isMacAqua()) {
if (false)
VueButtonIcon.installGenerated(this, i, d);
else
VueButtonIcon.installGenerated(this, new MenuProxyIcon(i), d);
//System.out.println(this + " *** installed generated, setPreferredSize " + d);
}
setPreferredSize(d);
}
protected Icon getButtonIcon() {
return mButtonIcon;
}
/**
* Gets the current color
*/
public Color getColor() {
return mCurColor;
}
/** @interface LWEditor */
public Object getPropertyKey() {
return mPropertyKey;
}
/** @interface LWEditor */
public void displayValue(Color c) {
setColor(c);
}
/** @interface LWEditor */
public Color produceValue() {
return getColor();
}
/** Simulate a user value selection */
public void selectValue(Color value) {
handleValueSelection(value);
}
protected void handleValueSelection(Color newPropertyValue) {
if (DEBUG.TOOL) System.out.println(this + " handleValueSelection: newPropertyValue=" + newPropertyValue);
// TODO: this is getting fired twice, once for ItemEvent stateChange=DESELECTED, and
// then the one we really want, with itemState=SELECTED. We want to ignore the former,
// as it's generating extra property sets on the selection that are immediately
// overriden by the SELECTED value. This should actually be harmless, but
// it's definitely unexpected internal behaviour. -- SMF 2007-05-01 14:55.37
if (newPropertyValue == null) // could be result of custom chooser
return;
// even if we were build from actions, in which case the LWComponents
// have already been changed via that action, call setPropertyValue
// here so any listening LWCToolPanels can update their held state,
// and so subclasses can update their displayed selected icons
// Okay, do NOT call this with the action? But what happens if nothing is selected?
if (newPropertyValue instanceof Action) {
System.out.println("Skipping setPropertyValue & firePropertyChanged for Action " + newPropertyValue);
} else {
Color oldValue = produceValue();
displayValue(newPropertyValue);
// System.out.println(newPropertyValue.toString());
firePropertyChanged(oldValue, newPropertyValue);
}
repaint();
}
/** fire a property change event even if old & new values are the same */
// COULD USE Component.firePropertyChange! all this is getting us is diagnostics!
protected void firePropertyChanged(Object oldValue, Object newValue)
{
if (getPropertyKey() != null) {
PropertyChangeListener[] listeners = getPropertyChangeListeners();
if (listeners.length > 0) {
PropertyChangeEvent event = new LWPropertyChangeEvent(this, getPropertyKey(), oldValue, newValue);
for (int i = 0; i< listeners.length; i++) {
if (DEBUG.TOOL && (DEBUG.EVENTS || DEBUG.META)) System.out.println(this + " fires " + event + " to " + listeners[i]);
listeners[i].propertyChange(event);
}
}
}
}
/** factory for superclass buildMenu */
protected Icon makeIcon(Object value) {
return new BlobIcon(16,16, (Color) value);
}
protected Object runCustomChooser() {
if (tufts.Util.isWindowsPlatform())
popupWindow.setAlwaysOnTop(false);
Color c = tufts.vue.VueUtil.runColorChooser("Select Custom Color", getColor(), this);
return c;
// todo: set up own listeners for color change in chooser
// --that way way can actually tweak color on map as they play
// with it in the chooser
}
ColorMenuButton() { this(sTestColors, true); }
/*private static String[] sTestNames = { "Black",
"White",
"Red",
"Green",
"Blue" };*/
private static Color[] sTestColors = { new Color(0,0,0),
new Color(255,255,255),
new Color(255,0,0),
new Color(0,255,0),
new Color(0,0,255) };
public void actionPerformed(ActionEvent arg0) {
// set popup window visibility
if (!popupWindow.isVisible()) {
// set location relative to button
Point location = getLocation();
SwingUtilities.convertPointToScreen(location, getParent());
if (false) {
// this code was causing the menu to drop too low on Mac OS X Leopard
// (so it would dissapear when mousing down from the color button to the pop-up menu).
// SMF 2008-03-12 -- See VUE-821
location.translate(0, getHeight()
+ (getBorder() == null ? 0
: getBorder().getBorderInsets(this).bottom));
} else {
// this makes things work on Leopard, but does it break things on Windows or Linux? SMF 2008-03-12
location.translate(0, getHeight());
}
rebuildMenu();
System.out.println(location);
GUI.keepLocationOnScreen(location, new Dimension(popupWindow.getWidth(),popupWindow.getHeight()));
System.out.println("G ? H : " + getWidth() + " " + getHeight());
System.out.println(location);
popupWindow.setLocation(location);
if (tufts.Util.isUnixPlatform())
{
popupWindow.setAlwaysOnTop(true);
//popupWindow.set
//popupWindow.toFront();
}
else
{
popupWindow.setAlwaysOnTop(false);
}
popupWindow.setVisible(true);
popupWindow.requestFocus();
} else {
// hide it otherwise
popupWindow.setVisible(false);
if (arg0.getSource() instanceof JButton)
{
JButton b = ((JButton)(arg0).getSource());
if (b.getIcon() instanceof BlobIcon)
{
BlobIcon blob = ((BlobIcon)b.getIcon());
selectValue(blob.getColor());
}
}
}
}
public void setPropertyKey(Object key) {
mPropertyKey = key;
}
}