001: /*
002: * @(#)JideSplitPaneDivider.java
003: *
004: * Copyright 2002 JIDE Software Inc. All rights reserved.
005: */
006: /*
007: * @(#)JideSplitPaneDivider.java
008: *
009: * Copyright 2002 JIDE Software Inc. All rights reserved.
010: */
011:
012: package com.jidesoft.swing;
013:
014: import com.jidesoft.plaf.UIDefaultsLookup;
015: import com.jidesoft.plaf.basic.Painter;
016:
017: import javax.swing.*;
018: import javax.swing.border.Border;
019: import javax.swing.event.MouseInputAdapter;
020: import java.awt.*;
021: import java.awt.event.MouseEvent;
022: import java.beans.PropertyChangeEvent;
023: import java.beans.PropertyChangeListener;
024:
025: /**
026: * Divider used by JideSplitPane.
027: */
028: public class JideSplitPaneDivider extends JPanel implements
029: PropertyChangeListener {
030: /**
031: * Handles mouse dragging message to do the actual dragging.
032: */
033: protected DragController _dragger;
034:
035: /**
036: * Size of the divider.
037: */
038: protected int _dividerSize = UIDefaultsLookup
039: .getInt("JideSplitPane.dividerSize"); // default - SET TO 0???
040:
041: /**
042: * JideSplitPane the receiver is contained in.
043: */
044: protected JideSplitPane _jideSplitPane;
045:
046: /**
047: * Handles mouse events from both this class, and the split pane.
048: * Mouse events are handled for the splitpane since you want to be able
049: * to drag when clicking on the border of the divider, which is not
050: * drawn by the divider.
051: */
052: protected MouseHandler _mouseHandler;
053:
054: /**
055: * Orientation of the JideSplitPane.
056: */
057: protected int _orientation;
058:
059: /**
060: * Cursor used for HORIZONTAL_SPLIT splitpanes.
061: */
062: static final Cursor HORIZONTAL_CURSOR = JideCursors
063: .getPredefinedCursor(JideCursors.HSPLIT_CURSOR);
064:
065: /**
066: * Cursor used for VERTICAL_SPLIT splitpanes.
067: */
068: static final Cursor VERTICAL_CURSOR = JideCursors
069: .getPredefinedCursor(JideCursors.VSPLIT_CURSOR);
070:
071: /**
072: * Default cursor.
073: */
074: static final Cursor DEFAULT_CURSOR = Cursor.getDefaultCursor();
075:
076: private Painter _gripperPainter;
077:
078: /**
079: * Creates an instance of BasicJideSplitPaneDivider. Registers this
080: * instance for mouse events and mouse dragged events.
081: */
082: public JideSplitPaneDivider(JideSplitPane splitPane) {
083: setJideSplitPane(splitPane);
084: _orientation = _jideSplitPane.getOrientation();
085:
086: // get divider size from JideSplitPane
087: setDividerSize(splitPane.getDividerSize());
088: setDefaultResizeCursor();
089:
090: setBackground(UIDefaultsLookup
091: .getColor("JideSplitPaneDivider.background"));
092: setBorder(UIDefaultsLookup
093: .getBorder("JideSplitPaneDivider.border"));
094: _gripperPainter = (Painter) UIDefaultsLookup
095: .get("JideSplitPaneDivider.gripperPainter");
096: setOpaque(false);
097: }
098:
099: public void setDefaultResizeCursor() {
100: setCursor((_orientation == JideSplitPane.HORIZONTAL_SPLIT) ? HORIZONTAL_CURSOR
101: : VERTICAL_CURSOR);
102: }
103:
104: /**
105: * Gets the <code>JideSplitPane</code>.
106: *
107: * @return the <code>JideSplitPane</code>
108: */
109: public JideSplitPane getJideSplitPane() {
110: return _jideSplitPane;
111: }
112:
113: /**
114: * Sets the JideSplitPane that is using this divider.
115: */
116: public void setJideSplitPane(JideSplitPane newPane) {
117: uninstallListeners();
118: _jideSplitPane = newPane;
119: installListeners();
120: }
121:
122: private void installListeners() {
123: if (_jideSplitPane != null) {
124: if (_mouseHandler == null) {
125: _mouseHandler = createMouseHandler();
126: }
127: _jideSplitPane.addMouseListener(_mouseHandler);
128: _jideSplitPane.addMouseMotionListener(_mouseHandler);
129: addMouseListener(_mouseHandler);
130: addMouseMotionListener(_mouseHandler);
131: _jideSplitPane.addPropertyChangeListener(this );
132: }
133: }
134:
135: private void uninstallListeners() {
136: if (_jideSplitPane != null) {
137: _jideSplitPane.removePropertyChangeListener(this );
138: if (_mouseHandler != null) {
139: _jideSplitPane.removeMouseListener(_mouseHandler);
140: _jideSplitPane.removeMouseMotionListener(_mouseHandler);
141: removeMouseListener(_mouseHandler);
142: removeMouseMotionListener(_mouseHandler);
143: _mouseHandler = null;
144: }
145: }
146: }
147:
148: protected MouseHandler createMouseHandler() {
149: return new MouseHandler();
150: }
151:
152: /**
153: * Sets the size of the divider to <code>newSize</code>. That is
154: * the width if the splitpane is <code>HORIZONTAL_SPLIT</code>, or
155: * the height of <code>VERTICAL_SPLIT</code>.
156: */
157: public void setDividerSize(int newSize) {
158: _dividerSize = newSize;
159: }
160:
161: /**
162: * Returns the size of the divider, that is the width if the splitpane
163: * is HORIZONTAL_SPLIT, or the height of VERTICAL_SPLIT.
164: */
165: public int getDividerSize() {
166: return _dividerSize;
167: }
168:
169: /**
170: * Returns dividerSize x dividerSize
171: */
172: @Override
173: public Dimension getPreferredSize() {
174: return new Dimension(getDividerSize(), getDividerSize());
175: }
176:
177: /**
178: * Returns dividerSize x dividerSize
179: */
180: @Override
181: public Dimension getMinimumSize() {
182: return getPreferredSize();
183: }
184:
185: /**
186: * Property change event, presumably from the JideSplitPane, will message
187: * updateOrientation if necessary.
188: */
189: public void propertyChange(PropertyChangeEvent e) {
190: if (e.getSource() == _jideSplitPane) {
191: if (e.getPropertyName().equals(
192: JideSplitPane.ORIENTATION_PROPERTY)) {
193: _orientation = _jideSplitPane.getOrientation();
194: setCursor((_orientation == JideSplitPane.HORIZONTAL_SPLIT) ? HORIZONTAL_CURSOR
195: : VERTICAL_CURSOR);
196: invalidate();
197: validate();
198: }
199: }
200: }
201:
202: /**
203: * Resets the UI property to a value from the current look and feel.
204: * <code>JComponent</code> subclasses must override this method
205: * like this:
206: * <pre>
207: * public void updateUI() {
208: * setUI((SliderUI)UIManager.getUI(this);
209: * }
210: * </pre>
211: *
212: * @see #setUI
213: * @see UIManager#getLookAndFeel
214: * @see UIManager#getUI
215: */
216: @Override
217: public void updateUI() {
218: super .updateUI();
219: setBackground(UIDefaultsLookup
220: .getColor("JideSplitPaneDivider.background"));
221: setBorder(UIDefaultsLookup
222: .getBorder("JideSplitPaneDivider.border"));
223: _gripperPainter = (Painter) UIDefaultsLookup
224: .get("JideSplitPaneDivider.gripperPainter");
225: }
226:
227: /**
228: * Paints the divider.
229: */
230: @Override
231: public void paint(Graphics g) {
232: super .paint(g);
233: // Paint the border.
234: Border border = getBorder();
235:
236: Dimension size = getSize();
237:
238: if (isOpaque()) {
239: g.setColor(getBackground());
240: g.fillRect(0, 0, size.width, size.height);
241: }
242:
243: if (border != null) {
244: border.paintBorder(this , g, 0, 0, size.width, size.height);
245: }
246:
247: if (_jideSplitPane.isShowGripper()) {
248: Rectangle rect = new Rectangle(size);
249: if (_gripperPainter != null) {
250: if (rect.width > rect.height) {
251: rect.x = rect.x + rect.width / 2 - 10;
252: rect.width = 22;
253: _gripperPainter.paint(this , g, rect,
254: SwingConstants.VERTICAL, 0);
255: } else {
256: rect.y = rect.y + rect.height / 2 - 10;
257: rect.height = 22;
258: _gripperPainter.paint(this , g, rect,
259: SwingConstants.HORIZONTAL, 0);
260: }
261: } else {
262: rect.x++;
263: rect.y++;
264: JideSwingUtilities
265: .drawGrip(
266: g,
267: rect,
268: 9,
269: UIDefaultsLookup
270: .getInt("JideSplitPane.dividerSize") / 3);
271: }
272: }
273: }
274:
275: /**
276: * Message to prepare for dragging. This messages the BasicJideSplitPaneUI
277: * with startDragging.
278: */
279: protected void prepareForDragging() {
280: _jideSplitPane.startDragging(this );
281: }
282:
283: protected void dragDividerTo(int location) {
284: _jideSplitPane.dragDividerTo(this , location);
285: }
286:
287: protected void finishDraggingTo(int location) {
288: _jideSplitPane.finishDraggingTo(this , location);
289: }
290:
291: protected int getPreviousDividerLocation(boolean ignoreVisibility) {
292: return _jideSplitPane.getPreviousDividerLocation(this ,
293: ignoreVisibility);
294: }
295:
296: protected int getNextDividerLocation(boolean ignoreVisibility) {
297: return _jideSplitPane.getNextDividerLocation(this ,
298: ignoreVisibility);
299: }
300:
301: /**
302: * Gets the first component. This divider is installed between two
303: * components. The first component is usually the one on the left or on the top.
304: *
305: * @return the first component
306: */
307: public Component getFirstComponent(boolean ignoreVisibility) {
308: int index = _jideSplitPane.indexOf(this );
309: if (index - 1 >= 0) {
310: for (int i = (index - 1); i >= 0; i--) {
311: if (ignoreVisibility
312: || _jideSplitPane.getComponent(i).isVisible()) {
313: return _jideSplitPane.getComponent(i);
314: }
315: }
316: // return an invisible component in lieu of null
317: return _jideSplitPane.getComponent(index - 1);
318: } else {
319: throw new IndexOutOfBoundsException(
320: "There is no component before divider " + index);
321: }
322: }
323:
324: /**
325: * Gets the second component. This divider is installed between two
326: * components. The second component is usually the one on the right or on the bottom.
327: *
328: * @return the first component
329: */
330: public Component getSecondComponent(boolean ignoreVisibility) {
331: int index = _jideSplitPane.indexOf(this );
332:
333: if (index + 1 < _jideSplitPane.getComponentCount()) {
334: for (int i = (index + 1); i >= 0; i++) {
335: if (ignoreVisibility
336: || _jideSplitPane.getComponent(i).isVisible()) {
337: return _jideSplitPane.getComponent(i);
338: }
339: }
340: // return an invisible component in lieu of null
341: return _jideSplitPane.getComponent(index + 1);
342: } else {
343: throw new IndexOutOfBoundsException(
344: "There is no component before divider " + index);
345: }
346: }
347:
348: /**
349: * MouseHandler is responsible for converting mouse events
350: * (released, dragged...) into the appropriate DragController
351: * methods.
352: * <p/>
353: */
354: protected class MouseHandler extends MouseInputAdapter {
355: /**
356: * Starts the dragging session by creating the appropriate instance
357: * of DragController.
358: */
359: @Override
360: public void mousePressed(MouseEvent e) {
361: if ((e.getSource() == JideSplitPaneDivider.this /*||
362: e.getSource() == _jideSplitPane*/)
363: && _dragger == null && _jideSplitPane.isEnabled()) {
364: if (getFirstComponent(true) != null
365: && getSecondComponent(true) != null) {
366: if (_orientation == JideSplitPane.HORIZONTAL_SPLIT) {
367: _dragger = new DragController(e);
368: } else {
369: _dragger = new VerticalDragController(e);
370: }
371: if (!_dragger.isValid()) {
372: _dragger = null;
373: } else {
374: prepareForDragging();
375: _dragger.continueDrag(e);
376: }
377: }
378: e.consume();
379: }
380: }
381:
382: /**
383: * If dragger is not null it is messaged with completeDrag.
384: */
385: @Override
386: public void mouseReleased(MouseEvent e) {
387: if (_dragger != null) {
388: if (e.getSource() == _jideSplitPane) {
389: _dragger.completeDrag(e.getX(), e.getY());
390: } else if (e.getSource() == JideSplitPaneDivider.this ) {
391: Point ourLoc = getLocation();
392: _dragger.completeDrag(e.getX() + ourLoc.x, e.getY()
393: + ourLoc.y);
394: }
395: _dragger = null;
396: e.consume();
397: }
398: }
399:
400: //
401: // MouseMotionListener
402: //
403:
404: /**
405: * If dragger is not null it is messaged with continueDrag.
406: */
407: @Override
408: public void mouseDragged(MouseEvent e) {
409: if (_dragger != null) {
410: if (e.getSource() == _jideSplitPane) {
411: _dragger.continueDrag(e.getX(), e.getY());
412: } else if (e.getSource() == JideSplitPaneDivider.this ) {
413: Point ourLoc = getLocation();
414: _dragger.continueDrag(e.getX() + ourLoc.x, e.getY()
415: + ourLoc.y);
416: }
417: e.consume();
418: }
419: }
420: }
421:
422: /**
423: * Handles the events during a dragging session for a
424: * HORIZONTAL_SPLIT oriented split pane. This continually
425: * messages <code>dragDividerTo</code> and then when done messages
426: * <code>finishDraggingTo</code>. When an instance is created it should be
427: * messaged with <code>isValid</code> to insure that dragging can happen
428: * (dragging won't be allowed if the two views can not be resized).
429: */
430: protected class DragController {
431: /**
432: * Initial location of the divider.
433: */
434: int initialX;
435:
436: /**
437: * Maximum and minimum positions to drag to.
438: */
439: int maxX, minX;
440:
441: /**
442: * Initial location the mouse down happened at.
443: */
444: int offset;
445:
446: protected DragController(MouseEvent e) {
447: Component leftC = getFirstComponent(false);
448: Component rightC = getSecondComponent(false);
449:
450: initialX = getLocation().x;
451: if (e.getSource() == JideSplitPaneDivider.this ) {
452: offset = e.getX();
453: } else { // splitPane
454: offset = e.getX() - initialX;
455: }
456: if (leftC == null || rightC == null || offset < -1
457: || offset >= _jideSplitPane.getSize().width) {
458: // Don't allow dragging.
459: maxX = -1;
460: } else {
461: Insets insets = _jideSplitPane.getInsets();
462:
463: if (leftC.isVisible()) {
464: minX = getPreviousDividerLocation(false)
465: + leftC.getMinimumSize().width;
466: if (insets != null) {
467: minX += insets.left;
468: }
469: } else {
470: minX = getPreviousDividerLocation(true);
471: if (insets != null) {
472: minX += insets.left;
473: }
474: }
475: if (rightC.isVisible()) {
476: int right = (insets != null) ? insets.right : 0;
477: maxX = Math.max(0, getNextDividerLocation(false)
478: - (getSize().width + right)
479: - rightC.getMinimumSize().width);
480: } else {
481: int right = (insets != null) ? insets.right : 0;
482: maxX = Math.max(0, getNextDividerLocation(true)
483: - (getSize().width + right));
484: }
485: if (maxX < minX)
486: minX = maxX = 0;
487: }
488: }
489:
490: /**
491: * Returns true if the dragging session is valid.
492: */
493: protected boolean isValid() {
494: return (maxX > 0);
495: }
496:
497: /**
498: * Returns the new position to put the divider at based on
499: * the passed in MouseEvent.
500: */
501: protected int positionForMouseEvent(MouseEvent e) {
502: int newX = (e.getSource() == JideSplitPaneDivider.this ) ? (e
503: .getX() + getLocation().x)
504: : e.getX();
505:
506: newX = Math.min(maxX, Math.max(minX, newX - offset));
507: return newX;
508: }
509:
510: /**
511: * Returns the x argument, since this is used for horizontal
512: * splits.
513: */
514: protected int getNeededLocation(int x, int y) {
515: int newX;
516:
517: newX = Math.min(maxX, Math.max(minX, x - offset));
518: return newX;
519: }
520:
521: protected void continueDrag(int newX, int newY) {
522: dragDividerTo(getNeededLocation(newX, newY));
523: }
524:
525: /**
526: * Messages dragDividerTo with the new location for the mouse
527: * event.
528: */
529: protected void continueDrag(MouseEvent e) {
530: dragDividerTo(positionForMouseEvent(e));
531: }
532:
533: protected void completeDrag(int x, int y) {
534: finishDraggingTo(getNeededLocation(x, y));
535: }
536:
537: /**
538: * Messages finishDraggingTo with the new location for the mouse
539: * event.
540: */
541: protected void completeDrag(MouseEvent e) {
542: finishDraggingTo(positionForMouseEvent(e));
543: }
544: } // End of BasicJideSplitPaneDivider.DragController
545:
546: /**
547: * Handles the events during a dragging session for a
548: * VERTICAL_SPLIT oriented split pane. This continually
549: * messages <code>dragDividerTo</code> and then when done messages
550: * <code>finishDraggingTo</code>. When an instance is created it should be
551: * messaged with <code>isValid</code> to insure that dragging can happen
552: * (dragging won't be allowed if the two views can not be resized).
553: */
554: protected class VerticalDragController extends DragController {
555: /* DragControllers ivars are now in terms of y, not x. */
556: protected VerticalDragController(MouseEvent e) {
557: super (e);
558: Component leftC = getFirstComponent(false);
559: Component rightC = getSecondComponent(false);
560:
561: initialX = getLocation().y;
562: if (e.getSource() == JideSplitPaneDivider.this ) {
563: offset = e.getY();
564: } else { // splitPane
565: offset = e.getY() - initialX;
566: }
567: if (leftC == null || rightC == null || offset < -1
568: || offset >= _jideSplitPane.getSize().height) {
569: // Don't allow dragging.
570: maxX = -1;
571: } else {
572: Insets insets = _jideSplitPane.getInsets();
573:
574: if (leftC.isVisible()) {
575: minX = getPreviousDividerLocation(false)
576: + leftC.getMinimumSize().height;
577: if (insets != null) {
578: minX += insets.top;
579: }
580: } else {
581: minX = 0;
582: }
583: if (rightC.isVisible()) {
584: int right = (insets != null) ? insets.top : 0;
585: maxX = Math.max(0, getNextDividerLocation(false)
586: - (getSize().height + right)
587: - rightC.getMinimumSize().height);
588: } else {
589: int right = (insets != null) ? insets.top : 0;
590: maxX = Math.max(0, getNextDividerLocation(true)
591: - (getSize().height + right));
592: }
593: if (maxX < minX)
594: minX = maxX = 0;
595: }
596: }
597:
598: /**
599: * Returns the y argument, since this is used for vertical
600: * splits.
601: */
602: @Override
603: protected int getNeededLocation(int x, int y) {
604: int newY;
605:
606: newY = Math.min(maxX, Math.max(minX, y - offset));
607: return newY;
608: }
609:
610: /**
611: * Returns the new position to put the divider at based on
612: * the passed in MouseEvent.
613: */
614: @Override
615: protected int positionForMouseEvent(MouseEvent e) {
616: int newY = (e.getSource() == JideSplitPaneDivider.this ) ? (e
617: .getY() + getLocation().y)
618: : e.getY();
619:
620: newY = Math.min(maxX, Math.max(minX, newY - offset));
621: return newY;
622: }
623: } // End of BasicSplitPaneDividier.VerticalDragController
624:
625: }
|