1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: FixedSplitPane.java 1464 2007-07-02 02:34:40Z dmsmith $
21   */
22  package org.crosswire.common.swing;
23  
24  import java.awt.Component;
25  import java.awt.ComponentOrientation;
26  import java.awt.Dimension;
27  
28  import javax.swing.JComponent;
29  import javax.swing.JSplitPane;
30  import javax.swing.border.Border;
31  import javax.swing.border.EmptyBorder;
32  import javax.swing.plaf.SplitPaneUI;
33  import javax.swing.plaf.basic.BasicSplitPaneUI;
34  
35  /**
36   * This is a hack to fix the setDividerLocation problem and other layout
37   * problems.
38   * <p>
39   * See Bug Parade 4101306, 4485465 for a description of the WIDE divider
40   * problem.
41   * <p>
42   * Bug Reports on JSplitpane setDividerLocation<br>
43   * 4101306, 4125713, 4148530
44   *<p>
45   * From the javadoc for setDividerLocation(double):
46   * -------------------------------------------<br>
47   * <p>
48   * This method is implemented in terms of setDividerLocation(int). This method
49   * immediately changes the size of the receiver based on its current size. If
50   * the receiver is not correctly realized and on screen, this method will have
51   * no effect (new divider location will become (current size *
52   * proportionalLocation) which is 0).<br>
53   * -------------------------------------------<br>
54   * So, as you can see the JSplitPane MUST be visible invoking this method
55   * otherwise it will not have the desired effect.
56   * <p>
57   * Another, Bug Report 4786896 notes that if the preferred sizes of the two
58   * components plus the divider of the split pane adds up to more than the
59   * preferred size of the JSplitPane, then JSplitPane will use the minimum size
60   * of the components.
61   * <p>
62   * Since the preferred way of managing the sizes of containers is not with pixel
63   * counts, the solution here is to set the preferred size to zero.
64   * <p>
65   * See Bug Parade: 4265389 for a description of the ComponentOrientation
66   * problem.
67   * 
68   * @see gnu.lgpl.License for license details.<br>
69   *      The copyright to this program is held by it's authors.
70   * @author DM Smith [dmsmith555 at yahoo dot com]
71   */
72  public class AltFixedSplitPane extends JSplitPane {
73      /* BUG_PARADE(DMS): many bugs here */
74  
75      /**
76       * Constructor for FixedSplitPane
77       */
78      public AltFixedSplitPane() {
79      }
80  
81      /**
82       * Constructor for FixedSplitPane that has no divider shadow and starts out
83       * with an empty border.
84       */
85      public AltFixedSplitPane(boolean visibleDividerBorder) {
86          this();
87          this.visibleDividerBorder = visibleDividerBorder;
88          setBorder(EMPTY_BORDER);
89      }
90  
91      /**
92       * Constructor for FixedSplitPane
93       */
94      public AltFixedSplitPane(int arg0) {
95          super(arg0);
96      }
97  
98      /**
99       * Constructor for FixedSplitPane
100      */
101     public AltFixedSplitPane(int arg0, boolean arg1) {
102         super(arg0, arg1);
103     }
104 
105     /**
106      * Constructor for FixedSplitPane
107      */
108     public AltFixedSplitPane(int arg0, Component arg1, Component arg2) {
109         super(arg0, arg1, arg2);
110     }
111 
112     /**
113      * Constructor for FixedSplitPane
114      */
115     public AltFixedSplitPane(int arg0, boolean arg1, Component arg2, Component arg3) {
116         super(arg0, arg1, arg2, arg3);
117     }
118 
119     /*
120      * (non-Javadoc)
121      * 
122      * @see
123      * java.awt.Component#setComponentOrientation(java.awt.ComponentOrientation)
124      */
125     @Override
126     public void setComponentOrientation(ComponentOrientation o) {
127         // Turns out that this is currently ignored by JSplitPane
128         // So we are handling it here.
129         boolean isLtoR = getComponentOrientation().isLeftToRight();
130 
131         // if the layout is stacked/vertical then we are done
132         if (getOrientation() == JSplitPane.VERTICAL_SPLIT) {
133             return;
134         }
135 
136         if (o.isLeftToRight() != isLtoR) {
137             // Swap the left and right
138             Component left = getLeftComponent();
139             Component right = getRightComponent();
140             setRightComponent(null);
141             setLeftComponent(right);
142             setRightComponent(left);
143 
144             // The resizing is now reversed
145             setResizeWeight(isLtoR ? 1 - getResizeWeight() : getResizeWeight());
146 
147             // also swap the proportional divider location
148             if (hasProportionalLocation) {
149                 setDividerLocation(isLtoR ? 1 - proportionalLocation : proportionalLocation);
150             }
151         }
152 
153         super.setComponentOrientation(o);
154     }
155 
156     /*
157      * (non-Javadoc)
158      * 
159      * @see java.awt.Container#addImpl(java.awt.Component, java.lang.Object,
160      * int)
161      */
162     @Override
163     protected void addImpl(Component comp, Object constraints, int index) {
164         if (comp instanceof JComponent) {
165             ((JComponent) comp).setPreferredSize(DOT);
166         }
167 
168         // flip the constraint if we are not left to right
169         Object realConstraints = constraints;
170         if (!getComponentOrientation().isLeftToRight()) {
171             realConstraints = constraints != null && constraints.equals(JSplitPane.RIGHT) ? JSplitPane.LEFT : JSplitPane.RIGHT;
172         }
173 
174         super.addImpl(comp, realConstraints, index);
175     }
176 
177     /*
178      * (non-Javadoc)
179      * 
180      * @see javax.swing.JSplitPane#setBottomComponent(java.awt.Component)
181      */
182     @Override
183     public void setBottomComponent(Component comp) {
184         if (comp instanceof JComponent) {
185             ((JComponent) comp).setPreferredSize(DOT);
186         }
187         super.setBottomComponent(comp);
188     }
189 
190     /*
191      * (non-Javadoc)
192      * 
193      * @see javax.swing.JSplitPane#setLeftComponent(java.awt.Component)
194      */
195     @Override
196     public void setLeftComponent(Component comp) {
197         if (comp instanceof JComponent) {
198             ((JComponent) comp).setPreferredSize(DOT);
199         }
200 
201         if (getComponentOrientation().isLeftToRight()) {
202             super.setLeftComponent(comp);
203         } else {
204             super.setRightComponent(comp);
205         }
206     }
207 
208     /*
209      * (non-Javadoc)
210      * 
211      * @see javax.swing.JSplitPane#setRightComponent(java.awt.Component)
212      */
213     @Override
214     public void setRightComponent(Component comp) {
215         if (comp instanceof JComponent) {
216             ((JComponent) comp).setPreferredSize(DOT);
217         }
218 
219         if (getComponentOrientation().isLeftToRight()) {
220             super.setRightComponent(comp);
221         } else {
222             super.setLeftComponent(comp);
223         }
224     }
225 
226     /*
227      * (non-Javadoc)
228      * 
229      * @see javax.swing.JSplitPane#setTopComponent(java.awt.Component)
230      */
231     @Override
232     public void setTopComponent(Component comp) {
233         if (comp instanceof JComponent) {
234             ((JComponent) comp).setPreferredSize(DOT);
235         }
236         super.setTopComponent(comp);
237     }
238 
239     /**
240      * Validates this container and all of its subcomponents. The first time
241      * this method is called, the initial divider position is set.
242      */
243     @Override
244     public void validate() {
245         if (firstValidate) {
246             firstValidate = false;
247             if (hasProportionalLocation) {
248                 setDividerLocation(proportionalLocation);
249             }
250         }
251         super.validate();
252     }
253 
254     /**
255      * Sets the divider location as a percentage of the JSplitPane's size.
256      */
257     @Override
258     public void setDividerLocation(double newProportionalLoc) {
259         double realProportionalLoc = newProportionalLoc;
260 
261         // When the orientation is reversed the proportions are reversed.
262         if (getComponentOrientation().isLeftToRight()) {
263             realProportionalLoc = 1 - realProportionalLoc;
264         }
265 
266         if (!firstValidate) {
267             hasProportionalLocation = true;
268             proportionalLocation = realProportionalLoc;
269         } else {
270             super.setDividerLocation(realProportionalLoc);
271         }
272     }
273 
274     /**
275      * Sets the divider location as a percentage of the JSplitPane's size.
276      */
277     @Override
278     public void setResizeWeight(double newResizeWeight) {
279         double realResizeWeight = newResizeWeight;
280 
281         // When the orientation is reversed the proportions are reversed.
282         if (getComponentOrientation().isLeftToRight()) {
283             realResizeWeight = 1 - realResizeWeight;
284         }
285 
286         super.setResizeWeight(realResizeWeight);
287     }
288 
289     /**
290      * Whether visible borders are hinted
291      * 
292      * @return the divider border visiblity hint
293      */
294     public boolean isVisibleDividerBorder() {
295         return visibleDividerBorder;
296     }
297 
298     /**
299      * Set a hint whether the border should be visible or not. Look and feels
300      * may ignore this.
301      * 
302      * @param newVisibility
303      */
304     public void setVisibleDividerBorder(boolean newVisibility) {
305         boolean oldVisibility = isVisibleDividerBorder();
306         if (oldVisibility == newVisibility) {
307             return;
308         }
309         visibleDividerBorder = newVisibility;
310         firePropertyChange(PROPERTYNAME_VISIBLE_DIVIDER_BORDER, oldVisibility, newVisibility);
311     }
312 
313     /*
314      * (non-Javadoc)
315      * 
316      * @see javax.swing.JComponent#updateUI()
317      */
318     @Override
319     public void updateUI() {
320         super.updateUI();
321         if (!visibleDividerBorder) {
322             SplitPaneUI splitPaneUI = getUI();
323             if (splitPaneUI instanceof BasicSplitPaneUI) {
324                 BasicSplitPaneUI basicUI = (BasicSplitPaneUI) splitPaneUI;
325                 basicUI.getDivider().setBorder(EMPTY_BORDER);
326             }
327         }
328     }
329 
330     private static final Dimension DOT = new Dimension(0, 0);
331     private boolean firstValidate = true;
332     private boolean hasProportionalLocation;
333     private double proportionalLocation;
334 
335     /**
336      * Property for border visibility
337      */
338     public static final String PROPERTYNAME_VISIBLE_DIVIDER_BORDER = "visibleDividerBorder";
339 
340     /**
341      * An Empty Border
342      */
343     private static final Border EMPTY_BORDER = new EmptyBorder(0, 0, 0, 0);
344 
345     /**
346      * Hint for whether Divider border should be visible.
347      */
348     private boolean visibleDividerBorder;
349 
350     /**
351      * Serialization ID
352      */
353     private static final long serialVersionUID = 3761687909593789241L;
354 }
355