1 /*
2  * Copyright (c) 2002, 2017, 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 
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.beans.*;
32 import java.util.*;
33 import javax.swing.*;
34 import javax.swing.plaf.*;
35 import javax.swing.plaf.basic.*;
36 
37 
38 /**
39  * Provides the Synth L&F UI delegate for
40  * {@link javax.swing.JSplitPane}.
41  *
42  * @author Scott Violet
43  * @since 1.7
44  */
45 public class SynthSplitPaneUI extends BasicSplitPaneUI
46                               implements PropertyChangeListener, SynthUI {
47     /**
48      * Keys to use for forward focus traversal when the JComponent is
49      * managing focus.
50      */
51     private Set<KeyStroke> managingFocusForwardTraversalKeys;
52 
53     /**
54      * Keys to use for backward focus traversal when the JComponent is
55      * managing focus.
56      */
57     private Set<KeyStroke> managingFocusBackwardTraversalKeys;
58 
59     /**
60      * Style for the JSplitPane.
61      */
62     private SynthStyle style;
63     /**
64      * Style for the divider.
65      */
66     private SynthStyle dividerStyle;
67 
68     /**
69      *
70      * Constructs a {@code SynthSplitPaneUI}.
71      */
SynthSplitPaneUI()72     public SynthSplitPaneUI() {}
73 
74     /**
75      * Creates a new SynthSplitPaneUI instance
76      *
77      * @param x component to create UI object for
78      * @return the UI object
79      */
createUI(JComponent x)80     public static ComponentUI createUI(JComponent x) {
81         return new SynthSplitPaneUI();
82     }
83 
84     /**
85      * Installs the UI defaults.
86      */
87     @Override
88     @SuppressWarnings("deprecation")
installDefaults()89     protected void installDefaults() {
90         updateStyle(splitPane);
91 
92         setOrientation(splitPane.getOrientation());
93         setContinuousLayout(splitPane.isContinuousLayout());
94 
95         resetLayoutManager();
96 
97         /* Install the nonContinuousLayoutDivider here to avoid having to
98         add/remove everything later. */
99         if(nonContinuousLayoutDivider == null) {
100             setNonContinuousLayoutDivider(
101                                 createDefaultNonContinuousLayoutDivider(),
102                                 true);
103         } else {
104             setNonContinuousLayoutDivider(nonContinuousLayoutDivider, true);
105         }
106 
107         // focus forward traversal key
108         if (managingFocusForwardTraversalKeys==null) {
109             managingFocusForwardTraversalKeys = new HashSet<KeyStroke>();
110             managingFocusForwardTraversalKeys.add(
111                 KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
112         }
113         splitPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
114                                         managingFocusForwardTraversalKeys);
115         // focus backward traversal key
116         if (managingFocusBackwardTraversalKeys==null) {
117             managingFocusBackwardTraversalKeys = new HashSet<KeyStroke>();
118             managingFocusBackwardTraversalKeys.add(
119                 KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
120         }
121         splitPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
122                                         managingFocusBackwardTraversalKeys);
123     }
124 
updateStyle(JSplitPane splitPane)125     private void updateStyle(JSplitPane splitPane) {
126         SynthContext context = getContext(splitPane, Region.SPLIT_PANE_DIVIDER,
127                                           ENABLED);
128         SynthStyle oldDividerStyle = dividerStyle;
129         dividerStyle = SynthLookAndFeel.updateStyle(context, this);
130 
131         context = getContext(splitPane, ENABLED);
132         SynthStyle oldStyle = style;
133 
134         style = SynthLookAndFeel.updateStyle(context, this);
135 
136         if (style != oldStyle) {
137             Object value = style.get(context, "SplitPane.size");
138             if (value == null) {
139                 value = Integer.valueOf(6);
140             }
141             LookAndFeel.installProperty(splitPane, "dividerSize", value);
142             dividerSize = ((Number)value).intValue();
143 
144             value = style.get(context, "SplitPane.oneTouchExpandable");
145             if (value != null) {
146                 LookAndFeel.installProperty(splitPane, "oneTouchExpandable", value);
147             }
148 
149             if (divider != null) {
150                 splitPane.remove(divider);
151                 divider.setDividerSize(splitPane.getDividerSize());
152             }
153             if (oldStyle != null) {
154                 uninstallKeyboardActions();
155                 installKeyboardActions();
156             }
157         }
158         if (style != oldStyle || dividerStyle != oldDividerStyle) {
159             // Only way to force BasicSplitPaneDivider to reread the
160             // necessary properties.
161             if (divider != null) {
162                 splitPane.remove(divider);
163             }
164             divider = createDefaultDivider();
165             divider.setBasicSplitPaneUI(this);
166             splitPane.add(divider, JSplitPane.DIVIDER);
167         }
168     }
169 
170     /**
171      * Installs the event listeners for the UI.
172      */
173     @Override
installListeners()174     protected void installListeners() {
175         super.installListeners();
176         splitPane.addPropertyChangeListener(this);
177     }
178 
179     /**
180      * Uninstalls the UI defaults.
181      */
182     @Override
uninstallDefaults()183     protected void uninstallDefaults() {
184         SynthContext context = getContext(splitPane, ENABLED);
185 
186         style.uninstallDefaults(context);
187         style = null;
188 
189         context = getContext(splitPane, Region.SPLIT_PANE_DIVIDER, ENABLED);
190         dividerStyle.uninstallDefaults(context);
191         dividerStyle = null;
192 
193         super.uninstallDefaults();
194     }
195 
196 
197     /**
198      * Uninstalls the event listeners from the UI.
199      */
200     @Override
uninstallListeners()201     protected void uninstallListeners() {
202         super.uninstallListeners();
203         splitPane.removePropertyChangeListener(this);
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
getContext(JComponent c)210     public SynthContext getContext(JComponent c) {
211         return getContext(c, SynthLookAndFeel.getComponentState(c));
212     }
213 
getContext(JComponent c, int state)214     private SynthContext getContext(JComponent c, int state) {
215         return SynthContext.getContext(c, style, state);
216     }
217 
getContext(JComponent c, Region region)218     SynthContext getContext(JComponent c, Region region) {
219         return getContext(c, region, getComponentState(c, region));
220     }
221 
getContext(JComponent c, Region region, int state)222     private SynthContext getContext(JComponent c, Region region, int state) {
223         if (region == Region.SPLIT_PANE_DIVIDER) {
224             return SynthContext.getContext(c, region, dividerStyle, state);
225         }
226         return SynthContext.getContext(c, region, style, state);
227     }
228 
getComponentState(JComponent c, Region subregion)229     private int getComponentState(JComponent c, Region subregion) {
230         int state = SynthLookAndFeel.getComponentState(c);
231 
232         if (divider.isMouseOver()) {
233             state |= MOUSE_OVER;
234         }
235         return state;
236     }
237 
238     /**
239      * {@inheritDoc}
240      */
241     @Override
propertyChange(PropertyChangeEvent e)242     public void propertyChange(PropertyChangeEvent e) {
243         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
244             updateStyle((JSplitPane)e.getSource());
245         }
246     }
247 
248     /**
249      * Creates the default divider.
250      */
251     @Override
createDefaultDivider()252     public BasicSplitPaneDivider createDefaultDivider() {
253         SynthSplitPaneDivider divider = new SynthSplitPaneDivider(this);
254 
255         divider.setDividerSize(splitPane.getDividerSize());
256         return divider;
257     }
258 
259     /**
260      * {@inheritDoc}
261      */
262     @Override
263     @SuppressWarnings("serial") // anonymous class
createDefaultNonContinuousLayoutDivider()264     protected Component createDefaultNonContinuousLayoutDivider() {
265         return new Canvas() {
266             public void paint(Graphics g) {
267                 paintDragDivider(g, 0, 0, getWidth(), getHeight());
268             }
269         };
270     }
271 
272     /**
273      * Notifies this UI delegate to repaint the specified component.
274      * This method paints the component background, then calls
275      * the {@link #paint(SynthContext,Graphics)} method.
276      *
277      * <p>In general, this method does not need to be overridden by subclasses.
278      * All Look and Feel rendering code should reside in the {@code paint} method.
279      *
280      * @param g the {@code Graphics} object used for painting
281      * @param c the component being painted
282      * @see #paint(SynthContext,Graphics)
283      */
284     @Override
285     public void update(Graphics g, JComponent c) {
286         SynthContext context = getContext(c);
287 
288         SynthLookAndFeel.update(context, g);
289         context.getPainter().paintSplitPaneBackground(context,
290                           g, 0, 0, c.getWidth(), c.getHeight());
291         paint(context, g);
292     }
293 
294     /**
295      * Paints the specified component according to the Look and Feel.
296      * <p>This method is not used by Synth Look and Feel.
297      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
298      *
299      * @param g the {@code Graphics} object used for painting
300      * @param c the component being painted
301      * @see #paint(SynthContext,Graphics)
302      */
303     @Override
304     public void paint(Graphics g, JComponent c) {
305         SynthContext context = getContext(c);
306 
307         paint(context, g);
308     }
309 
310     /**
311      * Paints the specified component. This implementation does nothing.
312      *
313      * @param context context for the component being painted
314      * @param g the {@code Graphics} object used for painting
315      * @see #update(Graphics,JComponent)
316      */
317     protected void paint(SynthContext context, Graphics g) {
318         // This is done to update package private variables in
319         // BasicSplitPaneUI
320         super.paint(g, splitPane);
321     }
322 
323     /**
324      * {@inheritDoc}
325      */
326     @Override
327     public void paintBorder(SynthContext context, Graphics g, int x,
328                             int y, int w, int h) {
329         context.getPainter().paintSplitPaneBorder(context, g, x, y, w, h);
330     }
331 
332     private void paintDragDivider(Graphics g, int x, int y, int w, int h) {
333         SynthContext context = getContext(splitPane,Region.SPLIT_PANE_DIVIDER);
334         context.setComponentState(((context.getComponentState() | MOUSE_OVER) ^
335                                    MOUSE_OVER) | PRESSED);
336         Shape oldClip = g.getClip();
337         g.clipRect(x, y, w, h);
338         context.getPainter().paintSplitPaneDragDivider(context, g, x, y, w, h,
339                                            splitPane.getOrientation());
340         g.setClip(oldClip);
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @Override
347     public void finishedPaintingChildren(JSplitPane jc, Graphics g) {
348         if(jc == splitPane && getLastDragLocation() != -1 &&
349                               !isContinuousLayout() && !draggingHW) {
350             if(jc.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
351                 paintDragDivider(g, getLastDragLocation(), 0, dividerSize - 1,
352                                  splitPane.getHeight() - 1);
353             } else {
354                 paintDragDivider(g, 0, getLastDragLocation(),
355                                  splitPane.getWidth() - 1, dividerSize - 1);
356             }
357         }
358     }
359 }
360