1 /*
   2  * Copyright (c) 1997, 2006, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.java.swing.plaf.windows;
  27 
  28 import javax.swing.plaf.basic.*;
  29 import javax.swing.border.*;
  30 import javax.swing.plaf.*;
  31 import javax.swing.*;
  32 
  33 import java.awt.*;
  34 
  35 import static com.sun.java.swing.plaf.windows.TMSchema.*;
  36 import static com.sun.java.swing.plaf.windows.TMSchema.Part.*;
  37 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
  38 import static java.lang.Boolean.FALSE;
  39 import static java.lang.Boolean.TRUE;
  40 import sun.awt.AppContext;
  41 
  42 
  43 /**
  44  * Windows button.
  45  * <p>
  46  * <strong>Warning:</strong>
  47  * Serialized objects of this class will not be compatible with
  48  * future Swing releases.  The current serialization support is appropriate
  49  * for short term storage or RMI between applications running the same
  50  * version of Swing.  A future release of Swing will provide support for
  51  * long term persistence.
  52  *
  53  * @author Jeff Dinkins
  54  *
  55  */
  56 public class WindowsButtonUI extends BasicButtonUI
  57 {
  58     protected int dashedRectGapX;
  59     protected int dashedRectGapY;
  60     protected int dashedRectGapWidth;
  61     protected int dashedRectGapHeight;
  62 
  63     protected Color focusColor;
  64 
  65     private boolean defaults_initialized = false;
  66 
  67     private static final Object WINDOWS_BUTTON_UI_KEY = new Object();
  68 
  69     // ********************************
  70     //          Create PLAF
  71     // ********************************
  72     public static ComponentUI createUI(JComponent c) {
  73         AppContext appContext = AppContext.getAppContext();
  74         WindowsButtonUI windowsButtonUI =
  75                 (WindowsButtonUI) appContext.get(WINDOWS_BUTTON_UI_KEY);
  76         if (windowsButtonUI == null) {
  77             windowsButtonUI = new WindowsButtonUI();
  78             appContext.put(WINDOWS_BUTTON_UI_KEY, windowsButtonUI);
  79         }
  80         return windowsButtonUI;
  81     }
  82 
  83 
  84     // ********************************
  85     //            Defaults
  86     // ********************************
  87     protected void installDefaults(AbstractButton b) {
  88         super.installDefaults(b);
  89         if(!defaults_initialized) {
  90             String pp = getPropertyPrefix();
  91             dashedRectGapX = UIManager.getInt(pp + "dashedRectGapX");
  92             dashedRectGapY = UIManager.getInt(pp + "dashedRectGapY");
  93             dashedRectGapWidth = UIManager.getInt(pp + "dashedRectGapWidth");
  94             dashedRectGapHeight = UIManager.getInt(pp + "dashedRectGapHeight");
  95             focusColor = UIManager.getColor(pp + "focus");
  96             defaults_initialized = true;
  97         }
  98 
  99         XPStyle xp = XPStyle.getXP();
 100         if (xp != null) {
 101             b.setBorder(xp.getBorder(b, getXPButtonType(b)));
 102             LookAndFeel.installProperty(b, "rolloverEnabled", Boolean.TRUE);
 103         }
 104     }
 105 
 106     protected void uninstallDefaults(AbstractButton b) {
 107         super.uninstallDefaults(b);
 108         defaults_initialized = false;
 109     }
 110 
 111     protected Color getFocusColor() {
 112         return focusColor;
 113     }
 114 
 115     // ********************************
 116     //         Paint Methods
 117     // ********************************
 118 
 119     /**
 120      * Overridden method to render the text without the mnemonic
 121      */
 122     protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) {
 123         WindowsGraphicsUtils.paintText(g, b, textRect, text, getTextShiftOffset());
 124     }
 125 
 126     protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect){
 127 
 128         // focus painted same color as text on Basic??
 129         int width = b.getWidth();
 130         int height = b.getHeight();
 131         g.setColor(getFocusColor());
 132         BasicGraphicsUtils.drawDashedRect(g, dashedRectGapX, dashedRectGapY,
 133                                           width - dashedRectGapWidth, height - dashedRectGapHeight);
 134     }
 135 
 136     protected void paintButtonPressed(Graphics g, AbstractButton b){
 137         setTextShiftOffset();
 138     }
 139 
 140     // ********************************
 141     //          Layout Methods
 142     // ********************************
 143     public Dimension getPreferredSize(JComponent c) {
 144         Dimension d = super.getPreferredSize(c);
 145 
 146         /* Ensure that the width and height of the button is odd,
 147          * to allow for the focus line if focus is painted
 148          */
 149         AbstractButton b = (AbstractButton)c;
 150         if (d != null && b.isFocusPainted()) {
 151             if(d.width % 2 == 0) { d.width += 1; }
 152             if(d.height % 2 == 0) { d.height += 1; }
 153         }
 154         return d;
 155     }
 156 
 157 
 158     /* These rectangles/insets are allocated once for all
 159      * ButtonUI.paint() calls.  Re-using rectangles rather than
 160      * allocating them in each paint call substantially reduced the time
 161      * it took paint to run.  Obviously, this method can't be re-entered.
 162      */
 163     private Rectangle viewRect = new Rectangle();
 164 
 165     public void paint(Graphics g, JComponent c) {
 166         AbstractButton b = (AbstractButton)c;
 167         
 168         if(isStateChangeAnimated(b)) {
 169             boolean rollover = b.getModel().isRollover();
 170             AnimationController anim = AnimationController.getAnimationController();
 171             boolean wasRollover = b.getClientProperty("WindowsButtonUI.wasRollover") == TRUE;
 172 
 173             if(rollover != wasRollover) {
 174                 State currentState = wasRollover?State.HOT:State.NORMAL;
 175                 State targetState = rollover?State.HOT:State.NORMAL;
 176                 anim.startAnimation(c, getXPButtonType(b), currentState, targetState, 250);
 177             }
 178             
 179             b.putClientProperty("WindowsButtonUI.animateToNullState", !rollover);
 180             b.putClientProperty("WindowsButtonUI.wasRollover", rollover);
 181         }
 182         
 183         if (XPStyle.getXP() != null) {
 184             WindowsButtonUI.paintXPButtonBackground(g, c);
 185         }
 186         super.paint(g, c);
 187     }
 188 
 189     private static boolean isStateChangeAnimated(AbstractButton button) {
 190         final String propName = "WindowsButtonUI.animateStateChange";
 191         
 192         if(button.getParent() instanceof JToolBar) {
 193             JToolBar toolBar = (JToolBar)button.getParent();
 194             if(toolBar.getClientProperty(propName)==TRUE)
 195                 return true;
 196         }
 197          
 198         return button.getClientProperty(propName)==TRUE;
 199     }
 200     
 201     static Part getXPButtonType(AbstractButton b) {
 202         if(b instanceof JCheckBox) {
 203             return Part.BP_CHECKBOX;
 204         }
 205         if(b instanceof JRadioButton) {
 206             return Part.BP_RADIOBUTTON;
 207         }
 208         boolean toolbar = (b.getParent() instanceof JToolBar);
 209         if(b.getClientProperty("WindowsButtonUI.displayAsInToolbar") == TRUE)
 210             toolbar = true;
 211         return toolbar ? Part.TP_BUTTON : Part.BP_PUSHBUTTON;
 212     }
 213 
 214     static State getXPButtonState(AbstractButton b) {
 215         Part part = getXPButtonType(b);
 216         ButtonModel model = b.getModel();
 217         State state = State.NORMAL;
 218         switch (part) {
 219         case BP_RADIOBUTTON:
 220             /* falls through */
 221         case BP_CHECKBOX:
 222             if (! model.isEnabled()) {
 223                 state = (model.isSelected()) ? State.CHECKEDDISABLED
 224                     : State.UNCHECKEDDISABLED;
 225             } else if (model.isPressed() && model.isArmed()) {
 226                 state = (model.isSelected()) ? State.CHECKEDPRESSED
 227                     : State.UNCHECKEDPRESSED;
 228             } else if (model.isRollover()) {
 229                 state = (model.isSelected()) ? State.CHECKEDHOT
 230                     : State.UNCHECKEDHOT;
 231             } else {
 232                 state = (model.isSelected()) ? State.CHECKEDNORMAL
 233                     : State.UNCHECKEDNORMAL;
 234             }
 235             break;
 236         case BP_PUSHBUTTON:
 237             /* falls through */
 238         case TP_BUTTON:
 239             boolean toolbar = (b.getParent() instanceof JToolBar);
 240             if (toolbar) {
 241                 if (model.isArmed() && model.isPressed()) {
 242                     state = State.PRESSED;
 243                 } else if (!model.isEnabled()) {
 244                     state = State.DISABLED;
 245                 } else if (model.isSelected() && model.isRollover()) {
 246                     state = State.HOTCHECKED;
 247                 } else if (model.isSelected()) {
 248                     state = State.CHECKED;
 249                 } else if (model.isRollover()) {
 250                     state = State.HOT;
 251                 } else if (b.hasFocus()) {
 252                     state = State.HOT;
 253                 }
 254             } else {
 255                 if ((model.isArmed() && model.isPressed())
 256                       || model.isSelected()) {
 257                     state = State.PRESSED;
 258                 } else if (!model.isEnabled()) {
 259                     state = State.DISABLED;
 260                 } else if (model.isRollover() || model.isPressed()) {
 261                     state = State.HOT;
 262                 } else if (b instanceof JButton
 263                            && ((JButton)b).isDefaultButton()) {
 264                     state = State.DEFAULTED;
 265                 } else if (b.hasFocus()) {
 266                     state = State.HOT;
 267                 }
 268             }
 269             break;
 270         default :
 271             state = State.NORMAL;
 272         }
 273 
 274         return state;
 275     }
 276 
 277     static void paintXPButtonBackground(Graphics g, JComponent c) {
 278         AbstractButton b = (AbstractButton)c;
 279 
 280         XPStyle xp = XPStyle.getXP();
 281 
 282         Part part = getXPButtonType(b);
 283 
 284         if (b.isContentAreaFilled() && b.getBorder() != null
 285                 && b.isBorderPainted() && xp != null) {
 286 
 287             Skin skin = xp.getSkin(b, part);
 288 
 289             State state = getXPButtonState(b);
 290             Dimension d = c.getSize();
 291             int dx = 0;
 292             int dy = 0;
 293             int dw = d.width;
 294             int dh = d.height;
 295 
 296             Border border = c.getBorder();
 297             Insets insets;
 298             if (border != null) {
 299                 // Note: The border may be compound, containing an outer
 300                 // opaque border (supplied by the application), plus an
 301                 // inner transparent margin border. We want to size the
 302                 // background to fill the transparent part, but stay
 303                 // inside the opaque part.
 304                 insets = WindowsButtonUI.getOpaqueInsets(border, c);
 305             } else {
 306                 insets = c.getInsets();
 307             }
 308             if (insets != null) {
 309                 dx += insets.left;
 310                 dy += insets.top;
 311                 dw -= (insets.left + insets.right);
 312                 dh -= (insets.top + insets.bottom);
 313             }
 314             
 315             if(c.getClientProperty("WindowsButtonUI.animateToNullState") == TRUE)
 316                 state = null;
 317             
 318             AnimationController.paintSkin(c, skin, g, dx, dy, dw, dh, state);
 319         }
 320     }
 321 
 322     /**
 323      * returns - b.getBorderInsets(c) if border is opaque
 324      *         - null if border is completely non-opaque
 325      *         - somewhere inbetween if border is compound and
 326      *              outside border is opaque and inside isn't
 327      */
 328     private static Insets getOpaqueInsets(Border b, Component c) {
 329         if (b == null) {
 330             return null;
 331         }
 332         if (b.isBorderOpaque()) {
 333             return b.getBorderInsets(c);
 334         } else if (b instanceof CompoundBorder) {
 335             CompoundBorder cb = (CompoundBorder)b;
 336             Insets iOut = getOpaqueInsets(cb.getOutsideBorder(), c);
 337             if (iOut != null && iOut.equals(cb.getOutsideBorder().getBorderInsets(c))) {
 338                 // Outside border is opaque, keep looking
 339                 Insets iIn = getOpaqueInsets(cb.getInsideBorder(), c);
 340                 if (iIn == null) {
 341                     // Inside is non-opaque, use outside insets
 342                     return iOut;
 343                 } else {
 344                     // Found non-opaque somewhere in the inside (which is
 345                     // also compound).
 346                     return new Insets(iOut.top + iIn.top, iOut.left + iIn.left,
 347                                       iOut.bottom + iIn.bottom, iOut.right + iIn.right);
 348                 }
 349             } else {
 350                 // Outside is either all non-opaque or has non-opaque
 351                 // border inside another compound border
 352                 return iOut;
 353             }
 354         } else {
 355             return null;
 356         }
 357     }
 358 }