1 /*
   2  * Copyright (c) 1998, 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.  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     /**
 162      * Constructs an instance of {@code MetalToolBarUI}.
 163      *
 164      * @param c a component
 165      * @return an instance of {@code MetalToolBarUI}
 166      */
 167     public static ComponentUI createUI( JComponent c )
 168     {
 169         return new MetalToolBarUI();
 170     }
 171 
 172     public void installUI( JComponent c )
 173     {
 174         super.installUI( c );
 175         register(c);
 176     }
 177 
 178     public void uninstallUI( JComponent c )
 179     {
 180         super.uninstallUI( c );
 181         nonRolloverBorder = null;
 182         unregister(c);
 183     }
 184 
 185     protected void installListeners() {
 186         super.installListeners();
 187 
 188         contListener = createContainerListener();
 189         if (contListener != null) {
 190             toolBar.addContainerListener(contListener);
 191         }
 192         rolloverListener = createRolloverListener();
 193         if (rolloverListener != null) {
 194             toolBar.addPropertyChangeListener(rolloverListener);
 195         }
 196     }
 197 
 198     protected void uninstallListeners() {
 199         super.uninstallListeners();
 200 
 201         if (contListener != null) {
 202             toolBar.removeContainerListener(contListener);
 203         }
 204         rolloverListener = createRolloverListener();
 205         if (rolloverListener != null) {
 206             toolBar.removePropertyChangeListener(rolloverListener);
 207         }
 208     }
 209 
 210     protected Border createRolloverBorder() {
 211         return super.createRolloverBorder();
 212     }
 213 
 214     protected Border createNonRolloverBorder() {
 215         return super.createNonRolloverBorder();
 216     }
 217 
 218 
 219     /**
 220      * Creates a non rollover border for Toggle buttons in the toolbar.
 221      */
 222     private Border createNonRolloverToggleBorder() {
 223         return createNonRolloverBorder();
 224     }
 225 
 226     protected void setBorderToNonRollover(Component c) {
 227         if (c instanceof JToggleButton && !(c instanceof JCheckBox)) {
 228             // 4735514, 4886944: The method createNonRolloverToggleBorder() is
 229             // private in BasicToolBarUI so we can't override it. We still need
 230             // to call super from this method so that it can save away the
 231             // original border and then we install ours.
 232 
 233             // Before calling super we get a handle to the old border, because
 234             // super will install a non-UIResource border that we can't
 235             // distinguish from one provided by an application.
 236             JToggleButton b = (JToggleButton)c;
 237             Border border = b.getBorder();
 238             super.setBorderToNonRollover(c);
 239             if (border instanceof UIResource) {
 240                 if (nonRolloverBorder == null) {
 241                     nonRolloverBorder = createNonRolloverToggleBorder();
 242                 }
 243                 b.setBorder(nonRolloverBorder);
 244             }
 245         } else {
 246             super.setBorderToNonRollover(c);
 247         }
 248     }
 249 
 250 
 251     /**
 252      * Creates a container listener that will be added to the JToolBar.
 253      * If this method returns null then it will not be added to the
 254      * toolbar.
 255      *
 256      * @return an instance of a <code>ContainerListener</code> or null
 257      */
 258     protected ContainerListener createContainerListener() {
 259         return null;
 260     }
 261 
 262     /**
 263      * Creates a property change listener that will be added to the JToolBar.
 264      * If this method returns null then it will not be added to the
 265      * toolbar.
 266      *
 267      * @return an instance of a <code>PropertyChangeListener</code> or null
 268      */
 269     protected PropertyChangeListener createRolloverListener() {
 270         return null;
 271     }
 272 
 273     protected MouseInputListener createDockingListener( )
 274     {
 275         return new MetalDockingListener( toolBar );
 276     }
 277 
 278     /**
 279      * Sets the offset of the mouse cursor inside the DragWindow.
 280      *
 281      * @param p the offset
 282      */
 283     protected void setDragOffset(Point p) {
 284         if (!GraphicsEnvironment.isHeadless()) {
 285             if (dragWindow == null) {
 286                 dragWindow = createDragWindow(toolBar);
 287             }
 288             dragWindow.setOffset(p);
 289         }
 290     }
 291 
 292     /**
 293      * If necessary paints the background of the component, then invokes
 294      * <code>paint</code>.
 295      *
 296      * @param g Graphics to paint to
 297      * @param c JComponent painting on
 298      * @throws NullPointerException if <code>g</code> or <code>c</code> is
 299      *         null
 300      * @see javax.swing.plaf.ComponentUI#update
 301      * @see javax.swing.plaf.ComponentUI#paint
 302      * @since 1.5
 303      */
 304     public void update(Graphics g, JComponent c) {
 305         if (g == null) {
 306             throw new NullPointerException("graphics must be non-null");
 307         }
 308         if (c.isOpaque() && (c.getBackground() instanceof UIResource) &&
 309                             ((JToolBar)c).getOrientation() ==
 310                       JToolBar.HORIZONTAL && UIManager.get(
 311                      "MenuBar.gradient") != null) {
 312             JRootPane rp = SwingUtilities.getRootPane(c);
 313             JMenuBar mb = (JMenuBar)findRegisteredComponentOfType(
 314                                     c, JMenuBar.class);
 315             if (mb != null && mb.isOpaque() &&
 316                               (mb.getBackground() instanceof UIResource)) {
 317                 Point point = new Point(0, 0);
 318                 point = SwingUtilities.convertPoint(c, point, rp);
 319                 int x = point.x;
 320                 int y = point.y;
 321                 point.x = point.y = 0;
 322                 point = SwingUtilities.convertPoint(mb, point, rp);
 323                 if (point.x == x && y == point.y + mb.getHeight() &&
 324                      mb.getWidth() == c.getWidth() &&
 325                      MetalUtils.drawGradient(c, g, "MenuBar.gradient",
 326                      0, -mb.getHeight(), c.getWidth(), c.getHeight() +
 327                      mb.getHeight(), true)) {
 328                     setLastMenuBar(mb);
 329                     paint(g, c);
 330                     return;
 331                 }
 332             }
 333             if (MetalUtils.drawGradient(c, g, "MenuBar.gradient",
 334                            0, 0, c.getWidth(), c.getHeight(), true)) {
 335                 setLastMenuBar(null);
 336                 paint(g, c);
 337                 return;
 338             }
 339         }
 340         setLastMenuBar(null);
 341         super.update(g, c);
 342     }
 343 
 344     private void setLastMenuBar(JMenuBar lastMenuBar) {
 345         if (MetalLookAndFeel.usingOcean()) {
 346             if (this.lastMenuBar != lastMenuBar) {
 347                 // The menubar we previously touched has changed, force it
 348                 // to repaint.
 349                 if (this.lastMenuBar != null) {
 350                     this.lastMenuBar.repaint();
 351                 }
 352                 if (lastMenuBar != null) {
 353                     lastMenuBar.repaint();
 354                 }
 355                 this.lastMenuBar = lastMenuBar;
 356             }
 357         }
 358     }
 359 
 360     /**
 361      * No longer used. The class cannot be removed for compatibility reasons.
 362      */
 363     protected class MetalContainerListener
 364         extends BasicToolBarUI.ToolBarContListener {}
 365 
 366     /**
 367      * No longer used. The class cannot be removed for compatibility reasons.
 368      */
 369     protected class MetalRolloverListener
 370         extends BasicToolBarUI.PropertyListener {}
 371 
 372     /**
 373      * {@code DockingListener} for {@code MetalToolBarUI}.
 374      */
 375     protected class MetalDockingListener extends DockingListener {
 376         private boolean pressedInBumps = false;
 377 
 378         /**
 379          * Constructs the {@code MetalDockingListener}.
 380          *
 381          * @param t an instance of {@code JToolBar}
 382          */
 383         public MetalDockingListener(JToolBar t) {
 384             super(t);
 385         }
 386 
 387         public void mousePressed(MouseEvent e) {
 388             super.mousePressed(e);
 389             if (!toolBar.isEnabled()) {
 390                 return;
 391             }
 392             pressedInBumps = false;
 393             Rectangle bumpRect = new Rectangle();
 394 
 395             if (toolBar.getOrientation() == JToolBar.HORIZONTAL) {
 396                 int x = MetalUtils.isLeftToRight(toolBar) ? 0 : toolBar.getSize().width-14;
 397                 bumpRect.setBounds(x, 0, 14, toolBar.getSize().height);
 398             } else {  // vertical
 399                 bumpRect.setBounds(0, 0, toolBar.getSize().width, 14);
 400             }
 401             if (bumpRect.contains(e.getPoint())) {
 402                 pressedInBumps = true;
 403                 Point dragOffset = e.getPoint();
 404                 if (!MetalUtils.isLeftToRight(toolBar)) {
 405                     dragOffset.x -= (toolBar.getSize().width
 406                                      - toolBar.getPreferredSize().width);
 407                 }
 408                 setDragOffset(dragOffset);
 409             }
 410         }
 411 
 412         public void mouseDragged(MouseEvent e) {
 413             if (pressedInBumps) {
 414                 super.mouseDragged(e);
 415             }
 416         }
 417     } // end class MetalDockingListener
 418 }