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      * 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, contentStyle, ENABLED);
 100         contentStyle = SynthLookAndFeel.updateStyle(context, this);
 101 
 102         context = getContext(c, Region.TOOL_BAR_DRAG_WINDOW, dragWindowStyle, 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 }