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 
26 package javax.swing.plaf.synth;
27 
28 import java.awt.Component;
29 import java.awt.Container;
30 import java.awt.Dimension;
31 import java.awt.Graphics;
32 import java.awt.Insets;
33 import java.awt.LayoutManager;
34 import java.awt.Rectangle;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
37 import javax.swing.Box;
38 import javax.swing.Icon;
39 import javax.swing.JComponent;
40 import javax.swing.JSeparator;
41 import javax.swing.JToolBar;
42 import javax.swing.plaf.ComponentUI;
43 import 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  */
51 public 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      */
createUI(JComponent c)66     public static ComponentUI createUI(JComponent c) {
67         return new SynthToolBarUI();
68     }
69 
70     /**
71      * {@inheritDoc}
72      */
73     @Override
installDefaults()74     protected void installDefaults() {
75         toolBar.setLayout(createLayout());
76         updateStyle(toolBar);
77     }
78 
79     /**
80      * {@inheritDoc}
81      */
82     @Override
installListeners()83     protected void installListeners() {
84         super.installListeners();
85         toolBar.addPropertyChangeListener(this);
86     }
87 
88     /**
89      * {@inheritDoc}
90      */
91     @Override
uninstallListeners()92     protected void uninstallListeners() {
93         super.uninstallListeners();
94         toolBar.removePropertyChangeListener(this);
95     }
96 
updateStyle(JToolBar c)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
uninstallDefaults()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
installComponents()148     protected void installComponents() {}
149 
150     /**
151      * {@inheritDoc}
152      */
153     @Override
uninstallComponents()154     protected void uninstallComponents() {}
155 
156     /**
157      * Creates a {@code LayoutManager} to use with the toolbar.
158      *
159      * @return a {@code LayoutManager} instance
160      */
createLayout()161     protected LayoutManager createLayout() {
162         return new SynthToolBarLayoutManager();
163     }
164 
165     /**
166      * {@inheritDoc}
167      */
168     @Override
getContext(JComponent c)169     public SynthContext getContext(JComponent c) {
170         return getContext(c, SynthLookAndFeel.getComponentState(c));
171     }
172 
getContext(JComponent c, int state)173     private SynthContext getContext(JComponent c, int state) {
174         return SynthContext.getContext(c, style, state);
175     }
176 
getContext(JComponent c, Region region, SynthStyle style)177     private SynthContext getContext(JComponent c, Region region, SynthStyle style) {
178         return SynthContext.getContext(c, region,
179                                        style, getComponentState(c, region));
180     }
181 
getContext(JComponent c, Region region, SynthStyle style, int state)182     private SynthContext getContext(JComponent c, Region region,
183                                     SynthStyle style, int state) {
184         return SynthContext.getContext(c, region, style, state);
185     }
186 
getComponentState(JComponent c, Region region)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
update(Graphics g, JComponent c)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
paint(Graphics g, JComponent c)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
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)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
setBorderToNonRollover(Component c)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
setBorderToRollover(Component c)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
setBorderToNormal(Component c)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      */
paint(SynthContext context, Graphics g)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      */
paintContent(SynthContext context, Graphics g, Rectangle bounds)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
paintDragWindow(Graphics g)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
propertyChange(PropertyChangeEvent e)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 {
addLayoutComponent(String name, Component comp)338         public void addLayoutComponent(String name, Component comp) {}
339 
removeLayoutComponent(Component comp)340         public void removeLayoutComponent(Component comp) {}
341 
minimumLayoutSize(Container parent)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 
preferredLayoutSize(Container parent)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 
layoutContainer(Container parent)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 
isGlue(Component c)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