0001: /*
0002: * @(#)JideSplitPane.java
0003: *
0004: * Copyright 2002 JIDE Software Inc. All rights reserved.
0005: */
0006:
0007: package com.jidesoft.swing;
0008:
0009: import com.jidesoft.plaf.LookAndFeelFactory;
0010: import com.jidesoft.plaf.UIDefaultsLookup;
0011:
0012: import javax.accessibility.*;
0013: import javax.swing.*;
0014: import java.awt.*;
0015: import java.awt.event.*;
0016: import java.util.Arrays;
0017:
0018: /**
0019: * <code>JideSplitPane</code> is used to divide multiple
0020: * <code>Component</code>s.
0021: * <p/>
0022: * These <code>Component</code>s in a split pane can be aligned
0023: * left to right using
0024: * <code>JideSplitPane.HORIZONTAL_SPLIT</code>, or top to bottom using
0025: * <code>JideSplitPane.VERTICAL_SPLIT</code>.
0026: */
0027: public class JideSplitPane extends JPanel implements ContainerListener,
0028: ComponentListener, Accessible {
0029:
0030: /**
0031: * The divider used for non-continuous layout is added to the split pane
0032: * with this object.
0033: */
0034: protected static final String NON_CONTINUOUS_DIVIDER = "nonContinuousDivider";
0035:
0036: /**
0037: * Vertical split indicates the <code>Component</code>s are
0038: * split along the y axis. For example the two or more
0039: * <code>Component</code>s will be split one on top of the other.
0040: */
0041: public final static int VERTICAL_SPLIT = 0;
0042:
0043: /**
0044: * Horizontal split indicates the <code>Component</code>s are
0045: * split along the x axis. For example the two or more
0046: * <code>Component</code>s will be split one to the left of the
0047: * other.
0048: */
0049: public final static int HORIZONTAL_SPLIT = 1;
0050:
0051: /**
0052: * Bound property name for orientation (horizontal or vertical).
0053: */
0054: public final static String ORIENTATION_PROPERTY = "orientation";
0055:
0056: /**
0057: * Bound property name for border size.
0058: */
0059: public final static String DIVIDER_SIZE_PROPERTY = "dividerSize";
0060:
0061: /**
0062: * Bound property name for border size.
0063: */
0064: public final static String PROPERTY_DIVIDER_LOCATION = "dividerLocation";
0065:
0066: /**
0067: * Bound property name for continuousLayout.
0068: */
0069: public final static String CONTINUOUS_LAYOUT_PROPERTY = "continuousLayout";
0070:
0071: /**
0072: * Bound property name for gripper.
0073: */
0074: public final static String GRIPPER_PROPERTY = "gripper";
0075:
0076: /**
0077: * Bound property name for proportional layout.
0078: */
0079: public final static String PROPORTIONAL_LAYOUT_PROPERTY = "proportionalLayout";
0080:
0081: /**
0082: * Bound property name for the proportions used in the layout.
0083: */
0084: public final static String PROPORTIONS_PROPERTY = "proportions";
0085:
0086: public static final String PROPERTY_HEAVYWEIGHT_COMPONENT_ENABLED = "heavyweightComponentEnabled";
0087:
0088: /**
0089: * How the views are split. The value of it can be either <code>HORIZONTAL_SPLIT</code>
0090: * or <code>VERTICAL_SPLIT</code>.
0091: */
0092: private int _orientation;
0093:
0094: /**
0095: * Size of the divider. All dividers have the same size. If <code>orientation</code> is
0096: * <code>HORIZONTAL_SPLIT</code>, the size will equal to the width of the divider
0097: * If <code>orientation</code> is <code>VERTICAL_SPLIT</code>, the size will equal
0098: * to the height of the divider.
0099: */
0100: private int _dividerSize = UIDefaultsLookup
0101: .getInt("JideSplitPane.dividerSize");
0102:
0103: // /**
0104: // * Instance for the shadow of the divider when non continuous layout
0105: // * is being used.
0106: // */
0107: // private Contour _nonContinuousLayoutDivider;
0108: private HeavyweightWrapper _nonContinuousLayoutDividerWrapper;
0109:
0110: /**
0111: * Continuous layout or not.
0112: */
0113: private boolean _continuousLayout = false;
0114:
0115: /**
0116: * Layered pane where _nonContinuousLayoutDivider is added to.
0117: */
0118: private Container _layeredPane;
0119:
0120: /**
0121: * If the gripper should be shown. Gripper is something on divider to indicate it can be dragged.
0122: */
0123: private boolean _showGripper = false;
0124:
0125: /**
0126: * Whether the contained panes should be laid out proportionally.
0127: */
0128: private boolean _proportionalLayout = false;
0129:
0130: /**
0131: * An array of the proportions to assign to the widths or heights of the contained
0132: * panes. Has one fewer elements than there are contained panes; the last pane
0133: * receives the remaining room.
0134: */
0135: private double[] _proportions;
0136:
0137: /**
0138: * For proportional layouts only, when this flag is true the initial layout uses even
0139: * proportions for the contained panes, unless the proportions are explicitly set.
0140: */
0141: private boolean _initiallyEven = true;
0142:
0143: private boolean _heavyweightComponentEnabled = false;
0144: public WindowAdapter _windowDeactivatedListener;
0145:
0146: /**
0147: * Creates a new <code>JideSplitPane</code> configured to arrange the child
0148: * components side-by-side horizontally.
0149: */
0150: public JideSplitPane() {
0151: this (JideSplitPane.HORIZONTAL_SPLIT);
0152: }
0153:
0154: /**
0155: * Creates a new <code>JideSplitPane</code> configured with the
0156: * specified orientation.
0157: *
0158: * @param newOrientation <code>JideSplitPane.HORIZONTAL_SPLIT</code> or
0159: * <code>JideSplitPane.VERTICAL_SPLIT</code>
0160: * @throws IllegalArgumentException if <code>orientation</code>
0161: * is not one of HORIZONTAL_SPLIT or VERTICAL_SPLIT.
0162: */
0163: public JideSplitPane(int newOrientation) {
0164: super ();
0165:
0166: _orientation = newOrientation;
0167:
0168: if (_orientation != HORIZONTAL_SPLIT
0169: && _orientation != VERTICAL_SPLIT)
0170: throw new IllegalArgumentException(
0171: "cannot create JideSplitPane, "
0172: + "orientation must be one of "
0173: + "JideSplitPane.HORIZONTAL_SPLIT "
0174: + "or JideSplitPane.VERTICAL_SPLIT");
0175:
0176: // setup layout
0177: LayoutManager layoutManager;
0178: if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
0179: layoutManager = new JideSplitPaneLayout(this ,
0180: JideSplitPaneLayout.X_AXIS);
0181: } else {
0182: layoutManager = new JideSplitPaneLayout(this ,
0183: JideSplitPaneLayout.Y_AXIS);
0184: }
0185: super .setLayout(layoutManager);
0186:
0187: setOpaque(false);
0188:
0189: // setup listener
0190: installListeners();
0191: }
0192:
0193: /**
0194: * Layout manager used by JideSplitPane.
0195: */
0196: private class JideSplitPaneLayout extends JideBoxLayout {
0197: public JideSplitPaneLayout(Container target) {
0198: super (target);
0199: setResetWhenInvalidate(false);
0200: }
0201:
0202: public JideSplitPaneLayout(Container target, int axis) {
0203: super (target, axis);
0204: setResetWhenInvalidate(false);
0205: }
0206:
0207: public JideSplitPaneLayout(Container target, int axis, int gap) {
0208: super (target, axis, gap);
0209: setResetWhenInvalidate(false);
0210: }
0211:
0212: int getDividerLocation(int index) {
0213: if (_componentSizes == null) {
0214: return -1;
0215: }
0216: if (index < 0 || (index + 1) << 1 >= _componentSizes.length)
0217: return -1;
0218: int location = 0;
0219: for (int i = 0; i < (index << 1) + 1; i++)
0220: location += _componentSizes[i];
0221: return location;
0222: }
0223:
0224: void setDividerLocation(int index, int location) {
0225: int oldLocation = getDividerLocation(index);
0226: if (oldLocation == -1 || oldLocation == location)
0227: return;
0228: int prevIndex = 2 * index;
0229: int nextIndex = 2 * index + 2;
0230: for (int i = prevIndex; i >= 0; i--) {
0231: if (_target.getComponent(i).isVisible()) {
0232: break;
0233: }
0234: prevIndex--;
0235: }
0236: for (int i = nextIndex; i < _target.getComponentCount(); i++) {
0237: if (_target.getComponent(i).isVisible()) {
0238: break;
0239: }
0240: nextIndex++;
0241: }
0242: _componentSizes[prevIndex] += location - oldLocation;
0243: _componentSizes[nextIndex] -= location - oldLocation;
0244: Component comp1 = _target.getComponent(prevIndex);
0245: Component comp2 = _target.getComponent(nextIndex);
0246: if (isProportionalLayout()) {
0247: replaceProportions();
0248: return;
0249: }
0250: ComponentOrientation o = _target.getComponentOrientation();
0251: if (resolveAxis(_axis, o) == X_AXIS) {
0252: if (comp1 instanceof JComponent) {
0253: ((JComponent) comp1)
0254: .setPreferredSize(new Dimension(
0255: _componentSizes[prevIndex], comp1
0256: .getPreferredSize().height));
0257: }
0258: if (comp2 instanceof JComponent) {
0259: ((JComponent) comp2)
0260: .setPreferredSize(new Dimension(
0261: _componentSizes[nextIndex], comp2
0262: .getPreferredSize().height));
0263: }
0264: // for (int i = 0; i < _target.getComponentCount(); i++) {
0265: // Component component = _target.getComponent(i);
0266: // if (component == comp1 || component == comp2 || !component.isVisible()) {
0267: // continue;
0268: // }
0269: // if (component instanceof JComponent) {
0270: // ((JComponent) component).setPreferredSize(new Dimension(component.getSize().width, component.getPreferredSize().height));
0271: // }
0272: // }
0273: } else {
0274: if (comp1 instanceof JComponent) {
0275: ((JComponent) comp1)
0276: .setPreferredSize(new Dimension(comp1
0277: .getPreferredSize().width,
0278: _componentSizes[prevIndex]));
0279: }
0280: if (comp2 instanceof JComponent) {
0281: ((JComponent) comp2)
0282: .setPreferredSize(new Dimension(comp2
0283: .getPreferredSize().width,
0284: _componentSizes[nextIndex]));
0285: }
0286: // for (int i = 0; i < _target.getComponentCount(); i++) {
0287: // Component component = _target.getComponent(i);
0288: // if (component == comp1 || component == comp2 || !component.isVisible()) {
0289: // continue;
0290: // }
0291: // if (component instanceof JComponent) {
0292: // ((JComponent) component).setPreferredSize(new Dimension(component.getPreferredSize().width, component.getSize().height));
0293: // }
0294: // }
0295: }
0296: }
0297:
0298: /**
0299: * Uses the component sizes to generate a new array of proportions, and replaces
0300: * the existing one.
0301: */
0302: private void replaceProportions() {
0303: setProportions(deduceProportions());
0304: }
0305:
0306: /**
0307: * Uses the component sizes to generate a new array of proportions.
0308: */
0309: private double[] deduceProportions() {
0310: double total = 0.0; // Total height or width of contained panes (not including dividers)
0311: for (int i = 0; i < _componentSizes.length; i += 2)
0312: // Note we're skipping dividers
0313: total += _componentSizes[i];
0314: double[] newProportions;
0315: if (total == 0.0)
0316: newProportions = null;
0317: else {
0318: newProportions = new double[(_componentSizes.length - 1) / 2];
0319: for (int i = 0; i < newProportions.length; ++i)
0320: newProportions[i] = _componentSizes[i * 2] / total;
0321: }
0322: return newProportions;
0323: }
0324:
0325: // For proportional layouts, override the key box layout method:
0326: @Override
0327: protected boolean calculateComponentSizes(int availableSize,
0328: int startIndex, int endIndex) {
0329: // Just go to super for non-proportional layout, or if there are no more
0330: // than one component.
0331: if (!isProportionalLayout() || getComponentCount() <= 1)
0332: return super .calculateComponentSizes(availableSize,
0333: startIndex, endIndex);
0334:
0335: // If we have no set proportions, either call super to get initial
0336: // proportions or set them up as even.
0337: if (_proportions == null) {
0338: if (!isInitiallyEven()) {
0339: if (!super .calculateComponentSizes(availableSize,
0340: startIndex, endIndex))
0341: return false;
0342: //<syd_0033> Luke's change
0343: // David: We now wait to set proportions until the user sets them directly. Otherwise recreated
0344: // docked frames end up with tiny sizes instead of using preferred size, since they are
0345: // initially added to their ContainerContainers without their contents.
0346: // _proportions = deduceProportions(); // Note no call to setProportions: no event
0347: //</syd_0033>
0348: return true;
0349: }
0350: // What remains is the "initially even" logic:
0351: int c = getPaneCount();
0352: double[] p = new double[c - 1];
0353: for (int i = 0; i < p.length; ++i)
0354: p[i] = 1.0 / c;
0355: _proportions = p; // Note no call to setProportions: no event may be sent here
0356: }
0357:
0358: // Spin through the dividers, setting up their sizes and subtracting from available.
0359: for (int i = 1; i < getComponentCount(); i += 2) {
0360: if (getComponent(i).isVisible())
0361: _componentSizes[i] = getDividerSize();
0362: availableSize -= _componentSizes[i];
0363: }
0364:
0365: if (availableSize < 0)
0366: return false;
0367:
0368: // Then spin through the panes and set their sizes according to the
0369: // proportions.
0370: double[] p = _proportions;
0371: double last = 1.0;
0372: for (int i = 0; i < p.length; ++i)
0373: last -= p[i];
0374: double total = 0.0;
0375: for (int i = 0; i < getComponentCount(); i += 2) {
0376: int j = i / 2;
0377: if (getComponent(i).isVisible())
0378: total += (j < p.length) ? p[j] : last;
0379: }
0380: int size = availableSize;
0381: for (int i = 0; i < p.length; ++i) {
0382: int j = i * 2;
0383: if (getComponent(j).isVisible()) {
0384: double d = p[i] / total;
0385: if (d <= 1.0)
0386: _componentSizes[j] = (int) (0.5 + size * d);
0387: }
0388: availableSize -= _componentSizes[j];
0389: }
0390: // Now set the last one to whatever is left over.
0391: if (availableSize < 0)
0392: return false;
0393:
0394: _componentSizes[_componentSizes.length - 1] = availableSize;
0395: return true;
0396: }
0397: }
0398:
0399: @Override
0400: public void updateUI() {
0401: if (UIDefaultsLookup.get("JideSplitPane.dividerSize") == null) {
0402: LookAndFeelFactory.installJideExtension();
0403: }
0404: super .updateUI();
0405: }
0406:
0407: /**
0408: * Install listeners
0409: */
0410: private void installListeners() {
0411: addContainerListener(this );
0412: }
0413:
0414: /**
0415: * Sets the size of the divider.
0416: *
0417: * @param newSize an integer giving the size of the divider in pixels
0418: */
0419: public void setDividerSize(int newSize) {
0420: int oldSize = _dividerSize;
0421:
0422: if (oldSize != newSize) {
0423: _dividerSize = newSize;
0424: firePropertyChange(DIVIDER_SIZE_PROPERTY, oldSize, newSize);
0425: }
0426: }
0427:
0428: /**
0429: * Returns the size of the divider.
0430: *
0431: * @return an integer giving the size of the divider in pixels
0432: */
0433: public int getDividerSize() {
0434: return _dividerSize;
0435: }
0436:
0437: /**
0438: * Inserts the specified pane to this container at the given
0439: * position.
0440: * Note: Divider is not counted.
0441: *
0442: * @param pane the pane to be added
0443: * @param index the position at which to insert the component.
0444: * @return the component <code>pane</code>
0445: */
0446: public Component insertPane(Component pane, int index) {
0447: return insertPane(pane, null, index);
0448: }
0449:
0450: /**
0451: * Inserts the specified pane to this container at the given
0452: * position.
0453: * Note: Divider is not counted.
0454: *
0455: * @param pane the pane to be added
0456: * @param constraint an object expressing layout constraints for this component
0457: * @param index the position at which to insert the component.
0458: * @return the component <code>pane</code>
0459: */
0460: public Component insertPane(Component pane, Object constraint,
0461: int index) {
0462: if (index <= 0) {
0463: addImpl(pane, constraint, 0);
0464: } else if (index >= getPaneCount()) {
0465: addImpl(pane, constraint, -1);
0466: } else {
0467: addImpl(pane, constraint, (index << 1) - 1);
0468: }
0469:
0470: return pane;
0471: }
0472:
0473: /**
0474: * Adds the specified pane to this container at the end.
0475: *
0476: * @param pane the pane to be added
0477: * @return the pane <code>pane</code>
0478: */
0479: public Component addPane(Component pane) {
0480: if (pane == null) {
0481: return null;
0482: }
0483: return super .add(pane);
0484: }
0485:
0486: /**
0487: * Removes the pane, specified by <code>index</code>,
0488: * from this container.
0489: *
0490: * @param pane the pane to be removed.
0491: */
0492: public void removePane(Component pane) {
0493: removePane(indexOfPane(pane));
0494: }
0495:
0496: /**
0497: * Replaces the pane at the position specified by index.
0498: *
0499: * @param pane new pane
0500: * @param index position
0501: */
0502: public void setPaneAt(Component pane, int index) {
0503: setPaneAt(pane, null, index);
0504: }
0505:
0506: /**
0507: * Replaces the pane at the position specified by index.
0508: *
0509: * @param pane new pane
0510: * @param constraint an object expressing layout constraints for this component
0511: * @param index position
0512: */
0513: public void setPaneAt(Component pane, Object constraint, int index) {
0514: double[] proportions = _proportions;
0515: _proportions = null; // Just turn them off temporarily
0516: removePane(index);
0517: insertPane(pane, constraint, index);
0518: _proportions = proportions;
0519: }
0520:
0521: /**
0522: * Removes the pane, specified by <code>index</code>,
0523: * from this container.
0524: *
0525: * @param index the index of the component to be removed.
0526: */
0527: public void removePane(int index) {
0528: if (index == 0) { // if first one
0529: super .remove(0); // the component
0530: } else { // not first one. then remove itself and the divier before it
0531: super .remove(index << 1); // component
0532: }
0533: }
0534:
0535: /**
0536: * Sets the orientation, or how the splitter is divided. The options
0537: * are:<ul>
0538: * <li>JideSplitPane.VERTICAL_SPLIT (above/below orientation of components)
0539: * <li>JideSplitPane.HORIZONTAL_SPLIT (left/right orientation of components)
0540: * </ul>
0541: *
0542: * @param orientation an integer specifying the orientation
0543: * @throws IllegalArgumentException if orientation is not one of:
0544: * HORIZONTAL_SPLIT or VERTICAL_SPLIT.
0545: */
0546: public void setOrientation(int orientation) {
0547: if ((orientation != VERTICAL_SPLIT)
0548: && (orientation != HORIZONTAL_SPLIT)) {
0549: throw new IllegalArgumentException(
0550: "JideSplitPane: orientation must " + "be one of "
0551: + "JideSplitPane.VERTICAL_SPLIT or "
0552: + "JideSplitPane.HORIZONTAL_SPLIT");
0553: }
0554:
0555: if (_orientation == orientation)
0556: return;
0557:
0558: int oldOrientation = _orientation;
0559: _orientation = orientation;
0560:
0561: // if (_orientation == JideSplitPane.HORIZONTAL_SPLIT)
0562: // setBorder(BorderFactory.createLineBorder(Color.RED));
0563: // else
0564: // setBorder(BorderFactory.createLineBorder(Color.CYAN));
0565: LayoutManager layoutManager;
0566: if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
0567: layoutManager = new JideSplitPaneLayout(this ,
0568: JideSplitPaneLayout.X_AXIS);
0569: } else {
0570: layoutManager = new JideSplitPaneLayout(this ,
0571: JideSplitPaneLayout.Y_AXIS);
0572: }
0573: super .setLayout(layoutManager);
0574: doLayout();
0575:
0576: firePropertyChange(ORIENTATION_PROPERTY, oldOrientation,
0577: orientation);
0578: }
0579:
0580: /**
0581: * Returns the orientation.
0582: *
0583: * @return an integer giving the orientation
0584: * @see #setOrientation
0585: */
0586: public int getOrientation() {
0587: return _orientation;
0588: }
0589:
0590: /**
0591: * Lays out the <code>JideSplitPane</code> layout based on the preferred size
0592: * children components, or based on the proportions if proportional layout is on. This
0593: * will likely result in changing the divider location.
0594: */
0595: public void resetToPreferredSizes() {
0596: doLayout();
0597: }
0598:
0599: /**
0600: * Sets this split pane to lay its constituents out proportionally if the given flag
0601: * is true, or by preferred sizes otherwise.
0602: */
0603: public void setProportionalLayout(boolean proportionalLayout) {
0604: if (proportionalLayout == _proportionalLayout)
0605: return;
0606: _proportionalLayout = proportionalLayout;
0607: revalidate();
0608: firePropertyChange(PROPORTIONAL_LAYOUT_PROPERTY,
0609: !proportionalLayout, proportionalLayout);
0610: if (!proportionalLayout)
0611: setProportions(null);
0612: }
0613:
0614: /**
0615: * Returns the proportional layout flag.
0616: */
0617: public boolean isProportionalLayout() {
0618: return _proportionalLayout;
0619: }
0620:
0621: /**
0622: * Sets the proportions to use in laying out this split pane's children. Only
0623: * applicable when {@link #isProportionalLayout} is true; calling it when false will
0624: * throw an exception. The given array must either be null, or have one fewer slots
0625: * than there are {@linkplain #getPaneCount() contained panes}. Each item in the
0626: * array (if not null) must be a number between 0 and 1, and the sum of all of them
0627: * must be no more than 1.
0628: */
0629: public void setProportions(double[] proportions) {
0630: // if ( ! _proportionalLayout )
0631: if (!_proportionalLayout && proportions != null)
0632: throw new IllegalStateException(
0633: "Can't set proportions on a non-proportional split pane");
0634: if (Arrays.equals(proportions, _proportions))
0635: return;
0636: if (proportions != null
0637: && proportions.length != getPaneCount() - 1)
0638: throw new IllegalArgumentException(
0639: "Must provide one fewer proportions than there are panes: got "
0640: + proportions.length + ", expected "
0641: + (getPaneCount() - 1));
0642: if (proportions != null) {
0643: double sum = 0.0;
0644: for (int i = 0; i < proportions.length; ++i) {
0645: if (proportions[i] < 0.0)
0646: proportions[i] = 0.0;
0647: if (proportions[i] > 1.0)
0648: proportions[i] = 1.0;
0649: sum += proportions[i];
0650: }
0651: if (sum > 1.0)
0652: throw new IllegalArgumentException(
0653: "Sum of proportions must be no more than 1, got "
0654: + sum);
0655: }
0656: double[] oldProportions = _proportions;
0657: _proportions = (proportions == null) ? null
0658: : (double[]) proportions.clone();
0659:
0660: LayoutManager layoutManager = getLayout();
0661: boolean reset = false;
0662: if (layoutManager instanceof JideBoxLayout) {
0663: reset = ((JideBoxLayout) layoutManager)
0664: .isResetWhenInvalidate();
0665: ((JideBoxLayout) layoutManager)
0666: .setResetWhenInvalidate(true);
0667: }
0668: revalidate();
0669: if (reset && layoutManager instanceof JideBoxLayout) {
0670: ((JideBoxLayout) layoutManager)
0671: .setResetWhenInvalidate(reset);
0672: }
0673: firePropertyChange(PROPORTIONS_PROPERTY, oldProportions,
0674: proportions);
0675: }
0676:
0677: /**
0678: * Returns the current array of proportions used for proportional layout, or null if
0679: * none has been established via {@link #setProportions} or via user action.
0680: */
0681: public double[] getProportions() {
0682: double[] answer = _proportions;
0683: if (answer != null)
0684: answer = (double[]) answer.clone();
0685: return answer;
0686: }
0687:
0688: /**
0689: * Sets the flag telling whether to do even proportions for the initial proportional
0690: * layout, in the absence of explicit proportions.
0691: */
0692: public void setInitiallyEven(boolean initiallyEven) {
0693: _initiallyEven = initiallyEven;
0694: }
0695:
0696: /**
0697: * Returns the flag that tells whether to do even proportions for the initial
0698: * proportional layout, in the absence of explicit proportions.
0699: */
0700: public boolean isInitiallyEven() {
0701: return _initiallyEven;
0702: }
0703:
0704: /**
0705: * Returns true, so that calls to <code>revalidate</code>
0706: * on any descendant of this <code>JideSplitPane</code>
0707: * will cause a request to be queued that
0708: * will validate the <code>JideSplitPane</code> and all its descendants.
0709: *
0710: * @return true
0711: * @see JComponent#revalidate
0712: */
0713: @Override
0714: public boolean isValidateRoot() {
0715: return true;
0716: }
0717:
0718: /**
0719: * Prepares dragging if it's not continuous layout. If it's continous layout, do nothing.
0720: */
0721: protected void startDragging(JideSplitPaneDivider divider) {
0722: if (!isContinuousLayout()) {
0723: Component topLevelAncestor = getTopLevelAncestor();
0724: if (_windowDeactivatedListener == null) {
0725: // this a listener to remove the dragging outline when window is deactivated
0726: _windowDeactivatedListener = new WindowAdapter() {
0727: @Override
0728: public void windowDeactivated(WindowEvent e) {
0729: stopDragging();
0730: if (e.getWindow() != null) {
0731: e.getWindow().removeWindowListener(
0732: _windowDeactivatedListener);
0733: }
0734: }
0735: };
0736: }
0737: if (topLevelAncestor instanceof Window)
0738: ((Window) topLevelAncestor)
0739: .addWindowListener(_windowDeactivatedListener);
0740: if (topLevelAncestor instanceof RootPaneContainer) {
0741: _layeredPane = ((RootPaneContainer) topLevelAncestor)
0742: .getLayeredPane();
0743:
0744: // left over, remove them
0745: if (_nonContinuousLayoutDividerWrapper == null) {
0746: Contour nonContinuousLayoutDivider = new Contour();
0747: _nonContinuousLayoutDividerWrapper = new HeavyweightWrapper(
0748: nonContinuousLayoutDivider);
0749: _nonContinuousLayoutDividerWrapper
0750: .setHeavyweight(isHeavyweightComponentEnabled());
0751: }
0752:
0753: _nonContinuousLayoutDividerWrapper
0754: .delegateSetCursor((_orientation == JideSplitPane.HORIZONTAL_SPLIT) ? JideSplitPaneDivider.HORIZONTAL_CURSOR
0755: : JideSplitPaneDivider.VERTICAL_CURSOR);
0756: _nonContinuousLayoutDividerWrapper
0757: .delegateSetVisible(false);
0758: _nonContinuousLayoutDividerWrapper.delegateAdd(
0759: _layeredPane, JLayeredPane.DRAG_LAYER);
0760:
0761: Rectangle bounds = getVisibleRect();
0762: Rectangle layeredPaneBounds = SwingUtilities
0763: .convertRectangle(this , bounds, _layeredPane);
0764: int dividerThickness = Math.min(4, getDividerSize());
0765: if (getOrientation() == HORIZONTAL_SPLIT) {
0766: _nonContinuousLayoutDividerWrapper
0767: .delegateSetBounds(layeredPaneBounds.x,
0768: layeredPaneBounds.y,
0769: dividerThickness,
0770: layeredPaneBounds.height);
0771: } else {
0772: _nonContinuousLayoutDividerWrapper
0773: .delegateSetBounds(layeredPaneBounds.x,
0774: layeredPaneBounds.y,
0775: layeredPaneBounds.width,
0776: dividerThickness);
0777: }
0778: }
0779: }
0780: }
0781:
0782: private void stopDragging() {
0783: if (!isContinuousLayout() && _layeredPane != null
0784: && _nonContinuousLayoutDividerWrapper != null) {
0785: _nonContinuousLayoutDividerWrapper
0786: .delegateSetVisible(false);
0787: _nonContinuousLayoutDividerWrapper
0788: .delegateRemove(_layeredPane);
0789: _nonContinuousLayoutDividerWrapper.delegateSetNull();
0790: _nonContinuousLayoutDividerWrapper = null;
0791: }
0792: }
0793:
0794: /**
0795: * Drags divider to right location. If it's continous layout,
0796: * really drag the divider; if not, only drag the shadow.
0797: *
0798: * @param divider the divider
0799: * @param location new location
0800: */
0801: protected void dragDividerTo(JideSplitPaneDivider divider,
0802: int location) {
0803: if (_layeredPane == null || isContinuousLayout()) {
0804: setDividerLocation(divider, location);
0805: } else {
0806: if (_nonContinuousLayoutDividerWrapper != null) {
0807: Point p;
0808: if (getOrientation() == HORIZONTAL_SPLIT) {
0809: p = SwingUtilities.convertPoint(this , location, 0,
0810: _layeredPane);
0811: } else {
0812: p = SwingUtilities.convertPoint(this , 0, location,
0813: _layeredPane);
0814: }
0815: int dividerThickness = Math.min(4, getDividerSize());
0816: if (getOrientation() == HORIZONTAL_SPLIT) {
0817: p.x += ((getDividerSize() - dividerThickness) >> 1);
0818: } else {
0819: p.y += ((getDividerSize() - dividerThickness) >> 1);
0820: }
0821: _nonContinuousLayoutDividerWrapper
0822: .delegateSetLocation(p);
0823: _nonContinuousLayoutDividerWrapper
0824: .delegateSetVisible(true);
0825: }
0826: }
0827: }
0828:
0829: /**
0830: * Finishs dragging. If it's not continous layout, clear up the shadow component.
0831: *
0832: * @param divider the divider
0833: * @param location new location
0834: */
0835: protected void finishDraggingTo(JideSplitPaneDivider divider,
0836: int location) {
0837: if (isContinuousLayout()
0838: || _nonContinuousLayoutDividerWrapper != null) {
0839: stopDragging();
0840: setDividerLocation(divider, location);
0841: }
0842: }
0843:
0844: /**
0845: * Returns the index of the divider. For example, the index of the first divider
0846: * is 0, the index of the second is 1.
0847: * Notes: Pane is not counted
0848: *
0849: * @param divider divider to get index
0850: * @return index of the divider. -1 if comp doesn't exist in this container
0851: */
0852: public int indexOfDivider(JideSplitPaneDivider divider) {
0853: int index = indexOf(divider);
0854: if (index == -1)
0855: return index;
0856: else {
0857: if (index % 2 == 0)
0858: System.err
0859: .println("Warning: divider's index is even. (index = "
0860: + index + ")");
0861: return (index - 1) / 2;
0862: }
0863: }
0864:
0865: /**
0866: * Returns the index of the pane. For example, the index of the first pane
0867: * is 0, the index of the second is 1.
0868: * Notes: divider is not counted
0869: *
0870: * @param pane pane to get index
0871: * @return index of the pane. -1 if comp doesn't exist in this container
0872: */
0873: public int indexOfPane(Component pane) {
0874: int index = indexOf(pane);
0875: if (index == -1)
0876: return -1;
0877: else {
0878: if (index % 2 != 0)
0879: System.err
0880: .println("Warning: pane's index is odd. (index = "
0881: + index + ")");
0882: return index >> 1;
0883: }
0884: }
0885:
0886: /**
0887: * Returns the index of the component.
0888: *
0889: * @param comp component to get index
0890: * @return index of the comp. -1 if comp doesn't exist in this container
0891: */
0892: public int indexOf(Component comp) {
0893: for (int i = 0; i < getComponentCount(); i++) {
0894: if (getComponent(i).equals(comp))
0895: return i;
0896: }
0897: return -1;
0898: }
0899:
0900: /**
0901: * Returns the divider at index.
0902: *
0903: * @param index index
0904: * @return the divider at the index
0905: */
0906: public JideSplitPaneDivider getDividerAt(int index) {
0907: if (index < 0 || index * 2 + 1 >= getComponentCount())
0908: return null;
0909: return (JideSplitPaneDivider) getComponent(index * 2 + 1);
0910: }
0911:
0912: /**
0913: * Returns the component at index.
0914: *
0915: * @param index index
0916: * @return the component at the index
0917: */
0918: public Component getPaneAt(int index) {
0919: if (index < 0 || index << 1 >= getComponentCount())
0920: return null;
0921: return getComponent(index << 1);
0922: }
0923:
0924: /**
0925: * Gets the count of panes, regardless of dividers.
0926: *
0927: * @return the count of panes
0928: */
0929: public int getPaneCount() {
0930: return (getComponentCount() + 1) >> 1;
0931: }
0932:
0933: /**
0934: * Set the divider location.
0935: *
0936: * @param divider the divider
0937: * @param location new location
0938: */
0939: public void setDividerLocation(JideSplitPaneDivider divider,
0940: int location) {
0941: setDividerLocation(indexOfDivider(divider), location);
0942: }
0943:
0944: /**
0945: * Set the divider location. You can only call this method to set the divider location
0946: * when the component is rendered on the screen. If the component has never been displayed before,
0947: * this method call has no effect.
0948: *
0949: * @param dividerIndex the divider index, starting from 0 for the first divider.
0950: * @param location new location
0951: */
0952: public void setDividerLocation(int dividerIndex, int location) {
0953: int old = ((JideSplitPaneLayout) getLayout())
0954: .getDividerLocation(dividerIndex);
0955: ((JideSplitPaneLayout) getLayout()).setDividerLocation(
0956: dividerIndex, location);
0957: firePropertyChange(PROPERTY_DIVIDER_LOCATION, old, location);
0958: revalidate();
0959: }
0960:
0961: /**
0962: * Get the divider location. You can only get a valid divider location
0963: * when the component is displayed on the screen. If the component has never
0964: * been displayed on screen, -1 will be returned.
0965: *
0966: * @param dividerIndex the divider index
0967: * @return the location of the divider.
0968: */
0969: public int getDividerLocation(int dividerIndex) {
0970: return ((JideSplitPaneLayout) getLayout())
0971: .getDividerLocation(dividerIndex);
0972: }
0973:
0974: /**
0975: * Invoked when a component has been added to the container.
0976: * Basically if you add anything which is not divider, a divider will
0977: * automatically added before or after the component.
0978: *
0979: * @param e ContainerEvent
0980: */
0981: public void componentAdded(ContainerEvent e) {
0982: e.getChild().addComponentListener(this );
0983: if (!(e.getChild() instanceof JideSplitPaneDivider)) {
0984: addExtraDividers();
0985: }
0986: setDividersVisible();
0987: resetToPreferredSizes();
0988: }
0989:
0990: /**
0991: * Invoked when a component has been removed from the container.
0992: * Basically if you remove anything which is not divider, a divider will
0993: * automatically deleted before or after the component.
0994: *
0995: * @param e ContainerEvent
0996: */
0997: public void componentRemoved(ContainerEvent e) {
0998: e.getChild().removeComponentListener(this );
0999: if (!(e.getChild() instanceof JideSplitPaneDivider)) {
1000: removeExtraDividers();
1001: }
1002: setDividersVisible();
1003: resetToPreferredSizes();
1004:
1005: /*
1006: if (getComponentCount() == 1 && getComponent(0) instanceof JideSplitPane) {
1007: JideSplitPane childPane = (JideSplitPane)getComponent(0);
1008: if(getOrientation() != childPane.getOrientation()) {
1009: setOrientation(childPane.getOrientation());
1010: }
1011: // copy all children of its splitpane child to this
1012: boolean savedAutoRemove = childPane.isAutomaticallyRemove();
1013: childPane.setAutomaticallyRemove(false);
1014: for(int i = 0; i < childPane.getComponentCount(); i ++) {
1015: if(childPane.getComponent(i) instanceof JideSplitPaneDivider)
1016: continue;
1017: System.out.println("Adding " + childPane.getComponent(i));
1018: add(childPane.getComponent(i));
1019: i --;
1020: }
1021: childPane.setAutomaticallyRemove(savedAutoRemove);
1022: System.out.println("Removing " + childPane);
1023: remove(childPane);
1024: }
1025:
1026: if (isAutomaticallyRemove() && getComponentCount() == 0 && getParent() != null) {
1027: System.out.println("Automatically Removing this " + this);
1028: getParent().remove(this);
1029: return;
1030: }
1031: */
1032: }
1033:
1034: public void componentResized(ComponentEvent e) {
1035: }
1036:
1037: public void componentMoved(ComponentEvent e) {
1038: }
1039:
1040: public void componentShown(ComponentEvent e) {
1041: if (e.getComponent() instanceof JideSplitPaneDivider) {
1042: return;
1043: }
1044: setDividersVisible();
1045: resetToPreferredSizes();
1046: }
1047:
1048: public void componentHidden(ComponentEvent e) {
1049: if (e.getComponent() instanceof JideSplitPaneDivider) {
1050: return;
1051: }
1052: setDividersVisible();
1053: resetToPreferredSizes();
1054: }
1055:
1056: /**
1057: * Remove extra divider. One is considered as extra dividers where two dividers are adjacent.
1058: */
1059: protected boolean removeExtraDividers() {
1060: int extra = 0;
1061:
1062: if (getComponentCount() == 0) {
1063: if (_proportions != null)
1064: setProportions(null);
1065: return false;
1066: }
1067:
1068: boolean changed = false;
1069: // remove first divider if it's one
1070: if (getComponent(0) instanceof JideSplitPaneDivider) {
1071: remove(0);
1072: removeProportion(0);
1073: changed = true;
1074: }
1075:
1076: for (int i = 0; i < getComponentCount(); i++) {
1077: Component comp = getComponent(i);
1078: if (comp instanceof JideSplitPaneDivider) {
1079: extra++;
1080: if (extra == 2) {
1081: remove(comp);
1082: if (_proportions != null
1083: && getPaneCount() == _proportions.length)
1084: removeProportion(i / 2);
1085: changed = true;
1086: extra--;
1087: i--;
1088: }
1089: } else
1090: extra = 0;
1091: }
1092:
1093: if (extra == 1) { // remove last one if it's a divider
1094: remove(getComponentCount() - 1);
1095: removeProportion((getComponentCount() + 1) / 2);
1096: changed = true;
1097: }
1098:
1099: return changed;
1100: }
1101:
1102: /**
1103: * Removes the proportion at the given pane index, spreading its value proportionally
1104: * across the other proportions. If it's the last proportion being removed, sets the
1105: * proportions to null.
1106: */
1107: protected void removeProportion(int paneIndex) {
1108: double[] oldProportions = _proportions;
1109: if (oldProportions == null)
1110: return;
1111: if (oldProportions.length <= 1) {
1112: setProportions(null);
1113: return;
1114: }
1115: double[] newProportions = new double[oldProportions.length - 1];
1116: double p;
1117: if (paneIndex < oldProportions.length)
1118: p = oldProportions[paneIndex];
1119: else {
1120: p = 1.0;
1121: for (int i = 0; i < oldProportions.length; ++i)
1122: p -= oldProportions[i];
1123: }
1124: double total = 1.0 - p;
1125: for (int i = 0; i < newProportions.length; ++i) {
1126: int j = (i < paneIndex) ? i : i + 1;
1127: newProportions[i] = oldProportions[j] / total;
1128: }
1129: setProportions(newProportions);
1130: }
1131:
1132: /**
1133: * Add divider if there are two panes side by side without a divider in between.
1134: */
1135: protected void addExtraDividers() {
1136: int extra = 0;
1137: for (int i = 0; i < getComponentCount(); i++) {
1138: Component comp = getComponent(i);
1139: if (!(comp instanceof JideSplitPaneDivider)) {
1140: extra++;
1141: if (extra == 2) {
1142: add(createSplitPaneDivider(),
1143: JideSplitPaneLayout.FIX, i);
1144: if (_proportions != null
1145: && getPaneCount() == _proportions.length + 2)
1146: addProportion((i + 1) / 2);
1147: extra = 0;
1148: continue;
1149: }
1150: } else
1151: extra = 0;
1152: }
1153: }
1154:
1155: /**
1156: * Adds a proportion at the given pane index, taking a proportional amount from each
1157: * of the existing proportions.
1158: */
1159: protected void addProportion(int paneIndex) {
1160: double[] oldProportions = _proportions;
1161: if (oldProportions == null)
1162: return;
1163: double[] newProportions = new double[oldProportions.length + 1];
1164: double p = 1.0 / (newProportions.length + 1);
1165: double total = 1.0 - p;
1166: for (int i = 0; i < newProportions.length; ++i) {
1167: if (i == paneIndex)
1168: newProportions[i] = p;
1169: else {
1170: int j = (i < paneIndex) ? i : i - 1;
1171: if (j < oldProportions.length)
1172: newProportions[i] = oldProportions[j] * total;
1173: else
1174: newProportions[i] = p;
1175: }
1176: }
1177: setProportions(newProportions);
1178: }
1179:
1180: /**
1181: * Before this method is call, the panes must be separated by dividers.
1182: */
1183: protected void setDividersVisible() {
1184: if (getComponentCount() == 1) {
1185: setVisible(getComponent(0).isVisible());
1186: } else if (getComponentCount() > 1) {
1187: boolean anyVisible = false;
1188: boolean anyPrevVisible = false;
1189: for (int i = 0; i < getComponentCount(); i++) {
1190: Component comp = getComponent(i);
1191: if (!(comp instanceof JideSplitPaneDivider)) {
1192: if (comp.isVisible() && !anyVisible) {
1193: anyVisible = true;
1194: }
1195: continue;
1196: }
1197: boolean visiblePrev = getComponent(i - 1).isVisible();
1198: boolean visibleNext = getComponent(i + 1).isVisible();
1199: if (visiblePrev && visibleNext) {
1200: comp.setVisible(true);
1201: } else if (!visiblePrev && !visibleNext) {
1202: comp.setVisible(false);
1203: } else if (visiblePrev && !visibleNext) {
1204: comp.setVisible(false);
1205: anyPrevVisible = true;
1206: } else if (visibleNext && !visiblePrev) {
1207: if (anyPrevVisible) {
1208: comp.setVisible(true);
1209: anyPrevVisible = false;
1210: } else {
1211: comp.setVisible(false);
1212: }
1213: }
1214: }
1215:
1216: setVisible(anyVisible);
1217: }
1218: }
1219:
1220: protected JideSplitPaneDivider createSplitPaneDivider() {
1221: return new JideSplitPaneDivider(this );
1222: }
1223:
1224: /**
1225: * Get previous divider's, if any, location from current divider.
1226: * If there is no previous divider, return 0.
1227: *
1228: * @param divider
1229: * @return the location of previous divider if any
1230: */
1231: protected int getPreviousDividerLocation(
1232: JideSplitPaneDivider divider, boolean ignoreVisibility) {
1233: int index = indexOfDivider(divider);
1234: if (index <= 0)
1235: return 0;
1236: else {
1237: for (int i = index - 1; i >= 0; i--) {
1238: if (ignoreVisibility || getDividerAt(i).isVisible()) {
1239: if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
1240: return getDividerAt(i).getBounds().x
1241: + getDividerSize();
1242: } else {
1243: return getDividerAt(i).getBounds().y
1244: + getDividerSize();
1245: }
1246: }
1247: }
1248: return 0;
1249: }
1250: }
1251:
1252: /**
1253: * Get previous divider's, if any, location from current divider.
1254: * If there is no previous divider, return 0.
1255: *
1256: * @param divider
1257: * @return the location of next divider if any
1258: */
1259: protected int getNextDividerLocation(JideSplitPaneDivider divider,
1260: boolean ignoreVisibility) {
1261: int index = indexOfDivider(divider);
1262: if (((index + 1) * 2) + 1 > getComponentCount())
1263: return getOrientation() == HORIZONTAL_SPLIT ? getWidth()
1264: : getHeight();
1265: else {
1266: for (int i = index + 1; (i * 2) + 1 < getComponentCount(); i++) {
1267: if (ignoreVisibility || getDividerAt(i).isVisible()) {
1268: if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
1269: return getDividerAt(i).getBounds().x
1270: - getDividerSize();
1271: } else {
1272: return getDividerAt(i).getBounds().y
1273: - getDividerSize();
1274: }
1275: }
1276: }
1277:
1278: return getOrientation() == HORIZONTAL_SPLIT ? getWidth()
1279: : getHeight();
1280: }
1281: }
1282:
1283: /**
1284: * Checks if the gripper is visible.
1285: *
1286: * @return true if gripper is visible
1287: */
1288: public boolean isShowGripper() {
1289: return _showGripper;
1290: }
1291:
1292: /**
1293: * Sets the visibility of gripper.
1294: *
1295: * @param showGripper true to show gripper
1296: */
1297: public void setShowGripper(boolean showGripper) {
1298: boolean oldShowGripper = _showGripper;
1299: if (oldShowGripper != showGripper) {
1300: _showGripper = showGripper;
1301: firePropertyChange(GRIPPER_PROPERTY, oldShowGripper,
1302: _showGripper);
1303: }
1304: }
1305:
1306: /**
1307: * Causes this container to lay out its components. Most programs
1308: * should not call this method directly, but should invoke
1309: * the <code>validate</code> method instead.
1310: *
1311: * @see LayoutManager#layoutContainer
1312: * @see #setLayout
1313: * @see #validate
1314: * @since JDK1.1
1315: */
1316: @Override
1317: public void doLayout() {
1318: if (removeExtraDividers()) {
1319: ((JideSplitPaneLayout) getLayout()).invalidateLayout(this );
1320: }
1321: super .doLayout();
1322: }
1323:
1324: /**
1325: * Determines whether the JSplitPane is set to use a continuous layout.
1326: */
1327: public boolean isContinuousLayout() {
1328: return _continuousLayout;
1329: }
1330:
1331: /**
1332: * Turn continuous layout on/off.
1333: */
1334: public void setContinuousLayout(boolean b) {
1335: boolean oldCD = _continuousLayout;
1336:
1337: _continuousLayout = b;
1338: firePropertyChange(CONTINUOUS_LAYOUT_PROPERTY, oldCD, b);
1339: }
1340:
1341: /**
1342: * Gets the AccessibleContext associated with this JideSplitPane.
1343: * For split panes, the AccessibleContext takes the form of an
1344: * AccessibleJideSplitPane.
1345: * A new AccessibleJideSplitPane instance is created if necessary.
1346: *
1347: * @return an AccessibleJideSplitPane that serves as the
1348: * AccessibleContext of this JideSplitPane
1349: */
1350: @Override
1351: public AccessibleContext getAccessibleContext() {
1352: if (accessibleContext == null) {
1353: accessibleContext = new AccessibleJideSplitPane();
1354: }
1355: return accessibleContext;
1356: }
1357:
1358: /**
1359: * This class implements accessibility support for the
1360: * <code>JideSplitPane</code> class. It provides an implementation of the
1361: * Java Accessibility API appropriate to split pane user-interface elements.
1362: */
1363: protected class AccessibleJideSplitPane extends
1364: AccessibleJComponent {
1365: /**
1366: * Gets the state set of this object.
1367: *
1368: * @return an instance of AccessibleState containing the current state
1369: * of the object
1370: * @see javax.accessibility.AccessibleState
1371: */
1372: @Override
1373: public AccessibleStateSet getAccessibleStateSet() {
1374: AccessibleStateSet states = super .getAccessibleStateSet();
1375: if (getOrientation() == VERTICAL_SPLIT) {
1376: states.add(AccessibleState.VERTICAL);
1377: } else {
1378: states.add(AccessibleState.HORIZONTAL);
1379: }
1380: return states;
1381: }
1382:
1383: /**
1384: * Gets the role of this object.
1385: *
1386: * @return an instance of AccessibleRole describing the role of
1387: * the object
1388: * @see AccessibleRole
1389: */
1390: @Override
1391: public AccessibleRole getAccessibleRole() {
1392: return AccessibleRole.SPLIT_PANE;
1393: }
1394: } // inner class AccessibleJideSplitPane
1395:
1396: /**
1397: * @return true if the heavyweight component is enabled.
1398: */
1399: public boolean isHeavyweightComponentEnabled() {
1400: return _heavyweightComponentEnabled;
1401: }
1402:
1403: /**
1404: * Enables heavyweight component.
1405: *
1406: * @param heavyweightComponentEnabled
1407: */
1408: public void setHeavyweightComponentEnabled(
1409: boolean heavyweightComponentEnabled) {
1410: boolean old = _heavyweightComponentEnabled;
1411: if (_heavyweightComponentEnabled != heavyweightComponentEnabled) {
1412: _heavyweightComponentEnabled = heavyweightComponentEnabled;
1413: firePropertyChange(PROPERTY_HEAVYWEIGHT_COMPONENT_ENABLED,
1414: old, _heavyweightComponentEnabled);
1415: }
1416: }
1417: }
|