1 /*
   2  * Copyright (c) 1998, 2013, 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 javax.swing.plaf.metal;
  27 
  28 import javax.swing.*;
  29 import java.awt.Color;
  30 import java.awt.Component;
  31 import java.awt.Container;
  32 import java.awt.Dimension;
  33 import java.awt.Frame;
  34 import java.awt.Graphics;
  35 import java.awt.GraphicsEnvironment;
  36 import java.awt.Insets;
  37 import java.awt.Point;
  38 import java.awt.Rectangle;
  39 import java.awt.event.*;
  40 import java.lang.ref.WeakReference;
  41 import java.util.*;
  42 
  43 import java.beans.PropertyChangeListener;
  44 
  45 import javax.swing.event.*;
  46 import javax.swing.border.*;
  47 import javax.swing.plaf.*;
  48 import javax.swing.plaf.basic.*;
  49 
  50 /**
  51  * A Metal Look and Feel implementation of ToolBarUI.  This implementation
  52  * is a "combined" view/controller.
  53  *
  54  * @author Jeff Shapiro
  55  */
  56 public class MetalToolBarUI extends BasicToolBarUI
  57 {
  58     /**
  59      * An array of WeakReferences that point to JComponents. This will contain
  60      * instances of JToolBars and JMenuBars and is used to find
  61      * JToolBars/JMenuBars that border each other.
  62      */
  63     private static List<WeakReference<JComponent>> components = new ArrayList<WeakReference<JComponent>>();
  64 
  65     /**
  66      * This protected field is implementation specific. Do not access directly
  67      * or override. Use the create method instead.
  68      *
  69      * @see #createContainerListener
  70      */
  71     protected ContainerListener contListener;
  72 
  73     /**
  74      * This protected field is implementation specific. Do not access directly
  75      * or override. Use the create method instead.
  76      *
  77      * @see #createRolloverListener
  78      */
  79     protected PropertyChangeListener rolloverListener;
  80 
  81     private static Border nonRolloverBorder;
  82 
  83     /**
  84      * Last menubar the toolbar touched.  This is only useful for ocean.
  85      */
  86     private JMenuBar lastMenuBar;
  87 
  88     /**
  89      * Registers the specified component.
  90      */
  91     synchronized static void register(JComponent c) {
  92         if (c == null) {
  93             // Exception is thrown as convenience for callers that are
  94             // typed to throw an NPE.
  95             throw new NullPointerException("JComponent must be non-null");
  96         }
  97         components.add(new WeakReference<JComponent>(c));
  98     }
  99 
 100     /**
 101      * Unregisters the specified component.
 102      */
 103     synchronized static void unregister(JComponent c) {
 104         for (int counter = components.size() - 1; counter >= 0; counter--) {
 105             // Search for the component, removing any flushed references
 106             // along the way.
 107             JComponent target = components.get(counter).get();
 108 
 109             if (target == c || target == null) {
 110                 components.remove(counter);
 111             }
 112         }
 113     }
 114 
 115     /**
 116      * Finds a previously registered component of class <code>target</code>
 117      * that shares the JRootPane ancestor of <code>from</code>.
 118      */
 119     synchronized static Object findRegisteredComponentOfType(JComponent from,
 120                                                              Class<?> target) {
 121         JRootPane rp = SwingUtilities.getRootPane(from);
 122         if (rp != null) {
 123             for (int counter = components.size() - 1; counter >= 0; counter--){
 124                 Object component = ((WeakReference)components.get(counter)).
 125                                    get();
 126 
 127                 if (component == null) {
 128                     // WeakReference has gone away, remove the WeakReference
 129                     components.remove(counter);
 130                 }
 131                 else if (target.isInstance(component) && SwingUtilities.
 132                          getRootPane((Component)component) == rp) {
 133                     return component;
 134                 }
 135             }
 136         }
 137         return null;
 138     }
 139 
 140     /**
 141      * Returns true if the passed in JMenuBar is above a horizontal
 142      * JToolBar.
 143      */
 144     static boolean doesMenuBarBorderToolBar(JMenuBar c) {
 145         JToolBar tb = (JToolBar)MetalToolBarUI.
 146                     findRegisteredComponentOfType(c, JToolBar.class);
 147         if (tb != null && tb.getOrientation() == JToolBar.HORIZONTAL) {
 148             JRootPane rp = SwingUtilities.getRootPane(c);
 149             Point point = new Point(0, 0);
 150             point = SwingUtilities.convertPoint(c, point, rp);
 151             int menuX = point.x;
 152             int menuY = point.y;
 153             point.x = point.y = 0;
 154             point = SwingUtilities.convertPoint(tb, point, rp);
 155             return (point.x == menuX && menuY + c.getHeight() == point.y &&
 156                     c.getWidth() == tb.getWidth());
 157         }
 158         return false;
 159     }
 160 
 161     public static ComponentUI createUI( JComponent c )
 162     {
 163         return new MetalToolBarUI();
 164     }
 165 
 166     public void installUI( JComponent c )
 167     {
 168         super.installUI( c );
 169         register(c);
 170     }
 171 
 172     public void uninstallUI( JComponent c )
 173     {
 174         super.uninstallUI( c );
 175         nonRolloverBorder = null;
 176         unregister(c);
 177     }
 178 
 179     protected void installListeners() {
 180         super.installListeners();
 181 
 182         contListener = createContainerListener();
 183         if (contListener != null) {
 184             toolBar.addContainerListener(contListener);
 185         }
 186         rolloverListener = createRolloverListener();
 187         if (rolloverListener != null) {
 188             toolBar.addPropertyChangeListener(rolloverListener);
 189         }
 190     }
 191 
 192     protected void uninstallListeners() {
 193         super.uninstallListeners();
 194 
 195         if (contListener != null) {
 196             toolBar.removeContainerListener(contListener);
 197         }
 198         rolloverListener = createRolloverListener();
 199         if (rolloverListener != null) {
 200             toolBar.removePropertyChangeListener(rolloverListener);
 201         }
 202     }
 203 
 204     protected Border createRolloverBorder() {
 205         return super.createRolloverBorder();
 206     }
 207 
 208     protected Border createNonRolloverBorder() {
 209         return super.createNonRolloverBorder();
 210     }
 211 
 212 
 213     /**
 214      * Creates a non rollover border for Toggle buttons in the toolbar.
 215      */
 216     private Border createNonRolloverToggleBorder() {
 217         return createNonRolloverBorder();
 218     }
 219 
 220     protected void setBorderToNonRollover(Component c) {
 221         if (c instanceof JToggleButton && !(c instanceof JCheckBox)) {
 222             // 4735514, 4886944: The method createNonRolloverToggleBorder() is
 223             // private in BasicToolBarUI so we can't override it. We still need
 224             // to call super from this method so that it can save away the
 225             // original border and then we install ours.
 226 
 227             // Before calling super we get a handle to the old border, because
 228             // super will install a non-UIResource border that we can't
 229             // distinguish from one provided by an application.
 230             JToggleButton b = (JToggleButton)c;
 231             Border border = b.getBorder();
 232             super.setBorderToNonRollover(c);
 233             if (border instanceof UIResource) {
 234                 if (nonRolloverBorder == null) {
 235                     nonRolloverBorder = createNonRolloverToggleBorder();
 236                 }
 237                 b.setBorder(nonRolloverBorder);
 238             }
 239         } else {
 240             super.setBorderToNonRollover(c);
 241         }
 242     }
 243 
 244 
 245     /**
 246      * Creates a container listener that will be added to the JToolBar.
 247      * If this method returns null then it will not be added to the
 248      * toolbar.
 249      *
 250      * @return an instance of a <code>ContainerListener</code> or null
 251      */
 252     protected ContainerListener createContainerListener() {
 253         return null;
 254     }
 255 
 256     /**
 257      * Creates a property change listener that will be added to the JToolBar.
 258      * If this method returns null then it will not be added to the
 259      * toolbar.
 260      *
 261      * @return an instance of a <code>PropertyChangeListener</code> or null
 262      */
 263     protected PropertyChangeListener createRolloverListener() {
 264         return null;
 265     }
 266 
 267     protected MouseInputListener createDockingListener( )
 268     {
 269         return new MetalDockingListener( toolBar );
 270     }
 271 
 272     protected void setDragOffset(Point p) {
 273         if (!GraphicsEnvironment.isHeadless()) {
 274             if (dragWindow == null) {
 275                 dragWindow = createDragWindow(toolBar);
 276             }
 277             dragWindow.setOffset(p);
 278         }
 279     }
 280 
 281     /**
 282      * If necessary paints the background of the component, then invokes
 283      * <code>paint</code>.
 284      *
 285      * @param g Graphics to paint to
 286      * @param c JComponent painting on
 287      * @throws NullPointerException if <code>g</code> or <code>c</code> is
 288      *         null
 289      * @see javax.swing.plaf.ComponentUI#update
 290      * @see javax.swing.plaf.ComponentUI#paint
 291      * @since 1.5
 292      */
 293     public void update(Graphics g, JComponent c) {
 294         if (g == null) {
 295             throw new NullPointerException("graphics must be non-null");
 296         }
 297         if (c.isOpaque() && (c.getBackground() instanceof UIResource) &&
 298                             ((JToolBar)c).getOrientation() ==
 299                       JToolBar.HORIZONTAL && UIManager.get(
 300                      "MenuBar.gradient") != null) {
 301             JRootPane rp = SwingUtilities.getRootPane(c);
 302             JMenuBar mb = (JMenuBar)findRegisteredComponentOfType(
 303                                     c, JMenuBar.class);
 304             if (mb != null && mb.isOpaque() &&
 305                               (mb.getBackground() instanceof UIResource)) {
 306                 Point point = new Point(0, 0);
 307                 point = SwingUtilities.convertPoint(c, point, rp);
 308                 int x = point.x;
 309                 int y = point.y;
 310                 point.x = point.y = 0;
 311                 point = SwingUtilities.convertPoint(mb, point, rp);
 312                 if (point.x == x && y == point.y + mb.getHeight() &&
 313                      mb.getWidth() == c.getWidth() &&
 314                      MetalUtils.drawGradient(c, g, "MenuBar.gradient",
 315                      0, -mb.getHeight(), c.getWidth(), c.getHeight() +
 316                      mb.getHeight(), true)) {
 317                     setLastMenuBar(mb);
 318                     paint(g, c);
 319                     return;
 320                 }
 321             }
 322             if (MetalUtils.drawGradient(c, g, "MenuBar.gradient",
 323                            0, 0, c.getWidth(), c.getHeight(), true)) {
 324                 setLastMenuBar(null);
 325                 paint(g, c);
 326                 return;
 327             }
 328         }
 329         setLastMenuBar(null);
 330         super.update(g, c);
 331     }
 332 
 333     private void setLastMenuBar(JMenuBar lastMenuBar) {
 334         if (MetalLookAndFeel.usingOcean()) {
 335             if (this.lastMenuBar != lastMenuBar) {
 336                 // The menubar we previously touched has changed, force it
 337                 // to repaint.
 338                 if (this.lastMenuBar != null) {
 339                     this.lastMenuBar.repaint();
 340                 }
 341                 if (lastMenuBar != null) {
 342                     lastMenuBar.repaint();
 343                 }
 344                 this.lastMenuBar = lastMenuBar;
 345             }
 346         }
 347     }
 348 
 349     // No longer used. Cannot remove for compatibility reasons
 350     protected class MetalContainerListener
 351         extends BasicToolBarUI.ToolBarContListener {}
 352 
 353     // No longer used. Cannot remove for compatibility reasons
 354     protected class MetalRolloverListener
 355         extends BasicToolBarUI.PropertyListener {}
 356 
 357     protected class MetalDockingListener extends DockingListener {
 358         private boolean pressedInBumps = false;
 359 
 360         public MetalDockingListener(JToolBar t) {
 361             super(t);
 362         }
 363 
 364         public void mousePressed(MouseEvent e) {
 365             super.mousePressed(e);
 366             if (!toolBar.isEnabled()) {
 367                 return;
 368             }
 369             pressedInBumps = false;
 370             Rectangle bumpRect = new Rectangle();
 371 
 372             if (toolBar.getOrientation() == JToolBar.HORIZONTAL) {
 373                 int x = MetalUtils.isLeftToRight(toolBar) ? 0 : toolBar.getSize().width-14;
 374                 bumpRect.setBounds(x, 0, 14, toolBar.getSize().height);
 375             } else {  // vertical
 376                 bumpRect.setBounds(0, 0, toolBar.getSize().width, 14);
 377             }
 378             if (bumpRect.contains(e.getPoint())) {
 379                 pressedInBumps = true;
 380                 Point dragOffset = e.getPoint();
 381                 if (!MetalUtils.isLeftToRight(toolBar)) {
 382                     dragOffset.x -= (toolBar.getSize().width
 383                                      - toolBar.getPreferredSize().width);
 384                 }
 385                 setDragOffset(dragOffset);
 386             }
 387         }
 388 
 389         public void mouseDragged(MouseEvent e) {
 390             if (pressedInBumps) {
 391                 super.mouseDragged(e);
 392             }
 393         }
 394     } // end class MetalDockingListener
 395 }