2
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Substance Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.substance.internal.utils;
33
import java.awt.event.MouseEvent;
34
import java.beans.PropertyChangeEvent;
35
import java.beans.PropertyChangeListener;
39
import javax.swing.border.EmptyBorder;
40
import javax.swing.plaf.basic.BasicSplitPaneDivider;
41
import javax.swing.plaf.basic.BasicSplitPaneUI;
43
import org.pushingpixels.lafwidget.LafWidgetUtilities;
44
import org.pushingpixels.substance.api.*;
45
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
46
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
47
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
48
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
49
import org.pushingpixels.substance.internal.ui.SubstanceSplitPaneUI;
50
import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
53
* Split pane divider in <code>Substance</code> look and feel.
55
* @author Kirill Grouchnikov
57
public class SubstanceSplitPaneDivider extends BasicSplitPaneDivider implements
60
* Listener for transition animations.
62
private RolloverControlListener substanceRolloverListener;
64
protected StateTransitionTracker stateTransitionTracker;
67
* Listener on property change events.
69
private PropertyChangeListener substancePropertyChangeListener;
71
* Surrogate button model for tracking the thumb transitions.
73
private ButtonModel gripModel;
81
public SubstanceSplitPaneDivider(SubstanceSplitPaneUI ui) {
83
this.setLayout(new SubstanceDividerLayout());
87
public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) {
88
if (this.splitPane != null) {
89
// fix for defect 358 - multiple listeners were installed
90
// on the same split pane
96
this.splitPane = newUI.getSplitPane();
98
this.gripModel = new DefaultButtonModel();
99
this.gripModel.setArmed(false);
100
this.gripModel.setSelected(false);
101
this.gripModel.setPressed(false);
102
this.gripModel.setRollover(false);
103
this.gripModel.setEnabled(this.splitPane.isEnabled());
105
this.stateTransitionTracker = new StateTransitionTracker(
106
this.splitPane, this.gripModel);
108
// fix for defect 109 - memory leak on changing skin
109
this.substanceRolloverListener = new RolloverControlListener(this,
111
this.addMouseListener(this.substanceRolloverListener);
112
this.addMouseMotionListener(this.substanceRolloverListener);
114
this.substancePropertyChangeListener = new PropertyChangeListener() {
116
public void propertyChange(PropertyChangeEvent evt) {
117
if ("enabled".equals(evt.getPropertyName())) {
118
boolean isEnabled = splitPane.isEnabled();
119
gripModel.setEnabled(isEnabled);
120
if (leftButton != null)
121
leftButton.setEnabled(isEnabled);
122
if (rightButton != null)
123
rightButton.setEnabled(isEnabled);
124
setEnabled(isEnabled);
128
// System.out.println("Registering " + this.hashCode() + ":"
129
// + this.substancePropertyChangeListener.hashCode() + " on "
130
// + this.splitPane.hashCode());
132
.addPropertyChangeListener(this.substancePropertyChangeListener);
134
this.stateTransitionTracker.registerModelListeners();
138
super.setBasicSplitPaneUI(newUI);
142
* Uninstalls this divider.
144
private void uninstall() {
146
// fix for defect 109 - memory leak on changing skin
147
this.removeMouseListener(this.substanceRolloverListener);
148
this.removeMouseMotionListener(this.substanceRolloverListener);
149
this.substanceRolloverListener = null;
151
if (this.substancePropertyChangeListener != null) {
152
// System.out.println("Unregistering " + this.hashCode() + ":"
153
// + this.substancePropertyChangeListener.hashCode()
154
// + " from " + this.splitPane.hashCode());
156
.removePropertyChangeListener(this.substancePropertyChangeListener);
157
this.substancePropertyChangeListener = null;
160
this.stateTransitionTracker.unregisterModelListeners();
166
* @see java.awt.Component#paint(java.awt.Graphics)
169
public void paint(Graphics g) {
170
if (SubstanceCoreUtilities.hasFlatAppearance(this.splitPane, true)) {
171
BackgroundPaintingUtils.updateIfOpaque(g, this.splitPane);
174
Graphics2D graphics = (Graphics2D) g.create();
176
ModelStateInfo modelStateInfo = this.stateTransitionTracker
177
.getModelStateInfo();
178
ComponentState currState = modelStateInfo.getCurrModelState();
179
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
180
.getStateContributionMap();
182
float alpha = SubstanceColorSchemeUtilities.getAlpha(this.splitPane,
185
// compute the grip handle dimension
186
int minSizeForGripPresence = SubstanceSizeUtils.getAdjustedSize(
187
SubstanceSizeUtils.getComponentFontSize(this), 30, 1, 2, false);
188
int maxGripSize = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
189
.getComponentFontSize(this), 40, 1, 3, false);
190
if (this.splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
191
int thumbHeight = this.getHeight();
192
if (thumbHeight >= minSizeForGripPresence) {
193
int gripHeight = thumbHeight / 4;
194
if (gripHeight > maxGripSize)
195
gripHeight = maxGripSize;
197
int thumbWidth = this.getWidth();
200
int gripY = (thumbHeight - gripHeight) / 2;
202
// draw the grip bumps
203
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
205
float contribution = activeEntry.getValue()
207
if (contribution == 0.0f)
210
ComponentState activeState = activeEntry.getKey();
211
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
212
this.splitPane, alpha * contribution, g));
213
SubstanceImageCreator.paintSplitDividerBumpImage(graphics,
214
this, gripX, gripY, thumbWidth, gripHeight, false,
215
SubstanceColorSchemeUtilities.getColorScheme(this,
216
ColorSchemeAssociationKind.MARK,
221
int thumbWidth = this.getWidth();
222
if (thumbWidth >= minSizeForGripPresence) {
223
int gripWidth = thumbWidth / 4;
224
if (gripWidth > maxGripSize)
225
gripWidth = maxGripSize;
227
int thumbHeight = this.getHeight();
228
// int gripHeight = thumbHeight * 2 / 3;
230
int gripX = (thumbWidth - gripWidth) / 2;
233
// draw the grip bumps
234
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
236
float contribution = activeEntry.getValue()
238
if (contribution == 0.0f)
241
ComponentState activeState = activeEntry.getKey();
242
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
243
this.splitPane, alpha * contribution, g));
244
SubstanceImageCreator.paintSplitDividerBumpImage(graphics,
245
this, gripX, gripY, gripWidth, thumbHeight, true,
246
SubstanceColorSchemeUtilities.getColorScheme(this,
247
ColorSchemeAssociationKind.MARK,
262
* javax.swing.plaf.basic.BasicSplitPaneDivider#createLeftOneTouchButton()
265
protected JButton createLeftOneTouchButton() {
266
JButton oneTouchButton = new JButton() {
267
// Don't want the button to participate in focus traversable.
269
public boolean isFocusable() {
273
Icon verticalSplit = new TransitionAwareIcon(oneTouchButton,
274
new TransitionAwareIcon.Delegate() {
276
public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
277
int fontSize = SubstanceSizeUtils
278
.getComponentFontSize(splitPane);
279
return SubstanceImageCreator.getArrowIcon(
281
.getSplitPaneArrowIconWidth(fontSize),
283
.getSplitPaneArrowIconHeight(fontSize),
285
.getArrowStrokeWidth(fontSize),
286
SwingConstants.NORTH, scheme);
288
}, "substance.splitPane.left.vertical");
289
Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton,
290
new TransitionAwareIcon.Delegate() {
292
public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
293
int fontSize = SubstanceSizeUtils
294
.getComponentFontSize(splitPane);
295
return SubstanceImageCreator.getArrowIcon(
297
.getSplitPaneArrowIconWidth(fontSize),
299
.getSplitPaneArrowIconHeight(fontSize),
301
.getArrowStrokeWidth(fontSize),
302
SwingConstants.WEST, scheme);
304
}, "substance.splitPane.left.horizontal");
306
.setIcon(this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit
309
oneTouchButton.putClientProperty(
310
SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE);
311
// fix for issue 281 - set empty border so that the arrow
312
// icon is not cropped
313
oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0));
315
oneTouchButton.setRequestFocusEnabled(false);
317
.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
318
oneTouchButton.setFocusPainted(false);
319
oneTouchButton.setBorderPainted(false);
320
return oneTouchButton;
327
* javax.swing.plaf.basic.BasicSplitPaneDivider#createRightOneTouchButton()
330
protected JButton createRightOneTouchButton() {
331
JButton oneTouchButton = new JButton() {
332
// Don't want the button to participate in focus traversable.
334
public boolean isFocusable() {
338
Icon verticalSplit = new TransitionAwareIcon(oneTouchButton,
339
new TransitionAwareIcon.Delegate() {
341
public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
342
int fontSize = SubstanceSizeUtils
343
.getComponentFontSize(splitPane);
344
return SubstanceImageCreator.getArrowIcon(
346
.getSplitPaneArrowIconWidth(fontSize),
348
.getSplitPaneArrowIconHeight(fontSize),
350
.getArrowStrokeWidth(fontSize),
351
SwingConstants.SOUTH, scheme);
353
}, "substance.splitPane.right.vertical");
354
Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton,
355
new TransitionAwareIcon.Delegate() {
357
public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
358
int fontSize = SubstanceSizeUtils
359
.getComponentFontSize(splitPane);
360
return SubstanceImageCreator.getArrowIcon(
362
.getSplitPaneArrowIconWidth(fontSize),
364
.getSplitPaneArrowIconHeight(fontSize),
366
.getArrowStrokeWidth(fontSize),
367
SwingConstants.EAST, scheme);
369
}, "substance.splitPane.right.horizontal");
371
.setIcon(this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit
374
oneTouchButton.putClientProperty(
375
SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE);
376
// fix for issue 281 - set empty border so that the arrow
377
// icon is not cropped
378
oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0));
381
.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
382
oneTouchButton.setFocusPainted(false);
383
oneTouchButton.setBorderPainted(false);
384
oneTouchButton.setRequestFocusEnabled(false);
385
// b.setOpaque(false);
386
return oneTouchButton;
390
* Updates the one-touch buttons.
393
* Split pane orientation.
395
public void updateOneTouchButtons(int orientation) {
396
if (orientation == JSplitPane.VERTICAL_SPLIT) {
397
if (this.leftButton != null) {
398
this.leftButton.setIcon(new TransitionAwareIcon(
399
this.leftButton, new TransitionAwareIcon.Delegate() {
401
public Icon getColorSchemeIcon(
402
SubstanceColorScheme scheme) {
403
int fontSize = SubstanceSizeUtils
404
.getComponentFontSize(splitPane);
405
return SubstanceImageCreator
408
.getSplitPaneArrowIconWidth(fontSize),
410
.getSplitPaneArrowIconHeight(fontSize),
412
.getArrowStrokeWidth(fontSize),
413
SwingConstants.NORTH, scheme);
415
}, "substance.splitPane.left.vertical"));
417
if (this.rightButton != null) {
418
this.rightButton.setIcon(new TransitionAwareIcon(
419
this.rightButton, new TransitionAwareIcon.Delegate() {
421
public Icon getColorSchemeIcon(
422
SubstanceColorScheme scheme) {
423
int fontSize = SubstanceSizeUtils
424
.getComponentFontSize(splitPane);
425
return SubstanceImageCreator
428
.getSplitPaneArrowIconWidth(fontSize),
430
.getSplitPaneArrowIconHeight(fontSize),
432
.getArrowStrokeWidth(fontSize),
433
SwingConstants.SOUTH, scheme);
435
}, "substance.splitPane.right.vertical"));
438
if (this.leftButton != null) {
439
this.leftButton.setIcon(new TransitionAwareIcon(
440
this.leftButton, new TransitionAwareIcon.Delegate() {
442
public Icon getColorSchemeIcon(
443
SubstanceColorScheme scheme) {
444
int fontSize = SubstanceSizeUtils
445
.getComponentFontSize(splitPane);
446
return SubstanceImageCreator
449
.getSplitPaneArrowIconWidth(fontSize),
451
.getSplitPaneArrowIconHeight(fontSize),
453
.getArrowStrokeWidth(fontSize),
454
SwingConstants.WEST, scheme);
456
}, "substance.splitPane.left.horizontal"));
458
if (this.rightButton != null) {
459
this.rightButton.setIcon(new TransitionAwareIcon(
460
this.rightButton, new TransitionAwareIcon.Delegate() {
462
public Icon getColorSchemeIcon(
463
SubstanceColorScheme scheme) {
464
int fontSize = SubstanceSizeUtils
465
.getComponentFontSize(splitPane);
466
return SubstanceImageCreator
469
.getSplitPaneArrowIconWidth(fontSize),
471
.getSplitPaneArrowIconHeight(fontSize),
473
.getArrowStrokeWidth(fontSize),
474
SwingConstants.EAST, scheme);
476
}, "substance.splitPane.right.horizontal"));
484
* @seeorg.pushingpixels.substance.utils.Trackable#isInside(java.awt.event.
488
public boolean isInside(MouseEvent me) {
489
// entire area is sensitive
494
public StateTransitionTracker getTransitionTracker() {
495
return this.stateTransitionTracker;
499
* Layout manager for the split pane divider.
501
* @author Kirill Grouchnikov
503
protected class SubstanceDividerLayout extends DividerLayout {
505
public void layoutContainer(Container c) {
506
if (leftButton != null && rightButton != null
507
&& c == SubstanceSplitPaneDivider.this) {
508
if (splitPane.isOneTouchExpandable()) {
509
Insets insets = getInsets();
511
if (orientation == JSplitPane.VERTICAL_SPLIT) {
512
int extraX = (insets != null) ? insets.left : 0;
513
int blockSize = getHeight();
515
if (insets != null) {
516
blockSize -= (insets.top + insets.bottom);
517
blockSize = Math.max(blockSize, 0);
520
int y = (c.getSize().height - blockSize) / 2;
522
int offset = SubstanceSizeUtils
523
.getSplitPaneButtonOffset(SubstanceSizeUtils
524
.getComponentFontSize(splitPane));
525
leftButton.setBounds(extraX + offset, y, leftButton
526
.getPreferredSize().width * 2 / 3, blockSize);
527
rightButton.setBounds(leftButton.getX()
528
+ leftButton.getWidth(), y, rightButton
529
.getPreferredSize().width * 2 / 3, blockSize);
531
int extraY = (insets != null) ? insets.top : 0;
532
int blockSize = getWidth();
534
if (insets != null) {
535
blockSize -= (insets.left + insets.right);
536
blockSize = Math.max(blockSize, 0);
539
int x = (c.getSize().width - blockSize) / 2;
541
int offset = SubstanceSizeUtils
542
.getSplitPaneButtonOffset(SubstanceSizeUtils
543
.getComponentFontSize(splitPane));
544
leftButton.setBounds(x, extraY + offset, blockSize,
545
leftButton.getPreferredSize().height * 2 / 3);
546
rightButton.setBounds(x, leftButton.getY()
547
+ leftButton.getHeight(), blockSize, leftButton
548
.getPreferredSize().height * 2 / 3);
551
leftButton.setBounds(-5, -5, 1, 1);
552
rightButton.setBounds(-5, -5, 1, 1);