/* * Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of Substance Kirill Grouchnikov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pushingpixels.substance.internal.utils; import java.awt.Container; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Map; import javax.swing.ButtonModel; import javax.swing.DefaultButtonModel; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JSplitPane; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneUI; import org.pushingpixels.lafwidget.LafWidgetUtilities; import org.pushingpixels.substance.api.ColorSchemeAssociationKind; import org.pushingpixels.substance.api.ComponentState; import org.pushingpixels.substance.api.SubstanceColorScheme; import org.pushingpixels.substance.api.SubstanceLookAndFeel; import org.pushingpixels.substance.internal.animation.StateTransitionTracker; import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo; import org.pushingpixels.substance.internal.animation.TransitionAwareUI; import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils; import org.pushingpixels.substance.internal.ui.SubstanceSplitPaneUI; import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon; /** * Split pane divider in <code>Substance</code> look and feel. * * @author Kirill Grouchnikov */ public class SubstanceSplitPaneDivider extends BasicSplitPaneDivider implements TransitionAwareUI { /** * Listener for transition animations. */ private RolloverControlListener substanceRolloverListener; protected StateTransitionTracker stateTransitionTracker; /** * Listener on property change events. */ private PropertyChangeListener substancePropertyChangeListener; /** * Surrogate button model for tracking the thumb transitions. */ private ButtonModel gripModel; /** * Simple constructor. * * @param ui * Associated UI. */ public SubstanceSplitPaneDivider(SubstanceSplitPaneUI ui) { super(ui); this.setLayout(new SubstanceDividerLayout()); } @Override public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) { if (this.splitPane != null) { // fix for defect 358 - multiple listeners were installed // on the same split pane this.uninstall(); } if (newUI != null) { // installing this.splitPane = newUI.getSplitPane(); this.gripModel = new DefaultButtonModel(); this.gripModel.setArmed(false); this.gripModel.setSelected(false); this.gripModel.setPressed(false); this.gripModel.setRollover(false); this.gripModel.setEnabled(this.splitPane.isEnabled()); this.stateTransitionTracker = new StateTransitionTracker(this.splitPane, this.gripModel); // fix for defect 109 - memory leak on changing skin this.substanceRolloverListener = new RolloverControlListener(this, this.gripModel); this.addMouseListener(this.substanceRolloverListener); this.addMouseMotionListener(this.substanceRolloverListener); this.substancePropertyChangeListener = (PropertyChangeEvent evt) -> { if ("enabled".equals(evt.getPropertyName())) { boolean isEnabled = splitPane.isEnabled(); gripModel.setEnabled(isEnabled); if (leftButton != null) leftButton.setEnabled(isEnabled); if (rightButton != null) rightButton.setEnabled(isEnabled); setEnabled(isEnabled); } }; this.splitPane.addPropertyChangeListener(this.substancePropertyChangeListener); this.stateTransitionTracker.registerModelListeners(); } else { uninstall(); } super.setBasicSplitPaneUI(newUI); } /** * Uninstalls this divider. */ private void uninstall() { // uninstalling // fix for defect 109 - memory leak on changing skin this.removeMouseListener(this.substanceRolloverListener); this.removeMouseMotionListener(this.substanceRolloverListener); this.substanceRolloverListener = null; if (this.substancePropertyChangeListener != null) { // System.out.println("Unregistering " + this.hashCode() + ":" // + this.substancePropertyChangeListener.hashCode() // + " from " + this.splitPane.hashCode()); this.splitPane.removePropertyChangeListener(this.substancePropertyChangeListener); this.substancePropertyChangeListener = null; } this.stateTransitionTracker.unregisterModelListeners(); } /* * (non-Javadoc) * * @see java.awt.Component#paint(java.awt.Graphics) */ @Override public void paint(Graphics g) { if (SubstanceCoreUtilities.hasFlatAppearance(this.splitPane, true)) { BackgroundPaintingUtils.updateIfOpaque(g, this.splitPane); } Graphics2D graphics = (Graphics2D) g.create(); ModelStateInfo modelStateInfo = this.stateTransitionTracker.getModelStateInfo(); ComponentState currState = modelStateInfo.getCurrModelState(); Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo .getStateContributionMap(); float alpha = SubstanceColorSchemeUtilities.getAlpha(this.splitPane, currState); // compute the grip handle dimension int minSizeForGripPresence = SubstanceSizeUtils .getAdjustedSize(SubstanceSizeUtils.getComponentFontSize(this), 30, 1, 2, false); int maxGripSize = SubstanceSizeUtils .getAdjustedSize(SubstanceSizeUtils.getComponentFontSize(this), 40, 1, 3, false); if (this.splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { int thumbHeight = this.getHeight(); if (thumbHeight >= minSizeForGripPresence) { int gripHeight = thumbHeight / 4; if (gripHeight > maxGripSize) gripHeight = maxGripSize; int thumbWidth = this.getWidth(); int gripX = 0; int gripY = (thumbHeight - gripHeight) / 2; // draw the grip bumps for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates .entrySet()) { float contribution = activeEntry.getValue().getContribution(); if (contribution == 0.0f) continue; ComponentState activeState = activeEntry.getKey(); graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.splitPane, alpha * contribution, g)); SubstanceImageCreator.paintSplitDividerBumpImage(graphics, this, gripX, gripY, thumbWidth, gripHeight, false, SubstanceColorSchemeUtilities.getColorScheme(this, ColorSchemeAssociationKind.MARK, activeState)); } } } else { int thumbWidth = this.getWidth(); if (thumbWidth >= minSizeForGripPresence) { int gripWidth = thumbWidth / 4; if (gripWidth > maxGripSize) gripWidth = maxGripSize; int thumbHeight = this.getHeight(); // int gripHeight = thumbHeight * 2 / 3; int gripX = (thumbWidth - gripWidth) / 2; int gripY = 1; // draw the grip bumps for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates .entrySet()) { float contribution = activeEntry.getValue().getContribution(); if (contribution == 0.0f) continue; ComponentState activeState = activeEntry.getKey(); graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.splitPane, alpha * contribution, g)); SubstanceImageCreator.paintSplitDividerBumpImage(graphics, this, gripX, gripY, gripWidth, thumbHeight, true, SubstanceColorSchemeUtilities.getColorScheme(this, ColorSchemeAssociationKind.MARK, activeState)); } } } graphics.dispose(); super.paint(g); } /* * (non-Javadoc) * * @see * javax.swing.plaf.basic.BasicSplitPaneDivider#createLeftOneTouchButton() */ @Override protected JButton createLeftOneTouchButton() { JButton oneTouchButton = new JButton() { // Don't want the button to participate in focus traversable. @Override public boolean isFocusable() { return false; } }; Icon verticalSplit = new TransitionAwareIcon(oneTouchButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.NORTH, scheme); }, "substance.splitPane.left.vertical"); Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.WEST, scheme); }, "substance.splitPane.left.horizontal"); oneTouchButton.setIcon( this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit : horizontalSplit); oneTouchButton.putClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE); // fix for issue 281 - set empty border so that the arrow // icon is not cropped oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0)); oneTouchButton.setRequestFocusEnabled(false); oneTouchButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); oneTouchButton.setFocusPainted(false); oneTouchButton.setBorderPainted(false); return oneTouchButton; } /* * (non-Javadoc) * * @see * javax.swing.plaf.basic.BasicSplitPaneDivider#createRightOneTouchButton() */ @Override protected JButton createRightOneTouchButton() { JButton oneTouchButton = new JButton() { // Don't want the button to participate in focus traversable. @Override public boolean isFocusable() { return false; } }; Icon verticalSplit = new TransitionAwareIcon(oneTouchButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.SOUTH, scheme); }, "substance.splitPane.right.vertical"); Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.EAST, scheme); }, "substance.splitPane.right.horizontal"); oneTouchButton.setIcon( this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit : horizontalSplit); oneTouchButton.putClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE); // fix for issue 281 - set empty border so that the arrow // icon is not cropped oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0)); oneTouchButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); oneTouchButton.setFocusPainted(false); oneTouchButton.setBorderPainted(false); oneTouchButton.setRequestFocusEnabled(false); // b.setOpaque(false); return oneTouchButton; } /** * Updates the one-touch buttons. * * @param orientation * Split pane orientation. */ public void updateOneTouchButtons(int orientation) { if (orientation == JSplitPane.VERTICAL_SPLIT) { if (this.leftButton != null) { this.leftButton.setIcon( new TransitionAwareIcon(this.leftButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.NORTH, scheme); }, "substance.splitPane.left.vertical")); } if (this.rightButton != null) { this.rightButton.setIcon( new TransitionAwareIcon(this.rightButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.SOUTH, scheme); }, "substance.splitPane.right.vertical")); } } else { if (this.leftButton != null) { this.leftButton.setIcon( new TransitionAwareIcon(this.leftButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.WEST, scheme); }, "substance.splitPane.left.horizontal")); } if (this.rightButton != null) { this.rightButton.setIcon( new TransitionAwareIcon(this.rightButton, (SubstanceColorScheme scheme) -> { int fontSize = SubstanceSizeUtils.getComponentFontSize(splitPane); return SubstanceImageCreator.getArrowIcon( SubstanceSizeUtils.getSplitPaneArrowIconWidth(fontSize), SubstanceSizeUtils.getSplitPaneArrowIconHeight(fontSize), SubstanceSizeUtils.getArrowStrokeWidth(fontSize) / 1.5f, SwingConstants.EAST, scheme); }, "substance.splitPane.right.horizontal")); } } } /* * (non-Javadoc) * * @seeorg.pushingpixels.substance.utils.Trackable#isInside(java.awt.event. * MouseEvent) */ public boolean isInside(MouseEvent me) { // entire area is sensitive return true; } @Override public StateTransitionTracker getTransitionTracker() { return this.stateTransitionTracker; } /** * Layout manager for the split pane divider. * * @author Kirill Grouchnikov */ protected class SubstanceDividerLayout extends DividerLayout { @Override public void layoutContainer(Container c) { if (leftButton != null && rightButton != null && c == SubstanceSplitPaneDivider.this) { if (splitPane.isOneTouchExpandable()) { Insets insets = getInsets(); if (orientation == JSplitPane.VERTICAL_SPLIT) { int extraX = (insets != null) ? insets.left : 0; int blockSize = getHeight(); if (insets != null) { blockSize -= (insets.top + insets.bottom); blockSize = Math.max(blockSize, 0); } int y = (c.getSize().height - blockSize) / 2; int offset = SubstanceSizeUtils.getSplitPaneButtonOffset( SubstanceSizeUtils.getComponentFontSize(splitPane)); leftButton.setBounds(extraX + offset, y, leftButton.getPreferredSize().width * 2 / 3, blockSize); rightButton.setBounds(leftButton.getX() + leftButton.getWidth(), y, rightButton.getPreferredSize().width * 2 / 3, blockSize); } else { int extraY = (insets != null) ? insets.top : 0; int blockSize = getWidth(); if (insets != null) { blockSize -= (insets.left + insets.right); blockSize = Math.max(blockSize, 0); } int x = (c.getSize().width - blockSize) / 2; int offset = SubstanceSizeUtils.getSplitPaneButtonOffset( SubstanceSizeUtils.getComponentFontSize(splitPane)); leftButton.setBounds(x, extraY + offset, blockSize, leftButton.getPreferredSize().height * 2 / 3); rightButton.setBounds(x, leftButton.getY() + leftButton.getHeight(), blockSize, leftButton.getPreferredSize().height * 2 / 3); } } else { leftButton.setBounds(-5, -5, 1, 1); rightButton.setBounds(-5, -5, 1, 1); } } } } }