/* * 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; } }