blob: d0ab6777c6cac1e496f0720bbee444c2458ffef7 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.ui;
import com.intellij.BundleBase;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.*;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.ProgressBarUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Pattern;
/**
* @author max
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public class UIUtil {
private static final AtomicNotNullLazyValue<Boolean> X_RENDER_ACTIVE = new AtomicNotNullLazyValue<Boolean>() {
@NotNull
@Override
protected Boolean compute() {
if (!SystemInfo.isXWindow) {
return false;
}
try {
final Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("sun.awt.X11GraphicsEnvironment");
final Method method = clazz.getMethod("isXRenderAvailable");
return (Boolean)method.invoke(null);
}
catch (Throwable e) {
return false;
}
}
};
private static final String[] STANDARD_FONT_SIZES =
{"8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72"};
public static void applyStyle(@NotNull ComponentStyle componentStyle, @NotNull Component comp) {
if (!(comp instanceof JComponent)) return;
JComponent c = (JComponent)comp;
if (isUnderAquaBasedLookAndFeel()) {
c.putClientProperty("JComponent.sizeVariant",
componentStyle == ComponentStyle.REGULAR ? "regular" : componentStyle == ComponentStyle.SMALL ? "small" : "mini");
}
else {
c.setFont(getFont(
componentStyle == ComponentStyle.REGULAR
? FontSize.NORMAL
: componentStyle == ComponentStyle.SMALL ? FontSize.SMALL : FontSize.MINI, c.getFont()));
}
Container p = c.getParent();
if (p != null) {
SwingUtilities.updateComponentTreeUI(p);
}
}
public static Cursor getTextCursor(final Color backgroundColor) {
return SystemInfo.isMac && ColorUtil.isDark(backgroundColor) ?
MacUIUtil.getInvertedTextCursor() : Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
}
/**
* Draws two horizontal lines, the first at {@code topY}, the second at {@code bottomY}.
* The purpose of this method (and the ground of the name) is to draw two lines framing a horizontal filled rectangle.
*
* @param g Graphics context to draw with.
* @param startX x-start point.
* @param endX x-end point.
* @param topY y-coordinate of the first line.
* @param bottomY y-coordinate of the second line.
* @param color color of the lines.
*/
public static void drawFramingLines(@NotNull Graphics2D g, int startX, int endX, int topY, int bottomY, @NotNull Color color) {
drawLine(g, startX, topY, endX, topY, null, color);
drawLine(g, startX, bottomY, endX, bottomY, null, color);
}
private static final GrayFilter DEFAULT_GRAY_FILTER = new GrayFilter(true, 65);
private static final GrayFilter DARCULA_GRAY_FILTER = new GrayFilter(true, 30);
public static GrayFilter getGrayFilter() {
return isUnderDarcula() ? DARCULA_GRAY_FILTER : DEFAULT_GRAY_FILTER;
}
public static boolean isAppleRetina() {
return isRetina() && SystemInfo.isAppleJvm;
}
public enum FontSize {NORMAL, SMALL, MINI}
public enum ComponentStyle {REGULAR, SMALL, MINI}
public enum FontColor {NORMAL, BRIGHTER}
public static final char MNEMONIC = BundleBase.MNEMONIC;
@NonNls public static final String HTML_MIME = "text/html";
@NonNls public static final String JSLIDER_ISFILLED = "JSlider.isFilled";
@NonNls public static final String ARIAL_FONT_NAME = "Arial";
@NonNls public static final String TABLE_FOCUS_CELL_BACKGROUND_PROPERTY = "Table.focusCellBackground";
@NonNls public static final String CENTER_TOOLTIP_DEFAULT = "ToCenterTooltip";
@NonNls public static final String CENTER_TOOLTIP_STRICT = "ToCenterTooltip.default";
public static final Pattern CLOSE_TAG_PATTERN = Pattern.compile("<\\s*([^<>/ ]+)([^<>]*)/\\s*>", Pattern.CASE_INSENSITIVE);
@NonNls public static final String FOCUS_PROXY_KEY = "isFocusProxy";
public static Key<Integer> KEEP_BORDER_SIDES = Key.create("keepBorderSides");
private static final Logger LOG = Logger.getInstance("#com.intellij.util.ui.UIUtil");
private static final Color UNFOCUSED_SELECTION_COLOR = Gray._212;
private static final Color ACTIVE_HEADER_COLOR = new Color(160, 186, 213);
private static final Color INACTIVE_HEADER_COLOR = Gray._128;
private static final Color BORDER_COLOR = Color.LIGHT_GRAY;
public static final Color AQUA_SEPARATOR_FOREGROUND_COLOR = Gray._190;
public static final Color AQUA_SEPARATOR_BACKGROUND_COLOR = Gray._240;
public static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);
public static final int DEFAULT_HGAP = 10;
public static final int DEFAULT_VGAP = 4;
public static final int LARGE_VGAP = 12;
public static final Insets PANEL_REGULAR_INSETS = new Insets(8, 12, 8, 12);
public static final Insets PANEL_SMALL_INSETS = new Insets(5, 8, 5, 8);
public static final Border DEBUG_MARKER_BORDER = new Border() {
private final Insets empty = new Insets(0, 0, 0, 0);
@Override
public Insets getBorderInsets(Component c) {
return empty;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics g2 = g.create();
try {
g2.setColor(JBColor.RED);
drawDottedRectangle(g2, x, y, x + width - 1, y + height - 1);
}
finally {
g2.dispose();
}
}
@Override
public boolean isBorderOpaque() {
return true;
}
};
// accessed only from EDT
private static final HashMap<Color, BufferedImage> ourAppleDotSamples = new HashMap<Color, BufferedImage>();
private static volatile Pair<String, Integer> ourSystemFontData = null;
@NonNls private static final String ROOT_PANE = "JRootPane.future";
private static final Ref<Boolean> ourRetina = Ref.create(SystemInfo.isMac ? null : false);
private UIUtil() {
}
public static boolean isRetina() {
synchronized (ourRetina) {
if (ourRetina.isNull()) {
ourRetina.set(false); // in case HiDPIScaledImage.drawIntoImage is not called for some reason
if (SystemInfo.isJavaVersionAtLeast("1.6.0_33") && SystemInfo.isAppleJvm) {
if (!"false".equals(System.getProperty("ide.mac.retina"))) {
ourRetina.set(IsRetina.isRetina());
return ourRetina.get();
}
} else if (SystemInfo.isJavaVersionAtLeast("1.7.0_40") && SystemInfo.isOracleJvm) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice device = env.getDefaultScreenDevice();
try {
Field field = device.getClass().getDeclaredField("scale");
if (field != null) {
field.setAccessible(true);
Object scale = field.get(device);
if (scale instanceof Integer && ((Integer)scale).intValue() == 2) {
ourRetina.set(true);
return true;
}
}
}
catch (Exception ignore) {
}
}
ourRetina.set(false);
}
return ourRetina.get();
}
}
public static boolean hasLeakingAppleListeners() {
// in version 1.6.0_29 Apple introduced a memory leak in JViewport class - they add a PropertyChangeListeners to the CToolkit
// but never remove them:
// JViewport.java:
// public JViewport() {
// ...
// final Toolkit toolkit = Toolkit.getDefaultToolkit();
// if(toolkit instanceof CToolkit)
// {
// final boolean isRunningInHiDPI = ((CToolkit)toolkit).runningInHiDPI();
// if(isRunningInHiDPI) setScrollMode(0);
// toolkit.addPropertyChangeListener("apple.awt.contentScaleFactor", new PropertyChangeListener() { ... });
// }
// }
return SystemInfo.isMac && System.getProperty("java.runtime.version").startsWith("1.6.0_29");
}
public static void removeLeakingAppleListeners() {
if (!hasLeakingAppleListeners()) return;
Toolkit toolkit = Toolkit.getDefaultToolkit();
String name = "apple.awt.contentScaleFactor";
for (PropertyChangeListener each : toolkit.getPropertyChangeListeners(name)) {
toolkit.removePropertyChangeListener(name, each);
}
}
public static String getHtmlBody(String text) {
return getHtmlBody(new Html(text));
}
public static String getHtmlBody(Html html) {
String text = html.getText();
String result;
if (!text.startsWith("<html>")) {
result = text.replaceAll("\n", "<br>");
}
else {
final int bodyIdx = text.indexOf("<body>");
final int closedBodyIdx = text.indexOf("</body>");
if (bodyIdx != -1 && closedBodyIdx != -1) {
result = text.substring(bodyIdx + "<body>".length(), closedBodyIdx);
}
else {
text = StringUtil.trimStart(text, "<html>").trim();
text = StringUtil.trimEnd(text, "</html>").trim();
text = StringUtil.trimStart(text, "<body>").trim();
text = StringUtil.trimEnd(text, "</body>").trim();
result = text;
}
}
return html.isKeepFont() ? result : result.replaceAll("<font(.*?)>", "").replaceAll("</font>", "");
}
public static void drawLinePickedOut(Graphics graphics, int x, int y, int x1, int y1) {
if (x == x1) {
int minY = Math.min(y, y1);
int maxY = Math.max(y, y1);
graphics.drawLine(x, minY + 1, x1, maxY - 1);
}
else if (y == y1) {
int minX = Math.min(x, x1);
int maxX = Math.max(x, x1);
graphics.drawLine(minX + 1, y, maxX - 1, y1);
}
else {
drawLine(graphics, x, y, x1, y1);
}
}
public static boolean isReallyTypedEvent(KeyEvent e) {
char c = e.getKeyChar();
if (c < 0x20 || c == 0x7F) return false;
if (SystemInfo.isMac) {
return !e.isMetaDown() && !e.isControlDown();
}
return !e.isAltDown() && !e.isControlDown();
}
public static int getStringY(@NotNull final String string, @NotNull final Rectangle bounds, @NotNull final Graphics2D g) {
final int centerY = bounds.height / 2;
final Font font = g.getFont();
final FontRenderContext frc = g.getFontRenderContext();
final Rectangle stringBounds = font.getStringBounds(string, frc).getBounds();
return (int)(centerY - stringBounds.height / 2.0 - stringBounds.y);
}
public static void setEnabled(Component component, boolean enabled, boolean recursively) {
component.setEnabled(enabled);
if (component instanceof JLabel) {
Color color = enabled ? getLabelForeground() : getLabelDisabledForeground();
if (color != null) {
component.setForeground(color);
}
}
if (recursively && enabled == component.isEnabled()) {
if (component instanceof Container) {
final Container container = (Container)component;
final int subComponentCount = container.getComponentCount();
for (int i = 0; i < subComponentCount; i++) {
setEnabled(container.getComponent(i), enabled, recursively);
}
}
}
}
public static void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
g.drawLine(x1, y1, x2, y2);
}
public static void drawLine(Graphics2D g, int x1, int y1, int x2, int y2, @Nullable Color bgColor, @Nullable Color fgColor) {
Color oldFg = g.getColor();
Color oldBg = g.getBackground();
if (fgColor != null) {
g.setColor(fgColor);
}
if (bgColor != null) {
g.setBackground(bgColor);
}
drawLine(g, x1, y1, x2, y2);
if (fgColor != null) {
g.setColor(oldFg);
}
if (bgColor != null) {
g.setBackground(oldBg);
}
}
@NotNull
public static String[] splitText(String text, FontMetrics fontMetrics, int widthLimit, char separator) {
ArrayList<String> lines = new ArrayList<String>();
String currentLine = "";
StringBuilder currentAtom = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
currentAtom.append(ch);
if (ch == separator) {
currentLine += currentAtom.toString();
currentAtom.setLength(0);
}
String s = currentLine + currentAtom.toString();
int width = fontMetrics.stringWidth(s);
if (width >= widthLimit - fontMetrics.charWidth('w')) {
if (currentLine.length() > 0) {
lines.add(currentLine);
currentLine = "";
}
else {
lines.add(currentAtom.toString());
currentAtom.setLength(0);
}
}
}
String s = currentLine + currentAtom.toString();
if (s.length() > 0) {
lines.add(s);
}
return ArrayUtil.toStringArray(lines);
}
public static void setActionNameAndMnemonic(String text, Action action) {
int mnemoPos = text.indexOf('&');
if (mnemoPos >= 0 && mnemoPos < text.length() - 2) {
String mnemoChar = text.substring(mnemoPos + 1, mnemoPos + 2).trim();
if (mnemoChar.length() == 1) {
action.putValue(Action.MNEMONIC_KEY, Integer.valueOf((int)mnemoChar.charAt(0)));
}
}
text = text.replaceAll("&", "");
action.putValue(Action.NAME, text);
}
public static Font getLabelFont(@NotNull FontSize size) {
return getFont(size, null);
}
@NotNull
public static Font getFont(@NotNull FontSize size, @Nullable Font base) {
if (base == null) base = getLabelFont();
return base.deriveFont(getFontSize(size));
}
public static float getFontSize(FontSize size) {
int defSize = getLabelFont().getSize();
switch (size) {
case SMALL:
return Math.max(defSize - 2f, 11f);
case MINI:
return Math.max(defSize - 4f, 9f);
default:
return defSize;
}
}
public static Color getLabelFontColor(FontColor fontColor) {
Color defColor = getLabelForeground();
if (fontColor == FontColor.BRIGHTER) {
return new JBColor(new Color(Math.min(defColor.getRed() + 50, 255), Math.min(defColor.getGreen() + 50, 255), Math.min(
defColor.getBlue() + 50, 255)), defColor.darker());
}
return defColor;
}
public static int getScrollBarWidth() {
return UIManager.getInt("ScrollBar.width");
}
public static Font getLabelFont() {
return UIManager.getFont("Label.font");
}
public static Color getLabelBackground() {
return UIManager.getColor("Label.background");
}
public static Color getLabelForeground() {
return UIManager.getColor("Label.foreground");
}
public static Color getLabelDisabledForeground() {
final Color color = UIManager.getColor("Label.disabledForeground");
if (color != null) return color;
return UIManager.getColor("Label.disabledText");
}
public static Icon getOptionPanelWarningIcon() {
return UIManager.getIcon("OptionPane.warningIcon");
}
/**
* @deprecated use com.intellij.util.ui.UIUtil#getQuestionIcon()
*/
public static Icon getOptionPanelQuestionIcon() {
return getQuestionIcon();
}
@NotNull
public static String removeMnemonic(@NotNull String s) {
if (s.indexOf('&') != -1) {
s = StringUtil.replace(s, "&", "");
}
if (s.indexOf('_') != -1) {
s = StringUtil.replace(s, "_", "");
}
if (s.indexOf(MNEMONIC) != -1) {
s = StringUtil.replace(s, String.valueOf(MNEMONIC), "");
}
return s;
}
public static int getDisplayMnemonicIndex(@NotNull String s) {
int idx = s.indexOf('&');
if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf('&')) return idx;
idx = s.indexOf(MNEMONIC);
if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf(MNEMONIC)) return idx;
return -1;
}
public static String replaceMnemonicAmpersand(final String value) {
return BundleBase.replaceMnemonicAmpersand(value);
}
public static Color getTableHeaderBackground() {
return UIManager.getColor("TableHeader.background");
}
public static Color getTreeTextForeground() {
return UIManager.getColor("Tree.textForeground");
}
public static Color getTreeSelectionBackground() {
if (isUnderNimbusLookAndFeel()) {
Color color = UIManager.getColor("Tree.selectionBackground");
if (color != null) return color;
color = UIManager.getColor("nimbusSelectionBackground");
if (color != null) return color;
}
return UIManager.getColor("Tree.selectionBackground");
}
public static Color getTreeTextBackground() {
return UIManager.getColor("Tree.textBackground");
}
public static Color getListSelectionForeground() {
final Color color = UIManager.getColor("List.selectionForeground");
if (color == null) {
return UIManager.getColor("List[Selected].textForeground"); // Nimbus
}
return color;
}
public static Color getFieldForegroundColor() {
return UIManager.getColor("field.foreground");
}
public static Color getTableSelectionBackground() {
if (isUnderNimbusLookAndFeel()) {
Color color = UIManager.getColor("Table[Enabled+Selected].textBackground");
if (color != null) return color;
color = UIManager.getColor("nimbusSelectionBackground");
if (color != null) return color;
}
return UIManager.getColor("Table.selectionBackground");
}
public static Color getActiveTextColor() {
return UIManager.getColor("textActiveText");
}
public static Color getInactiveTextColor() {
return UIManager.getColor("textInactiveText");
}
public static Color getSlightlyDarkerColor(Color c) {
float[] hsl = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), new float[3]);
return new Color(Color.HSBtoRGB(hsl[0], hsl[1], hsl[2] - .08f > 0 ? hsl[2] - .08f : hsl[2]));
}
/**
* @deprecated use com.intellij.util.ui.UIUtil#getTextFieldBackground()
*/
public static Color getActiveTextFieldBackgroundColor() {
return getTextFieldBackground();
}
public static Color getInactiveTextFieldBackgroundColor() {
return UIManager.getColor("TextField.inactiveBackground");
}
public static Font getTreeFont() {
return UIManager.getFont("Tree.font");
}
public static Font getListFont() {
return UIManager.getFont("List.font");
}
public static Color getTreeSelectionForeground() {
return UIManager.getColor("Tree.selectionForeground");
}
/**
* @deprecated use com.intellij.util.ui.UIUtil#getInactiveTextColor()
*/
public static Color getTextInactiveTextColor() {
return getInactiveTextColor();
}
public static void installPopupMenuColorAndFonts(final JComponent contentPane) {
LookAndFeel.installColorsAndFont(contentPane, "PopupMenu.background", "PopupMenu.foreground", "PopupMenu.font");
}
public static void installPopupMenuBorder(final JComponent contentPane) {
LookAndFeel.installBorder(contentPane, "PopupMenu.border");
}
/**
* @deprecated Motif is gone (to remove in IDEA 13)
*/
@SuppressWarnings("UnusedDeclaration")
public static boolean isMotifLookAndFeel() {
return false;
}
public static Color getTreeSelectionBorderColor() {
return UIManager.getColor("Tree.selectionBorderColor");
}
public static int getTreeRightChildIndent() {
return UIManager.getInt("Tree.rightChildIndent");
}
public static int getTreeLeftChildIndent() {
return UIManager.getInt("Tree.leftChildIndent");
}
public static Color getToolTipBackground() {
return UIManager.getColor("ToolTip.background");
}
public static Color getToolTipForeground() {
return UIManager.getColor("ToolTip.foreground");
}
public static Color getComboBoxDisabledForeground() {
return UIManager.getColor("ComboBox.disabledForeground");
}
public static Color getComboBoxDisabledBackground() {
return UIManager.getColor("ComboBox.disabledBackground");
}
public static Color getButtonSelectColor() {
return UIManager.getColor("Button.select");
}
public static Integer getPropertyMaxGutterIconWidth(final String propertyPrefix) {
return (Integer)UIManager.get(propertyPrefix + ".maxGutterIconWidth");
}
public static Color getMenuItemDisabledForeground() {
return UIManager.getColor("MenuItem.disabledForeground");
}
public static Object getMenuItemDisabledForegroundObject() {
return UIManager.get("MenuItem.disabledForeground");
}
public static Object getTabbedPanePaintContentBorder(final JComponent c) {
return c.getClientProperty("TabbedPane.paintContentBorder");
}
public static boolean isMenuCrossMenuMnemonics() {
return UIManager.getBoolean("Menu.crossMenuMnemonic");
}
public static Color getTableBackground() {
// Under GTK+ L&F "Table.background" often has main panel color, which looks ugly
return isUnderGTKLookAndFeel() ? getTreeTextBackground() : UIManager.getColor("Table.background");
}
public static Color getTableBackground(final boolean isSelected) {
return isSelected ? getTableSelectionBackground() : getTableBackground();
}
public static Color getTableSelectionForeground() {
if (isUnderNimbusLookAndFeel()) {
return UIManager.getColor("Table[Enabled+Selected].textForeground");
}
return UIManager.getColor("Table.selectionForeground");
}
public static Color getTableForeground() {
return UIManager.getColor("Table.foreground");
}
public static Color getTableForeground(final boolean isSelected) {
return isSelected ? getTableSelectionForeground() : getTableForeground();
}
public static Color getTableGridColor() {
return UIManager.getColor("Table.gridColor");
}
public static Color getListBackground() {
// Under GTK+ L&F "Table.background" often has main panel color, which looks ugly
return isUnderGTKLookAndFeel() ? getTreeTextBackground() : UIManager.getColor("List.background");
}
public static Color getListBackground(boolean isSelected) {
return isSelected ? getListSelectionBackground() : getListBackground();
}
public static Color getListForeground() {
return UIManager.getColor("List.foreground");
}
public static Color getListForeground(boolean isSelected) {
return isSelected ? getListSelectionForeground() : getListForeground();
}
public static Color getPanelBackground() {
return UIManager.getColor("Panel.background");
}
public static Color getTreeBackground() {
return UIManager.getColor("Tree.background");
}
public static Color getTreeForeground() {
return UIManager.getColor("Tree.foreground");
}
public static Color getTableFocusCellBackground() {
return UIManager.getColor(TABLE_FOCUS_CELL_BACKGROUND_PROPERTY);
}
public static Color getListSelectionBackground() {
if (isUnderNimbusLookAndFeel()) {
return UIManager.getColor("List[Selected].textBackground"); // Nimbus
}
return UIManager.getColor("List.selectionBackground");
}
public static Color getListUnfocusedSelectionBackground() {
return isUnderDarcula() ? Gray._52 : UNFOCUSED_SELECTION_COLOR;
}
public static Color getTreeSelectionBackground(boolean focused) {
return focused ? getTreeSelectionBackground() : getTreeUnfocusedSelectionBackground();
}
public static Color getTreeUnfocusedSelectionBackground() {
Color background = getTreeTextBackground();
return ColorUtil.isDark(background) ? new JBColor(Gray._30, new Color(13, 41, 62)) : UNFOCUSED_SELECTION_COLOR;
}
public static Color getTextFieldForeground() {
return UIManager.getColor("TextField.foreground");
}
public static Color getTextFieldBackground() {
return isUnderGTKLookAndFeel() ? UIManager.getColor("EditorPane.background") : UIManager.getColor("TextField.background");
}
public static Font getButtonFont() {
return UIManager.getFont("Button.font");
}
public static Font getToolTipFont() {
return UIManager.getFont("ToolTip.font");
}
public static Color getTabbedPaneBackground() {
return UIManager.getColor("TabbedPane.background");
}
public static void setSliderIsFilled(final JSlider slider, final boolean value) {
slider.putClientProperty("JSlider.isFilled", Boolean.valueOf(value));
}
public static Color getLabelTextForeground() {
return UIManager.getColor("Label.textForeground");
}
public static Color getControlColor() {
return UIManager.getColor("control");
}
public static Font getOptionPaneMessageFont() {
return UIManager.getFont("OptionPane.messageFont");
}
public static Font getMenuFont() {
return UIManager.getFont("Menu.font");
}
public static Color getSeparatorForeground() {
return UIManager.getColor("Separator.foreground");
}
public static Color getSeparatorBackground() {
return UIManager.getColor("Separator.background");
}
public static Color getSeparatorShadow() {
return UIManager.getColor("Separator.shadow");
}
public static Color getSeparatorHighlight() {
return UIManager.getColor("Separator.highlight");
}
public static Color getSeparatorColorUnderNimbus() {
return UIManager.getColor("nimbusBlueGrey");
}
public static Color getSeparatorColor() {
Color separatorColor = getSeparatorForeground();
if (isUnderAlloyLookAndFeel()) {
separatorColor = getSeparatorShadow();
}
if (isUnderNimbusLookAndFeel()) {
separatorColor = getSeparatorColorUnderNimbus();
}
//under GTK+ L&F colors set hard
if (isUnderGTKLookAndFeel()) {
separatorColor = Gray._215;
}
return separatorColor;
}
public static Border getTableFocusCellHighlightBorder() {
return UIManager.getBorder("Table.focusCellHighlightBorder");
}
public static void setLineStyleAngled(final ClientPropertyHolder component) {
component.putClientProperty("JTree.lineStyle", "Angled");
}
public static void setLineStyleAngled(final JTree component) {
component.putClientProperty("JTree.lineStyle", "Angled");
}
public static Color getTableFocusCellForeground() {
return UIManager.getColor("Table.focusCellForeground");
}
/**
* @deprecated use com.intellij.util.ui.UIUtil#getPanelBackground() instead
*/
public static Color getPanelBackgound() {
return getPanelBackground();
}
public static Border getTextFieldBorder() {
return UIManager.getBorder("TextField.border");
}
public static Border getButtonBorder() {
return UIManager.getBorder("Button.border");
}
public static Icon getErrorIcon() {
return UIManager.getIcon("OptionPane.errorIcon");
}
public static Icon getInformationIcon() {
return UIManager.getIcon("OptionPane.informationIcon");
}
public static Icon getQuestionIcon() {
return UIManager.getIcon("OptionPane.questionIcon");
}
public static Icon getWarningIcon() {
return UIManager.getIcon("OptionPane.warningIcon");
}
public static Icon getBalloonInformationIcon() {
return AllIcons.General.BalloonInformation;
}
public static Icon getBalloonWarningIcon() {
return AllIcons.General.BalloonWarning;
}
public static Icon getBalloonErrorIcon() {
return AllIcons.General.BalloonError;
}
public static Icon getRadioButtonIcon() {
return UIManager.getIcon("RadioButton.icon");
}
public static Icon getTreeNodeIcon(boolean expanded, boolean selected, boolean focused) {
boolean white = (selected && focused) || isUnderDarcula();
Icon selectedIcon = getTreeSelectedExpandedIcon();
Icon notSelectedIcon = getTreeExpandedIcon();
int width = Math.max(selectedIcon.getIconWidth(), notSelectedIcon.getIconWidth());
int height = Math.max(selectedIcon.getIconWidth(), notSelectedIcon.getIconWidth());
return new CenteredIcon(expanded ? (white ? getTreeSelectedExpandedIcon() : getTreeExpandedIcon())
: (white ? getTreeSelectedCollapsedIcon() : getTreeCollapsedIcon()),
width, height, false);
}
public static Icon getTreeCollapsedIcon() {
return UIManager.getIcon("Tree.collapsedIcon");
}
public static Icon getTreeExpandedIcon() {
return UIManager.getIcon("Tree.expandedIcon");
}
public static Icon getTreeIcon(boolean expanded) {
return expanded ? getTreeExpandedIcon() : getTreeCollapsedIcon();
}
public static Icon getTreeSelectedCollapsedIcon() {
return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula()
? AllIcons.Mac.Tree_white_right_arrow : getTreeCollapsedIcon();
}
public static Icon getTreeSelectedExpandedIcon() {
return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula()
? AllIcons.Mac.Tree_white_down_arrow : getTreeExpandedIcon();
}
public static Border getTableHeaderCellBorder() {
return UIManager.getBorder("TableHeader.cellBorder");
}
public static Color getWindowColor() {
return UIManager.getColor("window");
}
public static Color getTextAreaForeground() {
return UIManager.getColor("TextArea.foreground");
}
public static Color getOptionPaneBackground() {
return UIManager.getColor("OptionPane.background");
}
/**
* @deprecated Quaqua is gone (to remove in IDEA 13)
*/
@SuppressWarnings("UnusedDeclaration")
public static boolean isUnderQuaquaLookAndFeel() {
return false;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderAlloyLookAndFeel() {
return UIManager.getLookAndFeel().getName().contains("Alloy");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderAlloyIDEALookAndFeel() {
return isUnderAlloyLookAndFeel() && UIManager.getLookAndFeel().getName().contains("IDEA");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderWindowsLookAndFeel() {
return UIManager.getLookAndFeel().getName().equals("Windows");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderWindowsClassicLookAndFeel() {
return UIManager.getLookAndFeel().getName().equals("Windows Classic");
}
/**
* @deprecated Metal is gone (to remove in IDEA 13)
*/
@SuppressWarnings("UnusedDeclaration")
public static boolean isUnderMetalLookAndFeel() {
return false;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderNimbusLookAndFeel() {
return UIManager.getLookAndFeel().getName().contains("Nimbus");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderAquaLookAndFeel() {
return SystemInfo.isMac && UIManager.getLookAndFeel().getName().contains("Mac OS X");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderJGoodiesLookAndFeel() {
return UIManager.getLookAndFeel().getName().contains("JGoodies");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderAquaBasedLookAndFeel() {
return SystemInfo.isMac && (isUnderAquaLookAndFeel() || isUnderDarcula());
}
/**
* @deprecated Motif is gone (to remove in IDEA 13)
*/
@SuppressWarnings("UnusedDeclaration")
public static boolean isUnderMotif() {
return false;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderDarcula() {
return UIManager.getLookAndFeel().getName().contains("Darcula");
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isUnderGTKLookAndFeel() {
return UIManager.getLookAndFeel().getName().contains("GTK");
}
public static final Color GTK_AMBIANCE_TEXT_COLOR = new Color(223, 219, 210);
public static final Color GTK_AMBIANCE_BACKGROUND_COLOR = new Color(67, 66, 63);
@SuppressWarnings({"HardCodedStringLiteral"})
@Nullable
public static String getGtkThemeName() {
final LookAndFeel laf = UIManager.getLookAndFeel();
if (laf != null && "GTKLookAndFeel".equals(laf.getClass().getSimpleName())) {
try {
final Method method = laf.getClass().getDeclaredMethod("getGtkThemeName");
method.setAccessible(true);
final Object theme = method.invoke(laf);
if (theme != null) {
return theme.toString();
}
}
catch (Exception ignored) {
}
}
return null;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static boolean isMurrineBasedTheme() {
final String gtkTheme = getGtkThemeName();
return "Ambiance".equalsIgnoreCase(gtkTheme) ||
"Radiance".equalsIgnoreCase(gtkTheme) ||
"Dust".equalsIgnoreCase(gtkTheme) ||
"Dust Sand".equalsIgnoreCase(gtkTheme);
}
public static Color shade(final Color c, final double factor, final double alphaFactor) {
assert factor >= 0 : factor;
return new Color(
Math.min((int)Math.round(c.getRed() * factor), 255),
Math.min((int)Math.round(c.getGreen() * factor), 255),
Math.min((int)Math.round(c.getBlue() * factor), 255),
Math.min((int)Math.round(c.getAlpha() * alphaFactor), 255)
);
}
public static Color mix(final Color c1, final Color c2, final double factor) {
assert 0 <= factor && factor <= 1.0 : factor;
final double backFactor = 1.0 - factor;
return new Color(
Math.min((int)Math.round(c1.getRed() * backFactor + c2.getRed() * factor), 255),
Math.min((int)Math.round(c1.getGreen() * backFactor + c2.getGreen() * factor), 255),
Math.min((int)Math.round(c1.getBlue() * backFactor + c2.getBlue() * factor), 255)
);
}
public static boolean isFullRowSelectionLAF() {
return isUnderGTKLookAndFeel();
}
public static boolean isUnderNativeMacLookAndFeel() {
return isUnderAquaLookAndFeel() || isUnderDarcula();
}
public static int getListCellHPadding() {
return isUnderNativeMacLookAndFeel() ? 7 : 2;
}
public static int getListCellVPadding() {
return 1;
}
public static Insets getListCellPadding() {
return new Insets(getListCellVPadding(), getListCellHPadding(), getListCellVPadding(), getListCellHPadding());
}
public static Insets getListViewportPadding() {
return isUnderNativeMacLookAndFeel() ? new Insets(1, 0, 1, 0) : new Insets(5, 5, 5, 5);
}
public static boolean isToUseDottedCellBorder() {
return !isUnderNativeMacLookAndFeel();
}
/**
* @deprecated Quaqua is gone (to remove in IDEA 13)
*/
@SuppressWarnings("UnusedDeclaration")
public static void removeQuaquaVisualMarginsIn(Component component) {
}
public static boolean isControlKeyDown(MouseEvent mouseEvent) {
return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
}
public static String[] getValidFontNames(final boolean familyName) {
Set<String> result = new TreeSet<String>();
// adds fonts that can display symbols at [A, Z] + [a, z] + [0, 9]
for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
try {
if (isValidFont(font)) {
result.add(familyName ? font.getFamily() : font.getName());
}
}
catch (Exception ignore) {
// JRE has problems working with the font. Just skip.
}
}
// add label font (if isn't listed among above)
Font labelFont = getLabelFont();
if (labelFont != null && isValidFont(labelFont)) {
result.add(familyName ? labelFont.getFamily() : labelFont.getName());
}
return ArrayUtil.toStringArray(result);
}
public static String[] getStandardFontSizes() {
return STANDARD_FONT_SIZES;
}
public static boolean isValidFont(@NotNull Font font) {
try {
return font.canDisplay('a') &&
font.canDisplay('z') &&
font.canDisplay('A') &&
font.canDisplay('Z') &&
font.canDisplay('0') &&
font.canDisplay('1');
}
catch (Exception e) {
// JRE has problems working with the font. Just skip.
return false;
}
}
public static void setupEnclosingDialogBounds(final JComponent component) {
component.revalidate();
component.repaint();
final Window window = SwingUtilities.windowForComponent(component);
if (window != null &&
(window.getSize().height < window.getMinimumSize().height || window.getSize().width < window.getMinimumSize().width)) {
window.pack();
}
}
public static String displayPropertiesToCSS(Font font, Color fg) {
@NonNls StringBuilder rule = new StringBuilder("body {");
if (font != null) {
rule.append(" font-family: ");
rule.append(font.getFamily());
rule.append(" ; ");
rule.append(" font-size: ");
rule.append(font.getSize());
rule.append("pt ;");
if (font.isBold()) {
rule.append(" font-weight: 700 ; ");
}
if (font.isItalic()) {
rule.append(" font-style: italic ; ");
}
}
if (fg != null) {
rule.append(" color: #");
appendColor(fg, rule);
rule.append(" ; ");
}
rule.append(" }");
return rule.toString();
}
public static void appendColor(final Color color, final StringBuilder sb) {
if (color.getRed() < 16) sb.append('0');
sb.append(Integer.toHexString(color.getRed()));
if (color.getGreen() < 16) sb.append('0');
sb.append(Integer.toHexString(color.getGreen()));
if (color.getBlue() < 16) sb.append('0');
sb.append(Integer.toHexString(color.getBlue()));
}
/**
* @param g graphics.
* @param x top left X coordinate.
* @param y top left Y coordinate.
* @param x1 right bottom X coordinate.
* @param y1 right bottom Y coordinate.
*/
public static void drawDottedRectangle(Graphics g, int x, int y, int x1, int y1) {
int i1;
for (i1 = x; i1 <= x1; i1 += 2) {
drawLine(g, i1, y, i1, y);
}
for (i1 = i1 != x1 + 1 ? y + 2 : y + 1; i1 <= y1; i1 += 2) {
drawLine(g, x1, i1, x1, i1);
}
for (i1 = i1 != y1 + 1 ? x1 - 2 : x1 - 1; i1 >= x; i1 -= 2) {
drawLine(g, i1, y1, i1, y1);
}
for (i1 = i1 != x - 1 ? y1 - 2 : y1 - 1; i1 >= y; i1 -= 2) {
drawLine(g, x, i1, x, i1);
}
}
/**
* Should be invoked only in EDT.
*
* @param g Graphics surface
* @param startX Line start X coordinate
* @param endX Line end X coordinate
* @param lineY Line Y coordinate
* @param bgColor Background color (optional)
* @param fgColor Foreground color (optional)
* @param opaque If opaque the image will be dr
*/
public static void drawBoldDottedLine(final Graphics2D g,
final int startX,
final int endX,
final int lineY,
final Color bgColor,
final Color fgColor,
final boolean opaque) {
if ((SystemInfo.isMac && !isRetina()) || SystemInfo.isLinux) {
drawAppleDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
}
else {
drawBoringDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
}
}
public static void drawSearchMatch(final Graphics2D g,
final int startX,
final int endX,
final int height) {
Color c1 = new Color(255, 234, 162);
Color c2 = new Color(255, 208, 66);
drawSearchMatch(g, startX, endX, height, c1, c2);
}
public static void drawSearchMatch(Graphics2D g, int startX, int endX, int height, Color c1, Color c2) {
final boolean drawRound = endX - startX > 4;
final Composite oldComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
g.setPaint(getGradientPaint(startX, 2, c1, startX, height - 5, c2));
g.fillRect(startX, 3, endX - startX, height - 5);
if (drawRound) {
g.drawLine(startX - 1, 4, startX - 1, height - 4);
g.drawLine(endX, 4, endX, height - 4);
g.setColor(new Color(100, 100, 100, 50));
g.drawLine(startX - 1, 4, startX - 1, height - 4);
g.drawLine(endX, 4, endX, height - 4);
g.drawLine(startX, 3, endX - 1, 3);
g.drawLine(startX, height - 3, endX - 1, height - 3);
}
g.setComposite(oldComposite);
}
public static void drawRectPickedOut(Graphics2D g, int x, int y, int w, int h) {
g.drawLine(x + 1, y, x + w - 1, y);
g.drawLine(x + w, y + 1, x + w, y + h - 1);
g.drawLine(x + w - 1, y + h, x + 1, y + h);
g.drawLine(x, y + 1, x, y + h - 1);
}
private static void drawBoringDottedLine(final Graphics2D g,
final int startX,
final int endX,
final int lineY,
final Color bgColor,
final Color fgColor,
final boolean opaque) {
final Color oldColor = g.getColor();
// Fill 2 lines with background color
if (opaque && bgColor != null) {
g.setColor(bgColor);
drawLine(g, startX, lineY, endX, lineY);
drawLine(g, startX, lineY + 1, endX, lineY + 1);
}
// Draw dotted line:
//
// CCC CCC CCC ...
// CCC CCC CCC ...
//
// (where "C" - colored pixel, " " - white pixel)
final int step = 4;
final int startPosCorrection = startX % step < 3 ? 0 : 1;
g.setColor(fgColor != null ? fgColor : oldColor);
// Now draw bold line segments
for (int dotXi = (startX / step + startPosCorrection) * step; dotXi < endX; dotXi += step) {
g.drawLine(dotXi, lineY, dotXi + 1, lineY);
g.drawLine(dotXi, lineY + 1, dotXi + 1, lineY + 1);
}
// restore color
g.setColor(oldColor);
}
public static void drawGradientHToolbarBackground(final Graphics g, final int width, final int height) {
final Graphics2D g2d = (Graphics2D)g;
g2d.setPaint(getGradientPaint(0, 0, Gray._215, 0, height, Gray._200));
g2d.fillRect(0, 0, width, height);
}
public static void drawHeader(Graphics g, int x, int width, int height, boolean active, boolean drawTopLine) {
drawHeader(g, x, width, height, active, false, drawTopLine, true);
}
public static void drawHeader(Graphics g,
int x,
int width,
int height,
boolean active,
boolean toolWindow,
boolean drawTopLine,
boolean drawBottomLine) {
g.setColor(getPanelBackground());
g.fillRect(x, 0, width, height);
((Graphics2D)g).setPaint(getGradientPaint(0, 0, new Color(0, 0, 0, 5), 0, height, new Color(0, 0, 0, 20)));
g.fillRect(x, 0, width, height);
g.setColor(new Color(0, 0, 0, toolWindow ? 90 : 50));
if (drawTopLine) g.drawLine(x, 0, width, 0);
if (drawBottomLine) g.drawLine(x, height - 1, width, height - 1);
g.setColor(isUnderDarcula() ? Gray._255.withAlpha(30) : new Color(255, 255, 255, 100));
g.drawLine(x, drawTopLine ? 1 : 0, width, drawTopLine ? 1 : 0);
if (active) {
g.setColor(new Color(100, 150, 230, toolWindow ? 50 : 30));
g.fillRect(x, 0, width, height);
}
}
public static void drawDoubleSpaceDottedLine(final Graphics2D g,
final int start,
final int end,
final int xOrY,
final Color fgColor,
boolean horizontal) {
g.setColor(fgColor);
for (int dot = start; dot < end; dot += 3) {
if (horizontal) {
g.drawLine(dot, xOrY, dot, xOrY);
}
else {
g.drawLine(xOrY, dot, xOrY, dot);
}
}
}
private static void drawAppleDottedLine(final Graphics2D g,
final int startX,
final int endX,
final int lineY,
final Color bgColor,
final Color fgColor,
final boolean opaque) {
final Color oldColor = g.getColor();
// Fill 3 lines with background color
if (opaque && bgColor != null) {
g.setColor(bgColor);
drawLine(g, startX, lineY, endX, lineY);
drawLine(g, startX, lineY + 1, endX, lineY + 1);
drawLine(g, startX, lineY + 2, endX, lineY + 2);
}
// Draw apple like dotted line:
//
// CCC CCC CCC ...
// CCC CCC CCC ...
// CCC CCC CCC ...
//
// (where "C" - colored pixel, " " - white pixel)
final int step = 4;
final int startPosCorrection = startX % step < 3 ? 0 : 1;
// Optimization - lets draw dotted line using dot sample image.
// draw one dot by pixel:
// save old settings
final Composite oldComposite = g.getComposite();
// draw image "over" on top of background
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
// sample
final BufferedImage image = getAppleDotStamp(fgColor, oldColor);
// Now copy our dot several times
final int dotX0 = (startX / step + startPosCorrection) * step;
for (int dotXi = dotX0; dotXi < endX; dotXi += step) {
g.drawImage(image, dotXi, lineY, null);
}
//restore previous settings
g.setComposite(oldComposite);
}
private static BufferedImage getAppleDotStamp(final Color fgColor,
final Color oldColor) {
final Color color = fgColor != null ? fgColor : oldColor;
// let's avoid of generating tons of GC and store samples for different colors
BufferedImage sample = ourAppleDotSamples.get(color);
if (sample == null) {
sample = createAppleDotStamp(color);
ourAppleDotSamples.put(color, sample);
}
return sample;
}
private static BufferedImage createAppleDotStamp(final Color color) {
final BufferedImage image = createImage(3, 3, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g = image.createGraphics();
g.setColor(color);
// Each dot:
// | 20% | 50% | 20% |
// | 80% | 80% | 80% |
// | 50% | 100% | 50% |
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .2f));
g.drawLine(0, 0, 0, 0);
g.drawLine(2, 0, 2, 0);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
g.drawLine(0, 1, 2, 1);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f));
g.drawLine(1, 2, 1, 2);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .5f));
g.drawLine(1, 0, 1, 0);
g.drawLine(0, 2, 0, 2);
g.drawLine(2, 2, 2, 2);
// dispose graphics
g.dispose();
return image;
}
public static void applyRenderingHints(final Graphics g) {
Toolkit tk = Toolkit.getDefaultToolkit();
//noinspection HardCodedStringLiteral
Map map = (Map)tk.getDesktopProperty("awt.font.desktophints");
if (map != null) {
((Graphics2D)g).addRenderingHints(map);
}
}
public static BufferedImage createImage(int width, int height, int type) {
if (isRetina()) {
return RetinaImage.create(width, height, type);
}
//noinspection UndesirableClassUsage
return new BufferedImage(width, height, type);
}
public static void drawImage(Graphics g, Image image, int x, int y, ImageObserver observer) {
if (image instanceof JBHiDPIScaledImage) {
final Graphics2D newG = (Graphics2D)g.create(x, y, image.getWidth(observer), image.getHeight(observer));
newG.scale(0.5, 0.5);
Image img = ((JBHiDPIScaledImage)image).getDelegate();
if (img == null) {
img = image;
}
newG.drawImage(img, 0, 0, observer);
newG.scale(1, 1);
newG.dispose();
} else {
g.drawImage(image, x, y, observer);
}
}
public static void drawImage(Graphics g, BufferedImage image, BufferedImageOp op, int x, int y) {
if (image instanceof JBHiDPIScaledImage) {
final Graphics2D newG = (Graphics2D)g.create(x, y, image.getWidth(null), image.getHeight(null));
newG.scale(0.5, 0.5);
Image img = ((JBHiDPIScaledImage)image).getDelegate();
if (img == null) {
img = image;
}
newG.drawImage((BufferedImage)img, op, 0, 0);
newG.scale(1, 1);
newG.dispose();
} else {
((Graphics2D)g).drawImage(image, op, x, y);
}
}
public static void paintWithXorOnRetina(@NotNull Dimension size, @NotNull Graphics g, Consumer<Graphics2D> paintRoutine) {
paintWithXorOnRetina(size, g, true, paintRoutine);
}
/**
* Direct painting into component's graphics with XORMode is broken on retina-mode so we need to paint into an intermediate buffer first.
*/
public static void paintWithXorOnRetina(@NotNull Dimension size,
@NotNull Graphics g,
boolean useRetinaCondition,
Consumer<Graphics2D> paintRoutine) {
if (!useRetinaCondition || !isRetina() || Registry.is("ide.mac.retina.disableDrawingFix", false)) {
paintRoutine.consume((Graphics2D)g);
}
else {
Rectangle rect = g.getClipBounds();
if (rect == null) rect = new Rectangle(size);
//noinspection UndesirableClassUsage
Image image = new BufferedImage(rect.width * 2, rect.height * 2, BufferedImage.TYPE_INT_RGB);
Graphics2D imageGraphics = (Graphics2D)image.getGraphics();
imageGraphics.scale(2, 2);
imageGraphics.translate(-rect.x, -rect.y);
imageGraphics.setClip(rect.x, rect.y, rect.width, rect.height);
paintRoutine.consume(imageGraphics);
image.flush();
imageGraphics.dispose();
((Graphics2D)g).scale(0.5, 0.5);
g.drawImage(image, rect.x * 2, rect.y * 2, null);
}
}
/**
* Configures composite to use for drawing text with the given graphics container.
* <p/>
* The whole idea is that <a href="http://en.wikipedia.org/wiki/X_Rendering_Extension">XRender-based</a> pipeline doesn't support
* {@link AlphaComposite#SRC} and we should use {@link AlphaComposite#SRC_OVER} instead.
*
* @param g target graphics container
*/
public static void setupComposite(@NotNull Graphics2D g) {
g.setComposite(X_RENDER_ACTIVE.getValue() ? AlphaComposite.SrcOver : AlphaComposite.Src);
}
@TestOnly
public static void dispatchAllInvocationEvents() {
assert SwingUtilities.isEventDispatchThread() : Thread.currentThread();
final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
while (true) {
AWTEvent event = eventQueue.peekEvent();
if (event == null) break;
try {
AWTEvent event1 = eventQueue.getNextEvent();
if (event1 instanceof InvocationEvent) {
((InvocationEvent)event1).dispatch();
}
}
catch (Exception e) {
LOG.error(e); //?
}
}
}
@TestOnly
public static void pump() {
assert !SwingUtilities.isEventDispatchThread();
final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
queue.offer(queue);
}
});
try {
queue.take();
}
catch (InterruptedException e) {
LOG.error(e);
}
}
public static void addAwtListener(final AWTEventListener listener, long mask, Disposable parent) {
Toolkit.getDefaultToolkit().addAWTEventListener(listener, mask);
Disposer.register(parent, new Disposable() {
public void dispose() {
Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
}
});
}
public static void drawVDottedLine(Graphics2D g, int lineX, int startY, int endY, @Nullable final Color bgColor, final Color fgColor) {
if (bgColor != null) {
g.setColor(bgColor);
drawLine(g, lineX, startY, lineX, endY);
}
g.setColor(fgColor);
for (int i = (startY / 2) * 2; i < endY; i += 2) {
g.drawRect(lineX, i, 0, 0);
}
}
public static void drawHDottedLine(Graphics2D g, int startX, int endX, int lineY, @Nullable final Color bgColor, final Color fgColor) {
if (bgColor != null) {
g.setColor(bgColor);
drawLine(g, startX, lineY, endX, lineY);
}
g.setColor(fgColor);
for (int i = (startX / 2) * 2; i < endX; i += 2) {
g.drawRect(i, lineY, 0, 0);
}
}
public static void drawDottedLine(Graphics2D g, int x1, int y1, int x2, int y2, @Nullable final Color bgColor, final Color fgColor) {
if (x1 == x2) {
drawVDottedLine(g, x1, y1, y2, bgColor, fgColor);
}
else if (y1 == y2) {
drawHDottedLine(g, x1, x2, y1, bgColor, fgColor);
}
else {
throw new IllegalArgumentException("Only vertical or horizontal lines are supported");
}
}
public static void drawStringWithHighlighting(Graphics g, String s, int x, int y, Color foreground, Color highlighting) {
g.setColor(highlighting);
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
g.drawString(s, i, j);
}
}
g.setColor(foreground);
g.drawString(s, x, y);
}
public static boolean isFocusAncestor(@NotNull final JComponent component) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (owner == null) return false;
if (owner == component) return true;
return SwingUtilities.isDescendingFrom(owner, component);
}
public static boolean isCloseClick(MouseEvent e) {
return isCloseClick(e, MouseEvent.MOUSE_PRESSED);
}
public static boolean isCloseClick(MouseEvent e, int effectiveType) {
if (e.isPopupTrigger() || e.getID() != effectiveType) return false;
return e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown();
}
public static boolean isActionClick(MouseEvent e) {
return isActionClick(e, MouseEvent.MOUSE_PRESSED);
}
public static boolean isActionClick(MouseEvent e, int effectiveType) {
return isActionClick(e, effectiveType, false);
}
public static boolean isActionClick(MouseEvent e, int effectiveType, boolean allowShift) {
if (!allowShift && isCloseClick(e) || e.isPopupTrigger() || e.getID() != effectiveType) return false;
return e.getButton() == MouseEvent.BUTTON1;
}
@NotNull
public static Color getBgFillColor(@NotNull JComponent c) {
final Component parent = findNearestOpaque(c);
return parent == null ? c.getBackground() : parent.getBackground();
}
@Nullable
public static Component findNearestOpaque(JComponent c) {
Component eachParent = c;
while (eachParent != null) {
if (eachParent.isOpaque()) return eachParent;
eachParent = eachParent.getParent();
}
return eachParent;
}
@NonNls
public static String getCssFontDeclaration(final Font font) {
return getCssFontDeclaration(font, null, null, null);
}
@Language("HTML")
@NonNls
public static String getCssFontDeclaration(final Font font, @Nullable Color fgColor, @Nullable Color linkColor, @Nullable String liImg) {
URL resource = liImg != null ? SystemInfo.class.getResource(liImg) : null;
@NonNls String fontFamilyAndSize = "font-family:" + font.getFamily() + "; font-size:" + font.getSize() + ";";
@NonNls @Language("HTML")
String body = "body, div, td, p {" + fontFamilyAndSize + " " + (fgColor != null ? "color:" + ColorUtil.toHex(fgColor) : "") + "}";
if (resource != null) {
body += "ul {list-style-image: " + resource.toExternalForm() + "}";
}
@NonNls String link = linkColor != null ? "a {" + fontFamilyAndSize + " color:" + ColorUtil.toHex(linkColor) + "}" : "";
return "<style> " + body + " " + link + "</style>";
}
public static boolean isWinLafOnVista() {
return SystemInfo.isWinVistaOrNewer && "Windows".equals(UIManager.getLookAndFeel().getName());
}
public static boolean isStandardMenuLAF() {
return isWinLafOnVista() ||
isUnderNimbusLookAndFeel() ||
isUnderGTKLookAndFeel();
}
public static Color getFocusedFillColor() {
return toAlpha(getListSelectionBackground(), 100);
}
public static Color getFocusedBoundsColor() {
return getBoundsColor();
}
public static Color getBoundsColor() {
return getBorderColor();
}
public static Color getBoundsColor(boolean focused) {
return focused ? getFocusedBoundsColor() : getBoundsColor();
}
public static Color toAlpha(final Color color, final int alpha) {
Color actual = color != null ? color : Color.black;
return new Color(actual.getRed(), actual.getGreen(), actual.getBlue(), alpha);
}
public static void requestFocus(@NotNull final JComponent c) {
if (c.isShowing()) {
c.requestFocus();
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
c.requestFocus();
}
});
}
}
//todo maybe should do for all kind of listeners via the AWTEventMulticaster class
public static void dispose(final Component c) {
if (c == null) return;
final MouseListener[] mouseListeners = c.getMouseListeners();
for (MouseListener each : mouseListeners) {
c.removeMouseListener(each);
}
final MouseMotionListener[] motionListeners = c.getMouseMotionListeners();
for (MouseMotionListener each : motionListeners) {
c.removeMouseMotionListener(each);
}
final MouseWheelListener[] mouseWheelListeners = c.getMouseWheelListeners();
for (MouseWheelListener each : mouseWheelListeners) {
c.removeMouseWheelListener(each);
}
}
public static void disposeProgress(final JProgressBar progress) {
if (!isUnderNativeMacLookAndFeel()) return;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (isToDispose(progress)) {
progress.getUI().uninstallUI(progress);
progress.putClientProperty("isDisposed", Boolean.TRUE);
}
}
});
}
private static boolean isToDispose(final JProgressBar progress) {
final ProgressBarUI ui = progress.getUI();
if (ui == null) return false;
if (Boolean.TYPE.equals(progress.getClientProperty("isDisposed"))) return false;
try {
final Field progressBarField = ReflectionUtil.findField(ui.getClass(), JProgressBar.class, "progressBar");
progressBarField.setAccessible(true);
return progressBarField.get(ui) != null;
}
catch (NoSuchFieldException e) {
return true;
}
catch (IllegalAccessException e) {
return true;
}
}
@Nullable
public static Component findUltimateParent(Component c) {
if (c == null) return null;
Component eachParent = c;
while (true) {
if (eachParent.getParent() == null) return eachParent;
eachParent = eachParent.getParent();
}
}
/** @deprecated use {@linkplain Dialog#setModalityType(Dialog.ModalityType)} (to remove in IDEA 13) */
@SuppressWarnings("UnusedDeclaration")
public static void setToolkitModal(final JDialog dialog) {
dialog.setModalityType(Dialog.ModalityType.TOOLKIT_MODAL);
}
/** @deprecated use {@linkplain Window#setIconImages(List)} (to remove in IDEA 13) */
@SuppressWarnings("UnusedDeclaration")
public static void updateDialogIcon(final JDialog dialog, final List<Image> images) {
dialog.setIconImages(images);
}
/** @deprecated outdated (to remove in IDEA 13) */
@SuppressWarnings("UnusedDeclaration")
public static boolean hasJdk6Dialogs() {
return true;
}
public static Color getHeaderActiveColor() {
return ACTIVE_HEADER_COLOR;
}
public static Color getHeaderInactiveColor() {
return INACTIVE_HEADER_COLOR;
}
public static Color getBorderColor() {
return isUnderDarcula() ? Gray._50 : BORDER_COLOR;
}
public static Font getTitledBorderFont() {
Font defFont = getLabelFont();
return defFont.deriveFont(Math.max(defFont.getSize() - 2f, 11f));
}
/**
* @deprecated use getBorderColor instead
*/
public static Color getBorderInactiveColor() {
return getBorderColor();
}
/**
* @deprecated use getBorderColor instead
*/
public static Color getBorderActiveColor() {
return getBorderColor();
}
/**
* @deprecated use getBorderColor instead
*/
public static Color getBorderSeparatorColor() {
return getBorderColor();
}
public static HTMLEditorKit getHTMLEditorKit() {
final HTMLEditorKit kit = new HTMLEditorKit();
Font font = getLabelFont();
@NonNls String family = font != null ? font.getFamily() : "Tahoma";
int size = font != null ? font.getSize() : 11;
final StyleSheet styleSheet = kit.getStyleSheet();
styleSheet.addRule(String.format("body, div, p { font-family: %s; font-size: %s; } p { margin-top: 0; }", family, size));
kit.setStyleSheet(styleSheet);
return kit;
}
public static void removeScrollBorder(final Component c) {
new AwtVisitor(c) {
public boolean visit(final Component component) {
if (component instanceof JScrollPane) {
if (!hasNonPrimitiveParents(c, component)) {
final JScrollPane scrollPane = (JScrollPane)component;
Integer keepBorderSides = (Integer)scrollPane.getClientProperty(KEEP_BORDER_SIDES);
if (keepBorderSides != null) {
if (scrollPane.getBorder() instanceof LineBorder) {
Color color = ((LineBorder)scrollPane.getBorder()).getLineColor();
scrollPane.setBorder(new SideBorder(color, keepBorderSides.intValue()));
}
else {
scrollPane.setBorder(new SideBorder(getBoundsColor(), keepBorderSides.intValue()));
}
}
else {
scrollPane.setBorder(new SideBorder(getBoundsColor(), SideBorder.NONE));
}
}
}
return false;
}
};
}
public static boolean hasNonPrimitiveParents(Component stopParent, Component c) {
Component eachParent = c.getParent();
while (true) {
if (eachParent == null || eachParent == stopParent) return false;
if (!isPrimitive(eachParent)) return true;
eachParent = eachParent.getParent();
}
}
public static boolean isPrimitive(Component c) {
return c instanceof JPanel || c instanceof JLayeredPane;
}
public static Point getCenterPoint(Dimension container, Dimension child) {
return getCenterPoint(new Rectangle(new Point(), container), child);
}
public static Point getCenterPoint(Rectangle container, Dimension child) {
Point result = new Point();
Point containerLocation = container.getLocation();
Dimension containerSize = container.getSize();
result.x = containerLocation.x + containerSize.width / 2 - child.width / 2;
result.y = containerLocation.y + containerSize.height / 2 - child.height / 2;
return result;
}
public static String toHtml(String html) {
return toHtml(html, 0);
}
@NonNls
public static String toHtml(String html, final int hPadding) {
html = CLOSE_TAG_PATTERN.matcher(html).replaceAll("<$1$2></$1>");
Font font = getLabelFont();
@NonNls String family = font != null ? font.getFamily() : "Tahoma";
int size = font != null ? font.getSize() : 11;
return "<html><style>body { font-family: "
+ family + "; font-size: "
+ size + ";} ul li {list-style-type:circle;}</style>"
+ addPadding(html, hPadding) + "</html>";
}
public static String addPadding(final String html, int hPadding) {
return String.format("<p style=\"margin: 0 %dpx 0 %dpx;\">%s</p>", hPadding, hPadding, html);
}
public static String convertSpace2Nbsp(String html) {
@NonNls StringBuilder result = new StringBuilder();
int currentPos = 0;
int braces = 0;
while (currentPos < html.length()) {
String each = html.substring(currentPos, currentPos + 1);
if ("<".equals(each)) {
braces++;
}
else if (">".equals(each)) {
braces--;
}
if (" ".equals(each) && braces == 0) {
result.append("&nbsp;");
}
else {
result.append(each);
}
currentPos++;
}
return result.toString();
}
public static void invokeLaterIfNeeded(@NotNull Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
}
else {
SwingUtilities.invokeLater(runnable);
}
}
/**
* Invoke and wait in the event dispatch thread
* or in the current thread if the current thread
* is event queue thread.
*
* @param runnable a runnable to invoke
*/
public static void invokeAndWaitIfNeeded(@NotNull Runnable runnable) {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
}
else {
try {
SwingUtilities.invokeAndWait(runnable);
}
catch (Exception e) {
LOG.error(e);
}
}
}
public static boolean isFocusProxy(@Nullable Component c) {
return c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty(FOCUS_PROXY_KEY));
}
public static void setFocusProxy(JComponent c, boolean isProxy) {
c.putClientProperty(FOCUS_PROXY_KEY, isProxy ? Boolean.TRUE : null);
}
public static void maybeInstall(InputMap map, String action, KeyStroke stroke) {
if (map.get(stroke) == null) {
map.put(stroke, action);
}
}
/**
* Avoid blinking while changing background.
*
* @param component component.
* @param background new background.
*/
public static void changeBackGround(final Component component, final Color background) {
final Color oldBackGround = component.getBackground();
if (background == null || !background.equals(oldBackGround)) {
component.setBackground(background);
}
}
public static void initDefaultLAF() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
if (ourSystemFontData == null) {
final Font font = getLabelFont();
ourSystemFontData = Pair.create(font.getName(), font.getSize());
}
}
catch (Exception ignored) { }
}
@Nullable
public static Pair<String, Integer> getSystemFontData() {
return ourSystemFontData;
}
public static void addKeyboardShortcut(final JComponent target, final AbstractButton button, final KeyStroke keyStroke) {
target.registerKeyboardAction(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (button.isEnabled()) {
button.doClick();
}
}
},
keyStroke,
JComponent.WHEN_FOCUSED
);
}
public static void installComboBoxCopyAction(JComboBox comboBox) {
final Component editorComponent = comboBox.getEditor().getEditorComponent();
if (!(editorComponent instanceof JTextComponent)) return;
final InputMap inputMap = ((JTextComponent)editorComponent).getInputMap();
for (KeyStroke keyStroke : inputMap.allKeys()) {
if (DefaultEditorKit.copyAction.equals(inputMap.get(keyStroke))) {
comboBox.getInputMap().put(keyStroke, DefaultEditorKit.copyAction);
}
}
comboBox.getActionMap().put(DefaultEditorKit.copyAction, new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
if (!(e.getSource() instanceof JComboBox)) return;
final JComboBox comboBox = (JComboBox)e.getSource();
final String text;
final Object selectedItem = comboBox.getSelectedItem();
if (selectedItem instanceof String) {
text = (String)selectedItem;
}
else {
final Component component =
comboBox.getRenderer().getListCellRendererComponent(new JList(), selectedItem, 0, false, false);
if (component instanceof JLabel) {
text = ((JLabel)component).getText();
}
else if (component != null) {
final String str = component.toString();
// skip default Component.toString and handle SimpleColoredComponent case
text = str == null || str.startsWith(component.getClass().getName() + "[") ? null : str;
}
else {
text = null;
}
}
if (text != null) {
final JTextField textField = new JTextField(text);
textField.selectAll();
textField.copy();
}
}
});
}
@Nullable
public static ComboPopup getComboBoxPopup(JComboBox comboBox) {
final ComboBoxUI ui = comboBox.getUI();
if (ui instanceof BasicComboBoxUI) {
try {
final Field popup = BasicComboBoxUI.class.getDeclaredField("popup");
popup.setAccessible(true);
return (ComboPopup)popup.get(ui);
}
catch (NoSuchFieldException e) {
return null;
}
catch (IllegalAccessException e) {
return null;
}
}
return null;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static void fixFormattedField(JFormattedTextField field) {
if (SystemInfo.isMac) {
final Toolkit toolkit = Toolkit.getDefaultToolkit();
final int commandKeyMask = toolkit.getMenuShortcutKeyMask();
final InputMap inputMap = field.getInputMap();
final KeyStroke copyKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, commandKeyMask);
inputMap.put(copyKeyStroke, "copy-to-clipboard");
final KeyStroke pasteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, commandKeyMask);
inputMap.put(pasteKeyStroke, "paste-from-clipboard");
final KeyStroke cutKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, commandKeyMask);
inputMap.put(cutKeyStroke, "cut-to-clipboard");
}
}
public static boolean isPrinting(Graphics g) {
return g instanceof PrintGraphics;
}
public static int getSelectedButton(ButtonGroup group) {
Enumeration<AbstractButton> enumeration = group.getElements();
int i = 0;
while (enumeration.hasMoreElements()) {
AbstractButton button = enumeration.nextElement();
if (group.isSelected(button.getModel())) {
return i;
}
i++;
}
return -1;
}
public static void setSelectedButton(ButtonGroup group, int index) {
Enumeration<AbstractButton> enumeration = group.getElements();
int i = 0;
while (enumeration.hasMoreElements()) {
AbstractButton button = enumeration.nextElement();
group.setSelected(button.getModel(), index == i);
i++;
}
}
public static boolean isSelectionButtonDown(MouseEvent e) {
return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
}
@SuppressWarnings("deprecation")
public static void setComboBoxEditorBounds(int x, int y, int width, int height, JComponent editor) {
if (SystemInfo.isMac && isUnderAquaLookAndFeel()) {
// fix for too wide combobox editor, see AquaComboBoxUI.layoutContainer:
// it adds +4 pixels to editor width. WTF?!
editor.reshape(x, y, width - 4, height - 1);
}
else {
editor.reshape(x, y, width, height);
}
}
public static int fixComboBoxHeight(final int height) {
return SystemInfo.isMac && isUnderAquaLookAndFeel() ? 28 : height;
}
@Nullable
public static <T> T getParentOfType(Class<? extends T> cls, Component c) {
Component eachParent = c;
while (eachParent != null) {
if (cls.isAssignableFrom(eachParent.getClass())) {
@SuppressWarnings({"unchecked"}) final T t = (T)eachParent;
return t;
}
eachParent = eachParent.getParent();
}
return null;
}
public static void scrollListToVisibleIfNeeded(@NotNull final JList list) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final int selectedIndex = list.getSelectedIndex();
if (selectedIndex >= 0) {
final Rectangle visibleRect = list.getVisibleRect();
final Rectangle cellBounds = list.getCellBounds(selectedIndex, selectedIndex);
if (!visibleRect.contains(cellBounds)) {
list.scrollRectToVisible(cellBounds);
}
}
}
});
}
@Nullable
public static <T extends JComponent> T findComponentOfType(JComponent parent, Class<T> cls) {
if (parent == null || cls.isAssignableFrom(parent.getClass())) {
@SuppressWarnings({"unchecked"}) final T t = (T)parent;
return t;
}
for (Component component : parent.getComponents()) {
if (component instanceof JComponent) {
T comp = findComponentOfType((JComponent)component, cls);
if (comp != null) return comp;
}
}
return null;
}
public static <T extends JComponent> List<T> findComponentsOfType(JComponent parent, Class<T> cls) {
final ArrayList<T> result = new ArrayList<T>();
findComponentsOfType(parent, cls, result);
return result;
}
private static <T extends JComponent> void findComponentsOfType(JComponent parent, Class<T> cls, ArrayList<T> result) {
if (parent == null) return;
if (cls.isAssignableFrom(parent.getClass())) {
@SuppressWarnings({"unchecked"}) final T t = (T)parent;
result.add(t);
}
for (Component c : parent.getComponents()) {
if (c instanceof JComponent) {
findComponentsOfType((JComponent)c, cls, result);
}
}
}
public static class TextPainter {
private List<Pair<String, LineInfo>> myLines = new ArrayList<Pair<String, LineInfo>>();
private boolean myDrawShadow;
private Color myShadowColor;
private float myLineSpacing;
public TextPainter() {
myDrawShadow = /*isUnderAquaLookAndFeel() ||*/ isUnderDarcula();
myShadowColor = isUnderDarcula() ? Gray._0.withAlpha(100) : Gray._220;
myLineSpacing = 1.0f;
}
public TextPainter withShadow(final boolean drawShadow) {
myDrawShadow = drawShadow;
return this;
}
public TextPainter withShadow(final boolean drawShadow, final Color shadowColor) {
myDrawShadow = drawShadow;
myShadowColor = shadowColor;
return this;
}
public TextPainter withLineSpacing(final float lineSpacing) {
myLineSpacing = lineSpacing;
return this;
}
public TextPainter appendLine(final String text) {
if (text == null || text.length() == 0) return this;
myLines.add(Pair.create(text, new LineInfo()));
return this;
}
public TextPainter underlined(@Nullable final Color color) {
if (!myLines.isEmpty()) {
final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
info.underlined = true;
info.underlineColor = color;
}
return this;
}
public TextPainter withBullet(final char c) {
if (!myLines.isEmpty()) {
final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
info.withBullet = true;
info.bulletChar = c;
}
return this;
}
public TextPainter withBullet() {
return withBullet('\u2022');
}
public TextPainter underlined() {
return underlined(null);
}
public TextPainter smaller() {
if (!myLines.isEmpty()) {
myLines.get(myLines.size() - 1).getSecond().smaller = true;
}
return this;
}
public TextPainter center() {
if (!myLines.isEmpty()) {
myLines.get(myLines.size() - 1).getSecond().center = true;
}
return this;
}
/**
* _position(block width, block height) => (x, y) of the block
*/
public void draw(@NotNull final Graphics g, final PairFunction<Integer, Integer, Pair<Integer, Integer>> _position) {
final int[] maxWidth = {0};
final int[] height = {0};
final int[] maxBulletWidth = {0};
ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
@Override
public boolean process(final Pair<String, LineInfo> pair) {
final LineInfo info = pair.getSecond();
Font old = null;
if (info.smaller) {
old = g.getFont();
g.setFont(old.deriveFont(old.getSize() * 0.70f));
}
final FontMetrics fm = g.getFontMetrics();
final int bulletWidth = info.withBullet ? fm.stringWidth(" " + info.bulletChar) : 0;
maxBulletWidth[0] = Math.max(maxBulletWidth[0], bulletWidth);
maxWidth[0] = Math.max(fm.stringWidth(pair.getFirst() + bulletWidth), maxWidth[0]);
height[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
if (old != null) {
g.setFont(old);
}
return true;
}
});
final Pair<Integer, Integer> position = _position.fun(maxWidth[0] + 20, height[0]);
assert position != null;
final int[] yOffset = {position.getSecond()};
ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
@Override
public boolean process(final Pair<String, LineInfo> pair) {
final LineInfo info = pair.getSecond();
Font old = null;
if (info.smaller) {
old = g.getFont();
g.setFont(old.deriveFont(old.getSize() * 0.70f));
}
final int x = position.getFirst() + maxBulletWidth[0] + 10;
final FontMetrics fm = g.getFontMetrics();
int xOffset = x;
if (info.center) {
xOffset = x + (maxWidth[0] - fm.stringWidth(pair.getFirst())) / 2;
}
if (myDrawShadow) {
int xOff = isUnderDarcula() ? 1 : 0;
int yOff = 1;
final Color oldColor = g.getColor();
g.setColor(myShadowColor);
if (info.withBullet) {
g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar) + xOff, yOffset[0] + yOff);
}
g.drawString(pair.getFirst(), xOffset + xOff, yOffset[0] + yOff);
g.setColor(oldColor);
}
if (info.withBullet) {
g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar), yOffset[0]);
}
g.drawString(pair.getFirst(), xOffset, yOffset[0]);
if (info.underlined) {
Color c = null;
if (info.underlineColor != null) {
c = g.getColor();
g.setColor(info.underlineColor);
}
g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent(), x + maxWidth[0] + 10, yOffset[0] + fm.getDescent());
if (c != null) {
g.setColor(c);
}
if (myDrawShadow) {
c = g.getColor();
g.setColor(myShadowColor);
g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent() + 1, x + maxWidth[0] + 10,
yOffset[0] + fm.getDescent() + 1);
g.setColor(c);
}
}
yOffset[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
if (old != null) {
g.setFont(old);
}
return true;
}
});
}
private static class LineInfo {
private boolean underlined;
private boolean withBullet;
private char bulletChar;
private Color underlineColor;
private boolean smaller;
private boolean center;
}
}
@Nullable
public static JRootPane getRootPane(Component c) {
JRootPane root = getParentOfType(JRootPane.class, c);
if (root != null) return root;
Component eachParent = c;
while (eachParent != null) {
if (eachParent instanceof JComponent) {
@SuppressWarnings({"unchecked"}) WeakReference<JRootPane> pane =
(WeakReference<JRootPane>)((JComponent)eachParent).getClientProperty(ROOT_PANE);
if (pane != null) return pane.get();
}
eachParent = eachParent.getParent();
}
return null;
}
public static void setFutureRootPane(JComponent c, JRootPane pane) {
c.putClientProperty(ROOT_PANE, new WeakReference<JRootPane>(pane));
}
public static boolean isMeaninglessFocusOwner(@Nullable Component c) {
if (c == null || !c.isShowing()) return true;
return c instanceof JFrame || c instanceof JDialog || c instanceof JWindow || c instanceof JRootPane || isFocusProxy(c);
}
public static Timer createNamedTimer(@NonNls @NotNull final String name, int delay, @NotNull ActionListener listener) {
return new Timer(delay, listener) {
@Override
public String toString() {
return name;
}
};
}
public static boolean isDialogRootPane(JRootPane rootPane) {
if (rootPane != null) {
final Object isDialog = rootPane.getClientProperty("DIALOG_ROOT_PANE");
return isDialog instanceof Boolean && ((Boolean)isDialog).booleanValue();
}
return false;
}
@Nullable
public static JComponent mergeComponentsWithAnchor(PanelWithAnchor... panels) {
return mergeComponentsWithAnchor(Arrays.asList(panels));
}
@Nullable
public static JComponent mergeComponentsWithAnchor(Collection<? extends PanelWithAnchor> panels) {
JComponent maxWidthAnchor = null;
int maxWidth = 0;
for (PanelWithAnchor panel : panels) {
JComponent anchor = panel != null ? panel.getAnchor() : null;
if (anchor != null) {
int anchorWidth = anchor.getPreferredSize().width;
if (maxWidth < anchorWidth) {
maxWidth = anchorWidth;
maxWidthAnchor = anchor;
}
}
}
for (PanelWithAnchor panel : panels) {
if (panel != null) {
panel.setAnchor(maxWidthAnchor);
}
}
return maxWidthAnchor;
}
public static void setNotOpaqueRecursively(@NotNull Component component) {
if (!isUnderAquaLookAndFeel()) return;
if (component.getBackground().equals(getPanelBackground()) || component instanceof JScrollPane || component instanceof JViewport) {
if (component instanceof JComponent) {
((JComponent)component).setOpaque(false);
}
if (component instanceof Container) {
for (Component c : ((Container)component).getComponents()) {
setNotOpaqueRecursively(c);
}
}
}
}
public static void addInsets(@NotNull JComponent component, @NotNull Insets insets) {
if (component.getBorder() != null) {
component.setBorder(new CompoundBorder(new EmptyBorder(insets), component.getBorder()));
}
else {
component.setBorder(new EmptyBorder(insets));
}
}
public static Dimension addInsets(@NotNull Dimension dimension, @NotNull Insets insets) {
Dimension ans = new Dimension(dimension);
ans.width += insets.left;
ans.width += insets.right;
ans.height += insets.top;
ans.height += insets.bottom;
return ans;
}
public static void adjustWindowToMinimumSize(final Window window) {
if (window == null) return;
final Dimension minSize = window.getMinimumSize();
final Dimension size = window.getSize();
final Dimension newSize = new Dimension(Math.max(size.width, minSize.width), Math.max(size.height, minSize.height));
if (!newSize.equals(size)) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (window.isShowing()) {
window.setSize(newSize);
}
}
});
}
}
@Nullable
public static Color getColorAt(final Icon icon, final int x, final int y) {
if (0 <= x && x < icon.getIconWidth() && 0 <= y && y < icon.getIconHeight()) {
final BufferedImage image = createImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_RGB);
icon.paintIcon(null, image.getGraphics(), 0, 0);
final int[] pixels = new int[1];
final PixelGrabber pixelGrabber = new PixelGrabber(image, x, y, 1, 1, pixels, 0, 1);
try {
pixelGrabber.grabPixels();
return new Color(pixels[0]);
}
catch (InterruptedException ignored) {
}
}
return null;
}
public static void addBorder(JComponent component, Border border) {
if (component == null) return;
if (component.getBorder() != null) {
component.setBorder(new CompoundBorder(border, component.getBorder()));
}
else {
component.setBorder(border);
}
}
private static final Color DECORATED_ROW_BG_COLOR = new JBColor(new Color(242, 245, 249), new Color(79, 83, 84));
public static Color getDecoratedRowColor() {
return DECORATED_ROW_BG_COLOR;
}
@NotNull
public static Paint getGradientPaint(float x1, float y1, @NotNull Color c1, float x2, float y2, @NotNull Color c2) {
return (Registry.is("ui.no.bangs.and.whistles", false)) ? ColorUtil.mix(c1, c2, .5) : new GradientPaint(x1, y1, c1, x2, y2, c2);
}
@Nullable
public static Point getLocationOnScreen(@NotNull JComponent component) {
int dx = 0;
int dy = 0;
for (Container c = component; c != null; c = c.getParent()) {
if (c.isShowing()) {
Point locationOnScreen = c.getLocationOnScreen();
locationOnScreen.translate(dx, dy);
return locationOnScreen;
}
else {
Point location = c.getLocation();
dx += location.x;
dy += location.y;
}
}
return null;
}
@NotNull
public static Window getActiveWindow() {
Window[] windows = Window.getWindows();
for (Window each : windows) {
if (each.isVisible() && each.isActive()) return each;
}
return JOptionPane.getRootFrame();
}
}