/* * This file is part of WebLookAndFeel library. * * WebLookAndFeel library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebLookAndFeel library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebLookAndFeel library. If not, see . */ package com.alee.laf.checkbox; import com.alee.extended.checkbox.CheckState; import com.alee.global.StyleConstants; import com.alee.laf.WebLookAndFeel; import com.alee.laf.list.WebListElement; import com.alee.utils.*; import com.alee.utils.laf.ShapeProvider; import com.alee.utils.swing.WebTimer; import javax.swing.*; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicCheckBoxUI; import javax.swing.tree.TreeCellRenderer; import java.awt.*; import java.awt.event.*; import java.awt.geom.RoundRectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Map; /** * Custom UI for JCheckBox component. * * @author Mikle Garin */ public class WebCheckBoxUI extends BasicCheckBoxUI implements ShapeProvider { /** * Maximum border/background darkness. */ public static final int MAX_DARKNESS = 5; /** * Animation updates delay in milliseconds. */ public static final long UPDATE_DELAY = 40L; /** * Background gradient fractions. */ protected static final float[] fractions = { 0f, 1f }; /** * Style settings. */ protected Color borderColor = WebCheckBoxStyle.borderColor; protected Color darkBorderColor = WebCheckBoxStyle.darkBorderColor; protected Color disabledBorderColor = WebCheckBoxStyle.disabledBorderColor; protected Color topBgColor = WebCheckBoxStyle.topBgColor; protected Color bottomBgColor = WebCheckBoxStyle.bottomBgColor; protected Color topSelectedBgColor = WebCheckBoxStyle.topSelectedBgColor; protected Color bottomSelectedBgColor = WebCheckBoxStyle.bottomSelectedBgColor; protected int round = WebCheckBoxStyle.round; protected int shadeWidth = WebCheckBoxStyle.shadeWidth; protected Insets margin = WebCheckBoxStyle.margin; protected boolean animated = WebCheckBoxStyle.animated; protected boolean rolloverDarkBorderOnly = WebCheckBoxStyle.rolloverDarkBorderOnly; protected Stroke borderStroke = WebCheckBoxStyle.borderStroke; protected int iconWidth = WebCheckBoxStyle.iconWidth; protected int iconHeight = WebCheckBoxStyle.iconHeight; /** * Icon background painting variables. */ protected int bgDarkness = 0; protected boolean rollover; protected WebTimer bgTimer; /** * Check icon painting variables. */ protected CheckIcon checkIcon; protected boolean checking; protected WebTimer checkTimer; /** * Last displayed icon rect. */ protected Rectangle iconRect; /** * Checkbox to which this UI is applied. */ protected JCheckBox checkBox = null; /** * Checkbox listeners. */ protected PropertyChangeListener enabledStateListener; protected PropertyChangeListener modelChangeListener; protected MouseAdapter mouseAdapter; protected ItemListener itemListener; /** * Returns an instance of the WebCheckBoxUI for the specified component. * This tricky method is used by UIManager to create component UIs when needed. * * @param c component that will use UI instance * @return instance of the WebCheckBoxUI */ @SuppressWarnings ("UnusedParameters") public static ComponentUI createUI ( final JComponent c ) { return new WebCheckBoxUI (); } /** * Installs UI in the specified component. * * @param c component for this UI */ @Override public void installUI ( final JComponent c ) { super.installUI ( c ); // Saving checkbox to local variable checkBox = ( JCheckBox ) c; // Initial check state checkIcon = createCheckStateIcon (); checkIcon.setEnabled ( checkBox.isEnabled () ); checkIcon.setState ( checkBox.isSelected () ? CheckState.checked : CheckState.unchecked ); // Default settings SwingUtils.setOrientation ( checkBox ); LookAndFeel.installProperty ( checkBox, WebLookAndFeel.OPAQUE_PROPERTY, Boolean.FALSE ); checkBox.setIcon ( createIcon () ); setAnimated ( isAnimatedByDefault () ); updateBorder (); // Checkbox state change listeners installEnabledStateListeners (); installRolloverListeners (); installStateChangeListeners (); } /** * Uninstalls UI from the specified component. * * @param c component with this UI */ @Override public void uninstallUI ( final JComponent c ) { uninstallEnabledStateListeners (); uninstallRolloverListeners (); uninstallStateChangeListeners (); checkBox.setIcon ( null ); checkIcon = null; checkBox = null; super.uninstallUI ( c ); } /** * Creates check state icon. * * @return check state icon */ protected CheckIcon createCheckStateIcon () { return new SimpleCheckIcon (); } /** * Installs enabled state listeners. */ protected void installEnabledStateListeners () { enabledStateListener = new PropertyChangeListener () { @Override public void propertyChange ( final PropertyChangeEvent evt ) { checkIcon.setEnabled ( checkBox.isEnabled () ); } }; checkBox.addPropertyChangeListener ( WebLookAndFeel.ENABLED_PROPERTY, enabledStateListener ); } /** * Uninstalls enabled state listeners. */ protected void uninstallEnabledStateListeners () { checkBox.removePropertyChangeListener ( WebLookAndFeel.ENABLED_PROPERTY, enabledStateListener ); } /** * Installs rollover listeners. */ protected void installRolloverListeners () { // Background fade animation bgTimer = new WebTimer ( "WebCheckBoxUI.bgUpdater", UPDATE_DELAY, new ActionListener () { @Override public void actionPerformed ( final ActionEvent e ) { if ( rollover && bgDarkness < MAX_DARKNESS ) { bgDarkness++; checkBox.repaint (); } else if ( !rollover && bgDarkness > 0 ) { bgDarkness--; checkBox.repaint (); } else { bgTimer.stop (); } } } ); mouseAdapter = new MouseAdapter () { @Override public void mouseEntered ( final MouseEvent e ) { rollover = true; if ( isAnimated () && checkBox.isEnabled () ) { bgTimer.start (); } else { bgDarkness = MAX_DARKNESS; checkBox.repaint (); } } @Override public void mouseExited ( final MouseEvent e ) { rollover = false; if ( isAnimated () && checkBox.isEnabled () ) { bgTimer.start (); } else { bgDarkness = 0; checkBox.repaint (); } } }; checkBox.addMouseListener ( mouseAdapter ); } /** * Uninstalls rollover listeners. */ protected void uninstallRolloverListeners () { checkBox.removeMouseListener ( mouseAdapter ); } /** * Installs state change listeners. */ protected void installStateChangeListeners () { // Selection changes animation checkTimer = new WebTimer ( "WebCheckBoxUI.iconUpdater", UPDATE_DELAY, new ActionListener () { @Override public void actionPerformed ( final ActionEvent e ) { if ( checkIcon.isTransitionCompleted () ) { checkIcon.finishTransition (); checkTimer.stop (); } else { checkIcon.doStep (); checkBox.repaint (); } } } ); itemListener = new ItemListener () { @Override public void itemStateChanged ( final ItemEvent e ) { performStateChanged (); } }; checkBox.addItemListener ( itemListener ); // Proper state update on model changes modelChangeListener = new PropertyChangeListener () { @Override public void propertyChange ( final PropertyChangeEvent e ) { performStateChanged (); } }; checkBox.addPropertyChangeListener ( WebLookAndFeel.MODEL_PROPERTY, modelChangeListener ); } /** * Performs visual state change. * In case animation is possible and enabled state change will be animated. */ protected void performStateChanged () { if ( isAnimationAllowed () && isAnimated () && checkBox.isEnabled () ) { checkIcon.setNextState ( checkBox.isSelected () ? CheckState.checked : CheckState.unchecked ); checkTimer.start (); } else { checkTimer.stop (); checkIcon.setState ( checkBox.isSelected () ? CheckState.checked : CheckState.unchecked ); checkBox.repaint (); } } /** * Uninstalls state change listeners. */ protected void uninstallStateChangeListeners () { checkBox.removeItemListener ( itemListener ); checkBox.removePropertyChangeListener ( modelChangeListener ); } /** * {@inheritDoc} */ @Override public Shape provideShape () { return LafUtils.getWebBorderShape ( checkBox, getShadeWidth (), getRound () ); } /** * Returns whether checkbox should be animated by default or not. * * @return true if checkbox should be animated by default, false otherwise */ protected boolean isAnimatedByDefault () { // Workaround for Jide tristate checkbox return WebCheckBoxStyle.animated && !ReflectUtils.containsInClassOrSuperclassName ( checkBox.getClass (), "com.jidesoft.swing.TristateCheckBox" ); } /** * Returns whether checkbox can be animated or not. * * @return true if checkbox can be animated, false otherwise */ protected boolean isAnimationAllowed () { final Container parent = checkBox.getParent (); if ( parent == null ) { return true; } else { // Workaround for Jide checkbox list and tree final Class parentClass = parent.getClass (); return !ReflectUtils.containsInClassOrSuperclassName ( parentClass, "com.jidesoft.swing.CheckBoxList" ) && !ReflectUtils.containsInClassOrSuperclassName ( parentClass, "com.jidesoft.swing.CheckBoxTreeCellRenderer" ); } } /** * Updates custom UI border. */ protected void updateBorder () { // Preserve old borders if ( SwingUtils.isPreserveBorders ( checkBox ) ) { return; } // Actual margin final boolean ltr = checkBox.getComponentOrientation ().isLeftToRight (); final Insets m = new Insets ( margin.top, ltr ? margin.left : margin.right, margin.bottom, ltr ? margin.right : margin.left ); // Installing border checkBox.setBorder ( LafUtils.createWebBorder ( m ) ); } /** * Returns component margin. * * @return component margin */ public Insets getMargin () { return margin; } /** * Sets component margin. * * @param margin component margin */ public void setMargin ( final Insets margin ) { this.margin = margin; updateBorder (); } /** * Returns whether checkbox is animated or not. * * @return true if checkbox is animated, false otherwise */ public boolean isAnimated () { return animated && ( checkBox == null || checkBox.getParent () == null || !( checkBox.getParent () instanceof WebListElement || checkBox.getParent () instanceof TreeCellRenderer ) ); } /** * Sets whether checkbox is animated or not. * * @param animated whether checkbox is animated or not */ public void setAnimated ( final boolean animated ) { this.animated = animated; } /** * Returns whether should draw dark border only on rollover or not. * * @return true if should draw dark border only on rollover, false otherwise */ public boolean isRolloverDarkBorderOnly () { return rolloverDarkBorderOnly; } /** * Sets whether should draw dark border only on rollover or not. * * @param rolloverDarkBorderOnly whether should draw dark border only on rollover or not */ public void setRolloverDarkBorderOnly ( final boolean rolloverDarkBorderOnly ) { this.rolloverDarkBorderOnly = rolloverDarkBorderOnly; } /** * Returns border color. * * @return border color */ public Color getBorderColor () { return borderColor; } /** * Sets border color. * * @param borderColor border color */ public void setBorderColor ( final Color borderColor ) { this.borderColor = borderColor; } /** * Returns dark border color. * * @return dark border color */ public Color getDarkBorderColor () { return darkBorderColor; } /** * Sets dark border color. * * @param darkBorderColor dark border color */ public void setDarkBorderColor ( final Color darkBorderColor ) { this.darkBorderColor = darkBorderColor; } /** * Returns disabled border color. * * @return disabled border color */ public Color getDisabledBorderColor () { return disabledBorderColor; } /** * Sets disabled border color. * * @param disabledBorderColor disabled border color */ public void setDisabledBorderColor ( final Color disabledBorderColor ) { this.disabledBorderColor = disabledBorderColor; } /** * Returns top background color. * * @return top background color */ public Color getTopBgColor () { return topBgColor; } /** * Sets top background color. * * @param topBgColor top background color */ public void setTopBgColor ( final Color topBgColor ) { this.topBgColor = topBgColor; } /** * Returns bottom background color. * * @return bottom background color */ public Color getBottomBgColor () { return bottomBgColor; } /** * Sets bottom background color. * * @param bottomBgColor bottom background color */ public void setBottomBgColor ( final Color bottomBgColor ) { this.bottomBgColor = bottomBgColor; } /** * Returns top selected background color. * * @return top selected background color */ public Color getTopSelectedBgColor () { return topSelectedBgColor; } /** * Sets top selected background color. * * @param topSelectedBgColor top selected background color */ public void setTopSelectedBgColor ( final Color topSelectedBgColor ) { this.topSelectedBgColor = topSelectedBgColor; } /** * Returns bottom selected background color. * * @return bottom selected background color */ public Color getBottomSelectedBgColor () { return bottomSelectedBgColor; } /** * Sets bottom selected background color. * * @param bottomSelectedBgColor bottom selected background color */ public void setBottomSelectedBgColor ( final Color bottomSelectedBgColor ) { this.bottomSelectedBgColor = bottomSelectedBgColor; } /** * Returns border rounding. * * @return border rounding */ public int getRound () { return round; } /** * Sets border rounding. * * @param round border rounding */ public void setRound ( final int round ) { this.round = round; } /** * Returns border shade width. * * @return border shade width */ public int getShadeWidth () { return shadeWidth; } /** * Sets border shade width. * * @param shadeWidth border shade width */ public void setShadeWidth ( final int shadeWidth ) { this.shadeWidth = shadeWidth; } /** * Returns icon width. * * @return icon width */ public int getIconWidth () { return iconWidth; } /** * Sets icon width. * * @param iconWidth icon width */ public void setIconWidth ( final int iconWidth ) { this.iconWidth = iconWidth; } /** * Returns icon height. * * @return icon height */ public int getIconHeight () { return iconHeight; } /** * Sets icon height. * * @param iconHeight icon height */ public void setIconHeight ( final int iconHeight ) { this.iconHeight = iconHeight; } /** * Creates and returns checkbox icon. * * @return checkbox icon */ protected Icon createIcon () { return new CheckBoxIcon (); } /** * Returns background colors. * * @return background colors */ protected Color[] getBgColors () { if ( checkBox.isEnabled () ) { final float darkness = getBgDarkness (); if ( darkness < 1f ) { return new Color[]{ ColorUtils.getIntermediateColor ( topBgColor, topSelectedBgColor, darkness ), ColorUtils.getIntermediateColor ( bottomBgColor, bottomSelectedBgColor, darkness ) }; } else { return new Color[]{ topSelectedBgColor, bottomSelectedBgColor }; } } else { return new Color[]{ topBgColor, bottomBgColor }; } } /** * Returns background darkness. * * @return background darkness */ protected float getBgDarkness () { return ( float ) bgDarkness / MAX_DARKNESS; } /** * Returns icon bounds. * * @return icon bounds */ public Rectangle getIconRect () { return iconRect != null ? new Rectangle ( iconRect ) : new Rectangle (); } /** * Paints checkbox. * * @param g graphics context * @param c painted component */ @Override public synchronized void paint ( final Graphics g, final JComponent c ) { final Map hints = SwingUtils.setupTextAntialias ( g ); super.paint ( g, c ); SwingUtils.restoreTextAntialias ( g, hints ); } /** * Paints checkbox icon. * * @param g2d 2D graphics context * @param x icon location X coordinate * @param y icon location Y coordinate */ protected void paintIcon ( final Graphics2D g2d, final int x, final int y ) { // Icon background paintIconBackground ( g2d, x, y ); // Check icon paintCheckIcon ( g2d, x, y ); } /** * Paints checkbox icon background. * * @param g2d 2D graphics context * @param x icon location X coordinate * @param y icon location Y coordinate */ protected void paintIconBackground ( final Graphics2D g2d, final int x, final int y ) { final boolean enabled = checkBox.isEnabled (); // Button size and shape final Rectangle iconRect = new Rectangle ( x + shadeWidth, y + shadeWidth, iconWidth - shadeWidth * 2 - 1, iconHeight - shadeWidth * 2 - 1 ); final RoundRectangle2D shape = new RoundRectangle2D.Double ( iconRect.x, iconRect.y, iconRect.width, iconRect.height, round * 2, round * 2 ); // Shade if ( enabled ) { final Color shadeColor = checkBox.isFocusOwner () ? StyleConstants.fieldFocusColor : StyleConstants.shadeColor; GraphicsUtils.drawShade ( g2d, shape, shadeColor, shadeWidth ); } // Background final int radius = Math.round ( ( float ) Math.sqrt ( iconRect.width * iconRect.width / 2 ) ); final int cx = iconRect.x + iconRect.width / 2; final int cy = iconRect.y + iconRect.height / 2; g2d.setPaint ( new RadialGradientPaint ( cx, cy, radius, fractions, getBgColors () ) ); g2d.fill ( shape ); // Border final Stroke os = GraphicsUtils.setupStroke ( g2d, borderStroke ); g2d.setPaint ( enabled ? ( rolloverDarkBorderOnly ? ColorUtils.getIntermediateColor ( borderColor, darkBorderColor, getBgDarkness () ) : darkBorderColor ) : disabledBorderColor ); g2d.draw ( shape ); GraphicsUtils.restoreStroke ( g2d, os ); } /** * Paints checkbox check icon. * * @param g2d 2D graphics context * @param x icon location X coordinate * @param y icon location Y coordinate */ protected void paintCheckIcon ( final Graphics2D g2d, final int x, final int y ) { checkIcon.paintIcon ( checkBox, g2d, x, y, iconWidth, iconHeight ); } /** * Paints checkbox text. * * @param g graphics context * @param b abstract button * @param textRect text bounds * @param text text to be painted */ @Override protected void paintText ( final Graphics g, final AbstractButton b, final Rectangle textRect, final String text ) { final ButtonModel model = b.getModel (); final FontMetrics fm = SwingUtils.getFontMetrics ( b, g ); final int mnemonicIndex = b.getDisplayedMnemonicIndex (); // Drawing text if ( model.isEnabled () ) { // Normal text g.setColor ( b.getForeground () ); SwingUtils.drawStringUnderlineCharAt ( g, text, mnemonicIndex, textRect.x + getTextShiftOffset (), textRect.y + fm.getAscent () + getTextShiftOffset () ); } else { // Disabled text g.setColor ( b.getBackground ().brighter () ); SwingUtils.drawStringUnderlineCharAt ( g, text, mnemonicIndex, textRect.x, textRect.y + fm.getAscent () ); g.setColor ( b.getBackground ().darker () ); SwingUtils.drawStringUnderlineCharAt ( g, text, mnemonicIndex, textRect.x - 1, textRect.y + fm.getAscent () - 1 ); } } /** * Custom icon for tristate checkbox. */ protected class CheckBoxIcon implements Icon { /** * {@inheritDoc} */ @Override public void paintIcon ( final Component c, final Graphics g, final int x, final int y ) { // Updating actual icon rect iconRect = new Rectangle ( x, y, iconWidth, iconHeight ); // Painting checkbox icon final Graphics2D g2d = ( Graphics2D ) g; final Object aa = GraphicsUtils.setupAntialias ( g2d ); WebCheckBoxUI.this.paintIcon ( g2d, x, y ); GraphicsUtils.restoreAntialias ( g2d, aa ); } /** * {@inheritDoc} */ @Override public int getIconWidth () { return WebCheckBoxUI.this.getIconWidth (); } /** * {@inheritDoc} */ @Override public int getIconHeight () { return WebCheckBoxUI.this.getIconHeight (); } } }