1 /*
2  * Copyright (c) 2002, 2014, 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      */
createUI(JComponent x)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
update(Graphics g, JComponent c)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     }
87 
88     /**
89      * Paints the specified component according to the Look and Feel.
90      * <p>This method is not used by Synth Look and Feel.
91      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
92      *
93      * @param g the {@code Graphics} object used for painting
94      * @param c the component being painted
95      * @see #paint(SynthContext,Graphics)
96      */
97     @Override
paint(Graphics g, JComponent c)98     public void paint(Graphics g, JComponent c) {
99         SynthContext context = getContext(c);
100 
101         paint(context, g);
102     }
103 
104     /**
105      * Paints the specified component.
106      *
107      * @param context context for the component being painted
108      * @param g the {@code Graphics} object used for painting
109      * @see #update(Graphics,JComponent)
110      */
paint(SynthContext context, Graphics g)111     protected void paint(SynthContext context, Graphics g) {
112         Border vpBorder = scrollpane.getViewportBorder();
113         if (vpBorder != null) {
114             Rectangle r = scrollpane.getViewportBorderBounds();
115             vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
116         }
117     }
118 
119     /**
120      * {@inheritDoc}
121      */
122     @Override
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)123     public void paintBorder(SynthContext context, Graphics g, int x,
124                             int y, int w, int h) {
125         context.getPainter().paintScrollPaneBorder(context, g, x, y, w, h);
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
installDefaults(JScrollPane scrollpane)132     protected void installDefaults(JScrollPane scrollpane) {
133         updateStyle(scrollpane);
134     }
135 
updateStyle(JScrollPane c)136     private void updateStyle(JScrollPane c) {
137         SynthContext context = getContext(c, ENABLED);
138         SynthStyle oldStyle = style;
139 
140         style = SynthLookAndFeel.updateStyle(context, this);
141         if (style != oldStyle) {
142             Border vpBorder = scrollpane.getViewportBorder();
143             if ((vpBorder == null) ||( vpBorder instanceof UIResource)) {
144                 scrollpane.setViewportBorder(new ViewportBorder(context));
145             }
146             if (oldStyle != null) {
147                 uninstallKeyboardActions(c);
148                 installKeyboardActions(c);
149             }
150         }
151     }
152 
153     /**
154      * {@inheritDoc}
155      */
156     @Override
installListeners(JScrollPane c)157     protected void installListeners(JScrollPane c) {
158         super.installListeners(c);
159         c.addPropertyChangeListener(this);
160         if (UIManager.getBoolean("ScrollPane.useChildTextComponentFocus")){
161             viewportViewFocusHandler = new ViewportViewFocusHandler();
162             c.getViewport().addContainerListener(viewportViewFocusHandler);
163             Component view = c.getViewport().getView();
164             if (view instanceof JTextComponent) {
165                 view.addFocusListener(viewportViewFocusHandler);
166             }
167         }
168     }
169 
170     /**
171      * {@inheritDoc}
172      */
173     @Override
uninstallDefaults(JScrollPane c)174     protected void uninstallDefaults(JScrollPane c) {
175         SynthContext context = getContext(c, ENABLED);
176 
177         style.uninstallDefaults(context);
178 
179         if (scrollpane.getViewportBorder() instanceof UIResource) {
180             scrollpane.setViewportBorder(null);
181         }
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
uninstallListeners(JComponent c)188     protected void uninstallListeners(JComponent c) {
189         super.uninstallListeners(c);
190         c.removePropertyChangeListener(this);
191         if (viewportViewFocusHandler != null) {
192             JViewport viewport = ((JScrollPane) c).getViewport();
193             viewport.removeContainerListener(viewportViewFocusHandler);
194             if (viewport.getView()!= null) {
195                 viewport.getView().removeFocusListener(viewportViewFocusHandler);
196             }
197             viewportViewFocusHandler = null;
198         }
199     }
200 
201     /**
202      * {@inheritDoc}
203      */
204     @Override
getContext(JComponent c)205     public SynthContext getContext(JComponent c) {
206         return getContext(c, getComponentState(c));
207     }
208 
getContext(JComponent c, int state)209     private SynthContext getContext(JComponent c, int state) {
210         return SynthContext.getContext(c, style, state);
211     }
212 
getComponentState(JComponent c)213     private int getComponentState(JComponent c) {
214         int baseState = SynthLookAndFeel.getComponentState(c);
215         if (viewportViewFocusHandler!=null && viewportViewHasFocus){
216             baseState = baseState | FOCUSED;
217         }
218         return baseState;
219     }
220 
propertyChange(PropertyChangeEvent e)221     public void propertyChange(PropertyChangeEvent e) {
222         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
223             updateStyle(scrollpane);
224         }
225     }
226 
227 
228     @SuppressWarnings("serial") // Superclass is not serializable across versions
229     private class ViewportBorder extends AbstractBorder implements UIResource {
230         private Insets insets;
231 
ViewportBorder(SynthContext context)232         ViewportBorder(SynthContext context) {
233             this.insets = (Insets)context.getStyle().get(context,
234                                             "ScrollPane.viewportBorderInsets");
235             if (this.insets == null) {
236                 this.insets = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
237             }
238         }
239 
240         @Override
paintBorder(Component c, Graphics g, int x, int y, int width, int height)241         public void paintBorder(Component c, Graphics g, int x, int y,
242                             int width, int height) {
243             JComponent jc = (JComponent)c;
244             SynthContext context = getContext(jc);
245             SynthStyle style = context.getStyle();
246             if (style == null) {
247                 assert false: "SynthBorder is being used outside after the " +
248                               " UI has been uninstalled";
249                 return;
250             }
251             context.getPainter().paintViewportBorder(context, g, x, y, width,
252                                                      height);
253         }
254 
255         @Override
getBorderInsets(Component c, Insets insets)256         public Insets getBorderInsets(Component c, Insets insets) {
257             if (insets == null) {
258                 return new Insets(this.insets.top, this.insets.left,
259                                   this.insets.bottom, this.insets.right);
260             }
261             insets.top = this.insets.top;
262             insets.bottom = this.insets.bottom;
263             insets.left = this.insets.left;
264             insets.right = this.insets.left;
265             return insets;
266         }
267 
268         @Override
isBorderOpaque()269         public boolean isBorderOpaque() {
270             return false;
271         }
272     }
273 
274     /**
275      * Handle keeping track of the viewport's view's focus
276      */
277     private class ViewportViewFocusHandler implements ContainerListener,
278             FocusListener{
componentAdded(ContainerEvent e)279         public void componentAdded(ContainerEvent e) {
280             if (e.getChild() instanceof JTextComponent) {
281                 e.getChild().addFocusListener(this);
282                 viewportViewHasFocus = e.getChild().isFocusOwner();
283                 scrollpane.repaint();
284             }
285         }
286 
componentRemoved(ContainerEvent e)287         public void componentRemoved(ContainerEvent e) {
288             if (e.getChild() instanceof JTextComponent) {
289                 e.getChild().removeFocusListener(this);
290             }
291         }
292 
focusGained(FocusEvent e)293         public void focusGained(FocusEvent e) {
294             viewportViewHasFocus = true;
295             scrollpane.repaint();
296         }
297 
focusLost(FocusEvent e)298         public void focusLost(FocusEvent e) {
299             viewportViewHasFocus = false;
300             scrollpane.repaint();
301         }
302     }
303 }
304