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      * Constructs a {@code MetalToolBarUI}.
90      */
MetalToolBarUI()91     public MetalToolBarUI() {}
92 
93     /**
94      * Registers the specified component.
95      */
register(JComponent c)96     static synchronized void register(JComponent c) {
97         if (c == null) {
98             // Exception is thrown as convenience for callers that are
99             // typed to throw an NPE.
100             throw new NullPointerException("JComponent must be non-null");
101         }
102         components.add(new WeakReference<JComponent>(c));
103     }
104 
105     /**
106      * Unregisters the specified component.
107      */
unregister(JComponent c)108     static synchronized void unregister(JComponent c) {
109         for (int counter = components.size() - 1; counter >= 0; counter--) {
110             // Search for the component, removing any flushed references
111             // along the way.
112             JComponent target = components.get(counter).get();
113 
114             if (target == c || target == null) {
115                 components.remove(counter);
116             }
117         }
118     }
119 
120     /**
121      * Finds a previously registered component of class <code>target</code>
122      * that shares the JRootPane ancestor of <code>from</code>.
123      */
findRegisteredComponentOfType(JComponent from, Class<?> target)124     static synchronized Object findRegisteredComponentOfType(JComponent from,
125                                                              Class<?> target) {
126         JRootPane rp = SwingUtilities.getRootPane(from);
127         if (rp != null) {
128             for (int counter = components.size() - 1; counter >= 0; counter--){
129                 Object component = ((WeakReference)components.get(counter)).
130                                    get();
131 
132                 if (component == null) {
133                     // WeakReference has gone away, remove the WeakReference
134                     components.remove(counter);
135                 }
136                 else if (target.isInstance(component) && SwingUtilities.
137                          getRootPane((Component)component) == rp) {
138                     return component;
139                 }
140             }
141         }
142         return null;
143     }
144 
145     /**
146      * Returns true if the passed in JMenuBar is above a horizontal
147      * JToolBar.
148      */
doesMenuBarBorderToolBar(JMenuBar c)149     static boolean doesMenuBarBorderToolBar(JMenuBar c) {
150         JToolBar tb = (JToolBar)MetalToolBarUI.
151                     findRegisteredComponentOfType(c, JToolBar.class);
152         if (tb != null && tb.getOrientation() == JToolBar.HORIZONTAL) {
153             JRootPane rp = SwingUtilities.getRootPane(c);
154             Point point = new Point(0, 0);
155             point = SwingUtilities.convertPoint(c, point, rp);
156             int menuX = point.x;
157             int menuY = point.y;
158             point.x = point.y = 0;
159             point = SwingUtilities.convertPoint(tb, point, rp);
160             return (point.x == menuX && menuY + c.getHeight() == point.y &&
161                     c.getWidth() == tb.getWidth());
162         }
163         return false;
164     }
165 
166     /**
167      * Constructs an instance of {@code MetalToolBarUI}.
168      *
169      * @param c a component
170      * @return an instance of {@code MetalToolBarUI}
171      */
createUI( JComponent c )172     public static ComponentUI createUI( JComponent c )
173     {
174         return new MetalToolBarUI();
175     }
176 
installUI( JComponent c )177     public void installUI( JComponent c )
178     {
179         super.installUI( c );
180         register(c);
181     }
182 
uninstallUI( JComponent c )183     public void uninstallUI( JComponent c )
184     {
185         super.uninstallUI( c );
186         nonRolloverBorder = null;
187         unregister(c);
188     }
189 
installListeners()190     protected void installListeners() {
191         super.installListeners();
192 
193         contListener = createContainerListener();
194         if (contListener != null) {
195             toolBar.addContainerListener(contListener);
196         }
197         rolloverListener = createRolloverListener();
198         if (rolloverListener != null) {
199             toolBar.addPropertyChangeListener(rolloverListener);
200         }
201     }
202 
uninstallListeners()203     protected void uninstallListeners() {
204         super.uninstallListeners();
205 
206         if (contListener != null) {
207             toolBar.removeContainerListener(contListener);
208         }
209         rolloverListener = createRolloverListener();
210         if (rolloverListener != null) {
211             toolBar.removePropertyChangeListener(rolloverListener);
212         }
213     }
214 
createRolloverBorder()215     protected Border createRolloverBorder() {
216         return super.createRolloverBorder();
217     }
218 
createNonRolloverBorder()219     protected Border createNonRolloverBorder() {
220         return super.createNonRolloverBorder();
221     }
222 
223 
224     /**
225      * Creates a non rollover border for Toggle buttons in the toolbar.
226      */
createNonRolloverToggleBorder()227     private Border createNonRolloverToggleBorder() {
228         return createNonRolloverBorder();
229     }
230 
setBorderToNonRollover(Component c)231     protected void setBorderToNonRollover(Component c) {
232         if (c instanceof JToggleButton && !(c instanceof JCheckBox)) {
233             // 4735514, 4886944: The method createNonRolloverToggleBorder() is
234             // private in BasicToolBarUI so we can't override it. We still need
235             // to call super from this method so that it can save away the
236             // original border and then we install ours.
237 
238             // Before calling super we get a handle to the old border, because
239             // super will install a non-UIResource border that we can't
240             // distinguish from one provided by an application.
241             JToggleButton b = (JToggleButton)c;
242             Border border = b.getBorder();
243             super.setBorderToNonRollover(c);
244             if (border instanceof UIResource) {
245                 if (nonRolloverBorder == null) {
246                     nonRolloverBorder = createNonRolloverToggleBorder();
247                 }
248                 b.setBorder(nonRolloverBorder);
249             }
250         } else {
251             super.setBorderToNonRollover(c);
252         }
253     }
254 
255 
256     /**
257      * Creates a container 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>ContainerListener</code> or null
262      */
createContainerListener()263     protected ContainerListener createContainerListener() {
264         return null;
265     }
266 
267     /**
268      * Creates a property change listener that will be added to the JToolBar.
269      * If this method returns null then it will not be added to the
270      * toolbar.
271      *
272      * @return an instance of a <code>PropertyChangeListener</code> or null
273      */
createRolloverListener()274     protected PropertyChangeListener createRolloverListener() {
275         return null;
276     }
277 
createDockingListener( )278     protected MouseInputListener createDockingListener( )
279     {
280         return new MetalDockingListener( toolBar );
281     }
282 
283     /**
284      * Sets the offset of the mouse cursor inside the DragWindow.
285      *
286      * @param p the offset
287      */
setDragOffset(Point p)288     protected void setDragOffset(Point p) {
289         if (!GraphicsEnvironment.isHeadless()) {
290             if (dragWindow == null) {
291                 dragWindow = createDragWindow(toolBar);
292             }
293             dragWindow.setOffset(p);
294         }
295     }
296 
297     /**
298      * If necessary paints the background of the component, then invokes
299      * <code>paint</code>.
300      *
301      * @param g Graphics to paint to
302      * @param c JComponent painting on
303      * @throws NullPointerException if <code>g</code> or <code>c</code> is
304      *         null
305      * @see javax.swing.plaf.ComponentUI#update
306      * @see javax.swing.plaf.ComponentUI#paint
307      * @since 1.5
308      */
update(Graphics g, JComponent c)309     public void update(Graphics g, JComponent c) {
310         if (g == null) {
311             throw new NullPointerException("graphics must be non-null");
312         }
313         if (c.isOpaque() && (c.getBackground() instanceof UIResource) &&
314                             ((JToolBar)c).getOrientation() ==
315                       JToolBar.HORIZONTAL && UIManager.get(
316                      "MenuBar.gradient") != null) {
317             JRootPane rp = SwingUtilities.getRootPane(c);
318             JMenuBar mb = (JMenuBar)findRegisteredComponentOfType(
319                                     c, JMenuBar.class);
320             if (mb != null && mb.isOpaque() &&
321                               (mb.getBackground() instanceof UIResource)) {
322                 Point point = new Point(0, 0);
323                 point = SwingUtilities.convertPoint(c, point, rp);
324                 int x = point.x;
325                 int y = point.y;
326                 point.x = point.y = 0;
327                 point = SwingUtilities.convertPoint(mb, point, rp);
328                 if (point.x == x && y == point.y + mb.getHeight() &&
329                      mb.getWidth() == c.getWidth() &&
330                      MetalUtils.drawGradient(c, g, "MenuBar.gradient",
331                      0, -mb.getHeight(), c.getWidth(), c.getHeight() +
332                      mb.getHeight(), true)) {
333                     setLastMenuBar(mb);
334                     paint(g, c);
335                     return;
336                 }
337             }
338             if (MetalUtils.drawGradient(c, g, "MenuBar.gradient",
339                            0, 0, c.getWidth(), c.getHeight(), true)) {
340                 setLastMenuBar(null);
341                 paint(g, c);
342                 return;
343             }
344         }
345         setLastMenuBar(null);
346         super.update(g, c);
347     }
348 
setLastMenuBar(JMenuBar lastMenuBar)349     private void setLastMenuBar(JMenuBar lastMenuBar) {
350         if (MetalLookAndFeel.usingOcean()) {
351             if (this.lastMenuBar != lastMenuBar) {
352                 // The menubar we previously touched has changed, force it
353                 // to repaint.
354                 if (this.lastMenuBar != null) {
355                     this.lastMenuBar.repaint();
356                 }
357                 if (lastMenuBar != null) {
358                     lastMenuBar.repaint();
359                 }
360                 this.lastMenuBar = lastMenuBar;
361             }
362         }
363     }
364 
365     /**
366      * No longer used. The class cannot be removed for compatibility reasons.
367      */
368     protected class MetalContainerListener
369         extends BasicToolBarUI.ToolBarContListener {
370         /**
371          * Constructs a {@code MetalContainerListener}.
372          */
MetalContainerListener()373         protected MetalContainerListener() {}
374     }
375 
376     /**
377      * No longer used. The class cannot be removed for compatibility reasons.
378      */
379     protected class MetalRolloverListener
380         extends BasicToolBarUI.PropertyListener {
381         /**
382          * Constructs a {@code MetalRolloverListener}.
383          */
MetalRolloverListener()384         protected MetalRolloverListener() {}
385     }
386 
387     /**
388      * {@code DockingListener} for {@code MetalToolBarUI}.
389      */
390     protected class MetalDockingListener extends DockingListener {
391         private boolean pressedInBumps = false;
392 
393         /**
394          * Constructs the {@code MetalDockingListener}.
395          *
396          * @param t an instance of {@code JToolBar}
397          */
MetalDockingListener(JToolBar t)398         public MetalDockingListener(JToolBar t) {
399             super(t);
400         }
401 
mousePressed(MouseEvent e)402         public void mousePressed(MouseEvent e) {
403             super.mousePressed(e);
404             if (!toolBar.isEnabled()) {
405                 return;
406             }
407             pressedInBumps = false;
408             Rectangle bumpRect = new Rectangle();
409 
410             if (toolBar.getOrientation() == JToolBar.HORIZONTAL) {
411                 int x = MetalUtils.isLeftToRight(toolBar) ? 0 : toolBar.getSize().width-14;
412                 bumpRect.setBounds(x, 0, 14, toolBar.getSize().height);
413             } else {  // vertical
414                 bumpRect.setBounds(0, 0, toolBar.getSize().width, 14);
415             }
416             if (bumpRect.contains(e.getPoint())) {
417                 pressedInBumps = true;
418                 Point dragOffset = e.getPoint();
419                 if (!MetalUtils.isLeftToRight(toolBar)) {
420                     dragOffset.x -= (toolBar.getSize().width
421                                      - toolBar.getPreferredSize().width);
422                 }
423                 setDragOffset(dragOffset);
424             }
425         }
426 
mouseDragged(MouseEvent e)427         public void mouseDragged(MouseEvent e) {
428             if (pressedInBumps) {
429                 super.mouseDragged(e);
430             }
431         }
432     } // end class MetalDockingListener
433 }
434