1 /*
   2  * Copyright (c) 2002, 2010, 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 javax.swing.*;
  29 import javax.swing.text.JTextComponent;
  30 import javax.swing.border.*;
  31 import javax.swing.plaf.*;
  32 import javax.swing.plaf.basic.*;
  33 
  34 import java.beans.PropertyChangeListener;
  35 import java.beans.PropertyChangeEvent;
  36 
  37 import java.awt.*;
  38 import java.awt.event.ContainerListener;
  39 import java.awt.event.ContainerEvent;
  40 import java.awt.event.FocusListener;
  41 import java.awt.event.FocusEvent;
  42 
  43 /**
  44  * Provides the Synth L&F UI delegate for
  45  * {@link javax.swing.JScrollPane}.
  46  *
  47  * @author Scott Violet
  48  * @since 1.7
  49  */
  50 public class SynthScrollPaneUI extends BasicScrollPaneUI
  51                                implements PropertyChangeListener, SynthUI {
  52     private SynthStyle style;
  53     private boolean viewportViewHasFocus = false;
  54     private ViewportViewFocusHandler viewportViewFocusHandler;
  55 
  56     /**
  57      * Creates a new UI object for the given component.
  58      *
  59      * @param x component to create UI object for
  60      * @return the UI object
  61      */
  62     public static ComponentUI createUI(JComponent x) {
  63         return new SynthScrollPaneUI();
  64     }
  65 
  66     /**
  67      * Notifies this UI delegate to repaint the specified component.
  68      * This method paints the component background, then calls
  69      * the {@link #paint(SynthContext,Graphics)} method.
  70      *
  71      * <p>In general, this method does not need to be overridden by subclasses.
  72      * All Look and Feel rendering code should reside in the {@code paint} method.
  73      *
  74      * @param g the {@code Graphics} object used for painting
  75      * @param c the component being painted
  76      * @see #paint(SynthContext,Graphics)
  77      */
  78     @Override
  79     public void update(Graphics g, JComponent c) {
  80         SynthContext context = getContext(c);
  81 
  82         SynthLookAndFeel.update(context, g);
  83         context.getPainter().paintScrollPaneBackground(context,
  84                           g, 0, 0, c.getWidth(), c.getHeight());
  85         paint(context, g);
  86         context.dispose();
  87     }
  88 
  89     /**
  90      * Paints the specified component according to the Look and Feel.
  91      * <p>This method is not used by Synth Look and Feel.
  92      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
  93      *
  94      * @param g the {@code Graphics} object used for painting
  95      * @param c the component being painted
  96      * @see #paint(SynthContext,Graphics)
  97      */
  98     @Override
  99     public void paint(Graphics g, JComponent c) {
 100         SynthContext context = getContext(c);
 101 
 102         paint(context, g);
 103         context.dispose();
 104     }
 105 
 106     /**
 107      * Paints the specified component.
 108      *
 109      * @param context context for the component being painted
 110      * @param g the {@code Graphics} object used for painting
 111      * @see #update(Graphics,JComponent)
 112      */
 113     protected void paint(SynthContext context, Graphics g) {
 114         Border vpBorder = scrollpane.getViewportBorder();
 115         if (vpBorder != null) {
 116             Rectangle r = scrollpane.getViewportBorderBounds();
 117             vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
 118         }
 119     }
 120 
 121     /**
 122      * @inheritDoc
 123      */
 124     @Override
 125     public void paintBorder(SynthContext context, Graphics g, int x,
 126                             int y, int w, int h) {
 127         context.getPainter().paintScrollPaneBorder(context, g, x, y, w, h);
 128     }
 129 
 130     /**
 131      * @inheritDoc
 132      */
 133     @Override
 134     protected void installDefaults(JScrollPane scrollpane) {
 135         updateStyle(scrollpane);
 136     }
 137 
 138     private void updateStyle(JScrollPane c) {
 139         SynthContext context = getContext(c, ENABLED);
 140         SynthStyle oldStyle = style;
 141 
 142         style = SynthLookAndFeel.updateStyle(context, this);
 143         if (style != oldStyle) {
 144             Border vpBorder = scrollpane.getViewportBorder();
 145             if ((vpBorder == null) ||( vpBorder instanceof UIResource)) {
 146                 scrollpane.setViewportBorder(new ViewportBorder(context));
 147             }
 148             if (oldStyle != null) {
 149                 uninstallKeyboardActions(c);
 150                 installKeyboardActions(c);
 151             }
 152         }
 153         context.dispose();
 154     }
 155 
 156     /**
 157      * @inheritDoc
 158      */
 159     @Override
 160     protected void installListeners(JScrollPane c) {
 161         super.installListeners(c);
 162         c.addPropertyChangeListener(this);
 163         if (UIManager.getBoolean("ScrollPane.useChildTextComponentFocus")){
 164             viewportViewFocusHandler = new ViewportViewFocusHandler();
 165             c.getViewport().addContainerListener(viewportViewFocusHandler);
 166             Component view = c.getViewport().getView();
 167             if (view instanceof JTextComponent) {
 168                 view.addFocusListener(viewportViewFocusHandler);
 169             }
 170         }
 171     }
 172 
 173     /**
 174      * @inheritDoc
 175      */
 176     @Override
 177     protected void uninstallDefaults(JScrollPane c) {
 178         SynthContext context = getContext(c, ENABLED);
 179 
 180         style.uninstallDefaults(context);
 181         context.dispose();
 182 
 183         if (scrollpane.getViewportBorder() instanceof UIResource) {
 184             scrollpane.setViewportBorder(null);
 185         }
 186     }
 187 
 188     /**
 189      * @inheritDoc
 190      */
 191     @Override
 192     protected void uninstallListeners(JComponent c) {
 193         super.uninstallListeners(c);
 194         c.removePropertyChangeListener(this);
 195         if (viewportViewFocusHandler != null) {
 196             JViewport viewport = ((JScrollPane) c).getViewport();
 197             viewport.removeContainerListener(viewportViewFocusHandler);
 198             if (viewport.getView()!= null) {
 199                 viewport.getView().removeFocusListener(viewportViewFocusHandler);
 200             }
 201             viewportViewFocusHandler = null;
 202         }
 203     }
 204 
 205     /**
 206      * @inheritDoc
 207      */
 208     @Override
 209     public SynthContext getContext(JComponent c) {
 210         return getContext(c, getComponentState(c));
 211     }
 212 
 213     private SynthContext getContext(JComponent c, int state) {
 214         return SynthContext.getContext(SynthContext.class, c,
 215                     SynthLookAndFeel.getRegion(c), style, state);
 216     }
 217 
 218     private int getComponentState(JComponent c) {
 219         int baseState = SynthLookAndFeel.getComponentState(c);
 220         if (viewportViewFocusHandler!=null && viewportViewHasFocus){
 221             baseState = baseState | FOCUSED;
 222         }
 223         return baseState;
 224     }
 225 
 226     public void propertyChange(PropertyChangeEvent e) {
 227         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 228             updateStyle(scrollpane);
 229         }
 230     }
 231 
 232 
 233 
 234     private class ViewportBorder extends AbstractBorder implements UIResource {
 235         private Insets insets;
 236 
 237         ViewportBorder(SynthContext context) {
 238             this.insets = (Insets)context.getStyle().get(context,
 239                                             "ScrollPane.viewportBorderInsets");
 240             if (this.insets == null) {
 241                 this.insets = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
 242             }
 243         }
 244 
 245         @Override
 246         public void paintBorder(Component c, Graphics g, int x, int y,
 247                             int width, int height) {
 248             JComponent jc = (JComponent)c;
 249             SynthContext context = getContext(jc);
 250             SynthStyle style = context.getStyle();
 251             if (style == null) {
 252                 assert false: "SynthBorder is being used outside after the " +
 253                               " UI has been uninstalled";
 254                 return;
 255             }
 256             context.getPainter().paintViewportBorder(context, g, x, y, width,
 257                                                      height);
 258             context.dispose();
 259         }
 260 
 261         @Override
 262         public Insets getBorderInsets(Component c, Insets insets) {
 263             if (insets == null) {
 264                 return new Insets(this.insets.top, this.insets.left,
 265                                   this.insets.bottom, this.insets.right);
 266             }
 267             insets.top = this.insets.top;
 268             insets.bottom = this.insets.bottom;
 269             insets.left = this.insets.left;
 270             insets.right = this.insets.left;
 271             return insets;
 272         }
 273 
 274         @Override
 275         public boolean isBorderOpaque() {
 276             return false;
 277         }
 278     }
 279 
 280     /**
 281      * Handle keeping track of the viewport's view's focus
 282      */
 283     private class ViewportViewFocusHandler implements ContainerListener,
 284             FocusListener{
 285         public void componentAdded(ContainerEvent e) {
 286             if (e.getChild() instanceof JTextComponent) {
 287                 e.getChild().addFocusListener(this);
 288                 viewportViewHasFocus = e.getChild().isFocusOwner();
 289                 scrollpane.repaint();
 290             }
 291         }
 292 
 293         public void componentRemoved(ContainerEvent e) {
 294             if (e.getChild() instanceof JTextComponent) {
 295                 e.getChild().removeFocusListener(this);
 296             }
 297         }
 298 
 299         public void focusGained(FocusEvent e) {
 300             viewportViewHasFocus = true;
 301             scrollpane.repaint();
 302         }
 303 
 304         public void focusLost(FocusEvent e) {
 305             viewportViewHasFocus = false;
 306             scrollpane.repaint();
 307         }
 308     }
 309 }