package awt;

import java.applet.*;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

Open look style menu button
public class MenuButton extends Component { protected static Vector openPopups = new Vector(); static final int capWidth = 20; // The width of the Button's endcap static boolean sandbox = false; // true if silly warnings appear in all windows (applet) private String label; // The Button's text private boolean pressed = false; // true if the button is detented. private boolean mousePressed = false; // true if user is still pressing button
When the user pushes the button, an ActionEvent is posted. The application may then render a popup menu (on-the-fly generated popup menu), or do nothing in case the popup menu was already rendered (statically generated popup menu).
private ActionListener actionListener; public PopupWindow popup; // Add buttons etc here public static final boolean RIGHT = false; public static final boolean DOWN = true; private boolean direction;
Constructs a Button with no label.
public MenuButton(Component parent,boolean dir) { this("",parent,dir); }
Constructs a Button with the specified label. @param label the label of the button
public MenuButton(String label,Component parent,boolean dir) { this.label = label; enableEvents(AWTEvent.MOUSE_EVENT_MASK); popup = createPopup(parent,this); direction = dir; Frame fram = PopupWindow.getFrame(parent); if (fram.getWarningString() != null) sandbox = true; } public static PopupWindow createPopup(Component parent) { return createPopup(parent,null); } public static PopupWindow createPopup(Component parent,MenuButton par) { PopupWindow popup; popup = new PopupWindow(parent); popup.parentButton = par; popup.setBackground(Color.lightGray); return popup; }
gets the label @see setLabel
public String getLabel() { return label; } public void setBackground(Color c) { super.setBackground(c); repaint(); } public void setForeground(Color c) { super.setForeground(c); repaint(); } public void setEnabled(boolean b) { if (b != isEnabled()) { super.setEnabled(b); repaint(); } }
sets the label @see getLabel
public void setLabel(String label) { this.label = label; invalidate(); repaint(); }
factor is e.g. 0.7
private Color darker(Color col,float factor) { return new Color((int)(col.getRed() * factor), (int)(col.getGreen() * factor), (int)(col.getBlue() * factor)); } private Color brighter(Color col,float factor) { return new Color(Math.min((int)(col.getRed() / factor),255), Math.min((int)(col.getGreen() / factor),255), Math.min((int)(col.getBlue() / factor),255)); } public void paint(Graphics g) { int width = getSize().width - 1; int height = getSize().height - 1; Color interior = getBackground(); Color highlight1; Color highlight2; int labelWidth; int capwidth; Font f = getFont(); if (f != null) { FontMetrics fm = getFontMetrics(getFont()); labelWidth = fm.stringWidth(label) + 20; capwidth = width - labelWidth; if (capwidth > height) capwidth = height; if (capwidth < capWidth) capwidth = capWidth; if (capwidth > (width*4)/5) capwidth = (width*4)/5; } else { capwidth = capWidth; labelWidth = 0; } // ***** determine what colors to use if(pressed) { highlight1 = darker(interior,0.7f); highlight2 = brighter(interior,0.7f); } else { highlight1 = brighter(interior,0.7f); highlight2 = darker(interior,0.7f); } // ***** paint the interior of the button g.setColor(pressed ? darker(interior,0.85f) : interior); // left cap g.fillArc(0,0, // start capwidth,height, // size 90,180); // angle // right cap g.fillArc(width - capwidth,0, // start capwidth,height, // size 270,180); // angle // inner rectangle g.fillRect(capwidth/2, 0, width - capwidth + 1, height); // ***** highlight the perimeter of the button Button.drawOpenlookOval(g,0,0,width,height,highlight1,highlight2,capwidth); // lower arc right cap g.setColor(highlight2); g.drawArc(width - capwidth,0, // start capwidth,height, // size 270,180-40); // angle // ***** draw the label in the button if (f != null) { FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, (width-labelWidth) / 2, (height+fm.getHeight()) / 2 - fm.getMaxDescent() ); // very strange bug in jdk1.2 g.setColor(getBackground()); g.drawLine(0,0,0,0); } // draw triangle { int x = width - capWidth/2 - 10; int y = height/2 - 5; if (direction == RIGHT) { g.setColor(Color.darkGray); g.drawLine(x,y,x,y+10); g.drawLine(x,y+10,x+10,y+5); g.setColor(Color.white); g.drawLine(x,y,x+10,y+5); } else if (direction == DOWN) { g.setColor(Color.darkGray); g.drawLine(x,y,x+10,y); g.drawLine(x,y,x+5,y+10); g.setColor(Color.white); g.drawLine(x+10,y,x+5,y+10); } } // ghost if disabled if (!isEnabled()) { g.setColor(Color.white); for (int y = 0; y < height; y+=2) { for (int x = (y%4==0) ? 0 : 2; x < width; x+=4) g.drawLine(x,y,x,y); } } } public Dimension getPreferredSize() { Font f = getFont(); if (f != null) { FontMetrics fm = getFontMetrics(getFont()); return new Dimension(fm.stringWidth(label) + 20 + capWidth/* *2 */,fm.getHeight() + 5); // was +10 (gve) } else { return new Dimension(100, 50); } } public Dimension getMinimumSize() { return getPreferredSize(); } public void addActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.add(actionListener, listener); enableEvents(AWTEvent.MOUSE_EVENT_MASK); } public void removeActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.remove(actionListener, listener); } public void processMouseEvent(MouseEvent e) { if (isEnabled()) switch (e.getID()) { case MouseEvent.MOUSE_PRESSED: // render myself inverted.... pressed = mousePressed = true; repaint(); // Repaint might flicker a bit. To avoid this, you can use // double buffering (see the Gauge example). break; case MouseEvent.MOUSE_RELEASED: // render myself normal again mousePressed = false; if (pressed) { pressed = false; // Repaint might flicker a bit. To avoid this, you can use // double buffering repaint(); if (actionListener != null) actionListener.actionPerformed(new ActionEvent(this,ActionEvent.ACTION_PERFORMED,label)); if (popup.isVisible()) { // Popdown closeme(); break; } // If this menu has other popups open, close them first // before showing the new popup { Component c = this; for (; c != null; c = c.getParent()) { if (c instanceof PopupWindow) { if (((PopupWindow)c).parentButton != null) ((PopupWindow)c).parentButton.closechildpopups(); break; } } if (c == null) // pushed a top level menu button closepopups(); // close other open top level menu button } Point loc = getLocationOnScreen(); if (direction == DOWN) popup.setLocation(loc.x,loc.y+getSize().height); else if (direction == RIGHT) popup.setLocation(loc.x+getSize().width,loc.y); popup.setParentButton(this); popup.setVisible(true); } break; case MouseEvent.MOUSE_ENTERED: if (!pressed && mousePressed) { pressed = true; repaint(); } break; case MouseEvent.MOUSE_EXITED: if (pressed && mousePressed) { // Cancel! Don't send action event. pressed = false; // Repaint might flicker a bit. To avoid this, you can use // double buffering. repaint(); } break; } super.processMouseEvent(e); }
Close all child popups
private void closechildpopups() { for (int j = popup.getComponentCount()-1; j>=0; j--) { Component comp = popup.getComponent(j); if (!(comp instanceof MenuButton)) continue; MenuButton mb = (MenuButton)comp; mb.closeme(); } }
Close this popup and all children
private void closeme() { popup.setVisible(false); for (int j = popup.getComponentCount()-1; j>=0; j--) { Component comp = popup.getComponent(j); if (!(comp instanceof MenuButton)) continue; MenuButton mb = (MenuButton)comp; mb.closeme(); } }
User pressed something inside the popup windows; close them all.
public static void closepopups() { for (int i = openPopups.size()-1; i>=0; i--) { Window popup = (Window)(openPopups.elementAt(i)); popup.setVisible(false); } openPopups.setSize(0); } public void add(Component comp) { popup.add(comp); } public void pack() { // Don't pack right now, wait until we are made visible popup.setNeedsPacking(true); } public static MenuButton getParentMenuButton(Component comp) { while (true) { if (comp instanceof MenuButton) return (MenuButton)comp; if (comp instanceof PopupWindow) comp = ((PopupWindow)comp).parentComp; else comp = comp.getParent(); if (comp == null) return null; } } }
JDK 1.1.5 behaves oddly: when you call setBounds() on a child of a Window, that Window will never show up!!! Unfortunately, this is precisely what pack() does ... so we add a Panel, on which we never call setBounds(), and do everything there ... (sigh)
//PENDING: use System.getProperty("java.version") ? class PopupWindow extends Window implements MouseListener { public MenuButton parentButton;
null if standard behaviour; if jdk1.1.5, then all stuff is done on the panel, not on the window itself
private Panel panel; private boolean needspacking = false;
getparent() returns the Frame the popup belongs to. In parentComp, we store the menu button this popup belongs to
protected Component parentComp; public PopupWindow(Component comp) { this(getFrame(comp)); } public static Frame getFrame(Component comp) { while (!(comp instanceof Frame)) comp = comp.getParent(); return (Frame)comp; } public PopupWindow(Frame f) { super(f); boolean bogus = true; // jdk1.1.5 bogus behaviour? bogus = false; if (f.getWarningString() != null) // warning string is displayed above the contents of the panel // if we try to circumvent the jdk1.1.5 bug ... bogus = false; if (bogus) super.add(panel = new Panel()); setLayout(new GridPackLayout(0,1)); }
At the next setVisible(true), this Window will be packed first.
public void setNeedsPacking(boolean set) { needspacking = set; }
Register the button this popup belongs to
public void setParentButton(Component comp) { parentComp = comp; } public void setVisible(boolean b) { if (needspacking) pack(); super.setVisible(b); if (b) { // netscape 4.61win shows menus in top left corner without this! //System.out.println(getLocationOnScreen()+" "+getSize()); // System.out.print("."); } if (!b) { // Make invisible MenuButton.openPopups.removeElement(this); return; } listen(this); MenuButton.openPopups.addElement(this); // Position the window so that it doesn't fall off the screen. Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); // Compensate for things like task bars that float atop everything, even popups screen.height -= 20; screen.width -= 20; Dimension mydim = getPreferredSize(); Point p = getLocationOnScreen(); if (p.y+mydim.height > screen.height) { // falls below bottom of screen! p.y = screen.height - mydim.height; } if (p.x+mydim.width > screen.width) { // falls of right side of screen! p.x = screen.width - mydim.width; } setLocation(p); }
Listen to all buttons, ... inside this container
private void listen(Container cont) { for (int i = cont.getComponentCount()-1; i>=0; i--) { Component comp = cont.getComponent(i); if (comp instanceof MenuButton) continue; if (comp instanceof Container) listen((Container)comp); else comp.addMouseListener(this); } } public Component add(Component comp) { if (panel != null) return panel.add(comp); else return super.add(comp); }
Default implementation of pack(), with a jdk1.1.5 quirkaround.
private void standardpack() { //System.out.println("before pack() "+getSize()); if (panel == null) { // super.pack(); doLayout(); setSize(getPreferredSize()); //System.out.println("packed "+getSize()+" "+this); return; } if (getFont() == null) { // Cannot layout yet // so delay until next setVisible() call needspacking = true; return; } // Don't pack() the Window directly, since this // would call setBounds() on the panel // which is a child of the Window // (so JDK 1.1.5 flips) panel.doLayout(); setSize(panel.getPreferredSize()); //System.out.println("set size to "+getSize()); needspacking = false; }
Besides the default pack(), we try to increase the number of columns if the popup window becomes too high.
public void pack() { //System.out.println("Pack request on "+this); LayoutManager layout; if (panel == null) layout = getLayout(); else layout = panel.getLayout(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); while (true) { standardpack(); if (!(layout instanceof GridPackLayout)) return; Dimension mydim = getPreferredSize(); if (mydim.height <= screen.height) { return; } // Increase number of columns GridPackLayout grid = (GridPackLayout)layout; grid.setColumns(grid.getColumns()+1); } } public void setLayout(LayoutManager mgr) { if (panel != null) panel.setLayout(mgr); // keep on default behaviour during initialization else super.setLayout(mgr); } public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseReleased(MouseEvent e) { MenuButton.closepopups(); } }