File: Splitter.java

package info (click to toggle)
libswidgets-java 0.1.4-1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k, lenny, squeeze
  • size: 356 kB
  • ctags: 667
  • sloc: java: 3,436; xml: 64; makefile: 11
file content (509 lines) | stat: -rw-r--r-- 16,512 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
/*
 * Splitter.java
 */
package org.tigris.swidgets;

import java.awt.*;
import java.awt.event.*;

import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.plaf.SplitPaneUI;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;

/**
 * Acts as a seperator between components and will automatically
 * resize those components when the splitter is moved by dragging the
 * mouse across it.
 *
 * @author Bob Tarling
 *
 * TODO: Bring splitter to top when not dynamic resize
 * TODO: Add constructor and getter/setter for dynamic resize
 *
 * TODO: Implement the setLocation method, anything currently calling
 * setLocation should then call super.setLocation.
 */
public class Splitter extends JComponent {

    /**
     * The orientation for a horizontal splitter
     */
    public static final Orientation HORIZONTAL_SPLIT = Horizontal.getInstance();
    
    /**
     * The orientation for a vertical splitter
     */
    public static final Orientation VERTICAL_SPLIT = Vertical.getInstance();

    /** The side of the splitter of the component to be hidden on 
     * a quick hide action. */
    protected static final int NONE = -1;
    /** The side of the splitter to be hidden on a quick hide action: WEST */
    protected static final int WEST = 0;
    /** The side of the splitter to be hidden on a quick hide action: EAST */
    protected static final int EAST = 1;
    /** The side of the splitter to be hidden on a quick hide action: NORTH */
    protected static final int NORTH = 0;
    /** The side of the splitter to be hidden on a quick hide action: SOUTH */
    protected static final int SOUTH = 1;

    /**
     * The orientation of this splitter. Orientation does not
     * represent the shape of the splitter but rather the layout of
     * the objects being seperated by the splitter.  In other words a
     * horizontal splitter seperates components layed out in a
     * horizontal row.
     */
    private Orientation orientation;

    private int lastPosition;
    private int lastLength;
    
    /**
     * Is quick hide available and if so which component ahould it hide
     */
    private int quickHide = NONE;
    
    /**
     * True if a component has been hidden by using the quick hide
     * process of the Splitter
     */ 
    private boolean panelHidden = false;

    /**
     * True if components are resized dymically when the plitter is
     * dragged. If false then components are only resized when the
     * splitter is dropped by releasing the mouse.
     */ 
    private boolean dynamicResize = true;

    /**
     * The 2 components which the splitter is designed to seperate
     */
    private Component sideComponent[] = new Component[2];

    /**
     * The standard width of a splitter
     */
    private int splitterSize = 10;

    /**
     * The quick hide buttons
     */
    private ArrowButton buttonNorth = null;
    
    /**
     * The quick hide buttons
     */
    private ArrowButton buttonSouth = null;
    
    /**
     * Component which knows how to paint the split divider.
    **/
    private BasicSplitPaneDivider _divider = null;
    
    /**
     * Padding around the JSplitPane that is not included in the divider
    **/
    private static final int DIVIDER_PADDING = 4;
    
    /**
     * Minimum size of the splitter in pixels. Must be at least this size
     * to properly display the toggle buttons.
    **/
    private static final int MIN_SPLITTER_SIZE = 5;

    /**
     * The constructor
     *
     * @parameter orientation A Horizontal or Vertical object to
     * indicate whether this splitter is designed to seperate
     * components laid out horizontally or vertically.
     */ 
    public Splitter(Orientation orientation) {
        super();
        
        this.orientation = orientation;

        // Create a JSplitPane for the purpose of extracting the
        // divider UI and determining the splitter size.
        JSplitPane splitpane;
        if (orientation == HORIZONTAL_SPLIT) {
            splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
            splitterSize =
		Math.max(splitpane.getPreferredSize().width - DIVIDER_PADDING,
			 MIN_SPLITTER_SIZE);
        } else {
            splitpane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
            splitterSize =
		Math.max(splitpane.getPreferredSize().height - DIVIDER_PADDING,
			 MIN_SPLITTER_SIZE);
        }

        setLayout(new SerialLayout(orientation.getPerpendicular()));
        setSize(splitterSize, splitterSize);
        setPreferredSize(this.getSize());

        // Get the BasicSplitPaneDivider if the current look and feel
        // is based on the Basic look and feel. If not, the splitter
        // will still work, but the divider area will appear empty. 
        SplitPaneUI ui = splitpane.getUI();
        if (ui instanceof BasicSplitPaneUI)	{
            _divider = ((BasicSplitPaneUI) ui).createDefaultDivider();
            _divider.setSize(getSize());
        }

        setCursor(orientation.getCursor());
        
        MyMouseListener myMouseListener = new MyMouseListener();
        addMouseListener(myMouseListener);
        addMouseMotionListener(myMouseListener);       
    }

