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