/*
* 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;
import tufts.vue.gui.GUI;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
/**
*
* This class provides a popup radio button selector component.
* It is used for the main tool bar tool
*
* @author csb
* @version $Revision: 1.23 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $
**/
public class PaletteButton extends JRadioButton
{
/* this is thr eolumn threshold array to tell when to add another columng in the palette */
static int mColThreshold[] = VueResources.getIntArray("menuFlowThreshold") ;
// default offsets for drawing popup arrow via code
public int mArrowSize = 3;
public int mArrowHOffset = -9;
public int mArrowVOffset = -7;
/** the popup overlay icons for up and down states **/
public Icon mPopupIndicatorIconUp = null;
public Icon mPopupIndicatorDownIcon = null;
/** The currently selected palette item--if any **/
protected PaletteButtonItem mCurSelection = null;
/** the set of palette items for this buttons menu **/
protected PaletteButtonItem [] mItems = null;
/** does this button have a popup? **/
protected boolean mHasPopup = false;
/** do we need to draw an arrow via code **/
protected boolean mDrawArrowByCode = false;
/** the popup menu **/
protected JPopupMenu mPopup = null;
/** are we drawing the popup icon indcator with an image or in code? **/
protected boolean mUseIconIndicator = true;
/* the context object */
private Object mContext = null;
/** the current overlay popup indicator icon **/
protected Icon mPopupIndicatorIcon = null;
protected Icon mPopupIndicatorUpIcon = null;
/**
* Creates a new PaletteButton 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 PaletteButtonItems for the menu.
**/
public PaletteButton(PaletteButtonItem[] pItems)
{
if (pItems != null) {
setPaletteButtonItems(pItems);
setRolloverEnabled(true);
}
setBorder( null);
setFocusable(false);
GUI.applyToolbarColor(this);
}
/**
* Creates a new PaletteButton with no menus
**/
public PaletteButton() {
this(null);
}
/**
* Sets a user context object.
**/
public void setContext( Object pContext) {
mContext = pContext;
}
/**
* Gets teh user context object
**/
public Object getContext() {
return mContext;
}
/**
* Sets the popup indicator icon icon
*
* @param pIcon the icon
**
public void setPopupIndicatorIcon( Icon pIcon) {
mPopupIndicatorIcon = pIcon;
}
/**
* Gets teh popup indicator icon
* @return the icon
**/
public Icon getPopupIndicatorIcon() {
return mPopupIndicatorIcon;
}
/**
* This sets the state of the mUseArrowIcon property. It is
* used to tell how to draw the popup visual cue. If true,
* then it uses the image, otherwise, it draws the default
* arrow.
*
**/
public void setPopupIconIndicatorEnabled( boolean pState) {
mUseIconIndicator = pState;
}
/**
* isPopupIconIndicatorEnabled
* This method tells how to draw the popup arrow indicator
* on the button in paint(). If some image should be used, then this
* returns true. If the default arrow that's drawn by code
* should be used, this returns false.
*
* @ return boolean true if should use image to draw indictor; false if not
**/
public boolean isPopupIconIndicatorEnabled() {
//return mUseIconIndicator;
return false;
}
public void removePopupComponent(Component c)
{
mPopup.remove(c);
}
public void addPopupComponent(Component c)
{
mPopup.add(c);
}
/**
* This method adds a new PaleeteButtonItem to the PaletteButton's
* menu.
*
* @param pItem the new PaletteButtonItem to be added.
**/
public void addPaletteItem(PaletteButtonItem pItem) {
if( mItems == null) {
mItems = new PaletteButtonItem[1];
mItems[0] = pItem;
} else {
int len = mItems.length;
PaletteButtonItem newItems[] = new PaletteButtonItem[len+1];
for(int i=0; i< len; i++) {
newItems[i] = mItems[i];
}
newItems[len] = pItem;
setPaletteButtonItems(newItems);
}
}
/**
* This removes an item from the popup menu
* @param pItem the item to remove
**/
public void removePaletteItem( PaletteButtonItem pItem ) {
if( mItems != null) {
int len = mItems.length;
PaletteButtonItem [] newItems = null;
boolean found = false;
int slot = 0;
if( len > 1)
newItems = new PaletteButtonItem [len-1];
for( int i=0; i< len; i++) {
if( mItems[i].equals( pItem) ) {
found = true;
}
else {
newItems[slot] = mItems[i];
slot++;
}
}
if( found) {
if( len == 1)
newItems = null;
setPaletteButtonItems( newItems);
}
}
}
/**
* Sets the set of PaletteButtonItems for the popup menu
* @param pItems the array of items.
**/
public void setPaletteButtonItems(PaletteButtonItem [] pItems) {
if (DEBUG.INIT) System.out.println(this + " setPaletteButtonItems n=" + pItems.length);
mItems = pItems;
buildPalette();
}
/**
* getPaletteButtonItems
* Gets the array of PaletteButtonItems
* @return the array of items
**/
public PaletteButtonItem[] getPaletteButtonItems() {
return mItems;
}
/**
* This method builds the PaletteButton's palette menu and sets up
* all appropriate event listeners to handle menu selection. It
* also calculates the best layout fo rthe popup based on the
* the number of items.
**/
protected void buildPalette() {
// clear the old
mPopup = null;
if (mItems == null || mItems.length < 2) {
mHasPopup = false;
return;
}
mHasPopup = true;
int numItems = mItems.length;
int cols = 0;
while (mColThreshold[cols] < numItems && cols < mColThreshold.length)
cols++;
int rows = (numItems + (numItems % cols )) / cols ;
if (rows < 3 && GUI.isMacBrushedMetal() && VueUtil.getJavaVersion() < 1.5f /*&& tiger */) { // total hack for now
// JAVA BUG: there appears to be an absolute minimunm width & height
// for pop-up's: approx 125 pixels wide, no smaller, and approx 34 pixels
// tall, no smaller.
//
// This bug only shows up in Java 1.4.2 on Mac OS X Tiger (10.4+) if
// we're using the Metal version of the Mac Aqua Look & Feel.
// The default Mac Aqua L&F doesn't see the bug, and java 1.5 works fine.
//
// Note this bug affects almost all pop-ups: even combo-box's! (See
// VUE font-size), as well as many roll-over / tool-tip pop-ups.
// I'm guessing the bug is somewhere down in the PopupFactory or PopupMenuUI
// or the java small-window caching code shared by pop-ups and tool-tips.
//
// SMF 2005-08-11
// This forces the smaller menus we use into 1 row which use
// up the forced width better. These #'s hard-coded based
// on our current usage...
rows = 1;
cols = 3;
}
//JPopupMenu.setDefaultLightWeightPopupEnabled(false);
mPopup = new JPopupMenu();//PBPopupMenu(rows, cols);
new PalettePopupMenuHandler(this, mPopup); // installs mouse & popup listeners
for (int i = 0; i < numItems; i++) {
mPopup.add(mItems[i]);
mItems[i].setPaletteButton(this);
//mItems[i].addActionListener(this);
}
if (DEBUG.INIT)
System.out.println("*** CREATED POPUP " + mPopup
+ " margin=" + mPopup.getMargin()
+ " layout=" + mPopup.getLayout()
);
}
/**
* Given a trigger component (such as a label), when mouse is
* pressed on it, pop the given menu. Default location is below
* the given trigger.
*/
public static class PalettePopupMenuHandler extends tufts.vue.MouseAdapter
implements javax.swing.event.PopupMenuListener
{
private long mLastHidden;
private JPopupMenu mMenu;
public PalettePopupMenuHandler(Component trigger, JPopupMenu menu)
{
trigger.addMouseListener(this);
menu.addPopupMenuListener(this);
mMenu = menu;
}
public void mousePressed(MouseEvent e) {
long now = System.currentTimeMillis();
if ((now - mLastHidden > 100) && (mMenu.getComponentCount() > 1))
showMenu(e.getComponent());
}
/** show the menu relative to the given trigger that activated it */
public void showMenu(Component trigger) {
mMenu.show(trigger, getMenuX(trigger), getMenuY(trigger));
}
/** get menu X location relative to trigger: default is 0 */
public int getMenuX(Component trigger) { return 0; }
/** get menu Y location relative to trigger: default is trigger height (places below trigger) */
public int getMenuY(Component trigger) { return trigger.getHeight(); }
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
mLastHidden = System.currentTimeMillis();
//out("HIDING");
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) { /*out("SHOWING");*/ }
public void popupMenuCanceled(PopupMenuEvent e) { /*out("CANCELING");*/ }
// One gross thing about a pop-up menu is that there's no way to know that it
// was just hidden by a click on the component that popped it. That is, if you
// click on the menu launcher once, you want to pop it, and if you click again,
// you want to hide it. But the AWT system autmatically cancels the pop-up as
// soon as the mouse-press happens ANYWYERE, and even before we'd get a
// processMouseEvent, so by the time we get this MOUSE_PRESSED, the menu is
// already hidden, and it looks like we should show it again! So we have to use
// a simple timer.
}
/**
* This method sets teh display properties of the button based on
* the properties set in a PaletteButtonMenu item. This allows the
* primary tool button to reflect the current selected item on the main toolbar.
*
* @param pItem - the PaletteButtonItem to use as the source
**/
public void setPropertiesFromItem(AbstractButton pItem) {
this.setIcon( pItem.getIcon() );
this.setPressedIcon( pItem.getPressedIcon() );
this.setSelectedIcon( pItem.getSelectedIcon() );
this.setRolloverIcon( pItem.getRolloverIcon() );
this.setDisabledIcon( pItem.getDisabledIcon() );
if (pItem.getToolTipText() != null)
this.setToolTipText(pItem.getToolTipText());
this.setRolloverEnabled( getRolloverIcon() != null);
}
public void setPopupOverlayUpIcon( Icon pIcon) {
mPopupIndicatorUpIcon = pIcon;
}
public void setPopupOverlayDownIcon( Icon pIcon) {
mPopupIndicatorDownIcon = pIcon;
}
public void setOverlayIcons( Icon pUpIcon, Icon pDownIcon) {
setPopupOverlayUpIcon( pUpIcon);
setPopupOverlayDownIcon( pDownIcon);
}
public void setIcons( Icon pUp, Icon pDown, Icon pSelect, Icon pDisabled,
Icon pRollover ) {
this.setIcon( pUp);
this.setPressedIcon( pDown);
this.setSelectedIcon( pSelect);
this.setDisabledIcon( pDisabled);
this.setRolloverIcon( pRollover);
this.setRolloverEnabled( pRollover != null );
}
public void setSelected(boolean b) {
//System.out.println(this + " setSelected " + b);
super.setSelected(b);
}
protected void fireStateChanged() {
//System.out.println("PaletteButton: fireStateChanged, selected="+isSelected() + " " + getIcon());
super.fireStateChanged();
}
/**
* Overrides paint method and renders an additional icon ontop of
* of the normal rendering to indicate if this button contains
* a popup handler.
*
* @param Graphics g the Graphics.
**/
public void paint(java.awt.Graphics g) {
super.paint(g);
Dimension dim = getPreferredSize();
Insets insets = getInsets();
// now overlay the popup menu icon indicator
// either from an icon or by brute painting
if( !isPopupIconIndicatorEnabled()
&& mPopup != null
&& !mPopup.isVisible() && mPopup.getComponentCount() >1
) {
// draw popup arrow
Color saveColor = g.getColor();
g.setColor( Color.black);
int w = getWidth();
int h = getHeight();
int x1 = w + mArrowHOffset;
int y = h + mArrowVOffset;
int x2 = x1 + (mArrowSize * 2) -1;
//((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
//RenderingHints.VALUE_ANTIALIAS_ON);
for(int i=0; i< mArrowSize; i++) {
g.drawLine(x1,y,x2,y);
x1++;
x2--;
y++;
}
g.setColor( saveColor);
}
else // Use provided popup overlay icons
if( (mPopup != null) && ( !mPopup.isVisible() ) ) {
Icon overlay = mPopupIndicatorUpIcon;
if( isSelected() ) {
overlay = mPopupIndicatorDownIcon;
}
if( overlay != null) {
overlay.paintIcon( this, g, insets.top, insets.left);
}
}
}
public String toString() {
return "PaletteButton[" + getContext() + "]";
}
/**
* JPopupMenu subclass to set up appearance of pop-up menu.
*
* As of java 1.4.2 on Tiger (Mac OS X 10.4+) the layout
* of this is broken (too much space). OS X 10.3 works
* in 1.4.2, and it works fine in java 1.5 on 10.4+.
*
**/
public class PBPopupMenu extends JPopupMenu
{
public PBPopupMenu(int rows, int cols) {
//setBorderPainted(false);
setFocusable(false);
GUI.applyToolbarColor(this);
setBorder(new LineBorder(getBackground().darker().darker(), 1));
GridLayout grid = new GridLayout(rows, cols);
grid.setVgap(0);
grid.setHgap(0);
if (DEBUG.INIT) System.out.println("*** CREATED GRID LAYOUT " + grid.getRows() + "x" + grid.getColumns());
setLayout(grid);
}
public void menuSelectionChanged(boolean isIncluded) {
if (DEBUG.Enabled) System.out.println(this + " menuSelectionChanged included=" + isIncluded);
super.menuSelectionChanged(isIncluded);
}
public String toString() {
return "PBPopupMenu[" + getIcon() + "]";
}
}
} // end of class PaletteButton