    /**
     * Register a component to be resized by this splitter.
     *
     * @parameter side the side of the splitter to place the component
     * being one of the constants NORTH, SOUTH, EAST or WEST
     *
     * @param comp the component to be resized
     */
    public void registerComponent(int side, Component comp)
    {
        this.sideComponent[side] = comp;
        setVisible(this.sideComponent[WEST] != null
		   && this.sideComponent[EAST] != null);
    }

    /**
     * Get a registered component.
     *
     * @parameter side the side of the splitter of the component to
     * return, being one of the constants NORTH, SOUTH, EAST or WEST
     * @return the registered component
     */
    public Component getRegisteredComponent(int side)
    {
        return sideComponent[side];
    }

    /**
     * Change the quick hide action. If quick hide is turned on then
     * an arrow button appears on the splitter to allow the user to
     * instantly reposition the splitter to hide one of the
     * components.
     *
     * @parameter side the side of the splitter of the component to be
     * hidden on a quick hide action. This being one of the constants
     * NORTH, SOUTH, EAST, WEST or NONE.
     */
    public void setQuickHide(int side) {
        quickHide = side;
        // Only create the arrow buttons the first time they are required.
        if (side != NONE && buttonNorth == null) {
            buttonNorth = orientation.getStartArrowButton();
            buttonSouth = orientation.getEndArrowButton();
            
            ActionListener al = new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    toggleHide();
                }
            };
            buttonNorth.addActionListener(al);
            buttonSouth.addActionListener(al);
            add(buttonNorth);
            add(buttonSouth);
        }
        showButtons();
    }

    /*
     * Show the correct button symbol. The arrow button should point
     * in the direction that the splitter will move on the button
     * press. This will be towards the component to hide or if already
     * hidden it should point away from the hidden component.
     */
    private void showButtons() {
        if (buttonNorth != null) {
            if (panelHidden) {
                buttonNorth.setVisible(quickHide == SOUTH);
                buttonSouth.setVisible(quickHide == NORTH);
            }
            else {
                buttonNorth.setVisible(quickHide == NORTH);
                buttonSouth.setVisible(quickHide == SOUTH);
            }
        }
    }

    /*
     * Hide or restore the component currently selected as the quick
     * hide component.
     */
    public void toggleHide()
    {
        if (quickHide == NONE) return;
        
        int position = 0;

        if (panelHidden) {
            position = lastPosition;
            if (quickHide == EAST) {
                position = orientation.getPosition(this) - lastLength;
            }
        }
        else if (quickHide == EAST) {
            position =
		orientation.getPosition(this)
		+ orientation.getLength(sideComponent[EAST]);
        }

        lastLength = orientation.getLength(sideComponent[quickHide]);
        lastPosition = orientation.getPosition(this);

        setLocation(orientation.setPosition(getLocation(), position));

        resizeComponents(position - lastPosition);

        panelHidden = !panelHidden;
        showButtons();
    }

    /**
     * Resizes the divider delegate when this component is resized.
     *
     * @see java.awt.Component#setSize(java.awt.Dimension)
    **/
    public void setSize(Dimension d) {
        super.setSize(d);
        if (_divider != null) {
            _divider.setSize(d);
        }
    }

    /**
     * Resizes the divider delegate when this component is resized.
     *
     * @see java.awt.Component#setSize(int, int)
    **/
    public void setSize(int width, int height) {
        super.setSize(width, height);
        if (_divider != null) {
            _divider.setSize(width, height);
        }
    }

    /**
     * Delegates painting to the UI component responsible for the split pane
     * divider.
    **/
    public void paintComponent(Graphics g) {
        if (_divider != null) {
            _divider.paint(g);
        }
    }	
	
    /*
     * Attempt to move the splitter by a given amount. It may not be
     * possible to move the splitter as far as requested because it
     * may result in one of the components having a negative size or
     * breaking it min/max size.
     *
     * @parameter movement the distance in pixels to move the splitter
     * from its current position.
     *
     * @return the actual number of pixels the splitter was moved.
     */
    private int moveSplitter(int movement) {
        if (sideComponent[WEST] != null && sideComponent[EAST] != null) {

            int restrictedMovement = 0;
            
            if (movement >= 0) {
                restrictedMovement =
		    restrictMovement(sideComponent[WEST],
				     sideComponent[EAST],
				     movement,
				     -1);
            }
            else {
                restrictedMovement =
		    restrictMovement(sideComponent[EAST],
				     sideComponent[WEST],
				     movement,
				     1);
            }

            setLocation(orientation.addToPosition(getLocation(),
						  restrictedMovement));

            return restrictedMovement;
        }
        return 0;
    }

    /**
     * Resize and reposition the components according to the movement
     * of the splitter
     *
     * @parameter movement the distance the splitter has moved.
     */
    private void resizeComponents(int movement) {
        sideComponent[NORTH]
	    .setSize(orientation.addLength(sideComponent[NORTH].getSize(),
					   movement));
        sideComponent[SOUTH]
	    .setSize(orientation.subtractLength(sideComponent[SOUTH].getSize(),
						movement));
        sideComponent[SOUTH].
	    setLocation(orientation
			.addToPosition(sideComponent[SOUTH].getLocation(),
				       movement));
        sideComponent[NORTH].validate();
        sideComponent[SOUTH].validate();
    }
    
    /**
     * calculates any restriction of movement based on the min/max values of the
     * registered components.
     *
     * @parameter growingComponent The component that is expanding as
     * the result of a splitter move.
     * @parameter shrinkingComponent The component that is shrinking
     * as the result of a splitter move.
     * @parameter movement           The number of pixels of the attempted move
     * @parameter sign The direction of the move -ve or +ve (-1 or +1)
     */
    private int restrictMovement(Component growingComponent,
				 Component shrinkingComponent,
				 int movement, int sign)
    {

        Dimension maxSize = growingComponent.getMaximumSize();
        int maxLength = orientation.getLength(maxSize);
        int currentLength = orientation.getLength(growingComponent);

        if (currentLength + movement * sign > maxLength) {
            movement = (currentLength - maxLength) * sign;
        }

        Dimension minSize = shrinkingComponent.getMinimumSize();
        int minLength = orientation.getLength(minSize);
        currentLength = orientation.getLength(shrinkingComponent);

        if (currentLength + movement * sign < minLength) {
            movement = (minLength - currentLength) * sign;
        }

        return movement;
    }

    /**
     * The mouse listener to detect mouse interaction with this splitter
     */
    private class MyMouseListener
	implements MouseMotionListener, MouseListener
    {
        /**
         * When the mouse is pressed the splitter position is recorded
         * so that the the difference in position can be calculated
         * when the mouse is released.
         */
        private int positionOfSplitterWhenPressed;
        
        /**
         * A value is recorded here when the mouse is pressed on the
         * splitter. This allows the position of the mouse on the
         * splitter to remain consistent when the splitter is moved.
         */
        private int mousePositionOnSplitterWhenPressed;

        /**
         * On a mouse release make sure that components are repositioned.
         */
        public void mouseReleased(MouseEvent me)
        {
            if (!dynamicResize) {
                moveSplitter(orientation.getPosition(me)
			     - mousePositionOnSplitterWhenPressed);
                resizeComponents(orientation.getPosition(getLocation())
				 - positionOfSplitterWhenPressed);
            }
            mousePositionOnSplitterWhenPressed = 0;
            positionOfSplitterWhenPressed = 0;
        }

        /**
         * On a mouse drag attempt to reposition splitter.
         */
        public void mouseDragged(MouseEvent me)
        {
            int mouseMovement =
		orientation.getPosition(me)
		- mousePositionOnSplitterWhenPressed;
            int restrictedMovement = moveSplitter(mouseMovement);
            if (restrictedMovement == 0) return;
            
            if (dynamicResize) {
                resizeComponents(restrictedMovement);
            }
            if (panelHidden) {
                panelHidden = false;
                showButtons();
            }
        }

        /**
         * On a mouse press record the position of the splitter and
         * the position of the mouse on the splitter.
         *
         */
        public void mousePressed(MouseEvent me)
        {
            mousePositionOnSplitterWhenPressed = orientation.getPosition(me);
            positionOfSplitterWhenPressed =
		orientation.getPosition(getLocation());
        }

        /**
         * On a double click either hide or show the component
         * selected for quick hide.
         *
         */
        public void mouseClicked(MouseEvent me)
        {
            if (me.getClickCount() == 2
		&& sideComponent[WEST] != null
		&& sideComponent[EAST] != null)
	    {
                toggleHide();
            }
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse enters splitter area
         */
        public void mouseEntered(MouseEvent me)
        {
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse leaves splitter area
         */
        public void mouseExited(MouseEvent me)
        {
        }

        /**
         * Empty method to satisy interface only, there is
         * no action when mouse moves through splitter area
         */
        public void mouseMoved(MouseEvent me)
        {
        }
    }
}