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      */
register(JComponent c)91     static synchronized 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      */
unregister(JComponent c)103     static synchronized 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      */
findRegisteredComponentOfType(JComponent from, Class<?> target)119     static synchronized 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      */
doesMenuBarBorderToolBar(JMenuBar c)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      */
createUI( JComponent c )167     public static ComponentUI createUI( JComponent c )
168     {
169         return new MetalToolBarUI();
170     }
171 
installUI( JComponent c )172     public void installUI( JComponent c )
173     {
174         super.installUI( c );
175         register(c);
176     }
177 
uninstallUI( JComponent c )178     public void uninstallUI( JComponent c )
179     {
180         super.uninstallUI( c );
181         nonRolloverBorder = null;
182         unregister(c);
183     }
184 
installListeners()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 
uninstallListeners()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 
createRolloverBorder()210     protected Border createRolloverBorder() {
211         return super.createRolloverBorder();
212     }
213 
createNonRolloverBorder()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      */
createNonRolloverToggleBorder()222     private Border createNonRolloverToggleBorder() {
223         return createNonRolloverBorder();
224     }
225 
setBorderToNonRollover(Component c)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      */
createContainerListener()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      */
createRolloverListener()269     protected PropertyChangeListener createRolloverListener() {
270         return null;
271     }
272 
createDockingListener( )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      */
setDragOffset(Point p)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      */
update(Graphics g, JComponent c)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 
setLastMenuBar(JMenuBar lastMenuBar)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          */
MetalDockingListener(JToolBar t)383         public MetalDockingListener(JToolBar t) {
384             super(t);
385         }
386 
mousePressed(MouseEvent e)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 
mouseDragged(MouseEvent e)412         public void mouseDragged(MouseEvent e) {
413             if (pressedInBumps) {
414                 super.mouseDragged(e);
415             }
416         }
417     } // end class MetalDockingListener
418 }
419