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
26package javax.swing.plaf.metal;
27
28import javax.swing.*;
29import java.awt.Color;
30import java.awt.Component;
31import java.awt.Container;
32import java.awt.Dimension;
33import java.awt.Frame;
34import java.awt.Graphics;
35import java.awt.GraphicsEnvironment;
36import java.awt.Insets;
37import java.awt.Point;
38import java.awt.Rectangle;
39import java.awt.event.*;
40import java.lang.ref.WeakReference;
41import java.util.*;
42
43import java.beans.PropertyChangeListener;
44
45import javax.swing.event.*;
46import javax.swing.border.*;
47import javax.swing.plaf.*;
48import 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 */
56public 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    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     */
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     */
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     */
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}
419