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.io.*;
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 
  30 /**
  31  * This is experimental - The idea is to subclass all the LW components
  32  * from LWComponent to provide for some common capabilities.  The main
  33  * capability to be provided is the status rectangles as done for LWButton.
  34  * In particular the Focus and MouseOver rectangles are generically
  35  * useful, while other rectangles might be useful to other components.<p>
  36  *
  37  * To implement that, here is the idea ... borrowed from Win32 ... Each
  38  * of the LW components has both a client and non-client region.  We
  39  * call paintNC to paint the non-client region (Focus and MouseOver
  40  * rectangles), and the subclass might be permitted to implement paintNC
  41  * but for now they aren't.<p>
  42  *
  43  * Then the paint{Enabled,Disabled} methods are called as appropriate.
  44  * Note that paintDisabled is implemented in LWComponent to call paintEnabled
  45  * then stipple over the top of it.<p>
  46  *
  47  * So it is paintEnabled that the component should implement.  This method
  48  * needs to know the dimensions of the client area (getClientRegion?) and
  49  * the Graphics needs to have it's clip region set appropriately.<p>
  50  *
  51  * <b>KVETCHING</b>: <i>Kvetch</i> is a Yiddish word which means, basically,
  52  * to complain very precisely.  The LWComponent family tracks various pieces
  53  * of information over time that are used to check closely for correct behavior
  54  * in some circumstances.  The method <i>kvetch</i> is where this code lives
  55  * and is intended to check a broad range of conditions.<p>
  56  *
  57  * To turn off specific kvetch's, one simply specifies a System property
  58  * as in this table:<p>
  59  *
  60  * <table border="1">
  61  * <tr><th>Property name</th><th>Value</th><th>Discussion</th></tr>
  62  * <tr>
  63  *    <th>javasoft.awtsqe.lw.IGNORE_FOCUS_KVETCH</th>
  64  *    <th>true or false</th>
  65  *    <td>Specify whether the <i>hasFocus</i> kvetch is checked.</td>
  66  * </tr>
  67  * </table><p>
  68  *
  69  * <b>XXX To implement</b> - specifying colors.  NCBackground,
  70  * FocusRectColor, MouseOverColor are the threee colors.  paintNC
  71  * fills the NC region with NCBackground, and then pains the two
  72  * colors as appropriate.  There needs to be methods to get/specify
  73  * these colors.<p>
  74  *
  75  * <b>XXX To implement</b> - Specifying the component name and toString().
  76  * The subclass should only give the base class name, and a method
  77  * in LWComponent should construct a name from that.  For toString()
  78  * there needs to be a small amount of infrastructure built.<p>
  79  */
  80 
  81 public abstract class LWComponent extends Component {
  82 
  83   protected static Color ncBackgroundColor;
  84   protected static Color focusColor;
  85   protected static Color focusWrongColor;
  86   protected static Color mouseOverColor;
  87 
  88   static {
  89     ncBackgroundColor = Color.white;
  90     focusColor        = Color.black;
  91     focusWrongColor   = Color.magenta;
  92     mouseOverColor    = Color.blue;
  93   }
  94 
  95   /**
  96    * Flag indicating whether our records indicate that the component
  97    * should have focus.
  98    */
  99   protected boolean _shouldHaveFocus = false;
 100   protected boolean _shouldBeShowing = false;
 101 
 102   protected boolean mouseB1Pressed = false;
 103   protected boolean mouseB2Pressed = false;
 104   protected boolean mouseB3Pressed = false;
 105   protected boolean mouseInside    = false;
 106 
 107   protected static boolean tracingOn = false;
 108   protected static PrintStream traceOutput = null;
 109 
 110   // Uncommenting these lines turns on tracing for the package.
 111   //  static {
 112   //    tracingOn = true;
 113   //    traceOutput = System.err;
 114   //  }
 115 
 116   public LWComponent() {
 117     enableEvents(AWTEvent.MOUSE_EVENT_MASK
 118          /*| AWTEvent.MOUSE_MOTION_EVENT_MASK*/
 119            | AWTEvent.FOCUS_EVENT_MASK
 120            | AWTEvent.COMPONENT_EVENT_MASK);
 121   }
 122 
 123   /**
 124    * Print out an error message.
 125    * @param msg  the message
 126    */
 127   public static void errorMsg(String msg) {
 128     System.err.println("ERROR: " + msg);
 129   }
 130 
 131   /**
 132    * Print out a tracing message
 133    * @param msg  the message
 134    */
 135   public static void traceMsg(String msg) {
 136     if (LWComponent.tracingOn) {
 137       LWComponent.traceOutput.println(msg);
 138     }
 139   }
 140 
 141   /////////////////////////////////////////////
 142   /////// FLAGS FOR IGNORING KVETCH's /////////
 143   /////////////////////////////////////////////
 144 
 145   static boolean bIgnFocus = false;
 146 
 147   static {
 148     // Initialize the kvetch ignoring flags here.
 149     String ignFocus = System.getProperty("javasoft.awtsqe.lw.IGNORE_FOCUS_KVETCH",
 150                                          "false");
 151     bIgnFocus = ignFocus.trim().toLowerCase().equals("true");
 152   }
 153 
 154   /**
 155    * Check the <i>shoulds</i> and return a string indicating which
 156    * do not match the components actual state.
 157    * 
 158    * @return  the string indicating which do not match the components actual state
 159    */
 160   public String kvetch() {
 161     String ret = this.toString();
 162     boolean errors = false;
 163 
 164     if (!bIgnFocus) {
 165       if (hasFocus()) {
 166         if (!shouldHaveFocus()) {
 167           ret += "\nERROR: hasFocus indicates we have Focus, when we shouldn't.";
 168           errors = true;
 169         }
 170       } else {
 171         if (shouldHaveFocus()) {
 172           ret += "\nERROR: (see bug#4233658) hasFocus does not indicate we have Focus, when we should.";
 173           errors = true;
 174         }
 175       }
 176     }
 177 
 178     if (errors) {
 179       return ret;
 180     } else {
 181       return null;
 182     }
 183   }
 184 
 185   /**
 186    * Check the <i>shoulds</i> and return a string indicating which
 187    * do not match the components actual state.  Prints the output
 188    * to the given PrintStream.
 189    * @param out The PrintStream to print to.
 190    */
 191   public void kvetch(PrintStream out) {
 192     if (out != null) {
 193       String s = kvetch();
 194       if (s != null) {
 195         LWComponent.errorMsg(s);
 196       }
 197     }
 198   }
 199 
 200   /**
 201    * Turn on tracing for the LWComponent family.
 202    * @param out  the output stream
 203    */
 204   public static void startTracing(PrintStream out) {
 205     tracingOn = true;
 206     traceOutput = out;
 207   }
 208 
 209   /**
 210    * Turn off tracing for the LWComponent family.
 211    */
 212   public static void stopTracing() { tracingOn = false; traceOutput = null; }
 213 
 214   /**
 215    * Indicate whether it is believed the component should have focus.
 216    * @return {@code true} if the component should have focus
 217    */
 218   public boolean shouldHaveFocus() { return _shouldHaveFocus; }
 219 
 220   /**
 221    * Indicate whether it is believed the component should be showing.
 222    * @return  {@code true} if the component should be showing
 223    */
 224   public boolean shouldBeShowing() { return _shouldBeShowing; }
 225 
 226   @Override
 227   protected void processFocusEvent(FocusEvent e) {
 228     super.processFocusEvent(e);
 229     LWComponent.traceMsg("processFocusEvent " + e.toString());
 230     switch (e.getID()) {
 231     case FocusEvent.FOCUS_GAINED:
 232       _shouldHaveFocus = true;
 233       repaint();
 234       break;
 235     case FocusEvent.FOCUS_LOST:
 236       _shouldHaveFocus = false;
 237       repaint();
 238       break;
 239     }
 240   }
 241 
 242   @Override
 243   protected void processComponentEvent(ComponentEvent e) {
 244     super.processComponentEvent(e);
 245     LWComponent.traceMsg("processComponentEvent " + e.toString());
 246     switch (e.getID()) {
 247       case ComponentEvent.COMPONENT_MOVED:   break;
 248       case ComponentEvent.COMPONENT_RESIZED: break;
 249       case ComponentEvent.COMPONENT_SHOWN:   _shouldBeShowing = true;  break;
 250       case ComponentEvent.COMPONENT_HIDDEN:  _shouldBeShowing = false; break;
 251     }
 252   }
 253 
 254   @Override
 255   protected void processMouseEvent(MouseEvent e) {
 256     int mod = e.getModifiers();
 257     super.processMouseEvent(e);
 258     LWComponent.traceMsg("processMouseEvent " + e.toString());
 259     switch (e.getID()) {
 260     case MouseEvent.MOUSE_PRESSED:
 261       if ((mod & MouseEvent.BUTTON1_MASK) != 0) {
 262         if (mouseB1Pressed) {
 263           errorMsg("ERROR: MOUSE_PRESSED for B1 when already pressed, on "
 264               + this.toString());
 265         }
 266         mouseB1Pressed = true;
 267         break;
 268       }
 269       if ((mod & MouseEvent.BUTTON2_MASK) != 0) {
 270         if (mouseB2Pressed) {
 271           errorMsg("ERROR: MOUSE_PRESSED for B2 when already pressed, on "
 272               + this.toString());
 273         }
 274         mouseB2Pressed = true;
 275         break;
 276       }
 277       if ((mod & MouseEvent.BUTTON3_MASK) != 0) {
 278         if (mouseB3Pressed) {
 279           errorMsg("ERROR: MOUSE_PRESSED for B3 when already pressed, on "
 280               + this.toString());
 281         }
 282         mouseB3Pressed = true;
 283         break;
 284       }
 285       repaint();
 286       break;
 287     case MouseEvent.MOUSE_RELEASED:
 288       if ((mod & MouseEvent.BUTTON1_MASK) != 0) {
 289         if (!mouseB1Pressed) {
 290           errorMsg("ERROR: MOUSE_RELEASED for B1 when not pressed, on "
 291               + this.toString());
 292         }
 293         mouseB1Pressed = false;
 294         break;
 295       }
 296       if ((mod & MouseEvent.BUTTON2_MASK) != 0) {
 297         if (!mouseB2Pressed) {
 298           errorMsg("ERROR: MOUSE_RELEASED for B2 when not pressed, on "
 299               + this.toString());
 300         }
 301         mouseB2Pressed = false;
 302         break;
 303       }
 304       if ((mod & MouseEvent.BUTTON3_MASK) != 0) {
 305         if (!mouseB3Pressed) {
 306           errorMsg("ERROR: MOUSE_RELEASED for B3 when not pressed, on "
 307               + this.toString());
 308         }
 309         mouseB3Pressed = false;
 310         break;
 311       }
 312       repaint();
 313       break;
 314     case MouseEvent.MOUSE_CLICKED:
 315       break;
 316     case MouseEvent.MOUSE_ENTERED:
 317       if (mouseInside) {
 318         errorMsg("ERROR: MOUSE_ENTERED when mouse already inside component, on "
 319             + this.toString());
 320       }
 321       mouseInside = true;
 322       repaint();
 323       break;
 324     case MouseEvent.MOUSE_EXITED:
 325       if (!mouseInside) {
 326         errorMsg("ERROR: MOUSE_EXITED when mouse not inside component, on "
 327             + this.toString());
 328       }
 329       mouseInside = false;
 330       repaint();
 331       break;
 332     case MouseEvent.MOUSE_MOVED:
 333       break;
 334     case MouseEvent.MOUSE_DRAGGED:
 335       break;
 336     }
 337   }
 338 
 339   public Point getClientLocation() {
 340     return new Point(5, 5);
 341   }
 342 
 343   public Dimension getClientSize() {
 344     Dimension dim = getSize();
 345     dim.width -= 10;
 346     dim.height -= 10;
 347     return dim;
 348   }
 349 
 350   public Rectangle getClientBounds() {
 351     Dimension dim = getClientSize();
 352     return new Rectangle(5, 5, dim.width, dim.height);
 353   }
 354 
 355   public int getClientX() { return 5; }
 356   public int getClientY() { return 5; }
 357 
 358   /**
 359    * Set the color used for painting the non-client area of the component.
 360    * The default for this is Color.white.
 361    *
 362    * @param c The new color to use.
 363    */
 364   public void setNonClientColor(Color c) {
 365     LWComponent.ncBackgroundColor = c;
 366   }
 367 
 368   /**
 369    * Handle painting for the component.
 370    */
 371   @Override
 372   public void paint(Graphics g) {
 373     Dimension dim = getSize();
 374 
 375     kvetch(System.err);
 376 
 377     Color saveColor = g.getColor();
 378     super.paint(g);
 379 
 380     // ------------------- Paint the background -----------------
 381 
 382     // In jdk 1.2 (pre-release) there was a bug using clearRect
 383     // to paint the background of a lightweight.
 384     //g.clearRect(0, 0, dim.width, dim.height);
 385     g.setColor(getBackground());
 386     g.fillRect(0, 0, dim.width, dim.height);
 387 
 388     // ------------------- Paint the non-client area ------------
 389 
 390     g.setColor(ncBackgroundColor);
 391     //         x              y                width      height
 392     g.fillRect(0,             0,               dim.width, 5);
 393     g.fillRect(0,             5,               5,         dim.height - 10);
 394     g.fillRect(dim.width - 5, 5,               5,         dim.height - 10);
 395     g.fillRect(0,             dim.height - 5,  dim.width, 5);
 396 
 397     if (shouldHaveFocus() || hasFocus()) {
 398       g.setColor(shouldHaveFocus() && hasFocus()
 399          ? focusColor
 400          : focusWrongColor);
 401       g.drawRect(1, 1, dim.width - 3, dim.height - 3);
 402     }
 403 
 404     if (mouseInside) {
 405       g.setColor(mouseOverColor);
 406       g.drawRect(3, 3, dim.width - 7, dim.height - 7);
 407     }
 408 
 409     // ------------------- Paint disabledness, if true -----------
 410 
 411     if (!isEnabled()) {
 412       g.setColor(getBackground());
 413       Dimension size = getSize();
 414       int borderThickness = 0;
 415       int startX = borderThickness;
 416       int startY = borderThickness;
 417       int endX = startX + size.width  - 2 * borderThickness - 2;
 418       int endY = startY + size.height - 2 * borderThickness - 2;
 419       int x, y;
 420       for (y = startY; y <= endY; y += 1) {
 421         for (x = startX + (y % 2); x <= endX; x += 2) {
 422           g.fillRect(x, y, 1, 1);
 423         } // x
 424       } // y
 425     }
 426 
 427     g.setColor(saveColor);
 428   }
 429 
 430   /**
 431    * Restricts the Graphics to be within the "client area" of the
 432    * component.  Recall that the LWComponent series of components has
 433    * a "non-client area" of 5 pixels wide in which it draws two
 434    * status rectangles showing mouse-over and has-focus status. <p>
 435    *
 436    * Child classes of LWComponent are to call {@code restrictGraphicsToClientArea}
 437    * at the beginning of their {@code paint} method, and then call
 438    * {@code unrestrictGraphicsFromClientArea} afterwards.<p>
 439    *
 440    * In order to make those paint methods as convenient as possible, these
 441    * two methods make it appear as if the Graphics available to the
 442    * component is slightly smaller than it really is, by the amount
 443    * used in the non-client area (5 pixel wide border).<p>
 444    *
 445    * @param g The Graphics to restrict.
 446    */
 447   public void restrictGraphicsToClientArea(Graphics g) {
 448     Dimension dim = getSize();
 449     g.translate(5, 5);
 450     g.setClip(0, 0, dim.width - 10, dim.height - 10);
 451   }
 452 
 453   /**
 454    * Undo the restriction done in restrictGraphicsToClientArea.
 455    *
 456    * @param g The Graphics to unrestrict.
 457    */
 458   public void unrestrictGraphicsFromClientArea(Graphics g) {
 459     g.translate(-5, -5);
 460     Dimension dim = getSize();
 461     g.setClip(0, 0, dim.width, dim.height);
 462   }
 463 
 464 }