1/*
2 * Copyright (c) 2002, 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
26package javax.swing.plaf.synth;
27
28import java.awt.Component;
29import java.awt.Container;
30import java.awt.Dimension;
31import java.awt.Graphics;
32import java.awt.Insets;
33import java.awt.LayoutManager;
34import java.awt.Rectangle;
35import java.beans.PropertyChangeEvent;
36import java.beans.PropertyChangeListener;
37import javax.swing.Box;
38import javax.swing.Icon;
39import javax.swing.JComponent;
40import javax.swing.JSeparator;
41import javax.swing.JToolBar;
42import javax.swing.plaf.ComponentUI;
43import javax.swing.plaf.basic.BasicToolBarUI;
44
45/**
46 * Provides the Synth L&F UI delegate for
47 * {@link javax.swing.JToolBar}.
48 *
49 * @since 1.7
50 */
51public class SynthToolBarUI extends BasicToolBarUI
52                            implements PropertyChangeListener, SynthUI {
53    private Icon handleIcon = null;
54    private Rectangle contentRect = new Rectangle();
55
56    private SynthStyle style;
57    private SynthStyle contentStyle;
58    private SynthStyle dragWindowStyle;
59
60    /**
61     * Creates a new UI object for the given component.
62     *
63     * @param c component to create UI object for
64     * @return the UI object
65     */
66    public static ComponentUI createUI(JComponent c) {
67        return new SynthToolBarUI();
68    }
69
70    /**
71     * {@inheritDoc}
72     */
73    @Override
74    protected void installDefaults() {
75        toolBar.setLayout(createLayout());
76        updateStyle(toolBar);
77    }
78
79    /**
80     * {@inheritDoc}
81     */
82    @Override
83    protected void installListeners() {
84        super.installListeners();
85        toolBar.addPropertyChangeListener(this);
86    }
87
88    /**
89     * {@inheritDoc}
90     */
91    @Override
92    protected void uninstallListeners() {
93        super.uninstallListeners();
94        toolBar.removePropertyChangeListener(this);
95    }
96
97    private void updateStyle(JToolBar c) {
98        SynthContext context = getContext(
99                c, Region.TOOL_BAR_CONTENT, null, ENABLED);
100        contentStyle = SynthLookAndFeel.updateStyle(context, this);
101
102        context = getContext(c, Region.TOOL_BAR_DRAG_WINDOW, null, ENABLED);
103        dragWindowStyle = SynthLookAndFeel.updateStyle(context, this);
104
105        context = getContext(c, ENABLED);
106        SynthStyle oldStyle = style;
107
108        style = SynthLookAndFeel.updateStyle(context, this);
109        if (oldStyle != style) {
110            handleIcon =
111                style.getIcon(context, "ToolBar.handleIcon");
112            if (oldStyle != null) {
113                uninstallKeyboardActions();
114                installKeyboardActions();
115            }
116        }
117    }
118
119    /**
120     * {@inheritDoc}
121     */
122    @Override
123    protected void uninstallDefaults() {
124        SynthContext context = getContext(toolBar, ENABLED);
125
126        style.uninstallDefaults(context);
127        style = null;
128
129        handleIcon = null;
130
131        context = getContext(toolBar, Region.TOOL_BAR_CONTENT,
132                             contentStyle, ENABLED);
133        contentStyle.uninstallDefaults(context);
134        contentStyle = null;
135
136        context = getContext(toolBar, Region.TOOL_BAR_DRAG_WINDOW,
137                             dragWindowStyle, ENABLED);
138        dragWindowStyle.uninstallDefaults(context);
139        dragWindowStyle = null;
140
141        toolBar.setLayout(null);
142    }
143
144    /**
145     * {@inheritDoc}
146     */
147    @Override
148    protected void installComponents() {}
149
150    /**
151     * {@inheritDoc}
152     */
153    @Override
154    protected void uninstallComponents() {}
155
156    /**
157     * Creates a {@code LayoutManager} to use with the toolbar.
158     *
159     * @return a {@code LayoutManager} instance
160     */
161    protected LayoutManager createLayout() {
162        return new SynthToolBarLayoutManager();
163    }
164
165    /**
166     * {@inheritDoc}
167     */
168    @Override
169    public SynthContext getContext(JComponent c) {
170        return getContext(c, SynthLookAndFeel.getComponentState(c));
171    }
172
173    private SynthContext getContext(JComponent c, int state) {
174        return SynthContext.getContext(c, style, state);
175    }
176
177    private SynthContext getContext(JComponent c, Region region, SynthStyle style) {
178        return SynthContext.getContext(c, region,
179                                       style, getComponentState(c, region));
180    }
181
182    private SynthContext getContext(JComponent c, Region region,
183                                    SynthStyle style, int state) {
184        return SynthContext.getContext(c, region, style, state);
185    }
186
187    private int getComponentState(JComponent c, Region region) {
188        return SynthLookAndFeel.getComponentState(c);
189    }
190
191    /**
192     * Notifies this UI delegate to repaint the specified component.
193     * This method paints the component background, then calls
194     * the {@link #paint(SynthContext,Graphics)} method.
195     *
196     * <p>In general, this method does not need to be overridden by subclasses.
197     * All Look and Feel rendering code should reside in the {@code paint} method.
198     *
199     * @param g the {@code Graphics} object used for painting
200     * @param c the component being painted
201     * @see #paint(SynthContext,Graphics)
202     */
203    @Override
204    public void update(Graphics g, JComponent c) {
205        SynthContext context = getContext(c);
206
207        SynthLookAndFeel.update(context, g);
208        context.getPainter().paintToolBarBackground(context,
209                          g, 0, 0, c.getWidth(), c.getHeight(),
210                          toolBar.getOrientation());
211        paint(context, g);
212    }
213
214    /**
215     * Paints the specified component according to the Look and Feel.
216     * <p>This method is not used by Synth Look and Feel.
217     * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
218     *
219     * @param g the {@code Graphics} object used for painting
220     * @param c the component being painted
221     * @see #paint(SynthContext,Graphics)
222     */
223    @Override
224    public void paint(Graphics g, JComponent c) {
225        SynthContext context = getContext(c);
226
227        paint(context, g);
228    }
229
230    /**
231     * {@inheritDoc}
232     */
233    @Override
234    public void paintBorder(SynthContext context, Graphics g, int x,
235                            int y, int w, int h) {
236        context.getPainter().paintToolBarBorder(context, g, x, y, w, h,
237                                                toolBar.getOrientation());
238    }
239
240    /**
241     * This implementation does nothing, because the {@code rollover}
242     * property of the {@code JToolBar} class is not used
243     * in the Synth Look and Feel.
244     */
245    @Override
246    protected void setBorderToNonRollover(Component c) {}
247
248    /**
249     * This implementation does nothing, because the {@code rollover}
250     * property of the {@code JToolBar} class is not used
251     * in the Synth Look and Feel.
252     */
253    @Override
254    protected void setBorderToRollover(Component c) {}
255
256    /**
257     * This implementation does nothing, because the {@code rollover}
258     * property of the {@code JToolBar} class is not used
259     * in the Synth Look and Feel.
260     */
261    @Override
262    protected void setBorderToNormal(Component c) {}
263
264    /**
265     * Paints the toolbar.
266     *
267     * @param context context for the component being painted
268     * @param g the {@code Graphics} object used for painting
269     * @see #update(Graphics,JComponent)
270     */
271    protected void paint(SynthContext context, Graphics g) {
272        if (handleIcon != null && toolBar.isFloatable()) {
273            int startX = toolBar.getComponentOrientation().isLeftToRight() ?
274                0 : toolBar.getWidth() -
275                    SynthGraphicsUtils.getIconWidth(handleIcon, context);
276            SynthGraphicsUtils.paintIcon(handleIcon, context, g, startX, 0,
277                    SynthGraphicsUtils.getIconWidth(handleIcon, context),
278                    SynthGraphicsUtils.getIconHeight(handleIcon, context));
279        }
280
281        SynthContext subcontext = getContext(
282                toolBar, Region.TOOL_BAR_CONTENT, contentStyle);
283        paintContent(subcontext, g, contentRect);
284    }
285
286    /**
287     * Paints the toolbar content.
288     *
289     * @param context context for the component being painted
290     * @param g {@code Graphics} object used for painting
291     * @param bounds bounding box for the toolbar
292     */
293    protected void paintContent(SynthContext context, Graphics g,
294            Rectangle bounds) {
295        SynthLookAndFeel.updateSubregion(context, g, bounds);
296        context.getPainter().paintToolBarContentBackground(context, g,
297                             bounds.x, bounds.y, bounds.width, bounds.height,
298                             toolBar.getOrientation());
299        context.getPainter().paintToolBarContentBorder(context, g,
300                             bounds.x, bounds.y, bounds.width, bounds.height,
301                             toolBar.getOrientation());
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    @Override
308    protected void paintDragWindow(Graphics g) {
309        int w = dragWindow.getWidth();
310        int h = dragWindow.getHeight();
311        SynthContext context = getContext(
312                toolBar, Region.TOOL_BAR_DRAG_WINDOW, dragWindowStyle);
313        SynthLookAndFeel.updateSubregion(
314                context, g, new Rectangle(0, 0, w, h));
315        context.getPainter().paintToolBarDragWindowBackground(context,
316                                                           g, 0, 0, w, h,
317                                                           dragWindow.getOrientation());
318        context.getPainter().paintToolBarDragWindowBorder(context, g, 0, 0, w, h,
319                                                          dragWindow.getOrientation());
320    }
321
322    //
323    // PropertyChangeListener
324    //
325
326    /**
327     * {@inheritDoc}
328     */
329    @Override
330    public void propertyChange(PropertyChangeEvent e) {
331        if (SynthLookAndFeel.shouldUpdateStyle(e)) {
332            updateStyle((JToolBar)e.getSource());
333        }
334    }
335
336
337    class SynthToolBarLayoutManager implements LayoutManager {
338        public void addLayoutComponent(String name, Component comp) {}
339
340        public void removeLayoutComponent(Component comp) {}
341
342        public Dimension minimumLayoutSize(Container parent) {
343            JToolBar tb = (JToolBar)parent;
344            Insets insets = tb.getInsets();
345            Dimension dim = new Dimension();
346            SynthContext context = getContext(tb);
347
348            if (tb.getOrientation() == JToolBar.HORIZONTAL) {
349                dim.width = tb.isFloatable() ?
350                    SynthGraphicsUtils.getIconWidth(handleIcon, context) : 0;
351                Dimension compDim;
352                for (int i = 0; i < tb.getComponentCount(); i++) {
353                    Component component = tb.getComponent(i);
354                    if (component.isVisible()) {
355                        compDim = component.getMinimumSize();
356                        dim.width += compDim.width;
357                        dim.height = Math.max(dim.height, compDim.height);
358                    }
359                }
360            } else {
361                dim.height = tb.isFloatable() ?
362                    SynthGraphicsUtils.getIconHeight(handleIcon, context) : 0;
363                Dimension compDim;
364                for (int i = 0; i < tb.getComponentCount(); i++) {
365                    Component component = tb.getComponent(i);
366                    if (component.isVisible()) {
367                        compDim = component.getMinimumSize();
368                        dim.width = Math.max(dim.width, compDim.width);
369                        dim.height += compDim.height;
370                    }
371                }
372            }
373            dim.width += insets.left + insets.right;
374            dim.height += insets.top + insets.bottom;
375
376            return dim;
377        }
378
379        public Dimension preferredLayoutSize(Container parent) {
380            JToolBar tb = (JToolBar)parent;
381            Insets insets = tb.getInsets();
382            Dimension dim = new Dimension();
383            SynthContext context = getContext(tb);
384
385            if (tb.getOrientation() == JToolBar.HORIZONTAL) {
386                dim.width = tb.isFloatable() ?
387                    SynthGraphicsUtils.getIconWidth(handleIcon, context) : 0;
388                Dimension compDim;
389                for (int i = 0; i < tb.getComponentCount(); i++) {
390                    Component component = tb.getComponent(i);
391                    if (component.isVisible()) {
392                        compDim = component.getPreferredSize();
393                        dim.width += compDim.width;
394                        dim.height = Math.max(dim.height, compDim.height);
395                    }
396                }
397            } else {
398                dim.height = tb.isFloatable() ?
399                    SynthGraphicsUtils.getIconHeight(handleIcon, context) : 0;
400                Dimension compDim;
401                for (int i = 0; i < tb.getComponentCount(); i++) {
402                    Component component = tb.getComponent(i);
403                    if (component.isVisible()) {
404                        compDim = component.getPreferredSize();
405                        dim.width = Math.max(dim.width, compDim.width);
406                        dim.height += compDim.height;
407                    }
408                }
409            }
410            dim.width += insets.left + insets.right;
411            dim.height += insets.top + insets.bottom;
412
413            return dim;
414        }
415
416        public void layoutContainer(Container parent) {
417            JToolBar tb = (JToolBar)parent;
418            Insets insets = tb.getInsets();
419            boolean ltr = tb.getComponentOrientation().isLeftToRight();
420            SynthContext context = getContext(tb);
421
422            Component c;
423            Dimension d;
424
425            // JToolBar by default uses a somewhat modified BoxLayout as
426            // its layout manager. For compatibility reasons, we want to
427            // support Box "glue" as a way to move things around on the
428            // toolbar. "glue" is represented in BoxLayout as a Box.Filler
429            // with a minimum and preferred size of (0,0).
430            // So what we do here is find the number of such glue fillers
431            // and figure out how much space should be allocated to them.
432            int glueCount = 0;
433            for (int i=0; i<tb.getComponentCount(); i++) {
434                if (isGlue(tb.getComponent(i))) glueCount++;
435            }
436
437            if (tb.getOrientation() == JToolBar.HORIZONTAL) {
438                int handleWidth = tb.isFloatable() ?
439                    SynthGraphicsUtils.getIconWidth(handleIcon, context) : 0;
440
441                // Note: contentRect does not take insets into account
442                // since it is used for determining the bounds that are
443                // passed to paintToolBarContentBackground().
444                contentRect.x = ltr ? handleWidth : 0;
445                contentRect.y = 0;
446                contentRect.width = tb.getWidth() - handleWidth;
447                contentRect.height = tb.getHeight();
448
449                // However, we do take the insets into account here for
450                // the purposes of laying out the toolbar child components.
451                int x = ltr ?
452                    handleWidth + insets.left :
453                    tb.getWidth() - handleWidth - insets.right;
454                int baseY = insets.top;
455                int baseH = tb.getHeight() - insets.top - insets.bottom;
456
457                // we need to get the minimum width for laying things out
458                // so that we can calculate how much empty space needs to
459                // be distributed among the "glue", if any
460                int extraSpacePerGlue = 0;
461                if (glueCount > 0) {
462                    int minWidth = preferredLayoutSize(parent).width;
463                    extraSpacePerGlue = (tb.getWidth() - minWidth) / glueCount;
464                    if (extraSpacePerGlue < 0) extraSpacePerGlue = 0;
465                }
466
467                for (int i = 0; i < tb.getComponentCount(); i++) {
468                    c = tb.getComponent(i);
469                    if (c.isVisible()) {
470                        d = c.getPreferredSize();
471                        int y, h;
472                        if (d.height >= baseH || c instanceof JSeparator) {
473                            // Fill available height
474                            y = baseY;
475                            h = baseH;
476                        } else {
477                            // Center component vertically in the available space
478                            y = baseY + (baseH / 2) - (d.height / 2);
479                            h = d.height;
480                        }
481                        //if the component is a "glue" component then add to its
482                        //width the extraSpacePerGlue it is due
483                        if (isGlue(c)) d.width += extraSpacePerGlue;
484                        c.setBounds(ltr ? x : x - d.width, y, d.width, h);
485                        x = ltr ? x + d.width : x - d.width;
486                    }
487                }
488            } else {
489                int handleHeight = tb.isFloatable() ?
490                    SynthGraphicsUtils.getIconHeight(handleIcon, context) : 0;
491
492                // See notes above regarding the use of insets
493                contentRect.x = 0;
494                contentRect.y = handleHeight;
495                contentRect.width = tb.getWidth();
496                contentRect.height = tb.getHeight() - handleHeight;
497
498                int baseX = insets.left;
499                int baseW = tb.getWidth() - insets.left - insets.right;
500                int y = handleHeight + insets.top;
501
502                // we need to get the minimum height for laying things out
503                // so that we can calculate how much empty space needs to
504                // be distributed among the "glue", if any
505                int extraSpacePerGlue = 0;
506                if (glueCount > 0) {
507                    int minHeight = minimumLayoutSize(parent).height;
508                    extraSpacePerGlue = (tb.getHeight() - minHeight) / glueCount;
509                    if (extraSpacePerGlue < 0) extraSpacePerGlue = 0;
510                }
511
512                for (int i = 0; i < tb.getComponentCount(); i++) {
513                    c = tb.getComponent(i);
514                    if (c.isVisible()) {
515                        d = c.getPreferredSize();
516                        int x, w;
517                        if (d.width >= baseW || c instanceof JSeparator) {
518                            // Fill available width
519                            x = baseX;
520                            w = baseW;
521                        } else {
522                            // Center component horizontally in the available space
523                            x = baseX + (baseW / 2) - (d.width / 2);
524                            w = d.width;
525                        }
526                        //if the component is a "glue" component then add to its
527                        //height the extraSpacePerGlue it is due
528                        if (isGlue(c)) d.height += extraSpacePerGlue;
529                        c.setBounds(x, y, w, d.height);
530                        y += d.height;
531                    }
532                }
533            }
534        }
535
536        private boolean isGlue(Component c) {
537            if (c.isVisible() && c instanceof Box.Filler) {
538                Box.Filler f = (Box.Filler)c;
539                Dimension min = f.getMinimumSize();
540                Dimension pref = f.getPreferredSize();
541                return min.width == 0 &&  min.height == 0 &&
542                        pref.width == 0 && pref.height == 0;
543            }
544            return false;
545        }
546    }
547}
548