1 /*
   2  * Copyright (c) 2001, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package test.java.awt.event.helpers.lwcomponents;
  25 
  26 import java.awt.*;
  27 import java.awt.event.*;
  28 
  29 /**
  30  * Lightweight <i>Button</i> component with some nice features. This
  31  * component provides the capabilities of Buttons, namely that you it
  32  * displays a label string and, when clicked, causes the
  33  * ActionListener method to be called.<p>
  34  *
  35  * The look of the button is a little unusual. There are three
  36  * rectangles drawn at the border that indicate various states
  37  * of the button.  These are (listed from outside in)<p>
  38  * <ol>
  39  * <li><b>Focus</b>: Indicates that the LWButton has the focus.
  40  * <li><b>Mouse Over</b>: Indicates that the mouse is over the component.
  41  * <li><b>Mouse Pressed</b>: Indicates that the mouse has been pressed.
  42  * </ol>
  43  *
  44  * In addition, when the button has been activated (mouse clicked or
  45  * via keyboard activation) the button flashes briefly.
  46  */
  47 
  48 public class LWButton extends LWComponent {
  49 
  50   /*
  51    * The button's Label.
  52    * If Label is not specified it will default to "".
  53    * @serial
  54    * @see getLabel()
  55    * @see setLabel()
  56    */
  57   private String label;
  58   private boolean isInClick = false;
  59 
  60   private static final String base = "LWButton";
  61   private static int nameCounter = 0;
  62 
  63   private transient ActionListener actionListener;
  64 
  65   /*
  66    * The action to be performaed once a button has been
  67    * pressed.
  68    * actionCommand can be null.
  69    * @serial
  70    * @see getActionCommand()
  71    * @see setActionCommand()
  72    */
  73   String actionCommand;
  74 
  75   Color colMousePressed;
  76 
  77   public LWButton() { this(""); }
  78 
  79   public LWButton(String label) {
  80     this(label, Color.red, Color.green, Color.white);
  81   }
  82 
  83   /**
  84    * Initialize the LWButton, fully specifying all parameters.
  85    * @param label The string to display.
  86    * @param fgnd  The color to draw the label in.
  87    * @param bkgnd The color of the button itself.
  88    * @param mousePressed The Color of the MousePressed rectangle.
  89    */
  90   public LWButton(String label, Color fgnd, Color bkgnd, Color mousePressed) {
  91     super();
  92     this.label = label;
  93     setBackground(fgnd);
  94     setForeground(bkgnd);
  95     colMousePressed = mousePressed;
  96     setName(makeComponentName());
  97 
  98     enableEvents(  AWTEvent.MOUSE_EVENT_MASK
  99          | AWTEvent.KEY_EVENT_MASK
 100          | AWTEvent.ACTION_EVENT_MASK);
 101     setEnabled(true);
 102   }
 103 
 104   /**
 105    * Make the component flash briefly.
 106    */
 107   public void flash() {
 108     isInClick = true;
 109     repaint();
 110 
 111     class unClicker implements Runnable {
 112       @Override
 113       public void run() {
 114         try { Thread.sleep(100); } catch (InterruptedException ee) {}
 115         isInClick = false;
 116         repaint();
 117       }
 118     }
 119     try {
 120       unClicker uc = new unClicker();
 121       new Thread(uc).start();
 122     } catch (Exception e) {
 123       // In case we're in an applet and the security has not been
 124       // turned off (in which case we can't start a new thread)
 125       // we can catch that and set the flag back to how it should be.
 126       isInClick = false;
 127       repaint();
 128     }
 129   }
 130 
 131   /**
 132    * Set the MousePressed color (the color shown in the MousePressed rectangle
 133    * when the mouse is over the component).
 134    * @param c The color of the MousePressed rectangle.
 135    */
 136   public void setMousePressedColor(Color c) { colMousePressed = c; }
 137 
 138   /**
 139    * Get the MousePressed color.
 140    * @return The color of the MousePressed rectangle.
 141    */
 142   public Color getMousePressedColor() { return colMousePressed; }
 143 
 144   /**
 145    * Used to dispatch out the ActionEvent for a corresponding InputEvent.
 146    * @param e The InputEvent that is causing the ActionEvent dispatch.
 147    */
 148   private void sendActionEvent(InputEvent e) {
 149 
 150     int modifiers = e.getModifiers();
 151     int aModifiers = 0;
 152 
 153     if ((modifiers & MouseEvent.SHIFT_MASK) != 0) {
 154       aModifiers |= ActionEvent.SHIFT_MASK;
 155     }
 156     if ((modifiers & MouseEvent.CTRL_MASK) != 0) {
 157       aModifiers |= ActionEvent.CTRL_MASK;
 158     }
 159     if ((modifiers & MouseEvent.META_MASK) != 0) {
 160       aModifiers |= ActionEvent.META_MASK;
 161     }
 162     if ((modifiers & MouseEvent.ALT_MASK) != 0) {
 163       aModifiers |= ActionEvent.ALT_MASK;
 164     }
 165 
 166     ActionEvent ae = new ActionEvent(this,
 167                      ActionEvent.ACTION_PERFORMED,
 168                      actionCommand,
 169                      aModifiers);
 170     // XXX: What's the right way to send out the ActionEvent?
 171     //   My assumption was to put it into the system event queue
 172     //   and the it will be dispatched back into <i>processEvent</i>
 173     //   for us.  However this doesn't happen...?
 174     if (actionListener != null) {
 175       actionListener.actionPerformed(ae);
 176     }
 177     //Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ae);
 178   }
 179 
 180   /**
 181    * Set whether the component is enabled ({@code true}) or not.
 182    * @param enabled If {@code true}, the component is to be enabled.
 183    */
 184   @Override
 185   public void setEnabled(boolean enabled) {
 186     super.setEnabled(enabled);
 187 
 188     if (enabled) {
 189       enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
 190     } else {
 191       disableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
 192     }
 193     repaint(1);
 194   }
 195 
 196   /**
 197    * Indicates that LWButton component can receive focus.
 198    * @return  {@code true} if the LWButton component can receive focus
 199    */
 200   @Override
 201   public boolean isFocusTraversable() { return true; }
 202 
 203   /**
 204    * Construct a name for this component. Called by getName() when the
 205    * name is null.
 206    */
 207   String makeComponentName() {
 208     synchronized (getClass()) {
 209       return base + nameCounter++;
 210     }
 211   }
 212 
 213   /**
 214    * Handle painting the enabled version of the component.
 215    *
 216    * ASSUMES: g.color may be changed
 217    */
 218   @Override
 219   public void paint(Graphics g) {
 220 
 221     super.paint(g);
 222     restrictGraphicsToClientArea(g);
 223 
 224     Dimension dim = getClientSize();
 225 
 226     int s = Math.min(dim.width - 1, dim.height - 1);
 227 
 228     if (isInClick) {
 229       g.setColor(Color.white);
 230     } else {
 231       g.setColor(getBackground());
 232     }
 233 
 234     // In jdk 1.2 (pre-release) there was a bug using clearRect
 235     // to paint the background of a lightweight.
 236     //g.clearRect(loc.x, loc.y, dim.width, dim.height);
 237     g.fillRect(0, 0, dim.width, dim.height);
 238 
 239     if (mouseB1Pressed) {
 240       g.setColor(colMousePressed);
 241       //LWComponent.traceMsg("paint mousePressed " + this.toString());
 242       g.drawRect(1, 1, dim.width - 3, dim.height - 3);
 243     }
 244 
 245     Font f = getFont();
 246     if (f != null) {
 247       FontMetrics fm = getFontMetrics(f);
 248       g.setColor(getForeground());
 249       g.drawString(label,
 250                    s/2 - fm.stringWidth(label)/2,
 251                    s/2 + fm.getMaxDescent());
 252     }
 253 
 254     unrestrictGraphicsFromClientArea(g);
 255   }
 256 
 257   @Override
 258   public Dimension getPreferredSize() {
 259     Font f = getFont();
 260     if (f != null) {
 261       FontMetrics fm = getFontMetrics(f);
 262       int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
 263       return new Dimension(max, max);
 264     } else {
 265       return new Dimension(100, 100);
 266     }
 267   }
 268 
 269   @Override
 270   public Dimension getMinimumSize() {
 271     return getPreferredSize();
 272   }
 273 
 274   /**
 275    * Get the text displayed in the LWButton.
 276    * @return  the text displayed in the LWButton
 277    */
 278   public String getText() { return label; }
 279 
 280   /**
 281    * Set the text displayed in the LWButton.
 282    * @param s The text to be displayed.
 283    */
 284   public void setText(String s) {
 285     Font f = getFont();
 286     int oWidth = 0;
 287     int oHeight = 0;
 288     int nWidth = 0;
 289     int nHeight = 0;
 290     int invalidated = 0;
 291     FontMetrics fm = null;
 292 
 293     if (f != null) {
 294       fm = getFontMetrics(f);
 295       oWidth = fm.stringWidth(label);
 296       oHeight = fm.getHeight();
 297     }
 298 
 299     this.label = s;
 300 
 301     if (f != null) {
 302       nWidth = fm.stringWidth(label);
 303       nHeight = fm.getHeight();
 304 
 305       if ((nWidth > oWidth) || (nHeight > oHeight)) {
 306         invalidate();
 307         invalidated = 1;
 308       }
 309     }
 310 
 311     if (invalidated == 0) {
 312       repaint();
 313     }
 314   }
 315 
 316   /**
 317    * Set the command name for the action event fired
 318    * by this button. By default this action command is
 319    * set to match the label of the button.
 320    * @param     command  A string used to set the button's
 321    *                     action command.
 322    *            If the string is <code>null</code> then the action command
 323    *            is set to match the label of the button.
 324    * @see       java.awt.event.ActionEvent
 325    * @since     JDK1.1
 326    */
 327   public void setActionCommand(String command) {
 328     actionCommand = command;
 329   }
 330 
 331   /**
 332    * Returns the command name of the action event fired by this button.
 333    * If the command name is {@code null} (default) then this method
 334    * returns the label of the button.
 335    * 
 336    * @return the command name of the action event fired by this button
 337    *         or the label of the button (in case of {@code null})
 338    */
 339   public String getActionCommand() {
 340     return (actionCommand == null? label : actionCommand);
 341   }
 342 
 343   /**
 344    * Add the specified action listener to receive action events from
 345    * this button. Action events occur when a user presses or releases
 346    * the mouse over this button.
 347    * @param         l the action listener.
 348    * @see           java.awt.event.ActionListener
 349    * @see           #removeActionListener
 350    * @since         JDK1.1
 351    */
 352   public synchronized void addActionListener(ActionListener l) {
 353     actionListener = AWTEventMulticaster.add(actionListener, l);
 354     enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 355   }
 356 
 357   /**
 358    * Remove the specified action listener so that it no longer
 359    * receives action events from this button. Action events occur
 360    * when a user presses or releases the mouse over this button.
 361    * @param         l     the action listener.
 362    * @see           java.awt.event.ActionListener
 363    * @see           #addActionListener
 364    * @since         JDK1.1
 365    */
 366   public synchronized void removeActionListener(ActionListener l) {
 367     actionListener = AWTEventMulticaster.remove(actionListener, l);
 368   }
 369 
 370   @Override
 371   protected void processKeyEvent(KeyEvent e) {
 372     super.processKeyEvent(e);
 373     if (!isEnabled()) { return; }
 374     switch(e.getID()) {
 375     case KeyEvent.KEY_TYPED:
 376       switch (e.getKeyCode()) {
 377         case KeyEvent.VK_ENTER:
 378         case KeyEvent.VK_SPACE:
 379           flash();
 380           sendActionEvent(e);
 381           break;
 382       }
 383       break;
 384     }
 385   }
 386 
 387   @Override
 388   protected void processMouseEvent(MouseEvent e) {
 389     super.processMouseEvent(e);
 390     if (!isEnabled()) { return; }
 391     switch(e.getID()) {
 392     case MouseEvent.MOUSE_PRESSED:
 393       requestFocus();
 394       repaint();
 395       break;
 396     case MouseEvent.MOUSE_RELEASED:
 397       repaint();
 398       break;
 399     case MouseEvent.MOUSE_CLICKED:
 400       if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
 401         flash();
 402         sendActionEvent(e);
 403       }
 404       break;
 405     }
 406   }
 407 
 408   /**
 409    * Returns the parameter string representing the state of this
 410    * button. This string is useful for debugging.
 411    * @return     the parameter string of this button.
 412    */
 413   @Override
 414   protected String paramString() {
 415     return super.paramString() + ", label = " + label;
 416   }
 417 
 418 }