1 /*
2  * Copyright (c) 2011, 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 com.apple.laf;
27 
28 import java.awt.*;
29 import java.beans.PropertyChangeEvent;
30 
31 import javax.swing.*;
32 import javax.swing.border.Border;
33 import javax.swing.plaf.basic.BasicSplitPaneDivider;
34 
35 import apple.laf.*;
36 import apple.laf.JRSUIConstants.State;
37 
38 import com.apple.laf.AquaUtils.LazyKeyedSingleton;
39 import com.apple.laf.AquaUtils.RecyclableSingleton;
40 import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
41 
42 @SuppressWarnings("serial") // Superclass is not serializable across versions
43 public class AquaSplitPaneDividerUI extends BasicSplitPaneDivider {
44     final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getSplitPaneDivider());
45 
AquaSplitPaneDividerUI(final AquaSplitPaneUI ui)46     public AquaSplitPaneDividerUI(final AquaSplitPaneUI ui) {
47         super(ui);
48         setLayout(new AquaSplitPaneDividerUI.DividerLayout());
49     }
50 
51     /**
52      * Property change event, presumably from the JSplitPane, will message
53      * updateOrientation if necessary.
54      */
propertyChange(final PropertyChangeEvent e)55     public void propertyChange(final PropertyChangeEvent e) {
56         if (e.getSource() == splitPane) {
57             final String propName = e.getPropertyName();
58             if ("enabled".equals(propName)) {
59                 final boolean enabled = splitPane.isEnabled();
60                 if (leftButton != null) leftButton.setEnabled(enabled);
61                 if (rightButton != null) rightButton.setEnabled(enabled);
62             } else if (JSplitPane.ORIENTATION_PROPERTY.equals(propName)) {
63                 // need to regenerate the buttons, since we bake the orientation into them
64                 if (rightButton  != null) {
65                     remove(rightButton); rightButton = null;
66                 }
67                 if (leftButton != null) {
68                     remove(leftButton); leftButton = null;
69                 }
70                 oneTouchExpandableChanged();
71             }
72         }
73         super.propertyChange(e);
74     }
75 
getMaxDividerSize()76     public int getMaxDividerSize() {
77         return 10;
78     }
79 
80     /**
81      * Paints the divider.
82      */
paint(final Graphics g)83     public void paint(final Graphics g) {
84         final Dimension size = getSize();
85         int x = 0;
86         int y = 0;
87 
88         final boolean horizontal = splitPane.getOrientation() == SwingConstants.HORIZONTAL;
89         //System.err.println("Size = " + size + " orientation horiz = " + horizontal);
90         // size determines orientation
91         final int maxSize = getMaxDividerSize();
92         boolean doPaint = true;
93         if (horizontal) {
94             if (size.height > maxSize) {
95                 final int diff = size.height - maxSize;
96                 y = diff / 2;
97                 size.height = maxSize;
98             }
99             if (size.height < 4) doPaint = false;
100         } else {
101             if (size.width > maxSize) {
102                 final int diff = size.width - maxSize;
103                 x = diff / 2;
104                 size.width = maxSize;
105             }
106             if (size.width < 4) doPaint = false;
107         }
108 
109         if (doPaint) {
110             painter.state.set(getState());
111             painter.paint(g, splitPane, x, y, size.width, size.height);
112         }
113 
114         super.paint(g); // Ends up at Container.paint, which paints our JButton children
115     }
116 
getState()117     protected State getState() {
118         return splitPane.isEnabled() ? State.ACTIVE : State.DISABLED;
119     }
120 
createLeftOneTouchButton()121     protected JButton createLeftOneTouchButton() {
122         return createButtonForDirection(getDirection(true));
123     }
124 
createRightOneTouchButton()125     protected JButton createRightOneTouchButton() {
126         return createButtonForDirection(getDirection(false));
127     }
128 
129     static final LazyKeyedSingleton<Integer, Image> directionArrows = new LazyKeyedSingleton<Integer, Image>() {
130         protected Image getInstance(final Integer direction) {
131             final Image arrowImage = AquaImageFactory.getArrowImageForDirection(direction);
132             final int h = (arrowImage.getHeight(null) * 5) / 7;
133             final int w = (arrowImage.getWidth(null) * 5) / 7;
134             return AquaUtils.generateLightenedImage(arrowImage.getScaledInstance(w, h, Image.SCALE_SMOOTH), 50);
135         }
136     };
137 
138     // separate static, because the divider needs to be serializable
139     // see <rdar://problem/7590946> JSplitPane is not serializable when using Aqua look and feel
createButtonForDirection(final int direction)140     static JButton createButtonForDirection(final int direction) {
141         final JButton button = new JButton(new ImageIcon(directionArrows.get(Integer.valueOf(direction))));
142         button.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
143         button.setFocusPainted(false);
144         button.setRequestFocusEnabled(false);
145         button.setFocusable(false);
146         button.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
147         return button;
148     }
149 
getDirection(final boolean isLeft)150     int getDirection(final boolean isLeft) {
151         if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
152             return isLeft ? SwingConstants.WEST : SwingConstants.EAST;
153         }
154 
155         return isLeft ? SwingConstants.NORTH : SwingConstants.SOUTH;
156     }
157 
158     static final int kMaxPopupArrowSize = 9;
159     protected class DividerLayout extends BasicSplitPaneDivider.DividerLayout {
layoutContainer(final Container c)160         public void layoutContainer(final Container c) {
161             final int maxSize = getMaxDividerSize();
162             final Dimension size = getSize();
163 
164             if (leftButton == null || rightButton == null || c != AquaSplitPaneDividerUI.this) return;
165 
166             if (!splitPane.isOneTouchExpandable()) {
167                 leftButton.setBounds(-5, -5, 1, 1);
168                 rightButton.setBounds(-5, -5, 1, 1);
169                 return;
170             }
171 
172             final int blockSize = Math.min(getDividerSize(), kMaxPopupArrowSize); // make it 1 less than divider, or kMaxPopupArrowSize
173 
174             // put them at the right or the bottom
175             if (orientation == JSplitPane.VERTICAL_SPLIT) {
176                 int yPosition = 0;
177                 if (size.height > maxSize) {
178                     final int diff = size.height - maxSize;
179                     yPosition = diff / 2;
180                 }
181                 int xPosition = kMaxPopupArrowSize + ONE_TOUCH_OFFSET;
182 
183                 rightButton.setBounds(xPosition, yPosition, kMaxPopupArrowSize, blockSize);
184 
185                 xPosition -= (kMaxPopupArrowSize + ONE_TOUCH_OFFSET);
186                 leftButton.setBounds(xPosition, yPosition, kMaxPopupArrowSize, blockSize);
187             } else {
188                 int xPosition = 0;
189                 if (size.width > maxSize) {
190                     final int diff = size.width - maxSize;
191                     xPosition = diff / 2;
192                 }
193                 int yPosition = kMaxPopupArrowSize + ONE_TOUCH_OFFSET;
194 
195                 rightButton.setBounds(xPosition, yPosition, blockSize, kMaxPopupArrowSize);
196 
197                 yPosition -= (kMaxPopupArrowSize + ONE_TOUCH_OFFSET);
198                 leftButton.setBounds(xPosition, yPosition, blockSize, kMaxPopupArrowSize);
199             }
200         }
201     }
202 
getHorizontalSplitDividerGradientVariant()203     public static Border getHorizontalSplitDividerGradientVariant() {
204         return HorizontalSplitDividerGradientPainter.instance();
205     }
206 
207     static class HorizontalSplitDividerGradientPainter implements Border {
208         private static final RecyclableSingleton<HorizontalSplitDividerGradientPainter> instance = new RecyclableSingletonFromDefaultConstructor<HorizontalSplitDividerGradientPainter>(HorizontalSplitDividerGradientPainter.class);
instance()209         static HorizontalSplitDividerGradientPainter instance() {
210             return instance.get();
211         }
212 
213         final Color startColor = Color.white;
214         final Color endColor = new Color(217, 217, 217);
215         final Color borderLines = Color.lightGray;
216 
getBorderInsets(final Component c)217         public Insets getBorderInsets(final Component c) {
218             return new Insets(0, 0, 0, 0);
219         }
220 
isBorderOpaque()221         public boolean isBorderOpaque() {
222             return true;
223         }
224 
paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height)225         public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
226             if (!(g instanceof Graphics2D)) return;
227 
228             final Graphics2D g2d = (Graphics2D)g;
229             final Color oldColor = g2d.getColor();
230 
231             g2d.setPaint(new GradientPaint(0, 0, startColor, 0, height, endColor));
232             g2d.fillRect(x, y, width, height);
233             g2d.setColor(borderLines);
234             g2d.drawLine(x, y, x + width, y);
235             g2d.drawLine(x, y + height - 1, x + width, y + height - 1);
236 
237             g2d.setColor(oldColor);
238         }
239     }
240 }
241