/*
* 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.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JScrollBar;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.IconUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.text.JTextComponent;
import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.contrib.intellij.JBHiDPIScaledImage;
import org.pushingpixels.lafwidget.contrib.intellij.UIUtil;
import org.pushingpixels.lafwidget.icon.HiDpiAwareIcon;
import org.pushingpixels.lafwidget.icon.HiDpiAwareIconUiResource;
import org.pushingpixels.lafwidget.icon.IsHiDpiAware;
import org.pushingpixels.lafwidget.utils.TrackableThread;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceConstants.FocusKind;
import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
import org.pushingpixels.substance.api.SubstanceConstants.Side;
import org.pushingpixels.substance.api.SubstanceConstants.TabContentPaneBorderKind;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.SubstanceSkin;
import org.pushingpixels.substance.api.UiThreadingViolationException;
import org.pushingpixels.substance.api.colorscheme.BottleGreenColorScheme;
import org.pushingpixels.substance.api.colorscheme.LightAquaColorScheme;
import org.pushingpixels.substance.api.colorscheme.SunfireRedColorScheme;
import org.pushingpixels.substance.api.colorscheme.SunsetColorScheme;
import org.pushingpixels.substance.api.combo.ComboPopupPrototypeCallback;
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
import org.pushingpixels.substance.api.painter.decoration.SubstanceDecorationPainter;
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
import org.pushingpixels.substance.api.tabbed.TabCloseCallback;
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
import org.pushingpixels.substance.internal.ui.SubstanceButtonUI;
import org.pushingpixels.substance.internal.ui.SubstanceInternalFrameUI;
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
import org.pushingpixels.substance.internal.utils.combo.SubstanceComboPopup;
import org.pushingpixels.substance.internal.utils.icon.ArrowButtonTransitionAwareIcon;
import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
import org.pushingpixels.trident.swing.SwingRepaintCallback;
/**
* Various utility functions. This class is <b>for internal use only</b>.
*
* @author Kirill Grouchnikov
* @author Romain Guy
*/
public class SubstanceCoreUtilities {
/**
* Client property name for marking components covered by lightweight
* popups. This is tracking the fix for issue 297. The client property value
* should be an instance of {@link Boolean}.
*/
public static final String IS_COVERED_BY_LIGHTWEIGHT_POPUPS = "substancelaf.internal.paint.isCoveredByLightweightPopups";
public static final String TEXT_COMPONENT_AWARE = "substancelaf.internal.textComponentAware";
public static interface TextComponentAware<T> {
public JTextComponent getTextComponent(T t);
}
/**
* Private constructor. Is here to enforce using static methods only.
*/
private SubstanceCoreUtilities() {
}
/**
* Clips string based on specified font metrics and available width (in
* pixels). Returns the clipped string, which contains the beginning and the
* end of the input string separated by ellipses (...) in case the string is
* too long to fit into the specified width, and the origianl string
* otherwise.
*
* @param metrics
* Font metrics.
* @param availableWidth
* Available width in pixels.
* @param fullText
* String to clip.
* @return The clipped string, which contains the beginning and the end of
* the input string separated by ellipses (...) in case the string
* is too long to fit into the specified width, and the origianl
* string otherwise.
*/
public static String clipString(FontMetrics metrics, int availableWidth,
String fullText) {
if (metrics.stringWidth(fullText) <= availableWidth)
return fullText;
String ellipses = "...";
int ellipsesWidth = metrics.stringWidth(ellipses);
if (ellipsesWidth > availableWidth)
return "";
String starter = "";
String ender = "";
int w = fullText.length();
int w2 = (w / 2) + (w % 2);
String prevTitle = "";
for (int i = 0; i < w2; i++) {
String newStarter = starter + fullText.charAt(i);
String newEnder = ender;
if ((w - i) > w2)
newEnder = fullText.charAt(w - i - 1) + newEnder;
String newTitle = newStarter + ellipses + newEnder;
if (metrics.stringWidth(newTitle) <= availableWidth) {
starter = newStarter;
ender = newEnder;
prevTitle = newTitle;
continue;
}
return prevTitle;
}
return fullText;
}
/**
* Checks whether the specified button has associated icon.
*
* @param button
* Button.
* @return If the button has associated icon, <code>true</code> is returned,
* otherwise <code>false</code>.
*/
public static boolean hasIcon(AbstractButton button) {
return (button.getIcon() != null);
}
/**
* Checks whether the specified button has associated text.
*
* @param button
* Button.
* @return If the button has associated text, <code>true</code> is returned,
* otherwise <code>false</code>.
*/
public static boolean hasText(AbstractButton button) {
String text = button.getText();
if ((text != null) && (text.length() > 0)) {
return true;
}
return false;
}
/**
* Checks and answers if the specified button is in a combo box.
*
* @param button
* the button to check
* @return <code>true</code> if in combo box, <code>false</code> otherwise
*/
public static boolean isComboBoxButton(AbstractButton button) {
Container parent = button.getParent();
return (parent != null)
&& ((parent instanceof JComboBox) || (parent.getParent() instanceof JComboBox));
}
/**
* Checks and answers if the specified button is in a scroll bar.
*
* @param button
* the button to check
* @return <code>true</code> if in scroll bar, <code>false</code> otherwise
*/
public static boolean isScrollBarButton(AbstractButton button) {
Container parent = button.getParent();
return (parent != null)
&& ((parent instanceof JScrollBar) || (parent.getParent() instanceof JScrollBar));
}
/**
* Checks and answers if the specified button is in a spinner.
*
* @param button
* the button to check
* @return <code>true</code> if in spinner, <code>false</code> otherwise
*/
public static boolean isSpinnerButton(AbstractButton button) {
Container parent = button.getParent();
if (!(button instanceof SubstanceSpinnerButton))
return false;
return (parent != null)
&& ((parent instanceof JSpinner) || (parent.getParent() instanceof JSpinner));
}
/**
* Checks and answers if the specified button is in a toolbar.
*
* @param component
* the button to check
* @return <code>true</code> if in toolbar, <code>false</code> otherwise
*/
public static boolean isToolBarButton(JComponent component) {
if (component instanceof SubstanceDropDownButton)
return false;
if (component instanceof SubstanceSpinnerButton)
return false;
Container parent = component.getParent();
return (parent != null)
&& ((parent instanceof JToolBar) || (parent.getParent() instanceof JToolBar));
}
/**
* Checks answers if the specified component is a button in a scroll
* control, such as scroll bar or tabbed pane (as tab scroller).
*
* @param comp
* The component to check
* @return <code>true</code> if the specified component is a button in a
* scroll control, <code>false</code> otherwise
*/
public static boolean isScrollButton(JComponent comp) {
return (comp instanceof SubstanceScrollButton);
}
/**
* Checks whether the specified button never paints its background.
*
* @param button
* Button.
* @return <code>true</code> if the specified button never paints its
* background, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#BUTTON_PAINT_NEVER_PROPERTY
*/
public static boolean isButtonNeverPainted(JComponent button) {
// small optimizations for checkboxes and radio buttons
if (button instanceof JCheckBox)
return false;
if (button instanceof JRadioButton)
return false;
Object prop = button
.getClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY);
if (prop != null) {
if (Boolean.TRUE.equals(prop))
return true;
if (Boolean.FALSE.equals(prop))
return false;
}
if (button != null) {
Container parent = button.getParent();
if (parent instanceof JComponent) {
JComponent jparent = (JComponent) parent;
Object flatProperty = jparent
.getClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY);
if (flatProperty != null) {
if (Boolean.TRUE.equals(flatProperty))
return true;
if (Boolean.FALSE.equals(flatProperty))
return false;
}
}
}
return Boolean.TRUE.equals(UIManager
.get(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY));
}
/**
* Returns the focus ring kind of the specified component.
*
* @param component
* Component.
* @return The focus ring kind of the specified component.
* @see SubstanceLookAndFeel#FOCUS_KIND
*/
public static FocusKind getFocusKind(Component component) {
while (component != null) {
if (component instanceof JComponent) {
JComponent jcomp = (JComponent) component;
Object jcompFocusKind = jcomp
.getClientProperty(SubstanceLookAndFeel.FOCUS_KIND);
if (jcompFocusKind instanceof FocusKind)
return (FocusKind) jcompFocusKind;
}
component = component.getParent();
}
Object globalFocusKind = UIManager.get(SubstanceLookAndFeel.FOCUS_KIND);
if (globalFocusKind instanceof FocusKind)
return (FocusKind) globalFocusKind;
return FocusKind.ALL_INNER;
}
/**
* Returns indication whether the watermark should be drawn on the specified
* component.
*
* @param component
* Component.
* @return <code>true</code> if the watermark should be drawn on the
* specified component, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#WATERMARK_VISIBLE
*/
public static boolean toDrawWatermark(Component component) {
Component c = component;
while (c != null) {
if (c instanceof JComponent) {
JComponent jcomp = (JComponent) component;
Object obj = jcomp
.getClientProperty(SubstanceLookAndFeel.WATERMARK_VISIBLE);
if (obj != null) {
if (Boolean.TRUE.equals(obj))
return true;
if (Boolean.FALSE.equals(obj))
return false;
}
}
c = c.getParent();
}
Object obj = UIManager.get(SubstanceLookAndFeel.WATERMARK_VISIBLE);
if (Boolean.TRUE.equals(obj))
return true;
if (Boolean.FALSE.equals(obj))
return false;
// special cases - lists, tables and trees that show watermarks only
// when the WATERMARK_VISIBLE is set to Boolean.TRUE
if (component instanceof JList)
return false;
if (component instanceof JTree)
return false;
if (component instanceof JTable)
return false;
if (component instanceof JTextComponent)
return false;
return true;
}
/**
* Returns the button shaper of the specified button.
*
* @param comp
* The button.
* @return The button shaper of the specified button.
* @see SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY
* @see SubstanceSkin#getButtonShaper()
*/
public static SubstanceButtonShaper getButtonShaper(Component comp) {
if (comp instanceof JComponent) {
Object prop = ((JComponent) comp)
.getClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY);
if (prop instanceof SubstanceButtonShaper)
return (SubstanceButtonShaper) prop;
}
SubstanceSkin skin = SubstanceCoreUtilities.getSkin(comp);
if (skin == null)
return null;
return skin.getButtonShaper();
}
/**
* Returns the fill painter of the specified component.
*
* @param comp
* Component.
* @return The fill painter of the specified component.
* @see SubstanceSkin#getFillPainter()
*/
public static SubstanceFillPainter getFillPainter(Component comp) {
return SubstanceCoreUtilities.getSkin(comp).getFillPainter();
}
/**
* Retrieves the <code>modified</code> state for the specified component in
* a tabbed pane.
*
* @param tabComponent
* The associated tab component.
* @return <code>true</code> if the specified component in a tabbed pane is
* marked as modified, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#WINDOW_MODIFIED
*/
public static boolean isTabModified(Component tabComponent) {
boolean isWindowModified = false;
Component comp = tabComponent;
if (comp instanceof JComponent) {
JComponent jc = (JComponent) comp;
isWindowModified = Boolean.TRUE.equals(jc
.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
}
return isWindowModified;
}
/**
* Retrieves the <code>modified</code> state for the specified root pane.
*
* @param rootPane
* The root pane.
* @return <code>true</code> if the specified root pane is marked as
* modified, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#WINDOW_MODIFIED
*/
public static boolean isRootPaneModified(JRootPane rootPane) {
return Boolean.TRUE.equals(rootPane
.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
}
/**
* Retrieves the <code>modified</code> state for the specified internal
* frame.
*
* @param internalFrame
* The internal frame.
* @return <code>true</code> if the specified internal frame is marked as
* modified, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#WINDOW_MODIFIED
*/
public static boolean isInternalFrameModified(JInternalFrame internalFrame) {
return Boolean.TRUE.equals(internalFrame.getRootPane()
.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
}
/**
* Checks whether the specified tab has a close button.
*
* @param tabbedPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return <code>true</code> if the specified tab has a close button,
* <code>false</code> otherwise.
* @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_PROPERTY
*/
public static boolean hasCloseButton(JTabbedPane tabbedPane, int tabIndex) {
int tabCount = tabbedPane.getTabCount();
if ((tabIndex < 0) || (tabIndex >= tabCount))
return false;
// if tab is disabled, it doesn't have a close button
if (!tabbedPane.isEnabledAt(tabIndex))
return false;
// check property on tab component
Component tabComponent = tabbedPane.getComponentAt(tabIndex);
if (tabComponent instanceof JComponent) {
Object compProp = ((JComponent) tabComponent)
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
if (Boolean.TRUE.equals(compProp))
return true;
if (Boolean.FALSE.equals(compProp))
return false;
}
// check property on tabbed pane
Object tabProp = tabbedPane
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
if (Boolean.TRUE.equals(tabProp))
return true;
if (Boolean.FALSE.equals(tabProp))
return false;
// check property in UIManager
return UIManager
.getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
}
/**
* Returns the size of the close button for a tab in the specified tabbed
* pane.
*
* @param tabbedPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return The size of the close button for a tab in the specified tabbed
* pane.
*/
public static int getCloseButtonSize(JTabbedPane tabbedPane, int tabIndex) {
if (!SubstanceCoreUtilities.hasCloseButton(tabbedPane, tabIndex))
return 0;
return SubstanceSizeUtils.getTabCloseIconSize(SubstanceSizeUtils
.getComponentFontSize(tabbedPane));
}
/**
* Returns the content border kind of the specified tabbed pane.
*
* @param tabbedPane
* Tabbed pane.
* @return Content border kind of the specified tabbed pane.
* @see SubstanceLookAndFeel#TABBED_PANE_CONTENT_BORDER_KIND
*/
public static TabContentPaneBorderKind getContentBorderKind(
JTabbedPane tabbedPane) {
// check property on tabbed pane
Object tabProp = tabbedPane
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
if (tabProp instanceof TabContentPaneBorderKind)
return (TabContentPaneBorderKind) tabProp;
// check property in UIManager
Object globalProp = UIManager
.get(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
if (globalProp instanceof TabContentPaneBorderKind)
return (TabContentPaneBorderKind) globalProp;
return TabContentPaneBorderKind.DOUBLE_PLACEMENT;
}
/**
* Checks whether the specified tab should show modified animation only on
* its close button.
*
* @param tabbedPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return <code>true</code> if the specified tab should show modified
* animation only on its close button, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION
*/
public static boolean toAnimateCloseIconOfModifiedTab(
JTabbedPane tabbedPane, int tabIndex) {
int tabCount = tabbedPane.getTabCount();
if ((tabIndex < 0) || (tabIndex >= tabCount))
return false;
if (!hasCloseButton(tabbedPane, tabIndex))
return false;
// check property on tab component
Component tabComponent = tabbedPane.getComponentAt(tabIndex);
if (tabComponent instanceof JComponent) {
Object compProp = ((JComponent) tabComponent)
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
if (Boolean.TRUE.equals(compProp))
return true;
if (Boolean.FALSE.equals(compProp))
return false;
}
// check property on tabbed pane
Object tabProp = tabbedPane
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
if (Boolean.TRUE.equals(tabProp))
return true;
if (Boolean.FALSE.equals(tabProp))
return false;
// check property in UIManager
return UIManager
.getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
}
/**
* Retrieves transparent image of specified dimension.
*
* @param width
* Image width.
* @param height
* Image height.
* @return Transparent image of specified dimension.
*/
public static BufferedImage getBlankImage(int width, int height) {
if (MemoryAnalyzer.isRunning()) {
// see if the request is unusual
if ((width >= 100) || (height >= 100)) {
StackTraceElement[] stack = Thread.currentThread()
.getStackTrace();
StringBuffer sb = new StringBuffer();
int count = 0;
for (StackTraceElement stackEntry : stack) {
if (count++ > 8)
break;
sb.append(stackEntry.getClassName() + ".");
sb.append(stackEntry.getMethodName() + " [");
sb.append(stackEntry.getLineNumber() + "]");
sb.append("\n");
}
MemoryAnalyzer.enqueueUsage("Blank " + width + "*" + height
+ "\n" + sb.toString());
}
}
if (UIUtil.isRetina()) {
return new JBHiDPIScaledImage(width, height, BufferedImage.TYPE_INT_ARGB);
} else {
GraphicsEnvironment e = GraphicsEnvironment
.getLocalGraphicsEnvironment();
GraphicsDevice d = e.getDefaultScreenDevice();
GraphicsConfiguration c = d.getDefaultConfiguration();
return c.createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
}
public static boolean isHiDpiAwareImage(Image image) {
return image instanceof IsHiDpiAware;
}
/**
* Checks whether the specified button should have minimal size.
*
* @param button
* Button.
* @return <code>false</code> if the specified button should have minimal
* size, <code>true</code> otherwise.
* @see SubstanceLookAndFeel#BUTTON_NO_MIN_SIZE_PROPERTY
*/
public static boolean hasNoMinSizeProperty(AbstractButton button) {
Object noMinSizeProperty = button
.getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
if (Boolean.TRUE.equals(noMinSizeProperty))
return true;
if (Boolean.FALSE.equals(noMinSizeProperty))
return false;
Container parent = button.getParent();
if (parent instanceof JComponent) {
noMinSizeProperty = ((JComponent) parent)
.getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
if (Boolean.TRUE.equals(noMinSizeProperty))
return true;
if (Boolean.FALSE.equals(noMinSizeProperty))
return false;
}
return (Boolean.TRUE.equals(UIManager
.get(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY)));
}
/**
* Checks whether the specified component is flat.
*
* @param comp
* Component.
* @param defaultValue
* The value to return if there is no
* {@link SubstanceLookAndFeel#FLAT_PROPERTY} defined on button
* hierarchy or {@link UIManager}.
* @return <code>false</code> if the specified button is flat,
* <code>true</code> otherwise.
* @see SubstanceLookAndFeel#FLAT_PROPERTY
*/
public static boolean hasFlatAppearance(Component comp, boolean defaultValue) {
// small optimizations for checkboxes and radio buttons
if (comp instanceof JCheckBox)
return defaultValue;
if (comp instanceof JRadioButton)
return defaultValue;
Component c = comp;
// while (c != null) {
if (c instanceof JComponent) {
JComponent jcomp = (JComponent) c;
Object flatProperty = jcomp
.getClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY);
if (flatProperty != null) {
if (Boolean.TRUE.equals(flatProperty))
return true;
if (Boolean.FALSE.equals(flatProperty))
return false;
}
}
if (c != null) {
Container parent = c.getParent();
if (parent instanceof JComponent) {
JComponent jparent = (JComponent) parent;
Object flatProperty = jparent
.getClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY);
if (flatProperty != null) {
if (Boolean.TRUE.equals(flatProperty))
return true;
if (Boolean.FALSE.equals(flatProperty))
return false;
}
}
}
// }
Object flatProperty = UIManager.get(SubstanceLookAndFeel.FLAT_PROPERTY);
if (flatProperty != null) {
if (Boolean.TRUE.equals(flatProperty))
return true;
if (Boolean.FALSE.equals(flatProperty))
return false;
}
return defaultValue;
}
/**
* Computes whether the specified button has flat appearance.
*
* @param button
* Button.
* @return <code>true</code> if the button has flat appearance,
* <code>false</code> otherwise.
*/
public static boolean hasFlatAppearance(AbstractButton button) {
// small optimizations for checkboxes and radio buttons
if (button instanceof JCheckBox)
return false;
if (button instanceof JRadioButton)
return false;
return ((SubstanceCoreUtilities.isToolBarButton(button) && SubstanceCoreUtilities
.hasFlatAppearance(button, true)) || SubstanceCoreUtilities
.hasFlatAppearance(button, false));
}
/**
* Returns the popup flyout orientation for the specified combobox.
*
* @param combobox
* Combobox.
* @return The popup flyout orientation for the specified combobox.
* @see SubstanceLookAndFeel#COMBO_BOX_POPUP_FLYOUT_ORIENTATION
*/
public static int getPopupFlyoutOrientation(JComboBox combobox) {
Object comboProperty = combobox
.getClientProperty(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
if (comboProperty instanceof Integer)
return (Integer) comboProperty;
Object globalProperty = UIManager
.get(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
if (globalProperty instanceof Integer)
return (Integer) globalProperty;
return SwingConstants.SOUTH;
}
/**
* Makes the specified component and all its descendants non-opaque.
*
* @param comp
* Component.
* @param opacitySnapshot
* The "snapshot" map that will contain the original opacity
* status of the specified component and all its descendants.
*/
public static void makeNonOpaque(Component comp,
Map<Component, Boolean> opacitySnapshot) {
if (comp instanceof JComponent) {
JComponent jcomp = (JComponent) comp;
opacitySnapshot.put(comp, jcomp.isOpaque());
jcomp.setOpaque(false);
}
if (comp instanceof Container) {
Container cont = (Container) comp;
for (int i = 0; i < cont.getComponentCount(); i++)
makeNonOpaque(cont.getComponent(i), opacitySnapshot);
}
}
/**
* Restores the opacity of the specified component and all its descendants.
*
* @param comp
* Component.
* @param opacitySnapshot
* The "snapshot" map that contains the original opacity status
* of the specified component and all its descendants.
*/
public static void restoreOpaque(Component comp,
Map<Component, Boolean> opacitySnapshot) {
if (comp instanceof JComponent) {
JComponent jcomp = (JComponent) comp;
// fix for defect 159 - snapshot may not contain
// opacity for table header of a table when it's used
// inside tree cell renderer (wrapper in a scroll pane).
if (opacitySnapshot.containsKey(comp))
jcomp.setOpaque(opacitySnapshot.get(comp));
else
jcomp.setOpaque(true);
}
if (comp instanceof Container) {
Container cont = (Container) comp;
for (int i = 0; i < cont.getComponentCount(); i++)
restoreOpaque(cont.getComponent(i), opacitySnapshot);
}
}
/**
* Creates a compatible image (for efficient processing and drawing).
*
* @param image
* The original image.
* @return Compatible version of the original image.
* @author Romain Guy
*/
public static BufferedImage createCompatibleImage(BufferedImage image) {
GraphicsEnvironment e = GraphicsEnvironment
.getLocalGraphicsEnvironment();
GraphicsDevice d = e.getDefaultScreenDevice();
GraphicsConfiguration c = d.getDefaultConfiguration();
BufferedImage compatibleImage = c.createCompatibleImage(image
.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
/**
* Checks whether the specified component will show scheme-colorized icon in
* the default state.
*
* @param comp
* Component.
* @return <code>true</code> if the specified component will show
* scheme-colorized icon in the default state, <code>false</code>
* otherwise.
* @see SubstanceLookAndFeel#USE_THEMED_DEFAULT_ICONS
*/
public static boolean useThemedDefaultIcon(JComponent comp) {
if ((comp == null) || comp.getClass().isAnnotationPresent(SubstanceInternalButton.class)) {
return false;
}
return Boolean.TRUE.equals(UIManager
.get(SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS));
}
/**
* Returns the callback to be called upon tab closing (using the tab close
* button).
*
* @param me
* Mouse event.
* @param tabbedPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return Callback to be called upon tab closing (using the tab close
* button).
* @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_CALLBACK
*/
public static TabCloseCallback getTabCloseCallback(MouseEvent me,
JTabbedPane tabbedPane, int tabIndex) {
int tabCount = tabbedPane.getTabCount();
if ((tabIndex < 0) || (tabIndex >= tabCount))
return null;
// check property on tab component
Component tabComponent = tabbedPane.getComponentAt(tabIndex);
if (tabComponent instanceof JComponent) {
Object compProp = ((JComponent) tabComponent)
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
if (compProp instanceof TabCloseCallback)
return (TabCloseCallback) compProp;
}
// check property on tabbed pane
Object tabProp = tabbedPane
.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
if (tabProp instanceof TabCloseCallback)
return (TabCloseCallback) tabProp;
Object globProp = UIManager
.get(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
if (globProp instanceof TabCloseCallback)
return (TabCloseCallback) globProp;
return null;
}
/**
* Blends two images along Y-axis.
*
* @param imageTop
* The left image.
* @param imageBottom
* The right image.
* @param start
* Relative start of the blend area (in 0.0-1.0 range).
* @param end
* Relative end of the blend area (in 0.0-1.0 range).
* @return Blended image.
*/
public static BufferedImage blendImagesVertical(BufferedImage imageTop,
BufferedImage imageBottom, double start, double end) {
int width = imageTop.getWidth();
if (width != imageBottom.getWidth())
throw new IllegalArgumentException("Widths are not the same: "
+ imageTop.getWidth() + " and " + imageBottom.getWidth());
int height = imageTop.getHeight();
if (height != imageBottom.getHeight())
throw new IllegalArgumentException("Heights are not the same: "
+ imageTop.getHeight() + " and " + imageBottom.getHeight());
BufferedImage result = getBlankImage(width, height);
Graphics2D graphics = (Graphics2D) result.createGraphics();
int scaleFactor = UIUtil.getScaleFactor();
graphics.scale(1.0f / scaleFactor, 1.0f / scaleFactor);
int startY = (int) (start * height);
int endY = (int) (end * height);
int rampHeight = endY - startY;
if (rampHeight == 0) {
graphics.drawImage(imageTop, 0, 0, width, startY, 0, 0, width,
startY, null);
graphics.drawImage(imageBottom, 0, startY, width, height, 0,
startY, width, height, null);
} else {
BufferedImage rampBottom = getBlankImage(width, rampHeight);
Graphics2D rampBottomG = (Graphics2D) rampBottom.getGraphics();
rampBottomG.setPaint(new GradientPaint(
new Point(0, 0), new Color(0, 0, 0, 255),
new Point(0, rampHeight), new Color(0, 0, 0, 0)));
rampBottomG.fillRect(0, 0, width, rampHeight);
BufferedImage tempBottom = getBlankImage(width, height - startY);
Graphics2D tempBottomG = (Graphics2D) tempBottom.getGraphics();
tempBottomG.scale(1.0f / scaleFactor, 1.0f / scaleFactor);
tempBottomG.drawImage(imageBottom, 0, 0, width, height - startY, 0,
startY, width, height, null);
tempBottomG.setComposite(AlphaComposite.DstOut);
tempBottomG.drawImage(rampBottom, 0, 0, null);
tempBottomG.setComposite(AlphaComposite.SrcOver);
graphics.drawImage(imageTop, 0, 0, null);
graphics.drawImage(tempBottom, 0, startY, null);
}
graphics.dispose();
return result;
}
/**
* Blends two images along X-axis.
*
* @param imageLeft
* The left image.
* @param imageRight
* The right image.
* @param start
* Relative start of the blend area (in 0.0-1.0 range).
* @param end
* Relative end of the blend area (in 0.0-1.0 range).
* @return Blended image.
*/
public static BufferedImage blendImagesHorizontal(BufferedImage imageLeft,
BufferedImage imageRight, double start, double end) {
int width = imageLeft.getWidth();
if (width != imageRight.getWidth())
throw new IllegalArgumentException("Widths are not the same: "
+ imageLeft.getWidth() + " and " + imageRight.getWidth());
int height = imageLeft.getHeight();
if (height != imageRight.getHeight())
throw new IllegalArgumentException("Heights are not the same: "
+ imageLeft.getHeight() + " and " + imageRight.getHeight());
BufferedImage result = getBlankImage(width, height);
Graphics2D graphics = (Graphics2D) result.getGraphics().create();
int scaleFactor = UIUtil.getScaleFactor();
graphics.scale(1.0f / scaleFactor, 1.0f / scaleFactor);
int startX = (int) (start * width);
int endX = (int) (end * width);
int rampWidth = endX - startX;
if (rampWidth == 0) {
graphics.drawImage(imageLeft, 0, 0, startX, height, 0, 0, startX,
height, null);
graphics.drawImage(imageRight, startX, 0, width, height, startX, 0,
width, height, null);
} else {
BufferedImage rampRight = getBlankImage(rampWidth, height);
Graphics2D rampRightG = (Graphics2D) rampRight.getGraphics();
rampRightG
.setPaint(new GradientPaint(new Point(0, 0), new Color(0,
0, 0, 255), new Point(rampWidth, 0), new Color(0,
0, 0, 0)));
rampRightG.fillRect(0, 0, rampWidth, height);
BufferedImage tempRight = getBlankImage(width - startX, height);
Graphics2D tempRightG = (Graphics2D) tempRight.getGraphics();
tempRightG.drawImage(imageRight, 0, 0, width - startX, height,
startX, 0, width, height, null);
tempRightG.setComposite(AlphaComposite.DstOut);
tempRightG.drawImage(rampRight, 0, 0, null);
tempRightG.setComposite(AlphaComposite.SrcOver);
graphics.drawImage(imageLeft, 0, 0, null);
graphics.drawImage(tempRight, startX, 0, null);
}
graphics.dispose();
return result;
}
/**
* Returns the color scheme for the icon of option panes with the specified
* message type.
*
* @param messageType
* Option pane message type.
* @param mainScheme
* Main color scheme.
* @return Color scheme for the icon of option panes with the specified
* message type.
*/
public static SubstanceColorScheme getOptionPaneColorScheme(
int messageType, SubstanceColorScheme mainScheme) {
if (!SubstanceLookAndFeel.isToUseConstantThemesOnDialogs())
return mainScheme;
switch (messageType) {
case JOptionPane.INFORMATION_MESSAGE:
return new BottleGreenColorScheme();
case JOptionPane.QUESTION_MESSAGE:
return new LightAquaColorScheme();
case JOptionPane.WARNING_MESSAGE:
return new SunsetColorScheme();
case JOptionPane.ERROR_MESSAGE:
return new SunfireRedColorScheme();
}
return null;
}
/**
* Returns the popup prototype display value for the specified combo box.
* This value is used to compute the width of the combo popup.
*
* @param combo
* Combo box.
* @return The popup prototype display value for the specified combo box.
* @see SubstanceLookAndFeel#COMBO_POPUP_PROTOTYPE
*/
public static Object getComboPopupPrototypeDisplayValue(JComboBox combo) {
Object objProp = combo
.getClientProperty(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
if (objProp == null)
objProp = UIManager.get(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
if (objProp == null)
return null;
if (objProp instanceof ComboPopupPrototypeCallback) {
ComboPopupPrototypeCallback callback = (ComboPopupPrototypeCallback) objProp;
return callback.getPopupPrototypeDisplayValue(combo);
}
// check if this object is in the model ???
return objProp;
}
/**
* Returns the set of sides registered on the specified button.
*
* @param component
* Button.
* @param propertyName
* Client property name for retrieving the registered sides.
* @return Set of sides registered on the specified button.
*/
@SuppressWarnings("unchecked")
public static Set<Side> getSides(JComponent component, String propertyName) {
if (component == null) {
return null;
}
Object prop = component.getClientProperty(propertyName);
if (prop == null)
return null;
if (prop instanceof Set) {
return (Set<Side>) prop;
}
if (prop != null) {
if (prop instanceof Side) {
Set<Side> result = EnumSet.noneOf(Side.class);
result.add((Side) prop);
return result;
}
}
return null;
}
/**
* Returns the corner radius of the specified toolbar button.
*
* @param button
* Toolbar button.
* @param insets
* Button insets.
* @return Corner radius of the specified toolbar button.
* @see SubstanceLookAndFeel#CORNER_RADIUS
*/
public static float getToolbarButtonCornerRadius(JComponent button,
float insets) {
JToolBar toolbar = null;
Component c = button.getParent();
while (c != null) {
if (c instanceof JToolBar) {
toolbar = (JToolBar) c;
break;
}
c = c.getParent();
}
if (toolbar == null)
return 2.0f;
float width = button.getWidth() - 2 * insets;
float height = button.getHeight() -2 * insets;
float maxRadius = (width > height) ? (height) / 2.0f : (width) / 2.0f;
Object buttonProp = button
.getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
if (buttonProp instanceof Float)
return Math.min(maxRadius, ((Float) buttonProp).floatValue());
Object toolbarProp = toolbar
.getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
if (toolbarProp instanceof Float)
return Math.min(maxRadius, ((Float) toolbarProp).floatValue());
Object globalProp = UIManager.get(SubstanceLookAndFeel.CORNER_RADIUS);
if (globalProp instanceof Float)
return Math.min(maxRadius, ((Float) globalProp).floatValue());
return 2.0f;
}
/**
* Returns the number of echo characters per each password chanaracter.
*
* @param jpf
* Password field.
* @return The number of echo characters per each password chanaracter.
* @see SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR
*/
public static int getEchoPerChar(JPasswordField jpf) {
Object obj = jpf
.getClientProperty(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
if ((obj != null) && (obj instanceof Integer)) {
int result = (Integer) obj;
if (result >= 1)
return result;
}
obj = UIManager.get(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
if ((obj != null) && (obj instanceof Integer)) {
int result = (Integer) obj;
if (result >= 1)
return result;
}
return 1;
}
/**
* Creates a soft-clipped image. Code taken from <a href=
* "http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker.html"
* >here</a>.
*
* @author Chris Campbell.
*/
public static BufferedImage softClip(int width, int height,
BufferedImage source, Shape clipShape) {
// Create a translucent intermediate image in which we can perform
// the soft clipping
BufferedImage img = SubstanceCoreUtilities.getBlankImage(width, height);
Graphics2D g2 = img.createGraphics();
// Clear the image so all pixels have zero alpha
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, width, height);
// Render our clip shape into the image. Note that we enable
// antialiasing to achieve the soft clipping effect. Try
// commenting out the line that enables antialiasing, and
// you will see that you end up with the usual hard clipping.
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fill(clipShape);
// Here's the trick... We use SrcAtop, which effectively uses the
// alpha value as a coverage value for each pixel stored in the
// destination. For the areas outside our clip shape, the destination
// alpha will be zero, so nothing is rendered in those areas. For
// the areas inside our clip shape, the destination alpha will be fully
// opaque, so the full color is rendered. At the edges, the original
// antialiasing is carried over to give us the desired soft clipping
// effect.
g2.setComposite(AlphaComposite.SrcAtop);
g2.drawImage(source, 0, 0, null);
g2.dispose();
return img;
}
/**
* Checks whether the specified component has extra Substance-specific UI
* elements.
*
* @param component
* Component.
* @return <code>true</code> if the specified component has extra
* Substance-specific UI elements, <code>false</code> otherwise.
* @see SubstanceLookAndFeel#SHOW_EXTRA_WIDGETS
*/
public static boolean toShowExtraWidgets(Component component) {
Component c = component;
while (c != null) {
if (c instanceof JComponent) {
JComponent jcomp = (JComponent) c;
Object componentProp = jcomp
.getClientProperty(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS);
if (componentProp != null) {
if (Boolean.TRUE.equals(componentProp))
return false;
if (Boolean.FALSE.equals(componentProp))
return true;
}
}
c = c.getParent();
}
return Boolean.TRUE.equals(UIManager
.get(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS));
}
public static HiDpiAwareIcon getThemedIcon(Component comp, Icon orig) {
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
.getColorScheme(comp, ComponentState.ENABLED);
float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
return new HiDpiAwareIcon(SubstanceImageCreator.getColorSchemeImage(comp,
orig, colorScheme, brightnessFactor));
}
public static HiDpiAwareIcon getThemedIcon(JTabbedPane tab, int tabIndex, Icon orig) {
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
.getColorScheme(tab, tabIndex, ColorSchemeAssociationKind.TAB,
ComponentState.ENABLED);
float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
return new HiDpiAwareIcon(SubstanceImageCreator.getColorSchemeImage(tab,
orig, colorScheme, brightnessFactor));
}
public static Icon getOriginalIcon(AbstractButton b, Icon defaultIcon) {
ButtonModel model = b.getModel();
Icon icon = b.getIcon();
if (icon == null)
icon = defaultIcon;
if (icon.getClass().isAnnotationPresent(TransitionAware.class))
return icon;
Icon tmpIcon = null;
if (icon != null) {
if (!model.isEnabled()) {
if (model.isSelected()) {
tmpIcon = b.getDisabledSelectedIcon();
} else {
tmpIcon = b.getDisabledIcon();
}
} else if (model.isPressed() && model.isArmed()) {
tmpIcon = b.getPressedIcon();
} else if (b.isRolloverEnabled() && model.isRollover()) {
if (model.isSelected()) {
tmpIcon = b.getRolloverSelectedIcon();
if (tmpIcon == null) {
tmpIcon = b.getSelectedIcon();
}
} else {
tmpIcon = b.getRolloverIcon();
}
} else if (model.isSelected()) {
tmpIcon = b.getSelectedIcon();
}
if (tmpIcon != null) {
icon = tmpIcon;
}
}
return icon;
}
/**
* Returns the global menu gutter fill kind.
*
* @return The global menu gutter fill kind.
* @see SubstanceLookAndFeel#MENU_GUTTER_FILL_KIND
*/
public static MenuGutterFillKind getMenuGutterFillKind() {
Object globalSetting = UIManager
.get(SubstanceLookAndFeel.MENU_GUTTER_FILL_KIND);
if (globalSetting instanceof MenuGutterFillKind)
return (MenuGutterFillKind) globalSetting;
return MenuGutterFillKind.HARD_FILL;
}
/**
* Given a component, returns the parent for computing the
* {@link SubstanceDecorationPainter}.
*
* @param c
* Component.
* @return The parent for computing the {@link SubstanceDecorationPainter}.
*/
public static Container getHeaderParent(Component c) {
Component comp = c.getParent();
Container result = null;
while (comp != null) {
// the second part fixes the incorrect alignments on
// internal frames.
if ((comp instanceof JLayeredPane) && (result == null))
result = (Container) comp;
if ((result == null) && (comp instanceof Window))
result = (Container) comp;
comp = comp.getParent();
}
return result;
}
/**
* Paints the focus ring on the specified component.
*
* @param g
* Graphics context.
* @param mainComp
* The main component for the focus painting.
* @param focusedComp
* The actual component that has the focus. For example, the main
* component can be a {@link JSpinner}, while the focused
* component is a text field inside the the spinner editor.
* @param focusShape
* Focus shape. May be <code>null</code> - in this case, the
* bounds of <code>mainComp</code> will be used.
* @param textRect
* Text rectangle (if relevant).
* @param maxAlphaCoef
* Maximum alhpa coefficient for painting the focus. Values lower
* than 1.0 will result in a translucent focus ring (can be used
* to paint a focus ring that doesn't draw too much attention
* away from the content, for example on text components).
* @param extraPadding
* Extra padding between the component bounds and the focus ring
* painting.
*/
public static void paintFocus(Graphics g, Component mainComp,
Component focusedComp, TransitionAwareUI transitionAwareUI,
Shape focusShape, Rectangle textRect, float maxAlphaCoef,
float extraPadding) {
float focusStrength = transitionAwareUI.getTransitionTracker()
.getFocusStrength(focusedComp.hasFocus());
if (focusStrength == 0.0f)
return;
FocusKind focusKind = SubstanceCoreUtilities.getFocusKind(mainComp);
if (focusKind == FocusKind.NONE)
return;
Graphics2D graphics = (Graphics2D) g.create();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
float alpha = maxAlphaCoef * focusStrength;
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(mainComp,
alpha, g));
Color color = SubstanceColorUtilities.getFocusColor(mainComp, transitionAwareUI);
graphics.setColor(color);
focusKind.paintFocus(mainComp, focusedComp, transitionAwareUI,
graphics, focusShape, textRect, extraPadding);
graphics.dispose();
}
/**
* Returns indication whether the specified button is a close button on some
* title pane.
*
* @param ab
* Button.
* @return <code>true</code> if the specified button is a close button on
* some title pane, <code>false</code> otherwise.
*/
public static boolean isTitleCloseButton(JComponent ab) {
if ((ab instanceof SubstanceTitleButton)
&& Boolean.TRUE
.equals(ab
.getClientProperty(SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON)))
return true;
return false;
}
/**
* Uninstalls the specified menu item.
*
* @param menuItem
* Menu item.
*/
public static void uninstallMenu(JMenuItem menuItem) {
if (menuItem instanceof JMenu) {
JMenu menu = (JMenu) menuItem;
for (Component comp : menu.getMenuComponents())
if (comp instanceof JMenuItem)
SubstanceCoreUtilities.uninstallMenu((JMenuItem) comp);
}
ButtonUI menuItemUI = menuItem.getUI();
if (menuItemUI instanceof SubstanceMenu) {
SubstanceMenu sMenu = (SubstanceMenu) menuItemUI;
if (sMenu.getAssociatedMenuItem() != null) {
menuItemUI.uninstallUI(menuItem);
}
}
for (ActionListener actionListener : menuItem.getActionListeners())
menuItem.removeActionListener(actionListener);
menuItem.removeAll();
}
/**
* Returns an icon pointed to by the specified string.
*
* @param iconResource
* Resource location string.
* @return Icon.
*/
public static Icon getHiDpiAwareIcon(String iconResource) {
ClassLoader cl = getClassLoaderForResources();
URL iconUrl = cl.getResource(iconResource);
if (iconUrl == null)
return null;
return new HiDpiAwareIconUiResource(new ImageIcon(iconUrl));
}
/**
* Returns the class loader for loading the resource files. It is a fix by
* Dag Joar and Christian Schlichtherle for application running with
* -Xbootclasspath VM flag. In this case, the using
* MyClass.class.getClassLoader() would return null, but the context class
* loader will function properly that classes will be properly loaded
* regardless of whether the lib is added to the system class path, the
* extension class path and regardless of the class loader architecture set
* up by some frameworks.
*
* @return The class loader for loading the resource files.
*/
public static ClassLoader getClassLoaderForResources() {
ClassLoader cl = (ClassLoader) UIManager.get("ClassLoader");
if (cl == null)
cl = Thread.currentThread().getContextClassLoader();
return cl;
}
public static boolean isCoveredByLightweightPopups(final Component comp) {
JRootPane rootPane = SwingUtilities.getRootPane(comp);
if (rootPane == null)
return false;
Component[] popups = rootPane.getLayeredPane().getComponentsInLayer(
JLayeredPane.POPUP_LAYER);
if (popups == null)
return false;
// System.out.println("Has " + popups.length + " popups");
// Convert the component bounds to the layered pane.
// Can't use the SwingUtilities.convertRectangle() as that
// eventually will try to acquire the lock on the container
Rectangle compBoundsConverted = SwingUtilities.convertRectangle(comp
.getParent(), comp.getBounds(), rootPane.getLayeredPane());
// System.out.println("Bounds : \n\torig : " + comp.getBounds()
// + "\n\ttrans:" + compBoundsConverted);
int popupIndexToStartWith = getPopupParentIndexOf(comp, popups) - 1;
// String txt = (comp instanceof JMenuItem) ? ((JMenuItem) comp)
// .getText() : "";
// System.out.println("Starting scan from popup "
// + popupIndexToStartWith + " for " + txt);
for (int i = popupIndexToStartWith; i >= 0; i--) {
Component popup = popups[i];
// System.out.println("Popup " +
// popup.getClass().getName());
// System.out.println("Popup bounds " + popup.getBounds());
if (compBoundsConverted.intersects(popup.getBounds())) {
return true;
}
}
return false;
}
/**
* Gets a component and a list of popups and returns the index of the popup
* that is a parent of the specified component. Is used to track issue 297
* and prevent visual artifacts.
*
* @param comp
* Component.
* @param popups
* List of popups.
* @return Index of the popup which is component's parent if any, or the
* popup list length otherwise.
*/
public static int getPopupParentIndexOf(Component comp, Component[] popups) {
for (int i = 0; i < popups.length; i++) {
Component popup = popups[i];
Component currComp = comp;
while (currComp != null) {
if (currComp == popup) {
return i;
}
currComp = currComp.getParent();
}
}
return popups.length;
}
/**
* Returns the resource bundle for the specified component.
*
* @param jcomp
* Component.
* @return Resource bundle for the specified component.
*/
public static ResourceBundle getResourceBundle(JComponent jcomp) {
if (LafWidgetUtilities.toIgnoreGlobalLocale(jcomp)) {
return SubstanceLookAndFeel.getLabelBundle(jcomp.getLocale());
} else {
return SubstanceLookAndFeel.getLabelBundle();
}
}
/**
* Returns the border painter for the specified component.
*
* @param comp
* Component.
* @return Border painter for the specified component.
* @see SubstanceSkin#getBorderPainter()
*/
public static SubstanceBorderPainter getBorderPainter(Component comp) {
return SubstanceCoreUtilities.getSkin(comp).getBorderPainter();
}
/**
* Returns the highlight border painter for the specified component.
*
* @param comp
* Component.
* @return Highlight border painter for the specified component.
* @see SubstanceSkin#getBorderPainter()
* @see SubstanceSkin#getHighlightBorderPainter()
*/
public static SubstanceBorderPainter getHighlightBorderPainter(
Component comp) {
SubstanceBorderPainter result = SubstanceCoreUtilities.getSkin(comp)
.getHighlightBorderPainter();
if (result != null)
return result;
return getBorderPainter(comp);
}
/**
* Returns the component hierarchy.
*
* @param comp
* Component.
* @return Component hierarchy string.
*/
public static String getHierarchy(Component comp) {
StringBuffer buffer = new StringBuffer();
getHierarchy(comp, buffer, 0);
while (true) {
if (comp instanceof Window) {
Window w = (Window) comp;
comp = w.getOwner();
if (comp != null) {
buffer.append("Owner --->\n");
getHierarchy(comp, buffer, 0);
}
} else {
break;
}
}
return buffer.toString();
}
/**
* Computes the component hierarchy.
*
* @param comp
* Component.
* @param buffer
* Hierarchy representation buffer.
* @param level
* Hierarchy level.
*/
public static void getHierarchy(Component comp, StringBuffer buffer,
int level) {
for (int i = 0; i < level; i++)
buffer.append(" ");
String name = comp.getName();
if (comp instanceof Dialog)
name = ((Dialog) comp).getTitle();
if (comp instanceof Frame)
name = ((Frame) comp).getTitle();
buffer.append(comp.getClass().getName() + "[" + name + "]\n");
if (comp instanceof Container) {
Container cont = (Container) comp;
for (int i = 0; i < cont.getComponentCount(); i++)
getHierarchy(cont.getComponent(i), buffer, level + 1);
}
}
/**
* Returns the title pane of the specified root pane.
*
* @param rootPane
* Root pane.
* @return The title pane of the specified root pane.
*/
public static JComponent getTitlePane(JRootPane rootPane) {
JInternalFrame jif = (JInternalFrame) SwingUtilities
.getAncestorOfClass(JInternalFrame.class, rootPane);
if (jif != null) {
SubstanceInternalFrameUI ui = (SubstanceInternalFrameUI) jif
.getUI();
return ui.getTitlePane();
}
SubstanceRootPaneUI ui = (SubstanceRootPaneUI) rootPane.getUI();
if (ui == null)
return null;
return ui.getTitlePane();
}
/**
* Returns the arrow icon.
*
* @param comp
* Component.
* @param component
* Button.
* @param orientation
* Arrow orientation.
* @return Arrow icon.
*/
public static Icon getArrowIcon(AbstractButton button, int orientation) {
Icon result = new ArrowButtonTransitionAwareIcon(button, orientation);
return result;
}
/**
* Returns the arrow icon.
*
* @param comp
* Component.
* @param component
* Button.
* @param orientation
* Arrow orientation.
* @return Arrow icon.
*/
public static Icon getArrowIcon(JComponent comp,
TransitionAwareIcon.TransitionAwareUIDelegate transitionAwareUIDelegate,
int orientation) {
Icon result = new ArrowButtonTransitionAwareIcon(comp,
transitionAwareUIDelegate, orientation);
return result;
}
/**
* Returns the colorization factor for the specified component.
*
* @param c
* Component.
* @return The colorization factor for the specified component.
* @see SubstanceLookAndFeel#COLORIZATION_FACTOR
*/
public static double getColorizationFactor(Component c) {
JPopupMenu popupMenu = null;
while (c != null) {
if (c instanceof JComponent) {
JComponent jcomp = (JComponent) c;
Object compProp = jcomp
.getClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR);
if (compProp instanceof Double)
return (Double) compProp;
}
if (c instanceof JPopupMenu) {
popupMenu = (JPopupMenu) c;
}
c = c.getParent();
}
if (popupMenu != null) {
Component invoker = popupMenu.getInvoker();
if (popupMenu != invoker)
return getColorizationFactor(popupMenu.getInvoker());
}
Object globalProp = UIManager
.get(SubstanceLookAndFeel.COLORIZATION_FACTOR);
if (globalProp instanceof Double) {
return (Double) globalProp;
}
return 0.5;
}
/**
* Returns the skin of the specified component.
*
* @param c
* Component.
* @return The skin of the specified component.
* @see SubstanceLookAndFeel#SKIN_PROPERTY
*/
public static SubstanceSkin getSkin(Component c) {
if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
return null;
if (!SubstanceRootPaneUI.hasCustomSkinOnAtLeastOneRootPane())
return SubstanceLookAndFeel.getCurrentSkin();
SubstanceComboPopup comboPopup = (SubstanceComboPopup) SwingUtilities
.getAncestorOfClass(SubstanceComboPopup.class, c);
if (comboPopup != null) {
// special case for combobox popup - take the skin
// of the combobox itself - issue 439
return getSkin(comboPopup.getCombobox());
}
JRootPane rootPane = SwingUtilities.getRootPane(c);
if (c instanceof SubstanceInternalFrameTitlePane) {
// use correct root pane for painting the
// title panes of internal frames
Component frame = c.getParent();
if ((frame != null) && (frame instanceof JInternalFrame)) {
rootPane = ((JInternalFrame) frame).getRootPane();
}
}
if ((c != null)
&& (c.getParent() instanceof SubstanceInternalFrameTitlePane)) {
// use correct root pane for painting the
// title buttons of internal frames
Component frame = c.getParent().getParent();
if ((frame != null) && (frame instanceof JInternalFrame)) {
rootPane = ((JInternalFrame) frame).getRootPane();
}
}
if (rootPane != null) {
Object skinProp = rootPane
.getClientProperty(SubstanceLookAndFeel.SKIN_PROPERTY);
if (skinProp instanceof SubstanceSkin)
return (SubstanceSkin) skinProp;
}
return SubstanceLookAndFeel.getCurrentSkin();
}
/**
* Returns a hash key for the specified parameters.
*
* @param objects
* Key components.
* @return Hash key.
*/
public static HashMapKey getHashKey(Object... objects) {
return new HashMapKey(objects);
}
/**
* Stops all Substance threads. Improper use may result in UI artifacts and
* runtime exceptions.
*/
public static void stopThreads() {
TrackableThread.requestStopAllThreads();
}
/**
* Retrieves a single parameter from the VM flags.
*
* @param parameterName
* Parameter name.
* @return Parameter value.
*/
public static String getVmParameter(String parameterName) {
String paramValue = null;
try {
paramValue = System.getProperty(parameterName);
return paramValue;
} catch (Exception exc) {
// probably running in unsecure JNLP - ignore
return null;
}
}
/**
* Tests UI threading violations on creating the specified component.
*
* @param comp
* Component.
* @throws UiThreadingViolationException
* If the component is created off Event Dispatch Thread.
*/
public static void testComponentCreationThreadingViolation(Component comp) {
if (!SwingUtilities.isEventDispatchThread()) {
UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
"Component creation must be done on Event Dispatch Thread");
uiThreadingViolationError.printStackTrace(System.err);
throw uiThreadingViolationError;
}
}
/**
* Tests UI threading violations on changing the state the specified
* component.
*
* @param comp
* Component.
* @throws UiThreadingViolationException
* If the component is changing state off Event Dispatch Thread.
*/
public static void testComponentStateChangeThreadingViolation(Component comp) {
if (!SwingUtilities.isEventDispatchThread()) {
UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
"Component state change must be done on Event Dispatch Thread");
uiThreadingViolationError.printStackTrace(System.err);
throw uiThreadingViolationError;
}
}
/**
* Tests UI threading violations on closing the specified window.
*
* @param w
* Window.
* @throws UiThreadingViolationException
* If the window is closed off Event Dispatch Thread.
*/
public static void testWindowCloseThreadingViolation(Window w) {
if (!SwingUtilities.isEventDispatchThread()) {
UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
"Window close must be done on Event Dispatch Thread");
uiThreadingViolationError.printStackTrace(System.err);
throw uiThreadingViolationError;
}
}
public static void traceSubstanceApiUsage(Component comp, String message) {
Window w = SwingUtilities.getWindowAncestor(comp);
String wTitle = null;
if (w instanceof Frame) {
wTitle = ((Frame) w).getTitle();
}
if (w instanceof Dialog) {
wTitle = ((Dialog) w).getTitle();
}
String wClassName = (w != null) ? w.getClass().getName() : null;
throw new IllegalArgumentException(message + " [component "
+ comp.getClass().getSimpleName() + " in window "
+ wClassName + ":'" + wTitle + "' under "
+ UIManager.getLookAndFeel().getName() + "]");
}
/**
* Scans {@code imageList} for best-looking image of specified dimensions.
* Image can be scaled and/or padded with transparency.
*/
public static BufferedImage getScaledIconImage(
java.util.List<Image> imageList, int width, int height) {
if (width == 0 || height == 0) {
return null;
}
Image bestImage = null;
int bestWidth = 0;
int bestHeight = 0;
double bestSimilarity = 3; // Impossibly high value
for (Iterator<Image> i = imageList.iterator(); i.hasNext();) {
// Iterate imageList looking for best matching image.
// 'Similarity' measure is defined as good scale factor and small
// insets.
// Best possible similarity is 0 (no scale, no insets).
// It's found while the experiments that good-looking result is
// achieved
// with scale factors x1, x3/4, x2/3, xN, x1/N.
Image im = i.next();
if (im == null) {
continue;
}
int iw;
int ih;
try {
iw = im.getWidth(null);
ih = im.getHeight(null);
} catch (Exception e) {
continue;
}
if (isHiDpiAwareImage(im)) {
iw /= 2;
ih /= 2;
}
if (iw > 0 && ih > 0) {
// Calc scale factor
double scaleFactor = Math.min((double) width / (double) iw,
(double) height / (double) ih);
// Calculate scaled image dimensions
// adjusting scale factor to nearest "good" value
int adjw = 0;
int adjh = 0;
double scaleMeasure = 1; // 0 - best (no) scale, 1 - impossibly
// bad
if (scaleFactor >= 2) {
// Need to enlarge image more than twice
// Round down scale factor to multiply by integer value
scaleFactor = Math.floor(scaleFactor);
adjw = iw * (int) scaleFactor;
adjh = ih * (int) scaleFactor;
scaleMeasure = 1.0 - 0.5 / scaleFactor;
} else if (scaleFactor >= 1) {
// Don't scale
scaleFactor = 1.0;
adjw = iw;
adjh = ih;
scaleMeasure = 0;
} else if (scaleFactor >= 0.75) {
// Multiply by 3/4
scaleFactor = 0.75;
adjw = iw * 3 / 4;
adjh = ih * 3 / 4;
scaleMeasure = 0.3;
} else if (scaleFactor >= 0.6666) {
// Multiply by 2/3
scaleFactor = 0.6666;
adjw = iw * 2 / 3;
adjh = ih * 2 / 3;
scaleMeasure = 0.33;
} else {
// Multiply size by 1/scaleDivider
// where scaleDivider is minimum possible integer
// larger than 1/scaleFactor
double scaleDivider = Math.ceil(1.0 / scaleFactor);
scaleFactor = 1.0 / scaleDivider;
adjw = (int) Math.round(iw / scaleDivider);
adjh = (int) Math.round(ih / scaleDivider);
scaleMeasure = 1.0 - 1.0 / scaleDivider;
}
double similarity = ((double) width - (double) adjw) / width
+ ((double) height - (double) adjh) / height + // Large
// padding
// is
// bad
scaleMeasure; // Large rescale is bad
if (similarity < bestSimilarity) {
bestSimilarity = similarity;
bestImage = im;
bestWidth = adjw;
bestHeight = adjh;
}
if (similarity == 0)
break;
}
}
if (bestImage == null) {
// No images were found, possibly all are broken
return null;
}
BufferedImage bimage = getBlankImage(width, height);
Graphics2D g = bimage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
int x = (width - bestWidth) / 2;
int y = (height - bestHeight) / 2;
g.drawImage(bestImage, x, y, bestWidth, bestHeight, null);
g.dispose();
return bimage;
}
public static boolean canReplaceChildBackgroundColor(Color background) {
return (background instanceof UIResource)
|| (background instanceof SubstanceColorResource);
}
@SuppressWarnings("unchecked")
public static JTextComponent getTextComponentForTransitions(Component c) {
if (!(c instanceof JComponent))
return null;
TextComponentAware tcaui = (TextComponentAware) ((JComponent) c)
.getClientProperty(TEXT_COMPONENT_AWARE);
if (tcaui != null) {
return tcaui.getTextComponent(c);
}
if (c instanceof JTextComponent) {
return (JTextComponent) c;
}
return null;
}
public static SwingRepaintCallback getTextComponentRepaintCallback(
JTextComponent textComponent) {
Component c = textComponent;
while (c != null) {
if (c instanceof JComponent) {
TextComponentAware tcaui = (TextComponentAware) ((JComponent) c)
.getClientProperty(TEXT_COMPONENT_AWARE);
if (tcaui != null) {
return new SwingRepaintCallback(c);
}
}
c = c.getParent();
}
return new SwingRepaintCallback(textComponent);
}
public static boolean isOpaque(JComponent c) {
return c.isOpaque();
}
}