IDEA-131186 Vcs log does not look nice after look&feel switch
[idea/community.git] / platform / util / src / com / intellij / util / ui / UIUtil.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.util.ui;
17
18 import com.intellij.BundleBase;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.*;
23 import com.intellij.openapi.util.registry.Registry;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.vfs.CharsetToolkit;
26 import com.intellij.ui.*;
27 import com.intellij.util.*;
28 import com.intellij.util.containers.ContainerUtil;
29 import org.intellij.lang.annotations.Language;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33 import org.jetbrains.annotations.TestOnly;
34
35 import javax.sound.sampled.AudioInputStream;
36 import javax.sound.sampled.AudioSystem;
37 import javax.sound.sampled.Clip;
38 import javax.swing.*;
39 import javax.swing.Timer;
40 import javax.swing.border.Border;
41 import javax.swing.border.CompoundBorder;
42 import javax.swing.border.EmptyBorder;
43 import javax.swing.border.LineBorder;
44 import javax.swing.plaf.ButtonUI;
45 import javax.swing.plaf.ComboBoxUI;
46 import javax.swing.plaf.ProgressBarUI;
47 import javax.swing.plaf.basic.BasicComboBoxUI;
48 import javax.swing.plaf.basic.BasicRadioButtonUI;
49 import javax.swing.plaf.basic.ComboPopup;
50 import javax.swing.text.DefaultEditorKit;
51 import javax.swing.text.DefaultFormatterFactory;
52 import javax.swing.text.JTextComponent;
53 import javax.swing.text.NumberFormatter;
54 import javax.swing.text.html.HTMLEditorKit;
55 import javax.swing.text.html.StyleSheet;
56 import javax.swing.undo.UndoManager;
57 import java.awt.*;
58 import java.awt.event.*;
59 import java.awt.font.FontRenderContext;
60 import java.awt.im.InputContext;
61 import java.awt.image.BufferedImage;
62 import java.awt.image.BufferedImageOp;
63 import java.awt.image.ImageObserver;
64 import java.awt.image.PixelGrabber;
65 import java.beans.PropertyChangeListener;
66 import java.io.BufferedInputStream;
67 import java.io.IOException;
68 import java.io.InputStream;
69 import java.io.InputStreamReader;
70 import java.lang.ref.WeakReference;
71 import java.lang.reflect.Field;
72 import java.lang.reflect.InvocationTargetException;
73 import java.lang.reflect.Method;
74 import java.net.URL;
75 import java.text.NumberFormat;
76 import java.util.*;
77 import java.util.List;
78 import java.util.concurrent.BlockingQueue;
79 import java.util.concurrent.LinkedBlockingQueue;
80 import java.util.regex.Pattern;
81
82 /**
83  * @author max
84  */
85 @SuppressWarnings("StaticMethodOnlyUsedInOneClass")
86 public class UIUtil {
87
88   @NonNls public static final String BORDER_LINE = "<hr size=1 noshade>";
89   private static final StyleSheet DEFAULT_HTML_KIT_CSS;
90
91   static {
92     // save the default JRE CSS and ..
93     HTMLEditorKit kit = new HTMLEditorKit();
94     DEFAULT_HTML_KIT_CSS = kit.getStyleSheet();
95     // .. erase global ref to this CSS so no one can alter it
96     kit.setStyleSheet(null);
97   }
98
99   public static int getMultiClickInterval() {
100     Object property = Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
101     if (property instanceof Integer) {
102       return (Integer)property;
103     }
104     return 500;
105   }
106
107   private static final AtomicNotNullLazyValue<Boolean> X_RENDER_ACTIVE = new AtomicNotNullLazyValue<Boolean>() {
108     @NotNull
109     @Override
110     protected Boolean compute() {
111       if (!SystemInfo.isXWindow) {
112         return false;
113       }
114       try {
115         final Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("sun.awt.X11GraphicsEnvironment");
116         final Method method = clazz.getMethod("isXRenderAvailable");
117         return (Boolean)method.invoke(null);
118       }
119       catch (Throwable e) {
120         return false;
121       }
122     }
123   };
124
125   private static final String[] STANDARD_FONT_SIZES =
126     {"8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72"};
127
128   public static void applyStyle(@NotNull ComponentStyle componentStyle, @NotNull Component comp) {
129     if (!(comp instanceof JComponent)) return;
130
131     JComponent c = (JComponent)comp;
132
133     if (isUnderAquaBasedLookAndFeel()) {
134       c.putClientProperty("JComponent.sizeVariant",
135                           componentStyle == ComponentStyle.REGULAR ? "regular" : componentStyle == ComponentStyle.SMALL ? "small" : "mini");
136     }
137     else {
138       c.setFont(getFont(
139         componentStyle == ComponentStyle.REGULAR
140         ? FontSize.NORMAL
141         : componentStyle == ComponentStyle.SMALL ? FontSize.SMALL : FontSize.MINI, c.getFont()));
142     }
143     Container p = c.getParent();
144     if (p != null) {
145       SwingUtilities.updateComponentTreeUI(p);
146     }
147   }
148
149   public static Cursor getTextCursor(final Color backgroundColor) {
150     return SystemInfo.isMac && ColorUtil.isDark(backgroundColor) ?
151            MacUIUtil.getInvertedTextCursor() : Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
152   }
153
154   /**
155    * Draws two horizontal lines, the first at {@code topY}, the second at {@code bottomY}.
156    * The purpose of this method (and the ground of the name) is to draw two lines framing a horizontal filled rectangle.
157    *
158    * @param g       Graphics context to draw with.
159    * @param startX  x-start point.
160    * @param endX    x-end point.
161    * @param topY    y-coordinate of the first line.
162    * @param bottomY y-coordinate of the second line.
163    * @param color   color of the lines.
164    */
165   public static void drawFramingLines(@NotNull Graphics2D g, int startX, int endX, int topY, int bottomY, @NotNull Color color) {
166     drawLine(g, startX, topY, endX, topY, null, color);
167     drawLine(g, startX, bottomY, endX, bottomY, null, color);
168   }
169
170   private static final GrayFilter DEFAULT_GRAY_FILTER = new GrayFilter(true, 50);
171   private static final GrayFilter DARCULA_GRAY_FILTER = new GrayFilter(true, 30);
172
173   public static GrayFilter getGrayFilter() {
174     return isUnderDarcula() ? DARCULA_GRAY_FILTER : DEFAULT_GRAY_FILTER;
175   }
176
177   public static boolean isAppleRetina() {
178     return isRetina() && SystemInfo.isAppleJvm;
179   }
180
181   public enum FontSize {NORMAL, SMALL, MINI}
182
183   public enum ComponentStyle {REGULAR, SMALL, MINI}
184
185   public enum FontColor {NORMAL, BRIGHTER}
186
187   public static final char MNEMONIC = BundleBase.MNEMONIC;
188   @NonNls public static final String HTML_MIME = "text/html";
189   @NonNls public static final String JSLIDER_ISFILLED = "JSlider.isFilled";
190   @NonNls public static final String ARIAL_FONT_NAME = "Arial";
191   @NonNls public static final String TABLE_FOCUS_CELL_BACKGROUND_PROPERTY = "Table.focusCellBackground";
192   @NonNls public static final String CENTER_TOOLTIP_DEFAULT = "ToCenterTooltip";
193   @NonNls public static final String CENTER_TOOLTIP_STRICT = "ToCenterTooltip.default";
194
195   public static final Pattern CLOSE_TAG_PATTERN = Pattern.compile("<\\s*([^<>/ ]+)([^<>]*)/\\s*>", Pattern.CASE_INSENSITIVE);
196
197   @NonNls public static final String FOCUS_PROXY_KEY = "isFocusProxy";
198
199   public static Key<Integer> KEEP_BORDER_SIDES = Key.create("keepBorderSides");
200   private static Key<UndoManager> UNDO_MANAGER = Key.create("undoManager");
201   private static final AbstractAction REDO_ACTION = new AbstractAction() {
202     @Override
203     public void actionPerformed(ActionEvent e) {
204       Object source = e.getSource();
205       UndoManager manager = source instanceof JComponent ? getClientProperty((JComponent)source, UNDO_MANAGER) : null;
206       if (manager != null && manager.canRedo()) {
207         manager.redo();
208       }
209     }
210   };
211   private static final AbstractAction UNDO_ACTION = new AbstractAction() {
212     @Override
213     public void actionPerformed(ActionEvent e) {
214       Object source = e.getSource();
215       UndoManager manager = source instanceof JComponent ? getClientProperty((JComponent)source, UNDO_MANAGER) : null;
216       if (manager != null && manager.canUndo()) {
217         manager.undo();
218       }
219     }
220   };
221
222   private static final Logger LOG = Logger.getInstance("#com.intellij.util.ui.UIUtil");
223
224   private static final Color UNFOCUSED_SELECTION_COLOR = Gray._212;
225   private static final Color ACTIVE_HEADER_COLOR = new Color(160, 186, 213);
226   private static final Color INACTIVE_HEADER_COLOR = Gray._128;
227   private static final Color BORDER_COLOR = Color.LIGHT_GRAY;
228
229   public static final Color AQUA_SEPARATOR_FOREGROUND_COLOR = Gray._190;
230   public static final Color AQUA_SEPARATOR_BACKGROUND_COLOR = Gray._240;
231   public static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);
232
233   public static final int DEFAULT_HGAP = 10;
234   public static final int DEFAULT_VGAP = 4;
235   public static final int LARGE_VGAP = 12;
236
237   public static final Insets PANEL_REGULAR_INSETS = new Insets(8, 12, 8, 12);
238   public static final Insets PANEL_SMALL_INSETS = new Insets(5, 8, 5, 8);
239
240
241   public static final Border DEBUG_MARKER_BORDER = new Border() {
242     private final Insets empty = new Insets(0, 0, 0, 0);
243
244     @Override
245     public Insets getBorderInsets(Component c) {
246       return empty;
247     }
248
249     @Override
250     public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
251       Graphics g2 = g.create();
252       try {
253         g2.setColor(JBColor.RED);
254         drawDottedRectangle(g2, x, y, x + width - 1, y + height - 1);
255       }
256       finally {
257         g2.dispose();
258       }
259     }
260
261     @Override
262     public boolean isBorderOpaque() {
263       return true;
264     }
265   };
266
267   // accessed only from EDT
268   private static final HashMap<Color, BufferedImage> ourAppleDotSamples = new HashMap<Color, BufferedImage>();
269
270   private static volatile Pair<String, Integer> ourSystemFontData = null;
271
272   @NonNls private static final String ROOT_PANE = "JRootPane.future";
273
274   private static final Ref<Boolean> ourRetina = Ref.create(SystemInfo.isMac ? null : false);
275
276   private UIUtil() {
277   }
278
279   public static boolean isRetina() {
280     if (GraphicsEnvironment.isHeadless()) return false;
281
282     //Temporary workaround for HiDPI on Windows/Linux
283     if ("true".equalsIgnoreCase(System.getProperty("is.hidpi"))) {
284       return true;
285     }
286
287     synchronized (ourRetina) {
288       if (ourRetina.isNull()) {
289         ourRetina.set(false); // in case HiDPIScaledImage.drawIntoImage is not called for some reason
290
291         if (SystemInfo.isJavaVersionAtLeast("1.6.0_33") && SystemInfo.isAppleJvm) {
292           if (!"false".equals(System.getProperty("ide.mac.retina"))) {
293             ourRetina.set(IsRetina.isRetina());
294             return ourRetina.get();
295           }
296         } else if (SystemInfo.isJavaVersionAtLeast("1.7.0_40") && SystemInfo.isOracleJvm) {
297           try {
298             GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
299             final GraphicsDevice device = env.getDefaultScreenDevice();
300             Integer scale = ReflectionUtil.getField(device.getClass(), device, int.class, "scale");
301             if (scale != null && scale.intValue() == 2) {
302               ourRetina.set(true);
303               return true;
304             }
305           }
306           catch (AWTError ignore) {}
307           catch (Exception ignore) {}
308         }
309         ourRetina.set(false);
310       }
311
312       return ourRetina.get();
313     }
314   }
315
316   public static boolean hasLeakingAppleListeners() {
317     // in version 1.6.0_29 Apple introduced a memory leak in JViewport class - they add a PropertyChangeListeners to the CToolkit
318     // but never remove them:
319     // JViewport.java:
320     // public JViewport() {
321     //   ...
322     //   final Toolkit toolkit = Toolkit.getDefaultToolkit();
323     //   if(toolkit instanceof CToolkit)
324     //   {
325     //     final boolean isRunningInHiDPI = ((CToolkit)toolkit).runningInHiDPI();
326     //     if(isRunningInHiDPI) setScrollMode(0);
327     //     toolkit.addPropertyChangeListener("apple.awt.contentScaleFactor", new PropertyChangeListener() { ... });
328     //   }
329     // }
330
331     return SystemInfo.isMac && System.getProperty("java.runtime.version").startsWith("1.6.0_29");
332   }
333
334   public static void removeLeakingAppleListeners() {
335     if (!hasLeakingAppleListeners()) return;
336
337     Toolkit toolkit = Toolkit.getDefaultToolkit();
338     String name = "apple.awt.contentScaleFactor";
339     for (PropertyChangeListener each : toolkit.getPropertyChangeListeners(name)) {
340       toolkit.removePropertyChangeListener(name, each);
341     }
342   }
343
344   public static <T> T getClientProperty(@NotNull JComponent component, @NotNull Key<T> key) {
345     return (T)component.getClientProperty(key);
346   }
347
348   public static String getHtmlBody(String text) {
349     return getHtmlBody(new Html(text));
350   }
351
352   public static String getHtmlBody(Html html) {
353     String text = html.getText();
354     String result;
355     if (!text.startsWith("<html>")) {
356       result = text.replaceAll("\n", "<br>");
357     }
358     else {
359       final int bodyIdx = text.indexOf("<body>");
360       final int closedBodyIdx = text.indexOf("</body>");
361       if (bodyIdx != -1 && closedBodyIdx != -1) {
362         result = text.substring(bodyIdx + "<body>".length(), closedBodyIdx);
363       }
364       else {
365         text = StringUtil.trimStart(text, "<html>").trim();
366         text = StringUtil.trimEnd(text, "</html>").trim();
367         text = StringUtil.trimStart(text, "<body>").trim();
368         text = StringUtil.trimEnd(text, "</body>").trim();
369         result = text;
370       }
371     }
372
373     return html.isKeepFont() ? result : result.replaceAll("<font(.*?)>", "").replaceAll("</font>", "");
374   }
375
376   public static void drawLinePickedOut(Graphics graphics, int x, int y, int x1, int y1) {
377     if (x == x1) {
378       int minY = Math.min(y, y1);
379       int maxY = Math.max(y, y1);
380       graphics.drawLine(x, minY + 1, x1, maxY - 1);
381     }
382     else if (y == y1) {
383       int minX = Math.min(x, x1);
384       int maxX = Math.max(x, x1);
385       graphics.drawLine(minX + 1, y, maxX - 1, y1);
386     }
387     else {
388       drawLine(graphics, x, y, x1, y1);
389     }
390   }
391
392   public static boolean isReallyTypedEvent(KeyEvent e) {
393     char c = e.getKeyChar();
394     if (c < 0x20 || c == 0x7F) return false;
395
396     if (SystemInfo.isMac) {
397       return !e.isMetaDown() && !e.isControlDown();
398     }
399
400     return !e.isAltDown() && !e.isControlDown();
401   }
402
403   public static int getStringY(@NotNull final String string, @NotNull final Rectangle bounds, @NotNull final Graphics2D g) {
404     final int centerY = bounds.height / 2;
405     final Font font = g.getFont();
406     final FontRenderContext frc = g.getFontRenderContext();
407     final Rectangle stringBounds = font.getStringBounds(string, frc).getBounds();
408
409     return (int)(centerY - stringBounds.height / 2.0 - stringBounds.y);
410   }
411
412   public static void setEnabled(Component component, boolean enabled, boolean recursively) {
413     setEnabled(component, enabled, recursively, false);
414   }
415
416   public static void setEnabled(Component component, boolean enabled, boolean recursively, boolean visibleOnly) {
417     component.setEnabled(enabled);
418     if (component instanceof JComboBox && isUnderAquaLookAndFeel()) {
419       // On Mac JComboBox instances have children: com.apple.laf.AquaComboBoxButton and javax.swing.CellRendererPane.
420       // Disabling these children results in ugly UI: WEB-10733
421       return;
422     }
423     if (component instanceof JLabel) {
424       Color color = enabled ? getLabelForeground() : getLabelDisabledForeground();
425       if (color != null) {
426         component.setForeground(color);
427       }
428     }
429     if (recursively && enabled == component.isEnabled()) {
430       if (component instanceof Container) {
431         final Container container = (Container)component;
432         final int subComponentCount = container.getComponentCount();
433         for (int i = 0; i < subComponentCount; i++) {
434           Component child = container.getComponent(i);
435           if (visibleOnly && !child.isVisible()) continue;
436           setEnabled(child, enabled, recursively, visibleOnly);
437         }
438       }
439     }
440   }
441
442   public static void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
443     g.drawLine(x1, y1, x2, y2);
444   }
445
446   public static void drawLine(Graphics2D g, int x1, int y1, int x2, int y2, @Nullable Color bgColor, @Nullable Color fgColor) {
447     Color oldFg = g.getColor();
448     Color oldBg = g.getBackground();
449     if (fgColor != null) {
450       g.setColor(fgColor);
451     }
452     if (bgColor != null) {
453       g.setBackground(bgColor);
454     }
455     drawLine(g, x1, y1, x2, y2);
456     if (fgColor != null) {
457       g.setColor(oldFg);
458     }
459     if (bgColor != null) {
460       g.setBackground(oldBg);
461     }
462   }
463
464   @NotNull
465   public static String[] splitText(String text, FontMetrics fontMetrics, int widthLimit, char separator) {
466     ArrayList<String> lines = new ArrayList<String>();
467     String currentLine = "";
468     StringBuilder currentAtom = new StringBuilder();
469
470     for (int i = 0; i < text.length(); i++) {
471       char ch = text.charAt(i);
472       currentAtom.append(ch);
473
474       if (ch == separator) {
475         currentLine += currentAtom.toString();
476         currentAtom.setLength(0);
477       }
478
479       String s = currentLine + currentAtom.toString();
480       int width = fontMetrics.stringWidth(s);
481
482       if (width >= widthLimit - fontMetrics.charWidth('w')) {
483         if (!currentLine.isEmpty()) {
484           lines.add(currentLine);
485           currentLine = "";
486         }
487         else {
488           lines.add(currentAtom.toString());
489           currentAtom.setLength(0);
490         }
491       }
492     }
493
494     String s = currentLine + currentAtom.toString();
495     if (!s.isEmpty()) {
496       lines.add(s);
497     }
498
499     return ArrayUtil.toStringArray(lines);
500   }
501
502   public static void setActionNameAndMnemonic(@NotNull String text, @NotNull Action action) {
503     assignMnemonic(text, action);
504
505     text = text.replaceAll("&", "");
506     action.putValue(Action.NAME, text);
507   }
508   public static void assignMnemonic(@NotNull String text, @NotNull Action action) {
509     int mnemoPos = text.indexOf('&');
510     if (mnemoPos >= 0 && mnemoPos < text.length() - 2) {
511       String mnemoChar = text.substring(mnemoPos + 1, mnemoPos + 2).trim();
512       if (mnemoChar.length() == 1) {
513         action.putValue(Action.MNEMONIC_KEY, Integer.valueOf(mnemoChar.charAt(0)));
514       }
515     }
516   }
517
518
519   public static Font getLabelFont(@NotNull FontSize size) {
520     return getFont(size, null);
521   }
522
523   @NotNull
524   public static Font getFont(@NotNull FontSize size, @Nullable Font base) {
525     if (base == null) base = getLabelFont();
526
527     return base.deriveFont(getFontSize(size));
528   }
529
530   public static float getFontSize(FontSize size) {
531     int defSize = getLabelFont().getSize();
532     switch (size) {
533       case SMALL:
534         return Math.max(defSize - 2f, 11f);
535       case MINI:
536         return Math.max(defSize - 4f, 9f);
537       default:
538         return defSize;
539     }
540   }
541
542   public static Color getLabelFontColor(FontColor fontColor) {
543     Color defColor = getLabelForeground();
544     if (fontColor == FontColor.BRIGHTER) {
545       return new JBColor(new Color(Math.min(defColor.getRed() + 50, 255), Math.min(defColor.getGreen() + 50, 255), Math.min(
546         defColor.getBlue() + 50, 255)), defColor.darker());
547     }
548     return defColor;
549   }
550
551   public static int getScrollBarWidth() {
552     return UIManager.getInt("ScrollBar.width");
553   }
554
555   public static Font getLabelFont() {
556     return UIManager.getFont("Label.font");
557   }
558
559   public static Color getLabelBackground() {
560     return UIManager.getColor("Label.background");
561   }
562
563   public static Color getLabelForeground() {
564     return UIManager.getColor("Label.foreground");
565   }
566
567   public static Color getLabelDisabledForeground() {
568     final Color color = UIManager.getColor("Label.disabledForeground");
569     if (color != null) return color;
570     return UIManager.getColor("Label.disabledText");
571   }
572
573   /** @deprecated to remove in IDEA 14 */
574   @SuppressWarnings("UnusedDeclaration")
575   public static Icon getOptionPanelWarningIcon() {
576     return getWarningIcon();
577   }
578
579   /** @deprecated to remove in IDEA 14 */
580   @SuppressWarnings("UnusedDeclaration")
581   public static Icon getOptionPanelQuestionIcon() {
582     return getQuestionIcon();
583   }
584
585   @NotNull
586   public static String removeMnemonic(@NotNull String s) {
587     if (s.indexOf('&') != -1) {
588       s = StringUtil.replace(s, "&", "");
589     }
590     if (s.indexOf('_') != -1) {
591       s = StringUtil.replace(s, "_", "");
592     }
593     if (s.indexOf(MNEMONIC) != -1) {
594       s = StringUtil.replace(s, String.valueOf(MNEMONIC), "");
595     }
596     return s;
597   }
598
599   public static int getDisplayMnemonicIndex(@NotNull String s) {
600     int idx = s.indexOf('&');
601     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf('&')) return idx;
602
603     idx = s.indexOf(MNEMONIC);
604     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf(MNEMONIC)) return idx;
605
606     return -1;
607   }
608
609   public static String replaceMnemonicAmpersand(final String value) {
610     return BundleBase.replaceMnemonicAmpersand(value);
611   }
612
613   public static Color getTableHeaderBackground() {
614     return UIManager.getColor("TableHeader.background");
615   }
616
617   public static Color getTreeTextForeground() {
618     return UIManager.getColor("Tree.textForeground");
619   }
620
621   public static Color getTreeSelectionBackground() {
622     if (isUnderNimbusLookAndFeel()) {
623       Color color = UIManager.getColor("Tree.selectionBackground");
624       if (color != null) return color;
625       color = UIManager.getColor("nimbusSelectionBackground");
626       if (color != null) return color;
627     }
628     return UIManager.getColor("Tree.selectionBackground");
629   }
630
631   public static Color getTreeTextBackground() {
632     return UIManager.getColor("Tree.textBackground");
633   }
634
635   public static Color getListSelectionForeground() {
636     final Color color = UIManager.getColor("List.selectionForeground");
637     if (color == null) {
638       return UIManager.getColor("List[Selected].textForeground");  // Nimbus
639     }
640     return color;
641   }
642
643   public static Color getFieldForegroundColor() {
644     return UIManager.getColor("field.foreground");
645   }
646
647   public static Color getTableSelectionBackground() {
648     if (isUnderNimbusLookAndFeel()) {
649       Color color = UIManager.getColor("Table[Enabled+Selected].textBackground");
650       if (color != null) return color;
651       color = UIManager.getColor("nimbusSelectionBackground");
652       if (color != null) return color;
653     }
654     return UIManager.getColor("Table.selectionBackground");
655   }
656
657   public static Color getActiveTextColor() {
658     return UIManager.getColor("textActiveText");
659   }
660
661   public static Color getInactiveTextColor() {
662     return UIManager.getColor("textInactiveText");
663   }
664
665   public static Color getSlightlyDarkerColor(Color c) {
666     float[] hsl = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), new float[3]);
667     return new Color(Color.HSBtoRGB(hsl[0], hsl[1], hsl[2] - .08f > 0 ? hsl[2] - .08f : hsl[2]));
668   }
669
670   /**
671    * @deprecated use com.intellij.util.ui.UIUtil#getTextFieldBackground()
672    */
673   public static Color getActiveTextFieldBackgroundColor() {
674     return getTextFieldBackground();
675   }
676
677   public static Color getInactiveTextFieldBackgroundColor() {
678     return UIManager.getColor("TextField.inactiveBackground");
679   }
680
681   public static Font getTreeFont() {
682     return UIManager.getFont("Tree.font");
683   }
684
685   public static Font getListFont() {
686     return UIManager.getFont("List.font");
687   }
688
689   public static Color getTreeSelectionForeground() {
690     return UIManager.getColor("Tree.selectionForeground");
691   }
692
693   /**
694    * @deprecated use com.intellij.util.ui.UIUtil#getInactiveTextColor()
695    */
696   public static Color getTextInactiveTextColor() {
697     return getInactiveTextColor();
698   }
699
700   public static void installPopupMenuColorAndFonts(final JComponent contentPane) {
701     LookAndFeel.installColorsAndFont(contentPane, "PopupMenu.background", "PopupMenu.foreground", "PopupMenu.font");
702   }
703
704   public static void installPopupMenuBorder(final JComponent contentPane) {
705     LookAndFeel.installBorder(contentPane, "PopupMenu.border");
706   }
707
708   public static Color getTreeSelectionBorderColor() {
709     return UIManager.getColor("Tree.selectionBorderColor");
710   }
711
712   public static int getTreeRightChildIndent() {
713     return UIManager.getInt("Tree.rightChildIndent");
714   }
715
716   public static int getTreeLeftChildIndent() {
717     return UIManager.getInt("Tree.leftChildIndent");
718   }
719
720   public static Color getToolTipBackground() {
721     return UIManager.getColor("ToolTip.background");
722   }
723
724   public static Color getToolTipForeground() {
725     return UIManager.getColor("ToolTip.foreground");
726   }
727
728   public static Color getComboBoxDisabledForeground() {
729     return UIManager.getColor("ComboBox.disabledForeground");
730   }
731
732   public static Color getComboBoxDisabledBackground() {
733     return UIManager.getColor("ComboBox.disabledBackground");
734   }
735
736   public static Color getButtonSelectColor() {
737     return UIManager.getColor("Button.select");
738   }
739
740   public static Integer getPropertyMaxGutterIconWidth(final String propertyPrefix) {
741     return (Integer)UIManager.get(propertyPrefix + ".maxGutterIconWidth");
742   }
743
744   public static Color getMenuItemDisabledForeground() {
745     return UIManager.getColor("MenuItem.disabledForeground");
746   }
747
748   public static Object getMenuItemDisabledForegroundObject() {
749     return UIManager.get("MenuItem.disabledForeground");
750   }
751
752   public static Object getTabbedPanePaintContentBorder(final JComponent c) {
753     return c.getClientProperty("TabbedPane.paintContentBorder");
754   }
755
756   public static boolean isMenuCrossMenuMnemonics() {
757     return UIManager.getBoolean("Menu.crossMenuMnemonic");
758   }
759
760   public static Color getTableBackground() {
761     // Under GTK+ L&F "Table.background" often has main panel color, which looks ugly
762     return isUnderGTKLookAndFeel() ? getTreeTextBackground() : UIManager.getColor("Table.background");
763   }
764
765   public static Color getTableBackground(final boolean isSelected) {
766     return isSelected ? getTableSelectionBackground() : getTableBackground();
767   }
768
769   public static Color getTableSelectionForeground() {
770     if (isUnderNimbusLookAndFeel()) {
771       return UIManager.getColor("Table[Enabled+Selected].textForeground");
772     }
773     return UIManager.getColor("Table.selectionForeground");
774   }
775
776   public static Color getTableForeground() {
777     return UIManager.getColor("Table.foreground");
778   }
779
780   public static Color getTableForeground(final boolean isSelected) {
781     return isSelected ? getTableSelectionForeground() : getTableForeground();
782   }
783
784   public static Color getTableGridColor() {
785     return UIManager.getColor("Table.gridColor");
786   }
787
788   public static Color getListBackground() {
789     if (isUnderNimbusLookAndFeel()) {
790       final Color color = UIManager.getColor("List.background");
791       //noinspection UseJBColor
792       return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
793     }
794     // Under GTK+ L&F "Table.background" often has main panel color, which looks ugly
795     return isUnderGTKLookAndFeel() ? getTreeTextBackground() : UIManager.getColor("List.background");
796   }
797
798   public static Color getListBackground(boolean isSelected) {
799     return isSelected ? getListSelectionBackground() : getListBackground();
800   }
801
802   public static Color getListForeground() {
803     return UIManager.getColor("List.foreground");
804   }
805
806   public static Color getListForeground(boolean isSelected) {
807     return isSelected ? getListSelectionForeground() : getListForeground();
808   }
809
810   public static Color getPanelBackground() {
811     return UIManager.getColor("Panel.background");
812   }
813
814   public static Color getTreeBackground() {
815     return UIManager.getColor("Tree.background");
816   }
817
818   public static Color getTreeForeground() {
819     return UIManager.getColor("Tree.foreground");
820   }
821
822   public static Color getTableFocusCellBackground() {
823     return UIManager.getColor(TABLE_FOCUS_CELL_BACKGROUND_PROPERTY);
824   }
825
826   public static Color getListSelectionBackground() {
827     if (isUnderNimbusLookAndFeel()) {
828       return UIManager.getColor("List[Selected].textBackground");  // Nimbus
829     }
830     return UIManager.getColor("List.selectionBackground");
831   }
832
833   public static Color getListUnfocusedSelectionBackground() {
834     return new JBColor(UNFOCUSED_SELECTION_COLOR, new Color(13, 41, 62));
835   }
836
837   public static Color getTreeSelectionBackground(boolean focused) {
838     return focused ? getTreeSelectionBackground() : getTreeUnfocusedSelectionBackground();
839   }
840
841   public static Color getTreeUnfocusedSelectionBackground() {
842     Color background = getTreeTextBackground();
843     return ColorUtil.isDark(background) ? new JBColor(Gray._30, new Color(13, 41, 62)) : UNFOCUSED_SELECTION_COLOR;
844   }
845
846   public static Color getTextFieldForeground() {
847     return UIManager.getColor("TextField.foreground");
848   }
849
850   public static Color getTextFieldBackground() {
851     return isUnderGTKLookAndFeel() ? UIManager.getColor("EditorPane.background") : UIManager.getColor("TextField.background");
852   }
853
854   public static Font getButtonFont() {
855     return UIManager.getFont("Button.font");
856   }
857
858   public static Font getToolTipFont() {
859     return UIManager.getFont("ToolTip.font");
860   }
861
862   public static Color getTabbedPaneBackground() {
863     return UIManager.getColor("TabbedPane.background");
864   }
865
866   public static void setSliderIsFilled(final JSlider slider, final boolean value) {
867     slider.putClientProperty("JSlider.isFilled", Boolean.valueOf(value));
868   }
869
870   public static Color getLabelTextForeground() {
871     return UIManager.getColor("Label.textForeground");
872   }
873
874   public static Color getControlColor() {
875     return UIManager.getColor("control");
876   }
877
878   public static Font getOptionPaneMessageFont() {
879     return UIManager.getFont("OptionPane.messageFont");
880   }
881
882   public static Font getMenuFont() {
883     return UIManager.getFont("Menu.font");
884   }
885
886   public static Color getSeparatorForeground() {
887     return UIManager.getColor("Separator.foreground");
888   }
889
890   public static Color getSeparatorBackground() {
891     return UIManager.getColor("Separator.background");
892   }
893
894   public static Color getSeparatorShadow() {
895     return UIManager.getColor("Separator.shadow");
896   }
897
898   public static Color getSeparatorHighlight() {
899     return UIManager.getColor("Separator.highlight");
900   }
901
902   public static Color getSeparatorColorUnderNimbus() {
903     return UIManager.getColor("nimbusBlueGrey");
904   }
905
906   public static Color getSeparatorColor() {
907     Color separatorColor = getSeparatorForeground();
908     if (isUnderAlloyLookAndFeel()) {
909       separatorColor = getSeparatorShadow();
910     }
911     if (isUnderNimbusLookAndFeel()) {
912       separatorColor = getSeparatorColorUnderNimbus();
913     }
914     //under GTK+ L&F colors set hard
915     if (isUnderGTKLookAndFeel()) {
916       separatorColor = Gray._215;
917     }
918     return separatorColor;
919   }
920
921   public static Border getTableFocusCellHighlightBorder() {
922     return UIManager.getBorder("Table.focusCellHighlightBorder");
923   }
924
925   public static void setLineStyleAngled(final ClientPropertyHolder component) {
926     component.putClientProperty("JTree.lineStyle", "Angled");
927   }
928
929   public static void setLineStyleAngled(final JTree component) {
930     component.putClientProperty("JTree.lineStyle", "Angled");
931   }
932
933   public static Color getTableFocusCellForeground() {
934     return UIManager.getColor("Table.focusCellForeground");
935   }
936
937   /**
938    * @deprecated use com.intellij.util.ui.UIUtil#getPanelBackground() instead
939    */
940   public static Color getPanelBackgound() {
941     return getPanelBackground();
942   }
943
944   public static Border getTextFieldBorder() {
945     return UIManager.getBorder("TextField.border");
946   }
947
948   public static Border getButtonBorder() {
949     return UIManager.getBorder("Button.border");
950   }
951
952   @NotNull
953   public static Icon getErrorIcon() {
954     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.errorIcon"), AllIcons.General.ErrorDialog);
955   }
956
957   @NotNull
958   public static Icon getInformationIcon() {
959     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.informationIcon"), AllIcons.General.InformationDialog);
960   }
961
962   @NotNull
963   public static Icon getQuestionIcon() {
964     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.questionIcon"), AllIcons.General.QuestionDialog);
965   }
966
967   @NotNull
968   public static Icon getWarningIcon() {
969     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.warningIcon"), AllIcons.General.WarningDialog);
970   }
971
972   public static Icon getBalloonInformationIcon() {
973     return AllIcons.General.BalloonInformation;
974   }
975
976   public static Icon getBalloonWarningIcon() {
977     return AllIcons.General.BalloonWarning;
978   }
979
980   public static Icon getBalloonErrorIcon() {
981     return AllIcons.General.BalloonError;
982   }
983
984   public static Icon getRadioButtonIcon() {
985     return UIManager.getIcon("RadioButton.icon");
986   }
987
988   public static Icon getTreeNodeIcon(boolean expanded, boolean selected, boolean focused) {
989     boolean white = (selected && focused) || isUnderDarcula();
990
991     Icon selectedIcon = getTreeSelectedExpandedIcon();
992     Icon notSelectedIcon = getTreeExpandedIcon();
993
994     int width = Math.max(selectedIcon.getIconWidth(), notSelectedIcon.getIconWidth());
995     int height = Math.max(selectedIcon.getIconWidth(), notSelectedIcon.getIconWidth());
996
997     return new CenteredIcon(expanded ? (white ? getTreeSelectedExpandedIcon() : getTreeExpandedIcon())
998                                      : (white ? getTreeSelectedCollapsedIcon() : getTreeCollapsedIcon()),
999                             width, height, false);
1000   }
1001
1002   public static Icon getTreeCollapsedIcon() {
1003     return UIManager.getIcon("Tree.collapsedIcon");
1004   }
1005
1006   public static Icon getTreeExpandedIcon() {
1007     return UIManager.getIcon("Tree.expandedIcon");
1008   }
1009
1010   public static Icon getTreeIcon(boolean expanded) {
1011     return expanded ? getTreeExpandedIcon() : getTreeCollapsedIcon();
1012   }
1013
1014   public static Icon getTreeSelectedCollapsedIcon() {
1015     return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() || isUnderIntelliJLaF()
1016            ? AllIcons.Mac.Tree_white_right_arrow : getTreeCollapsedIcon();
1017   }
1018
1019   public static Icon getTreeSelectedExpandedIcon() {
1020     return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() || isUnderIntelliJLaF()
1021            ? AllIcons.Mac.Tree_white_down_arrow : getTreeExpandedIcon();
1022   }
1023
1024   public static Border getTableHeaderCellBorder() {
1025     return UIManager.getBorder("TableHeader.cellBorder");
1026   }
1027
1028   public static Color getWindowColor() {
1029     return UIManager.getColor("window");
1030   }
1031
1032   public static Color getTextAreaForeground() {
1033     return UIManager.getColor("TextArea.foreground");
1034   }
1035
1036   public static Color getOptionPaneBackground() {
1037     return UIManager.getColor("OptionPane.background");
1038   }
1039
1040   @SuppressWarnings({"HardCodedStringLiteral"})
1041   public static boolean isUnderAlloyLookAndFeel() {
1042     return UIManager.getLookAndFeel().getName().contains("Alloy");
1043   }
1044
1045   @SuppressWarnings({"HardCodedStringLiteral"})
1046   public static boolean isUnderAlloyIDEALookAndFeel() {
1047     return isUnderAlloyLookAndFeel() && UIManager.getLookAndFeel().getName().contains("IDEA");
1048   }
1049
1050   @SuppressWarnings({"HardCodedStringLiteral"})
1051   public static boolean isUnderWindowsLookAndFeel() {
1052     return UIManager.getLookAndFeel().getName().equals("Windows");
1053   }
1054
1055   @SuppressWarnings({"HardCodedStringLiteral"})
1056   public static boolean isUnderWindowsClassicLookAndFeel() {
1057     return UIManager.getLookAndFeel().getName().equals("Windows Classic");
1058   }
1059
1060   @SuppressWarnings({"HardCodedStringLiteral"})
1061   public static boolean isUnderNimbusLookAndFeel() {
1062     return UIManager.getLookAndFeel().getName().contains("Nimbus");
1063   }
1064
1065   @SuppressWarnings({"HardCodedStringLiteral"})
1066   public static boolean isUnderAquaLookAndFeel() {
1067     return SystemInfo.isMac && UIManager.getLookAndFeel().getName().contains("Mac OS X");
1068   }
1069
1070   @SuppressWarnings({"HardCodedStringLiteral"})
1071   public static boolean isUnderJGoodiesLookAndFeel() {
1072     return UIManager.getLookAndFeel().getName().contains("JGoodies");
1073   }
1074
1075   @SuppressWarnings({"HardCodedStringLiteral"})
1076   public static boolean isUnderAquaBasedLookAndFeel() {
1077     return SystemInfo.isMac && (isUnderAquaLookAndFeel() || isUnderDarcula());
1078   }
1079
1080   @SuppressWarnings({"HardCodedStringLiteral"})
1081   public static boolean isUnderDarcula() {
1082     return UIManager.getLookAndFeel().getName().contains("Darcula");
1083   }
1084
1085   @SuppressWarnings({"HardCodedStringLiteral"})
1086   public static boolean isUnderIntelliJLaF() {
1087     return UIManager.getLookAndFeel().getName().contains("IntelliJ");
1088   }
1089
1090   @SuppressWarnings({"HardCodedStringLiteral"})
1091   public static boolean isUnderGTKLookAndFeel() {
1092     return UIManager.getLookAndFeel().getName().contains("GTK");
1093   }
1094
1095   public static final Color GTK_AMBIANCE_TEXT_COLOR = new Color(223, 219, 210);
1096   public static final Color GTK_AMBIANCE_BACKGROUND_COLOR = new Color(67, 66, 63);
1097
1098   @SuppressWarnings({"HardCodedStringLiteral"})
1099   @Nullable
1100   public static String getGtkThemeName() {
1101     final LookAndFeel laf = UIManager.getLookAndFeel();
1102     if (laf != null && "GTKLookAndFeel".equals(laf.getClass().getSimpleName())) {
1103       try {
1104         final Method method = laf.getClass().getDeclaredMethod("getGtkThemeName");
1105         method.setAccessible(true);
1106         final Object theme = method.invoke(laf);
1107         if (theme != null) {
1108           return theme.toString();
1109         }
1110       }
1111       catch (Exception ignored) {
1112       }
1113     }
1114     return null;
1115   }
1116
1117   @SuppressWarnings({"HardCodedStringLiteral"})
1118   public static boolean isMurrineBasedTheme() {
1119     final String gtkTheme = getGtkThemeName();
1120     return "Ambiance".equalsIgnoreCase(gtkTheme) ||
1121            "Radiance".equalsIgnoreCase(gtkTheme) ||
1122            "Dust".equalsIgnoreCase(gtkTheme) ||
1123            "Dust Sand".equalsIgnoreCase(gtkTheme);
1124   }
1125
1126   public static Color shade(final Color c, final double factor, final double alphaFactor) {
1127     assert factor >= 0 : factor;
1128     return new Color(
1129       Math.min((int)Math.round(c.getRed() * factor), 255),
1130       Math.min((int)Math.round(c.getGreen() * factor), 255),
1131       Math.min((int)Math.round(c.getBlue() * factor), 255),
1132       Math.min((int)Math.round(c.getAlpha() * alphaFactor), 255)
1133     );
1134   }
1135
1136   public static Color mix(final Color c1, final Color c2, final double factor) {
1137     assert 0 <= factor && factor <= 1.0 : factor;
1138     final double backFactor = 1.0 - factor;
1139     return new Color(
1140       Math.min((int)Math.round(c1.getRed() * backFactor + c2.getRed() * factor), 255),
1141       Math.min((int)Math.round(c1.getGreen() * backFactor + c2.getGreen() * factor), 255),
1142       Math.min((int)Math.round(c1.getBlue() * backFactor + c2.getBlue() * factor), 255)
1143     );
1144   }
1145
1146   public static boolean isFullRowSelectionLAF() {
1147     return isUnderGTKLookAndFeel();
1148   }
1149
1150   public static boolean isUnderNativeMacLookAndFeel() {
1151     return isUnderAquaLookAndFeel() || isUnderDarcula();
1152   }
1153
1154   public static int getListCellHPadding() {
1155     return isUnderNativeMacLookAndFeel() ? 7 : 2;
1156   }
1157
1158   public static int getListCellVPadding() {
1159     return 1;
1160   }
1161
1162   public static Insets getListCellPadding() {
1163     return new Insets(getListCellVPadding(), getListCellHPadding(), getListCellVPadding(), getListCellHPadding());
1164   }
1165
1166   public static Insets getListViewportPadding() {
1167     return isUnderNativeMacLookAndFeel() ? new Insets(1, 0, 1, 0) : new Insets(5, 5, 5, 5);
1168   }
1169
1170   public static boolean isToUseDottedCellBorder() {
1171     return !isUnderNativeMacLookAndFeel();
1172   }
1173
1174   public static boolean isControlKeyDown(MouseEvent mouseEvent) {
1175     return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
1176   }
1177
1178   public static String[] getValidFontNames(final boolean familyName) {
1179     Set<String> result = new TreeSet<String>();
1180
1181     // adds fonts that can display symbols at [A, Z] + [a, z] + [0, 9]
1182     for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
1183       try {
1184         if (isValidFont(font)) {
1185           result.add(familyName ? font.getFamily() : font.getName());
1186         }
1187       }
1188       catch (Exception ignore) {
1189         // JRE has problems working with the font. Just skip.
1190       }
1191     }
1192
1193     // add label font (if isn't listed among above)
1194     Font labelFont = getLabelFont();
1195     if (labelFont != null && isValidFont(labelFont)) {
1196       result.add(familyName ? labelFont.getFamily() : labelFont.getName());
1197     }
1198
1199     return ArrayUtil.toStringArray(result);
1200   }
1201
1202   public static String[] getStandardFontSizes() {
1203     return STANDARD_FONT_SIZES;
1204   }
1205
1206   public static boolean isValidFont(@NotNull Font font) {
1207     try {
1208       return font.canDisplay('a') &&
1209              font.canDisplay('z') &&
1210              font.canDisplay('A') &&
1211              font.canDisplay('Z') &&
1212              font.canDisplay('0') &&
1213              font.canDisplay('1');
1214     }
1215     catch (Exception e) {
1216       // JRE has problems working with the font. Just skip.
1217       return false;
1218     }
1219   }
1220
1221   public static void setupEnclosingDialogBounds(final JComponent component) {
1222     component.revalidate();
1223     component.repaint();
1224     final Window window = SwingUtilities.windowForComponent(component);
1225     if (window != null &&
1226         (window.getSize().height < window.getMinimumSize().height || window.getSize().width < window.getMinimumSize().width)) {
1227       window.pack();
1228     }
1229   }
1230
1231   public static String displayPropertiesToCSS(Font font, Color fg) {
1232     @NonNls StringBuilder rule = new StringBuilder("body {");
1233     if (font != null) {
1234       rule.append(" font-family: ");
1235       rule.append(font.getFamily());
1236       rule.append(" ; ");
1237       rule.append(" font-size: ");
1238       rule.append(font.getSize());
1239       rule.append("pt ;");
1240       if (font.isBold()) {
1241         rule.append(" font-weight: 700 ; ");
1242       }
1243       if (font.isItalic()) {
1244         rule.append(" font-style: italic ; ");
1245       }
1246     }
1247     if (fg != null) {
1248       rule.append(" color: #");
1249       appendColor(fg, rule);
1250       rule.append(" ; ");
1251     }
1252     rule.append(" }");
1253     return rule.toString();
1254   }
1255
1256   public static void appendColor(final Color color, final StringBuilder sb) {
1257     if (color.getRed() < 16) sb.append('0');
1258     sb.append(Integer.toHexString(color.getRed()));
1259     if (color.getGreen() < 16) sb.append('0');
1260     sb.append(Integer.toHexString(color.getGreen()));
1261     if (color.getBlue() < 16) sb.append('0');
1262     sb.append(Integer.toHexString(color.getBlue()));
1263   }
1264
1265   /**
1266    * @param g  graphics.
1267    * @param x  top left X coordinate.
1268    * @param y  top left Y coordinate.
1269    * @param x1 right bottom X coordinate.
1270    * @param y1 right bottom Y coordinate.
1271    */
1272   public static void drawDottedRectangle(Graphics g, int x, int y, int x1, int y1) {
1273     int i1;
1274     for (i1 = x; i1 <= x1; i1 += 2) {
1275       drawLine(g, i1, y, i1, y);
1276     }
1277
1278     for (i1 = i1 != x1 + 1 ? y + 2 : y + 1; i1 <= y1; i1 += 2) {
1279       drawLine(g, x1, i1, x1, i1);
1280     }
1281
1282     for (i1 = i1 != y1 + 1 ? x1 - 2 : x1 - 1; i1 >= x; i1 -= 2) {
1283       drawLine(g, i1, y1, i1, y1);
1284     }
1285
1286     for (i1 = i1 != x - 1 ? y1 - 2 : y1 - 1; i1 >= y; i1 -= 2) {
1287       drawLine(g, x, i1, x, i1);
1288     }
1289   }
1290
1291   /**
1292    * Should be invoked only in EDT.
1293    *
1294    * @param g       Graphics surface
1295    * @param startX  Line start X coordinate
1296    * @param endX    Line end X coordinate
1297    * @param lineY   Line Y coordinate
1298    * @param bgColor Background color (optional)
1299    * @param fgColor Foreground color (optional)
1300    * @param opaque  If opaque the image will be dr
1301    */
1302   public static void drawBoldDottedLine(final Graphics2D g,
1303                                         final int startX,
1304                                         final int endX,
1305                                         final int lineY,
1306                                         final Color bgColor,
1307                                         final Color fgColor,
1308                                         final boolean opaque) {
1309     if ((SystemInfo.isMac && !isRetina()) || SystemInfo.isLinux) {
1310       drawAppleDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
1311     }
1312     else {
1313       drawBoringDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
1314     }
1315   }
1316
1317   public static void drawSearchMatch(final Graphics2D g,
1318                                      final int startX,
1319                                      final int endX,
1320                                      final int height) {
1321     Color c1 = new Color(255, 234, 162);
1322     Color c2 = new Color(255, 208, 66);
1323     drawSearchMatch(g, startX, endX, height, c1, c2);
1324   }
1325
1326   public static void drawSearchMatch(Graphics2D g, int startX, int endX, int height, Color c1, Color c2) {
1327     final boolean drawRound = endX - startX > 4;
1328
1329     final Composite oldComposite = g.getComposite();
1330     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
1331     g.setPaint(getGradientPaint(startX, 2, c1, startX, height - 5, c2));
1332
1333     if (isRetina()) {
1334       g.fillRoundRect(startX - 1, 2, endX - startX + 1, height - 4, 5, 5);
1335       g.setComposite(oldComposite);
1336       return;
1337     }
1338
1339     g.fillRect(startX, 3, endX - startX, height - 5);
1340
1341     if (drawRound) {
1342       g.drawLine(startX - 1, 4, startX - 1, height - 4);
1343       g.drawLine(endX, 4, endX, height - 4);
1344
1345       g.setColor(new Color(100, 100, 100, 50));
1346       g.drawLine(startX - 1, 4, startX - 1, height - 4);
1347       g.drawLine(endX, 4, endX, height - 4);
1348
1349       g.drawLine(startX, 3, endX - 1, 3);
1350       g.drawLine(startX, height - 3, endX - 1, height - 3);
1351     }
1352
1353     g.setComposite(oldComposite);
1354   }
1355
1356   public static void drawRectPickedOut(Graphics2D g, int x, int y, int w, int h) {
1357     g.drawLine(x + 1, y, x + w - 1, y);
1358     g.drawLine(x + w, y + 1, x + w, y + h - 1);
1359     g.drawLine(x + w - 1, y + h, x + 1, y + h);
1360     g.drawLine(x, y + 1, x, y + h - 1);
1361   }
1362
1363   private static void drawBoringDottedLine(final Graphics2D g,
1364                                            final int startX,
1365                                            final int endX,
1366                                            final int lineY,
1367                                            final Color bgColor,
1368                                            final Color fgColor,
1369                                            final boolean opaque) {
1370     final Color oldColor = g.getColor();
1371
1372     // Fill 2 lines with background color
1373     if (opaque && bgColor != null) {
1374       g.setColor(bgColor);
1375
1376       drawLine(g, startX, lineY, endX, lineY);
1377       drawLine(g, startX, lineY + 1, endX, lineY + 1);
1378     }
1379
1380     // Draw dotted line:
1381     //
1382     // CCC CCC CCC ...
1383     // CCC CCC CCC ...
1384     //
1385     // (where "C" - colored pixel, " " - white pixel)
1386
1387     final int step = 4;
1388     final int startPosCorrection = startX % step < 3 ? 0 : 1;
1389
1390     g.setColor(fgColor != null ? fgColor : oldColor);
1391     // Now draw bold line segments
1392     for (int dotXi = (startX / step + startPosCorrection) * step; dotXi < endX; dotXi += step) {
1393       g.drawLine(dotXi, lineY, dotXi + 1, lineY);
1394       g.drawLine(dotXi, lineY + 1, dotXi + 1, lineY + 1);
1395     }
1396
1397     // restore color
1398     g.setColor(oldColor);
1399   }
1400
1401   public static void drawGradientHToolbarBackground(final Graphics g, final int width, final int height) {
1402     final Graphics2D g2d = (Graphics2D)g;
1403     g2d.setPaint(getGradientPaint(0, 0, Gray._215, 0, height, Gray._200));
1404     g2d.fillRect(0, 0, width, height);
1405   }
1406
1407   public static void drawHeader(Graphics g, int x, int width, int height, boolean active, boolean drawTopLine) {
1408     drawHeader(g, x, width, height, active, false, drawTopLine, true);
1409   }
1410
1411   public static void drawHeader(Graphics g,
1412                                 int x,
1413                                 int width,
1414                                 int height,
1415                                 boolean active,
1416                                 boolean toolWindow,
1417                                 boolean drawTopLine,
1418                                 boolean drawBottomLine) {
1419     g.setColor(getPanelBackground());
1420     g.fillRect(x, 0, width, height);
1421
1422     ((Graphics2D)g).setPaint(getGradientPaint(0, 0, new Color(0, 0, 0, 5), 0, height, new Color(0, 0, 0, 20)));
1423     g.fillRect(x, 0, width, height);
1424
1425     g.setColor(new Color(0, 0, 0, toolWindow ? 90 : 50));
1426     if (drawTopLine) g.drawLine(x, 0, width, 0);
1427     if (drawBottomLine) g.drawLine(x, height - 1, width, height - 1);
1428
1429     g.setColor(isUnderDarcula() ? Gray._255.withAlpha(30) : new Color(255, 255, 255, 100));
1430     g.drawLine(x, drawTopLine ? 1 : 0, width, drawTopLine ? 1 : 0);
1431
1432     if (active) {
1433       g.setColor(new Color(100, 150, 230, toolWindow ? 50 : 30));
1434       g.fillRect(x, 0, width, height);
1435     }
1436   }
1437
1438   public static void drawDoubleSpaceDottedLine(final Graphics2D g,
1439                                                final int start,
1440                                                final int end,
1441                                                final int xOrY,
1442                                                final Color fgColor,
1443                                                boolean horizontal) {
1444
1445     g.setColor(fgColor);
1446     for (int dot = start; dot < end; dot += 3) {
1447       if (horizontal) {
1448         g.drawLine(dot, xOrY, dot, xOrY);
1449       }
1450       else {
1451         g.drawLine(xOrY, dot, xOrY, dot);
1452       }
1453     }
1454   }
1455
1456   private static void drawAppleDottedLine(final Graphics2D g,
1457                                           final int startX,
1458                                           final int endX,
1459                                           final int lineY,
1460                                           final Color bgColor,
1461                                           final Color fgColor,
1462                                           final boolean opaque) {
1463     final Color oldColor = g.getColor();
1464
1465     // Fill 3 lines with background color
1466     if (opaque && bgColor != null) {
1467       g.setColor(bgColor);
1468
1469       drawLine(g, startX, lineY, endX, lineY);
1470       drawLine(g, startX, lineY + 1, endX, lineY + 1);
1471       drawLine(g, startX, lineY + 2, endX, lineY + 2);
1472     }
1473
1474     // Draw apple like dotted line:
1475     //
1476     // CCC CCC CCC ...
1477     // CCC CCC CCC ...
1478     // CCC CCC CCC ...
1479     //
1480     // (where "C" - colored pixel, " " - white pixel)
1481
1482     final int step = 4;
1483     final int startPosCorrection = startX % step < 3 ? 0 : 1;
1484
1485     // Optimization - lets draw dotted line using dot sample image.
1486
1487     // draw one dot by pixel:
1488
1489     // save old settings
1490     final Composite oldComposite = g.getComposite();
1491     // draw image "over" on top of background
1492     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
1493
1494     // sample
1495     final BufferedImage image = getAppleDotStamp(fgColor, oldColor);
1496
1497     // Now copy our dot several times
1498     final int dotX0 = (startX / step + startPosCorrection) * step;
1499     for (int dotXi = dotX0; dotXi < endX; dotXi += step) {
1500       g.drawImage(image, dotXi, lineY, null);
1501     }
1502
1503     //restore previous settings
1504     g.setComposite(oldComposite);
1505   }
1506
1507   private static BufferedImage getAppleDotStamp(final Color fgColor,
1508                                                 final Color oldColor) {
1509     final Color color = fgColor != null ? fgColor : oldColor;
1510
1511     // let's avoid of generating tons of GC and store samples for different colors
1512     BufferedImage sample = ourAppleDotSamples.get(color);
1513     if (sample == null) {
1514       sample = createAppleDotStamp(color);
1515       ourAppleDotSamples.put(color, sample);
1516     }
1517     return sample;
1518   }
1519
1520   private static BufferedImage createAppleDotStamp(final Color color) {
1521     final BufferedImage image = createImage(3, 3, BufferedImage.TYPE_INT_ARGB);
1522     final Graphics2D g = image.createGraphics();
1523
1524     g.setColor(color);
1525
1526     // Each dot:
1527     // | 20%  | 50%  | 20% |
1528     // | 80%  | 80%  | 80% |
1529     // | 50%  | 100% | 50% |
1530
1531     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .2f));
1532     g.drawLine(0, 0, 0, 0);
1533     g.drawLine(2, 0, 2, 0);
1534
1535     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
1536     g.drawLine(0, 1, 2, 1);
1537
1538     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f));
1539     g.drawLine(1, 2, 1, 2);
1540
1541     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .5f));
1542     g.drawLine(1, 0, 1, 0);
1543     g.drawLine(0, 2, 0, 2);
1544     g.drawLine(2, 2, 2, 2);
1545
1546     // dispose graphics
1547     g.dispose();
1548
1549     return image;
1550   }
1551
1552   public static void applyRenderingHints(final Graphics g) {
1553     Toolkit tk = Toolkit.getDefaultToolkit();
1554     //noinspection HardCodedStringLiteral
1555     Map map = (Map)tk.getDesktopProperty("awt.font.desktophints");
1556     if (map != null) {
1557       ((Graphics2D)g).addRenderingHints(map);
1558     }
1559   }
1560
1561
1562   public static BufferedImage createImage(int width, int height, int type) {
1563     if (isRetina()) {
1564       return RetinaImage.create(width, height, type);
1565     }
1566     //noinspection UndesirableClassUsage
1567     return new BufferedImage(width, height, type);
1568   }
1569
1570   public static void drawImage(Graphics g, Image image, int x, int y, ImageObserver observer) {
1571     if (image instanceof JBHiDPIScaledImage) {
1572       final Graphics2D newG = (Graphics2D)g.create(x, y, image.getWidth(observer), image.getHeight(observer));
1573       newG.scale(0.5, 0.5);
1574       Image img = ((JBHiDPIScaledImage)image).getDelegate();
1575       if (img == null) {
1576         img = image;
1577       }
1578       newG.drawImage(img, 0, 0, observer);
1579       newG.scale(1, 1);
1580       newG.dispose();
1581     } else {
1582       g.drawImage(image, x, y, observer);
1583     }
1584   }
1585
1586   public static void drawImage(Graphics g, BufferedImage image, BufferedImageOp op, int x, int y) {
1587     if (image instanceof JBHiDPIScaledImage) {
1588       final Graphics2D newG = (Graphics2D)g.create(x, y, image.getWidth(null), image.getHeight(null));
1589       newG.scale(0.5, 0.5);
1590       Image img = ((JBHiDPIScaledImage)image).getDelegate();
1591       if (img == null) {
1592         img = image;
1593       }
1594       newG.drawImage((BufferedImage)img, op, 0, 0);
1595       newG.scale(1, 1);
1596       newG.dispose();
1597     } else {
1598       ((Graphics2D)g).drawImage(image, op, x, y);
1599     }
1600   }
1601
1602
1603   public static void paintWithXorOnRetina(@NotNull Dimension size, @NotNull Graphics g, Consumer<Graphics2D> paintRoutine) {
1604     paintWithXorOnRetina(size, g, true, paintRoutine);
1605   }
1606
1607   /**
1608    * Direct painting into component's graphics with XORMode is broken on retina-mode so we need to paint into an intermediate buffer first.
1609    */
1610   public static void paintWithXorOnRetina(@NotNull Dimension size,
1611                                           @NotNull Graphics g,
1612                                           boolean useRetinaCondition,
1613                                           Consumer<Graphics2D> paintRoutine) {
1614     if (!useRetinaCondition || !isRetina() || Registry.is("ide.mac.retina.disableDrawingFix")) {
1615       paintRoutine.consume((Graphics2D)g);
1616     }
1617     else {
1618       Rectangle rect = g.getClipBounds();
1619       if (rect == null) rect = new Rectangle(size);
1620
1621       //noinspection UndesirableClassUsage
1622       Image image = new BufferedImage(rect.width * 2, rect.height * 2, BufferedImage.TYPE_INT_RGB);
1623       Graphics2D imageGraphics = (Graphics2D)image.getGraphics();
1624
1625       imageGraphics.scale(2, 2);
1626       imageGraphics.translate(-rect.x, -rect.y);
1627       imageGraphics.setClip(rect.x, rect.y, rect.width, rect.height);
1628
1629       paintRoutine.consume(imageGraphics);
1630       image.flush();
1631       imageGraphics.dispose();
1632
1633       ((Graphics2D)g).scale(0.5, 0.5);
1634       g.drawImage(image, rect.x * 2, rect.y * 2, null);
1635     }
1636   }
1637
1638   /**
1639    * Configures composite to use for drawing text with the given graphics container.
1640    * <p/>
1641    * The whole idea is that <a href="http://en.wikipedia.org/wiki/X_Rendering_Extension">XRender-based</a> pipeline doesn't support
1642    * {@link AlphaComposite#SRC} and we should use {@link AlphaComposite#SRC_OVER} instead.
1643    *
1644    * @param g target graphics container
1645    */
1646   public static void setupComposite(@NotNull Graphics2D g) {
1647     g.setComposite(X_RENDER_ACTIVE.getValue() ? AlphaComposite.SrcOver : AlphaComposite.Src);
1648   }
1649
1650   /** @see #pump() */
1651   @TestOnly
1652   public static void dispatchAllInvocationEvents() {
1653     assert SwingUtilities.isEventDispatchThread() : Thread.currentThread() + "; EDT: "+getEventQueueThread();
1654     final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1655     while (true) {
1656       AWTEvent event = eventQueue.peekEvent();
1657       if (event == null) break;
1658       try {
1659         AWTEvent event1 = eventQueue.getNextEvent();
1660         if (event1 instanceof InvocationEvent) {
1661           ((InvocationEvent)event1).dispatch();
1662         }
1663       }
1664       catch (Exception e) {
1665         LOG.error(e); //?
1666       }
1667     }
1668   }
1669   private static Thread getEventQueueThread() {
1670     EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1671     try {
1672       Method method = ReflectionUtil.getDeclaredMethod(EventQueue.class, "getDispatchThread");
1673       return (Thread)method.invoke(eventQueue);
1674     }
1675     catch (Exception e) {
1676       throw new RuntimeException(e);
1677     }
1678   }
1679
1680   /** @see #dispatchAllInvocationEvents() */
1681   @TestOnly
1682   public static void pump() {
1683     assert !SwingUtilities.isEventDispatchThread();
1684     final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
1685     SwingUtilities.invokeLater(new Runnable() {
1686       @Override
1687       public void run() {
1688         queue.offer(queue);
1689       }
1690     });
1691     try {
1692       queue.take();
1693     }
1694     catch (InterruptedException e) {
1695       LOG.error(e);
1696     }
1697   }
1698
1699   public static void addAwtListener(final AWTEventListener listener, long mask, Disposable parent) {
1700     Toolkit.getDefaultToolkit().addAWTEventListener(listener, mask);
1701     Disposer.register(parent, new Disposable() {
1702       @Override
1703       public void dispose() {
1704         Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
1705       }
1706     });
1707   }
1708
1709   public static void drawVDottedLine(Graphics2D g, int lineX, int startY, int endY, @Nullable final Color bgColor, final Color fgColor) {
1710     if (bgColor != null) {
1711       g.setColor(bgColor);
1712       drawLine(g, lineX, startY, lineX, endY);
1713     }
1714
1715     g.setColor(fgColor);
1716     for (int i = (startY / 2) * 2; i < endY; i += 2) {
1717       g.drawRect(lineX, i, 0, 0);
1718     }
1719   }
1720
1721   public static void drawHDottedLine(Graphics2D g, int startX, int endX, int lineY, @Nullable final Color bgColor, final Color fgColor) {
1722     if (bgColor != null) {
1723       g.setColor(bgColor);
1724       drawLine(g, startX, lineY, endX, lineY);
1725     }
1726
1727     g.setColor(fgColor);
1728
1729     for (int i = (startX / 2) * 2; i < endX; i += 2) {
1730       g.drawRect(i, lineY, 0, 0);
1731     }
1732   }
1733
1734   public static void drawDottedLine(Graphics2D g, int x1, int y1, int x2, int y2, @Nullable final Color bgColor, final Color fgColor) {
1735     if (x1 == x2) {
1736       drawVDottedLine(g, x1, y1, y2, bgColor, fgColor);
1737     }
1738     else if (y1 == y2) {
1739       drawHDottedLine(g, x1, x2, y1, bgColor, fgColor);
1740     }
1741     else {
1742       throw new IllegalArgumentException("Only vertical or horizontal lines are supported");
1743     }
1744   }
1745
1746   public static void drawStringWithHighlighting(Graphics g, String s, int x, int y, Color foreground, Color highlighting) {
1747     g.setColor(highlighting);
1748     for (int i = x - 1; i <= x + 1; i++) {
1749       for (int j = y - 1; j <= y + 1; j++) {
1750         g.drawString(s, i, j);
1751       }
1752     }
1753     g.setColor(foreground);
1754     g.drawString(s, x, y);
1755   }
1756
1757   public static boolean isFocusAncestor(@NotNull final JComponent component) {
1758     final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1759     if (owner == null) return false;
1760     if (owner == component) return true;
1761     return SwingUtilities.isDescendingFrom(owner, component);
1762   }
1763
1764
1765   public static boolean isCloseClick(MouseEvent e) {
1766     return isCloseClick(e, MouseEvent.MOUSE_PRESSED);
1767   }
1768
1769   public static boolean isCloseClick(MouseEvent e, int effectiveType) {
1770     if (e.isPopupTrigger() || e.getID() != effectiveType) return false;
1771     return e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown();
1772   }
1773
1774   public static boolean isActionClick(MouseEvent e) {
1775     return isActionClick(e, MouseEvent.MOUSE_PRESSED);
1776   }
1777
1778   public static boolean isActionClick(MouseEvent e, int effectiveType) {
1779     return isActionClick(e, effectiveType, false);
1780   }
1781
1782   public static boolean isActionClick(MouseEvent e, int effectiveType, boolean allowShift) {
1783     if (!allowShift && isCloseClick(e) || e.isPopupTrigger() || e.getID() != effectiveType) return false;
1784     return e.getButton() == MouseEvent.BUTTON1;
1785   }
1786
1787   @NotNull
1788   public static Color getBgFillColor(@NotNull JComponent c) {
1789     final Component parent = findNearestOpaque(c);
1790     return parent == null ? c.getBackground() : parent.getBackground();
1791   }
1792
1793   @Nullable
1794   public static Component findNearestOpaque(JComponent c) {
1795     return findParentByCondition(c, new Condition<Component>() {
1796       @Override
1797       public boolean value(Component component) {
1798         return component.isOpaque();
1799       }
1800     });
1801   }
1802
1803   @Nullable
1804   public static Component findParentByCondition(@NotNull JComponent c, Condition<Component> condition) {
1805     Component eachParent = c;
1806     while (eachParent != null) {
1807       if (condition.value(eachParent)) return eachParent;
1808       eachParent = eachParent.getParent();
1809     }
1810     return null;
1811   }
1812
1813   @NonNls
1814   public static String getCssFontDeclaration(final Font font) {
1815     return getCssFontDeclaration(font, null, null, null);
1816   }
1817
1818   @Language("HTML")
1819   @NonNls
1820   public static String getCssFontDeclaration(final Font font, @Nullable Color fgColor, @Nullable Color linkColor, @Nullable String liImg) {
1821     URL resource = liImg != null ? SystemInfo.class.getResource(liImg) : null;
1822
1823     @NonNls String fontFamilyAndSize = "font-family:'" + font.getFamily() + "'; font-size:" + font.getSize() + "pt;";
1824     @NonNls @Language("HTML")
1825     String body = "body, div, td, p {" + fontFamilyAndSize + " " + (fgColor != null ? "color:#" + ColorUtil.toHex(fgColor)+";" : "") + "}\n";
1826     if (resource != null) {
1827       body += "ul {list-style-image:url('" + resource.toExternalForm() + "');}\n";
1828     }
1829     @NonNls String link = linkColor != null ? "a {" + fontFamilyAndSize + " color:#"+ColorUtil.toHex(linkColor) + ";}\n" : "";
1830     return "<style>\n" + body + link + "</style>";
1831   }
1832
1833   public static boolean isWinLafOnVista() {
1834     return SystemInfo.isWinVistaOrNewer && "Windows".equals(UIManager.getLookAndFeel().getName());
1835   }
1836
1837   public static boolean isStandardMenuLAF() {
1838     return isWinLafOnVista() ||
1839            isUnderNimbusLookAndFeel() ||
1840            isUnderGTKLookAndFeel();
1841   }
1842
1843   public static Color getFocusedFillColor() {
1844     return toAlpha(getListSelectionBackground(), 100);
1845   }
1846
1847   public static Color getFocusedBoundsColor() {
1848     return getBoundsColor();
1849   }
1850
1851   public static Color getBoundsColor() {
1852     return getBorderColor();
1853   }
1854
1855   public static Color getBoundsColor(boolean focused) {
1856     return focused ? getFocusedBoundsColor() : getBoundsColor();
1857   }
1858
1859   public static Color toAlpha(final Color color, final int alpha) {
1860     Color actual = color != null ? color : Color.black;
1861     return new Color(actual.getRed(), actual.getGreen(), actual.getBlue(), alpha);
1862   }
1863
1864   public static void requestFocus(@NotNull final JComponent c) {
1865     if (c.isShowing()) {
1866       c.requestFocus();
1867     }
1868     else {
1869       SwingUtilities.invokeLater(new Runnable() {
1870         @Override
1871         public void run() {
1872           c.requestFocus();
1873         }
1874       });
1875     }
1876   }
1877
1878   //todo maybe should do for all kind of listeners via the AWTEventMulticaster class
1879
1880   public static void dispose(final Component c) {
1881     if (c == null) return;
1882
1883     final MouseListener[] mouseListeners = c.getMouseListeners();
1884     for (MouseListener each : mouseListeners) {
1885       c.removeMouseListener(each);
1886     }
1887
1888     final MouseMotionListener[] motionListeners = c.getMouseMotionListeners();
1889     for (MouseMotionListener each : motionListeners) {
1890       c.removeMouseMotionListener(each);
1891     }
1892
1893     final MouseWheelListener[] mouseWheelListeners = c.getMouseWheelListeners();
1894     for (MouseWheelListener each : mouseWheelListeners) {
1895       c.removeMouseWheelListener(each);
1896     }
1897   }
1898
1899   public static void disposeProgress(final JProgressBar progress) {
1900     if (!isUnderNativeMacLookAndFeel()) return;
1901
1902     SwingUtilities.invokeLater(new Runnable() {
1903       @Override
1904       public void run() {
1905         if (isToDispose(progress)) {
1906           progress.getUI().uninstallUI(progress);
1907           progress.putClientProperty("isDisposed", Boolean.TRUE);
1908         }
1909       }
1910     });
1911   }
1912
1913   private static boolean isToDispose(final JProgressBar progress) {
1914     final ProgressBarUI ui = progress.getUI();
1915
1916     if (ui == null) return false;
1917     if (Boolean.TYPE.equals(progress.getClientProperty("isDisposed"))) return false;
1918
1919     try {
1920       final Field progressBarField = ReflectionUtil.findField(ui.getClass(), JProgressBar.class, "progressBar");
1921       progressBarField.setAccessible(true);
1922       return progressBarField.get(ui) != null;
1923     }
1924     catch (NoSuchFieldException e) {
1925       return true;
1926     }
1927     catch (IllegalAccessException e) {
1928       return true;
1929     }
1930   }
1931
1932   @Nullable
1933   public static Component findUltimateParent(Component c) {
1934     if (c == null) return null;
1935
1936     Component eachParent = c;
1937     while (true) {
1938       if (eachParent.getParent() == null) return eachParent;
1939       eachParent = eachParent.getParent();
1940     }
1941   }
1942
1943   public static Color getHeaderActiveColor() {
1944     return ACTIVE_HEADER_COLOR;
1945   }
1946
1947   public static Color getHeaderInactiveColor() {
1948     return INACTIVE_HEADER_COLOR;
1949   }
1950
1951   /**
1952    * @deprecated
1953    * @use JBColor.border()
1954    */
1955   public static Color getBorderColor() {
1956     return isUnderDarcula() ? Gray._50 : BORDER_COLOR;
1957   }
1958
1959   public static Font getTitledBorderFont() {
1960     Font defFont = getLabelFont();
1961     return defFont.deriveFont(defFont.getSize() - 1f);
1962   }
1963
1964   /**
1965    * @deprecated use getBorderColor instead
1966    */
1967   public static Color getBorderInactiveColor() {
1968     return getBorderColor();
1969   }
1970
1971   /**
1972    * @deprecated use getBorderColor instead
1973    */
1974   public static Color getBorderActiveColor() {
1975     return getBorderColor();
1976   }
1977
1978   /**
1979    * @deprecated use getBorderColor instead
1980    */
1981   public static Color getBorderSeparatorColor() {
1982     return getBorderColor();
1983   }
1984
1985   @Nullable
1986   public static StyleSheet loadStyleSheet(@Nullable URL url) {
1987     if (url == null) return null;
1988     try {
1989       StyleSheet styleSheet = new StyleSheet();
1990       styleSheet.loadRules(new InputStreamReader(url.openStream(), CharsetToolkit.UTF8), url);
1991       return styleSheet;
1992     }
1993     catch (IOException e) {
1994       LOG.warn(url + " loading failed", e);
1995       return null;
1996     }
1997   }
1998
1999   public static HTMLEditorKit getHTMLEditorKit() {
2000     Font font = getLabelFont();
2001     @NonNls String family = !SystemInfo.isWindows && font != null ? font.getFamily() : "Tahoma";
2002     int size = font != null ? font.getSize() : 11;
2003
2004     final String customCss = String.format("body, div, p { font-family: %s; font-size: %s; } p { margin-top: 0; }", family, size);
2005
2006     final StyleSheet style = new StyleSheet();
2007     style.addStyleSheet(isUnderDarcula() ? (StyleSheet)UIManager.getDefaults().get("StyledEditorKit.JBDefaultStyle") : DEFAULT_HTML_KIT_CSS);
2008     style.addRule(customCss);
2009
2010     return new HTMLEditorKit() {
2011       @Override
2012       public StyleSheet getStyleSheet() {
2013         return style;
2014       }
2015     };
2016   }
2017
2018   public static void removeScrollBorder(final Component c) {
2019     new AwtVisitor(c) {
2020       @Override
2021       public boolean visit(final Component component) {
2022         if (component instanceof JScrollPane) {
2023           if (!hasNonPrimitiveParents(c, component)) {
2024             final JScrollPane scrollPane = (JScrollPane)component;
2025             Integer keepBorderSides = getClientProperty(scrollPane, KEEP_BORDER_SIDES);
2026             if (keepBorderSides != null) {
2027               if (scrollPane.getBorder() instanceof LineBorder) {
2028                 Color color = ((LineBorder)scrollPane.getBorder()).getLineColor();
2029                 scrollPane.setBorder(new SideBorder(color, keepBorderSides.intValue()));
2030               }
2031               else {
2032                 scrollPane.setBorder(new SideBorder(getBoundsColor(), keepBorderSides.intValue()));
2033               }
2034             }
2035             else {
2036               scrollPane.setBorder(new SideBorder(getBoundsColor(), SideBorder.NONE));
2037             }
2038           }
2039         }
2040         return false;
2041       }
2042     };
2043   }
2044
2045   public static boolean hasNonPrimitiveParents(Component stopParent, Component c) {
2046     Component eachParent = c.getParent();
2047     while (true) {
2048       if (eachParent == null || eachParent == stopParent) return false;
2049       if (!isPrimitive(eachParent)) return true;
2050       eachParent = eachParent.getParent();
2051     }
2052   }
2053
2054   public static boolean isPrimitive(Component c) {
2055     return c instanceof JPanel || c instanceof JLayeredPane;
2056   }
2057
2058   public static Point getCenterPoint(Dimension container, Dimension child) {
2059     return getCenterPoint(new Rectangle(new Point(), container), child);
2060   }
2061
2062   public static Point getCenterPoint(Rectangle container, Dimension child) {
2063     Point result = new Point();
2064
2065     Point containerLocation = container.getLocation();
2066     Dimension containerSize = container.getSize();
2067
2068     result.x = containerLocation.x + containerSize.width / 2 - child.width / 2;
2069     result.y = containerLocation.y + containerSize.height / 2 - child.height / 2;
2070
2071     return result;
2072   }
2073
2074   public static String toHtml(String html) {
2075     return toHtml(html, 0);
2076   }
2077
2078   @NonNls
2079   public static String toHtml(String html, final int hPadding) {
2080     html = CLOSE_TAG_PATTERN.matcher(html).replaceAll("<$1$2></$1>");
2081     Font font = getLabelFont();
2082     @NonNls String family = font != null ? font.getFamily() : "Tahoma";
2083     int size = font != null ? font.getSize() : 11;
2084     return "<html><style>body { font-family: "
2085            + family + "; font-size: "
2086            + size + ";} ul li {list-style-type:circle;}</style>"
2087            + addPadding(html, hPadding) + "</html>";
2088   }
2089
2090   public static String addPadding(final String html, int hPadding) {
2091     return String.format("<p style=\"margin: 0 %dpx 0 %dpx;\">%s</p>", hPadding, hPadding, html);
2092   }
2093
2094   public static String convertSpace2Nbsp(String html) {
2095     @NonNls StringBuilder result = new StringBuilder();
2096     int currentPos = 0;
2097     int braces = 0;
2098     while (currentPos < html.length()) {
2099       String each = html.substring(currentPos, currentPos + 1);
2100       if ("<".equals(each)) {
2101         braces++;
2102       }
2103       else if (">".equals(each)) {
2104         braces--;
2105       }
2106
2107       if (" ".equals(each) && braces == 0) {
2108         result.append("&nbsp;");
2109       }
2110       else {
2111         result.append(each);
2112       }
2113       currentPos++;
2114     }
2115
2116     return result.toString();
2117   }
2118
2119   public static void invokeLaterIfNeeded(@NotNull Runnable runnable) {
2120     if (SwingUtilities.isEventDispatchThread()) {
2121       runnable.run();
2122     }
2123     else {
2124       SwingUtilities.invokeLater(runnable);
2125     }
2126   }
2127
2128   /**
2129    * Invoke and wait in the event dispatch thread
2130    * or in the current thread if the current thread
2131    * is event queue thread.
2132    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2133    * @param runnable a runnable to invoke
2134    * @see #invokeAndWaitIfNeeded(com.intellij.util.ThrowableRunnable)
2135    */
2136   public static void invokeAndWaitIfNeeded(@NotNull Runnable runnable) {
2137     if (SwingUtilities.isEventDispatchThread()) {
2138       runnable.run();
2139     }
2140     else {
2141       try {
2142         SwingUtilities.invokeAndWait(runnable);
2143       }
2144       catch (Exception e) {
2145         LOG.error(e);
2146       }
2147     }
2148   }
2149
2150   /**
2151    * Invoke and wait in the event dispatch thread
2152    * or in the current thread if the current thread
2153    * is event queue thread.
2154    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2155    * @param computable a runnable to invoke
2156    * @see #invokeAndWaitIfNeeded(com.intellij.util.ThrowableRunnable)
2157    */
2158   public static <T> T invokeAndWaitIfNeeded(@NotNull final Computable<T> computable) {
2159     final Ref<T> result = Ref.create();
2160     invokeAndWaitIfNeeded(new Runnable() {
2161       @Override
2162       public void run() {
2163         result.set(computable.compute());
2164       }
2165     });
2166     return result.get();
2167   }
2168
2169   /**
2170    * Invoke and wait in the event dispatch thread
2171    * or in the current thread if the current thread
2172    * is event queue thread.
2173    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2174    * @param runnable a runnable to invoke
2175    * @see #invokeAndWaitIfNeeded(com.intellij.util.ThrowableRunnable)
2176    */
2177   public static void invokeAndWaitIfNeeded(@NotNull final ThrowableRunnable runnable) throws Throwable {
2178     if (SwingUtilities.isEventDispatchThread()) {
2179       runnable.run();
2180     }
2181     else {
2182       final Ref<Throwable> ref = new Ref<Throwable>();
2183       SwingUtilities.invokeAndWait(new Runnable() {
2184         @Override
2185         public void run() {
2186           try {
2187             runnable.run();
2188           }
2189           catch (Throwable throwable) {
2190             ref.set(throwable);
2191           }
2192         }
2193       });
2194       if (!ref.isNull()) throw ref.get();
2195     }
2196   }
2197
2198   public static boolean isFocusProxy(@Nullable Component c) {
2199     return c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty(FOCUS_PROXY_KEY));
2200   }
2201
2202   public static void setFocusProxy(JComponent c, boolean isProxy) {
2203     c.putClientProperty(FOCUS_PROXY_KEY, isProxy ? Boolean.TRUE : null);
2204   }
2205
2206   public static void maybeInstall(InputMap map, String action, KeyStroke stroke) {
2207     if (map.get(stroke) == null) {
2208       map.put(stroke, action);
2209     }
2210   }
2211
2212   /**
2213    * Avoid blinking while changing background.
2214    *
2215    * @param component  component.
2216    * @param background new background.
2217    */
2218   public static void changeBackGround(final Component component, final Color background) {
2219     final Color oldBackGround = component.getBackground();
2220     if (background == null || !background.equals(oldBackGround)) {
2221       component.setBackground(background);
2222     }
2223   }
2224
2225   public static void initDefaultLAF() {
2226     try {
2227       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
2228
2229       if (ourSystemFontData == null) {
2230         Font font = getLabelFont();
2231         if (SystemInfo.isWindows) {
2232           //noinspection HardCodedStringLiteral
2233           Font winFont = (Font)Toolkit.getDefaultToolkit().getDesktopProperty("win.messagebox.font");
2234           if (winFont != null) font = winFont;
2235         }
2236         ourSystemFontData = Pair.create(font.getName(), font.getSize());
2237       }
2238     }
2239     catch (Exception ignored) { }
2240   }
2241
2242   @Nullable
2243   public static Pair<String, Integer> getSystemFontData() {
2244     return ourSystemFontData;
2245   }
2246
2247   public static void addKeyboardShortcut(final JComponent target, final AbstractButton button, final KeyStroke keyStroke) {
2248     target.registerKeyboardAction(
2249       new ActionListener() {
2250         @Override
2251         public void actionPerformed(ActionEvent e) {
2252           if (button.isEnabled()) {
2253             button.doClick();
2254           }
2255         }
2256       },
2257       keyStroke,
2258       JComponent.WHEN_FOCUSED
2259     );
2260   }
2261
2262   public static void installComboBoxCopyAction(JComboBox comboBox) {
2263     final Component editorComponent = comboBox.getEditor().getEditorComponent();
2264     if (!(editorComponent instanceof JTextComponent)) return;
2265     final InputMap inputMap = ((JTextComponent)editorComponent).getInputMap();
2266     for (KeyStroke keyStroke : inputMap.allKeys()) {
2267       if (DefaultEditorKit.copyAction.equals(inputMap.get(keyStroke))) {
2268         comboBox.getInputMap().put(keyStroke, DefaultEditorKit.copyAction);
2269       }
2270     }
2271     comboBox.getActionMap().put(DefaultEditorKit.copyAction, new AbstractAction() {
2272       @Override
2273       public void actionPerformed(final ActionEvent e) {
2274         if (!(e.getSource() instanceof JComboBox)) return;
2275         final JComboBox comboBox = (JComboBox)e.getSource();
2276         final String text;
2277         final Object selectedItem = comboBox.getSelectedItem();
2278         if (selectedItem instanceof String) {
2279           text = (String)selectedItem;
2280         }
2281         else {
2282           final Component component =
2283             comboBox.getRenderer().getListCellRendererComponent(new JList(), selectedItem, 0, false, false);
2284           if (component instanceof JLabel) {
2285             text = ((JLabel)component).getText();
2286           }
2287           else if (component != null) {
2288             final String str = component.toString();
2289             // skip default Component.toString and handle SimpleColoredComponent case
2290             text = str == null || str.startsWith(component.getClass().getName() + "[") ? null : str;
2291           }
2292           else {
2293             text = null;
2294           }
2295         }
2296         if (text != null) {
2297           final JTextField textField = new JTextField(text);
2298           textField.selectAll();
2299           textField.copy();
2300         }
2301       }
2302     });
2303   }
2304
2305   @Nullable
2306   public static ComboPopup getComboBoxPopup(@NotNull JComboBox comboBox) {
2307     final ComboBoxUI ui = comboBox.getUI();
2308     if (ui instanceof BasicComboBoxUI) {
2309       return ReflectionUtil.getField(BasicComboBoxUI.class, ui, ComboPopup.class, "popup");
2310     }
2311
2312     return null;
2313   }
2314
2315   @SuppressWarnings({"HardCodedStringLiteral"})
2316   public static void fixFormattedField(JFormattedTextField field) {
2317     if (SystemInfo.isMac) {
2318       final Toolkit toolkit = Toolkit.getDefaultToolkit();
2319       final int commandKeyMask = toolkit.getMenuShortcutKeyMask();
2320       final InputMap inputMap = field.getInputMap();
2321       final KeyStroke copyKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, commandKeyMask);
2322       inputMap.put(copyKeyStroke, "copy-to-clipboard");
2323       final KeyStroke pasteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, commandKeyMask);
2324       inputMap.put(pasteKeyStroke, "paste-from-clipboard");
2325       final KeyStroke cutKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, commandKeyMask);
2326       inputMap.put(cutKeyStroke, "cut-to-clipboard");
2327     }
2328   }
2329
2330   public static boolean isPrinting(Graphics g) {
2331     return g instanceof PrintGraphics;
2332   }
2333
2334   public static int getSelectedButton(ButtonGroup group) {
2335     Enumeration<AbstractButton> enumeration = group.getElements();
2336     int i = 0;
2337     while (enumeration.hasMoreElements()) {
2338       AbstractButton button = enumeration.nextElement();
2339       if (group.isSelected(button.getModel())) {
2340         return i;
2341       }
2342       i++;
2343     }
2344     return -1;
2345   }
2346
2347   public static void setSelectedButton(ButtonGroup group, int index) {
2348     Enumeration<AbstractButton> enumeration = group.getElements();
2349     int i = 0;
2350     while (enumeration.hasMoreElements()) {
2351       AbstractButton button = enumeration.nextElement();
2352       group.setSelected(button.getModel(), index == i);
2353       i++;
2354     }
2355   }
2356
2357   public static boolean isSelectionButtonDown(MouseEvent e) {
2358     return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
2359   }
2360
2361   @SuppressWarnings("deprecation")
2362   public static void setComboBoxEditorBounds(int x, int y, int width, int height, JComponent editor) {
2363     if (SystemInfo.isMac && isUnderAquaLookAndFeel()) {
2364       // fix for too wide combobox editor, see AquaComboBoxUI.layoutContainer:
2365       // it adds +4 pixels to editor width. WTF?!
2366       editor.reshape(x, y, width - 4, height - 1);
2367     }
2368     else {
2369       editor.reshape(x, y, width, height);
2370     }
2371   }
2372
2373   public static int fixComboBoxHeight(final int height) {
2374     return SystemInfo.isMac && isUnderAquaLookAndFeel() ? 28 : height;
2375   }
2376
2377
2378   /**
2379    * The main difference from javax.swing.SwingUtilities#isDescendingFrom(Component, Component) is that this method
2380    * uses getInvoker() instead of getParent() when it meets JPopupMenu
2381    * @param child child component
2382    * @param parent parent component
2383    * @return true if parent if a top parent of child, false otherwise
2384    *
2385    * @see javax.swing.SwingUtilities#isDescendingFrom(java.awt.Component, java.awt.Component)
2386    */
2387   public static boolean isDescendingFrom(@Nullable Component child, @NotNull Component parent) {
2388     while (child != null && child != parent) {
2389       child =  child instanceof JPopupMenu  ? ((JPopupMenu)child).getInvoker()
2390                                             : child.getParent();
2391     }
2392     return child == parent;
2393   }
2394
2395   @Nullable
2396   public static <T> T getParentOfType(Class<? extends T> cls, Component c) {
2397     Component eachParent = c;
2398     while (eachParent != null) {
2399       if (cls.isAssignableFrom(eachParent.getClass())) {
2400         @SuppressWarnings({"unchecked"}) final T t = (T)eachParent;
2401         return t;
2402       }
2403
2404       eachParent = eachParent.getParent();
2405     }
2406
2407     return null;
2408   }
2409
2410   public static void scrollListToVisibleIfNeeded(@NotNull final JList list) {
2411     SwingUtilities.invokeLater(new Runnable() {
2412       @Override
2413       public void run() {
2414         final int selectedIndex = list.getSelectedIndex();
2415         if (selectedIndex >= 0) {
2416           final Rectangle visibleRect = list.getVisibleRect();
2417           final Rectangle cellBounds = list.getCellBounds(selectedIndex, selectedIndex);
2418           if (!visibleRect.contains(cellBounds)) {
2419             list.scrollRectToVisible(cellBounds);
2420           }
2421         }
2422       }
2423     });
2424   }
2425
2426   @Nullable
2427   public static <T extends JComponent> T findComponentOfType(JComponent parent, Class<T> cls) {
2428     if (parent == null || cls.isAssignableFrom(parent.getClass())) {
2429       @SuppressWarnings({"unchecked"}) final T t = (T)parent;
2430       return t;
2431     }
2432     for (Component component : parent.getComponents()) {
2433       if (component instanceof JComponent) {
2434         T comp = findComponentOfType((JComponent)component, cls);
2435         if (comp != null) return comp;
2436       }
2437     }
2438     return null;
2439   }
2440
2441   public static <T extends JComponent> List<T> findComponentsOfType(JComponent parent, Class<T> cls) {
2442     final ArrayList<T> result = new ArrayList<T>();
2443     findComponentsOfType(parent, cls, result);
2444     return result;
2445   }
2446
2447   private static <T extends JComponent> void findComponentsOfType(JComponent parent, Class<T> cls, ArrayList<T> result) {
2448     if (parent == null) return;
2449     if (cls.isAssignableFrom(parent.getClass())) {
2450       @SuppressWarnings({"unchecked"}) final T t = (T)parent;
2451       result.add(t);
2452     }
2453     for (Component c : parent.getComponents()) {
2454       if (c instanceof JComponent) {
2455         findComponentsOfType((JComponent)c, cls, result);
2456       }
2457     }
2458   }
2459
2460   public static class TextPainter {
2461     private final List<Pair<String, LineInfo>> myLines = new ArrayList<Pair<String, LineInfo>>();
2462     private boolean myDrawShadow;
2463     private Color myShadowColor;
2464     private float myLineSpacing;
2465
2466     public TextPainter() {
2467       myDrawShadow = /*isUnderAquaLookAndFeel() ||*/ isUnderDarcula();
2468       myShadowColor = isUnderDarcula() ? Gray._0.withAlpha(100) : Gray._220;
2469       myLineSpacing = 1.0f;
2470     }
2471
2472     public TextPainter withShadow(final boolean drawShadow) {
2473       myDrawShadow = drawShadow;
2474       return this;
2475     }
2476
2477     public TextPainter withShadow(final boolean drawShadow, final Color shadowColor) {
2478       myDrawShadow = drawShadow;
2479       myShadowColor = shadowColor;
2480       return this;
2481     }
2482
2483     public TextPainter withLineSpacing(final float lineSpacing) {
2484       myLineSpacing = lineSpacing;
2485       return this;
2486     }
2487
2488     public TextPainter appendLine(final String text) {
2489       if (text == null || text.isEmpty()) return this;
2490       myLines.add(Pair.create(text, new LineInfo()));
2491       return this;
2492     }
2493
2494     public TextPainter underlined(@Nullable final Color color) {
2495       if (!myLines.isEmpty()) {
2496         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
2497         info.underlined = true;
2498         info.underlineColor = color;
2499       }
2500
2501       return this;
2502     }
2503
2504     public TextPainter withBullet(final char c) {
2505       if (!myLines.isEmpty()) {
2506         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
2507         info.withBullet = true;
2508         info.bulletChar = c;
2509       }
2510
2511       return this;
2512     }
2513
2514     public TextPainter withBullet() {
2515       return withBullet('\u2022');
2516     }
2517
2518     public TextPainter underlined() {
2519       return underlined(null);
2520     }
2521
2522     public TextPainter smaller() {
2523       if (!myLines.isEmpty()) {
2524         myLines.get(myLines.size() - 1).getSecond().smaller = true;
2525       }
2526
2527       return this;
2528     }
2529
2530     public TextPainter center() {
2531       if (!myLines.isEmpty()) {
2532         myLines.get(myLines.size() - 1).getSecond().center = true;
2533       }
2534
2535       return this;
2536     }
2537
2538     /**
2539      * _position(block width, block height) => (x, y) of the block
2540      */
2541     public void draw(@NotNull final Graphics g, final PairFunction<Integer, Integer, Couple<Integer>> _position) {
2542       final int[] maxWidth = {0};
2543       final int[] height = {0};
2544       final int[] maxBulletWidth = {0};
2545       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
2546         @Override
2547         public boolean process(final Pair<String, LineInfo> pair) {
2548           final LineInfo info = pair.getSecond();
2549           Font old = null;
2550           if (info.smaller) {
2551             old = g.getFont();
2552             g.setFont(old.deriveFont(old.getSize() * 0.70f));
2553           }
2554
2555           final FontMetrics fm = g.getFontMetrics();
2556
2557           final int bulletWidth = info.withBullet ? fm.stringWidth(" " + info.bulletChar) : 0;
2558           maxBulletWidth[0] = Math.max(maxBulletWidth[0], bulletWidth);
2559
2560           maxWidth[0] = Math.max(fm.stringWidth(pair.getFirst().replace("<shortcut>", "").replace("</shortcut>", "") + bulletWidth), maxWidth[0]);
2561           height[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2562
2563           if (old != null) {
2564             g.setFont(old);
2565           }
2566
2567           return true;
2568         }
2569       });
2570
2571       final Couple<Integer> position = _position.fun(maxWidth[0] + 20, height[0]);
2572       assert position != null;
2573
2574       final int[] yOffset = {position.getSecond()};
2575       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
2576         @Override
2577         public boolean process(final Pair<String, LineInfo> pair) {
2578           final LineInfo info = pair.getSecond();
2579           String text = pair.first;
2580           String shortcut = "";
2581           if (pair.first.contains("<shortcut>")) {
2582             shortcut = text.substring(text.indexOf("<shortcut>") + "<shortcut>".length(), text.indexOf("</shortcut>"));
2583             text = text.substring(0, text.indexOf("<shortcut>"));
2584           }
2585
2586           Font old = null;
2587           if (info.smaller) {
2588             old = g.getFont();
2589             g.setFont(old.deriveFont(old.getSize() * 0.70f));
2590           }
2591
2592           final int x = position.getFirst() + maxBulletWidth[0] + 10;
2593
2594           final FontMetrics fm = g.getFontMetrics();
2595           int xOffset = x;
2596           if (info.center) {
2597             xOffset = x + (maxWidth[0] - fm.stringWidth(text)) / 2;
2598           }
2599
2600           if (myDrawShadow) {
2601             int xOff = isUnderDarcula() ? 1 : 0;
2602             int yOff = 1;
2603             Color oldColor = g.getColor();
2604             g.setColor(myShadowColor);
2605
2606             if (info.withBullet) {
2607               g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar) + xOff, yOffset[0] + yOff);
2608             }
2609
2610             g.drawString(text, xOffset + xOff, yOffset[0] + yOff);
2611             g.setColor(oldColor);
2612           }
2613
2614           if (info.withBullet) {
2615             g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar), yOffset[0]);
2616           }
2617
2618           g.drawString(text, xOffset, yOffset[0]);
2619           if (!StringUtil.isEmpty(shortcut)) {
2620             Color oldColor = g.getColor();
2621             if (isUnderDarcula()) {
2622               g.setColor(new Color(60, 118, 249));
2623             }
2624             g.drawString(shortcut, xOffset + fm.stringWidth(text + (isUnderDarcula() ? " " : "")), yOffset[0]);
2625             g.setColor(oldColor);
2626           }
2627
2628           if (info.underlined) {
2629             Color c = null;
2630             if (info.underlineColor != null) {
2631               c = g.getColor();
2632               g.setColor(info.underlineColor);
2633             }
2634
2635             g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent(), x + maxWidth[0] + 10, yOffset[0] + fm.getDescent());
2636             if (c != null) {
2637               g.setColor(c);
2638
2639             }
2640
2641             if (myDrawShadow) {
2642               c = g.getColor();
2643               g.setColor(myShadowColor);
2644               g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent() + 1, x + maxWidth[0] + 10,
2645                          yOffset[0] + fm.getDescent() + 1);
2646               g.setColor(c);
2647             }
2648           }
2649
2650           yOffset[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2651
2652           if (old != null) {
2653             g.setFont(old);
2654           }
2655
2656           return true;
2657         }
2658       });
2659     }
2660
2661     private static class LineInfo {
2662       private boolean underlined;
2663       private boolean withBullet;
2664       private char bulletChar;
2665       private Color underlineColor;
2666       private boolean smaller;
2667       private boolean center;
2668     }
2669   }
2670
2671   @Nullable
2672   public static JRootPane getRootPane(Component c) {
2673     JRootPane root = getParentOfType(JRootPane.class, c);
2674     if (root != null) return root;
2675     Component eachParent = c;
2676     while (eachParent != null) {
2677       if (eachParent instanceof JComponent) {
2678         @SuppressWarnings({"unchecked"}) WeakReference<JRootPane> pane =
2679           (WeakReference<JRootPane>)((JComponent)eachParent).getClientProperty(ROOT_PANE);
2680         if (pane != null) return pane.get();
2681       }
2682       eachParent = eachParent.getParent();
2683     }
2684
2685     return null;
2686   }
2687
2688   public static void setFutureRootPane(JComponent c, JRootPane pane) {
2689     c.putClientProperty(ROOT_PANE, new WeakReference<JRootPane>(pane));
2690   }
2691
2692   public static boolean isMeaninglessFocusOwner(@Nullable Component c) {
2693     if (c == null || !c.isShowing()) return true;
2694
2695     return c instanceof JFrame || c instanceof JDialog || c instanceof JWindow || c instanceof JRootPane || isFocusProxy(c);
2696   }
2697
2698   public static Timer createNamedTimer(@NonNls @NotNull final String name, int delay, @NotNull ActionListener listener) {
2699     return new Timer(delay, listener) {
2700       @Override
2701       public String toString() {
2702         return name;
2703       }
2704     };
2705   }
2706
2707   public static boolean isDialogRootPane(JRootPane rootPane) {
2708     if (rootPane != null) {
2709       final Object isDialog = rootPane.getClientProperty("DIALOG_ROOT_PANE");
2710       return isDialog instanceof Boolean && ((Boolean)isDialog).booleanValue();
2711     }
2712     return false;
2713   }
2714
2715   @Nullable
2716   public static JComponent mergeComponentsWithAnchor(PanelWithAnchor... panels) {
2717     return mergeComponentsWithAnchor(Arrays.asList(panels));
2718   }
2719
2720   @Nullable
2721   public static JComponent mergeComponentsWithAnchor(Collection<? extends PanelWithAnchor> panels) {
2722     JComponent maxWidthAnchor = null;
2723     int maxWidth = 0;
2724     for (PanelWithAnchor panel : panels) {
2725       JComponent anchor = panel != null ? panel.getAnchor() : null;
2726       if (anchor != null) {
2727         int anchorWidth = anchor.getPreferredSize().width;
2728         if (maxWidth < anchorWidth) {
2729           maxWidth = anchorWidth;
2730           maxWidthAnchor = anchor;
2731         }
2732       }
2733     }
2734     for (PanelWithAnchor panel : panels) {
2735       if (panel != null) {
2736         panel.setAnchor(maxWidthAnchor);
2737       }
2738     }
2739     return maxWidthAnchor;
2740   }
2741
2742   public static void setNotOpaqueRecursively(@NotNull Component component) {
2743     if (!isUnderAquaLookAndFeel()) return;
2744
2745     if (component.getBackground().equals(getPanelBackground()) 
2746         || component instanceof JScrollPane 
2747         || component instanceof JViewport 
2748         || component instanceof JLayeredPane) {
2749       if (component instanceof JComponent) {
2750         ((JComponent)component).setOpaque(false);
2751       }
2752       if (component instanceof Container) {
2753         for (Component c : ((Container)component).getComponents()) {
2754           setNotOpaqueRecursively(c);
2755         }
2756       }
2757     }
2758   }
2759
2760   public static void addInsets(@NotNull JComponent component, @NotNull Insets insets) {
2761     if (component.getBorder() != null) {
2762       component.setBorder(new CompoundBorder(new EmptyBorder(insets), component.getBorder()));
2763     }
2764     else {
2765       component.setBorder(new EmptyBorder(insets));
2766     }
2767   }
2768
2769   public static Dimension addInsets(@NotNull Dimension dimension, @NotNull Insets insets) {
2770     Dimension ans = new Dimension(dimension);
2771     ans.width += insets.left;
2772     ans.width += insets.right;
2773     ans.height += insets.top;
2774     ans.height += insets.bottom;
2775
2776     return ans;
2777   }
2778
2779   public static void adjustWindowToMinimumSize(final Window window) {
2780     if (window == null) return;
2781     final Dimension minSize = window.getMinimumSize();
2782     final Dimension size = window.getSize();
2783     final Dimension newSize = new Dimension(Math.max(size.width, minSize.width), Math.max(size.height, minSize.height));
2784
2785     if (!newSize.equals(size)) {
2786       //noinspection SSBasedInspection
2787       SwingUtilities.invokeLater(new Runnable() {
2788         @Override
2789         public void run() {
2790           if (window.isShowing()) {
2791             window.setSize(newSize);
2792           }
2793         }
2794       });
2795     }
2796   }
2797
2798   @Nullable
2799   public static Color getColorAt(final Icon icon, final int x, final int y) {
2800     if (0 <= x && x < icon.getIconWidth() && 0 <= y && y < icon.getIconHeight()) {
2801       final BufferedImage image = createImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_RGB);
2802       icon.paintIcon(null, image.getGraphics(), 0, 0);
2803
2804       final int[] pixels = new int[1];
2805       final PixelGrabber pixelGrabber = new PixelGrabber(image, x, y, 1, 1, pixels, 0, 1);
2806       try {
2807         pixelGrabber.grabPixels();
2808         return new Color(pixels[0]);
2809       }
2810       catch (InterruptedException ignored) {
2811       }
2812     }
2813
2814     return null;
2815   }
2816
2817   public static void addBorder(JComponent component, Border border) {
2818     if (component == null) return;
2819
2820     if (component.getBorder() != null) {
2821       component.setBorder(new CompoundBorder(border, component.getBorder()));
2822     }
2823     else {
2824       component.setBorder(border);
2825     }
2826   }
2827
2828   private static final Color DECORATED_ROW_BG_COLOR = new JBColor(new Color(242, 245, 249), new Color(65, 69, 71));
2829
2830   public static Color getDecoratedRowColor() {
2831     return DECORATED_ROW_BG_COLOR;
2832   }
2833
2834   @NotNull
2835   public static Paint getGradientPaint(float x1, float y1, @NotNull Color c1, float x2, float y2, @NotNull Color c2) {
2836     return (Registry.is("ui.no.bangs.and.whistles")) ? ColorUtil.mix(c1, c2, .5) : new GradientPaint(x1, y1, c1, x2, y2, c2);
2837   }
2838
2839   @Nullable
2840   public static Point getLocationOnScreen(@NotNull JComponent component) {
2841     int dx = 0;
2842     int dy = 0;
2843     for (Container c = component; c != null; c = c.getParent()) {
2844       if (c.isShowing()) {
2845         Point locationOnScreen = c.getLocationOnScreen();
2846         locationOnScreen.translate(dx, dy);
2847         return locationOnScreen;
2848       }
2849       else {
2850         Point location = c.getLocation();
2851         dx += location.x;
2852         dy += location.y;
2853       }
2854     }
2855     return null;
2856   }
2857
2858   @NotNull
2859   public static Window getActiveWindow() {
2860     Window[] windows = Window.getWindows();
2861     for (Window each : windows) {
2862       if (each.isVisible() && each.isActive()) return each;
2863     }
2864     return JOptionPane.getRootFrame();
2865   }
2866
2867   public static void setAutoRequestFocus (final Window onWindow, final boolean set){
2868     if (SystemInfo.isMac) return;
2869     if (SystemInfo.isJavaVersionAtLeast("1.7")) {
2870       try {
2871         Method setAutoRequestFocusMethod  = onWindow.getClass().getMethod("setAutoRequestFocus",new Class [] {boolean.class});
2872         setAutoRequestFocusMethod.invoke(onWindow, set);
2873       }
2874       catch (NoSuchMethodException e) { LOG.debug(e); }
2875       catch (InvocationTargetException e) { LOG.debug(e); }
2876       catch (IllegalAccessException e) { LOG.debug(e); }
2877     }
2878   }
2879
2880   //May have no usages but it's useful in runtime (Debugger "watches", some logging etc.)
2881   public static String getDebugText(Component c) {
2882     StringBuilder builder  = new StringBuilder();
2883     getAllTextsRecursivelyImpl(c, builder);
2884     return builder.toString();
2885   }
2886
2887   private static void getAllTextsRecursivelyImpl(Component component, StringBuilder builder) {
2888     String candidate = "";
2889     int limit = builder.length() > 60 ? 20 : 40;
2890     if (component instanceof JLabel) candidate = ((JLabel)component).getText();
2891     if (component instanceof JTextComponent) candidate = ((JTextComponent)component).getText();
2892     if (component instanceof AbstractButton) candidate = ((AbstractButton)component).getText();
2893     if (StringUtil.isNotEmpty(candidate)) {
2894       builder.append(candidate.length() > limit ? (candidate.substring(0, limit - 3) + "...") : candidate).append('|');
2895     }
2896     if (component instanceof Container) {
2897       Component[] components = ((Container)component).getComponents();
2898       for (Component child : components) {
2899         getAllTextsRecursivelyImpl(child, builder);
2900       }
2901     }
2902   }
2903
2904   public static boolean isAncestor(@NotNull Component ancestor, @Nullable Component descendant) {
2905     while (descendant != null) {
2906       if (descendant == ancestor) {
2907         return true;
2908       }
2909       descendant = descendant.getParent();
2910     }
2911     return false;
2912   }
2913
2914   public static void resetUndoRedoActions(@NotNull JTextComponent textComponent) {
2915     UndoManager undoManager = getClientProperty(textComponent, UNDO_MANAGER);
2916     if (undoManager != null) {
2917       undoManager.discardAllEdits();
2918     }
2919   }
2920
2921   public static void addUndoRedoActions(@NotNull final JTextComponent textComponent) {
2922     UndoManager undoManager = new UndoManager();
2923     textComponent.putClientProperty(UNDO_MANAGER, undoManager);
2924     textComponent.getDocument().addUndoableEditListener(undoManager);
2925     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, SystemInfo.isMac? InputEvent.META_MASK : InputEvent.CTRL_MASK), "undoKeystroke");
2926     textComponent.getActionMap().put("undoKeystroke", UNDO_ACTION);
2927     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, (SystemInfo.isMac? InputEvent.META_MASK : InputEvent.CTRL_MASK) | InputEvent.SHIFT_MASK), "redoKeystroke");
2928     textComponent.getActionMap().put("redoKeystroke", REDO_ACTION);
2929   }
2930
2931   public static void playSoundFromResource(final String resourceName) {
2932     final Class callerClass = ReflectionUtil.getGrandCallerClass();
2933     if (callerClass == null) return;
2934     playSoundFromStream(new Factory<InputStream>() {
2935       @Override
2936       public InputStream create() {
2937         return callerClass.getResourceAsStream(resourceName);
2938       }
2939     });
2940   }
2941
2942   public static void playSoundFromStream(final Factory<InputStream> streamProducer) {
2943     new Thread(new Runnable() {
2944       // The wrapper thread is unnecessary, unless it blocks on the
2945       // Clip finishing; see comments.
2946       @Override
2947       public void run() {
2948         try {
2949           Clip clip = AudioSystem.getClip();
2950           InputStream stream = streamProducer.create();
2951           if (!stream.markSupported()) stream = new BufferedInputStream(stream);
2952           AudioInputStream inputStream = AudioSystem.getAudioInputStream(stream);
2953           clip.open(inputStream);
2954
2955           clip.start();
2956         } catch (Exception ignore) {
2957           LOG.info(ignore);
2958         }
2959       }
2960     }).start();
2961   }
2962
2963   // Experimental!!!
2964   // It seems to work under Windows
2965   @Nullable
2966   public static String getCurrentKeyboardLayout() {
2967     InputContext instance = InputContext.getInstance();
2968     Class<? extends InputContext> instanceClass = instance.getClass();
2969     Class<?> superclass = instanceClass.getSuperclass();
2970     if (superclass.getName().equals("sun.awt.im.InputContext")) {
2971       try {
2972         Object inputMethodLocator = ReflectionUtil.getField(superclass, instance, null, "inputMethodLocator");
2973         Locale locale = ReflectionUtil.getField(inputMethodLocator.getClass(), inputMethodLocator, Locale.class, "locale");
2974         return locale.getLanguage().toUpperCase(Locale.getDefault());
2975       }
2976       catch (Exception ignored) {
2977       }
2978     }
2979     return null;
2980   }
2981
2982   private static Map<String, String> ourRealFontFamilies = null;
2983
2984   //Experimental, seems to be reliable under MacOS X only
2985
2986   public static String getRealFontFamily(String genericFontFamily) {
2987     if (ourRealFontFamilies != null && ourRealFontFamilies.get(genericFontFamily) != null) {
2988       return ourRealFontFamilies.get(genericFontFamily);
2989     }
2990     String pattern = "Real Font Family";
2991     List<String> GENERIC = Arrays.asList(Font.DIALOG, Font.DIALOG_INPUT, Font.MONOSPACED, Font.SANS_SERIF, Font.SERIF);
2992     int patternSize = 50;
2993     BufferedImage image = createImage(1, 1, BufferedImage.TYPE_INT_ARGB);
2994     Graphics graphics = image.getGraphics();
2995     graphics.setFont(new Font(genericFontFamily, Font.PLAIN, patternSize));
2996     Object patternBounds = graphics.getFontMetrics().getStringBounds(pattern, graphics);
2997     for (String family: GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()) {
2998       if (GENERIC.contains(family)) continue;
2999       graphics.setFont(new Font(family, Font.PLAIN, patternSize));
3000       if (graphics.getFontMetrics().getStringBounds(pattern, graphics).equals(patternBounds)) {
3001         if (ourRealFontFamilies == null) {
3002           ourRealFontFamilies = new HashMap<String, String>();
3003         }
3004         ourRealFontFamilies.put(genericFontFamily, family);
3005         return family;
3006       }
3007     }
3008     return genericFontFamily;
3009   }
3010
3011   @NotNull
3012   public static String rightArrow() {
3013     return FontUtil.rightArrow(getLabelFont());
3014   }
3015
3016   public static EmptyBorder getTextAlignBorder(@NotNull JToggleButton alignSource) {
3017     ButtonUI ui = alignSource.getUI();
3018     int leftGap = alignSource.getIconTextGap();
3019     Border border = alignSource.getBorder();
3020     if (border != null) {
3021       leftGap += border.getBorderInsets(alignSource).left;
3022     }
3023     if (ui instanceof BasicRadioButtonUI) {
3024       leftGap += ((BasicRadioButtonUI)alignSource.getUI()).getDefaultIcon().getIconWidth();
3025     }
3026     else {
3027       Method method = ReflectionUtil.getMethod(ui.getClass(), "getDefaultIcon", JComponent.class);
3028       if (method != null) {
3029         try {
3030           Object o = method.invoke(ui, alignSource);
3031           if (o instanceof Icon) {
3032             leftGap += ((Icon)o).getIconWidth();
3033           }
3034         }
3035         catch (IllegalAccessException e) {
3036           e.printStackTrace();
3037         }
3038         catch (InvocationTargetException e) {
3039           e.printStackTrace();
3040         }
3041       }
3042     }
3043     return new EmptyBorder(0, leftGap, 0, 0);
3044   }
3045
3046   public static Color getSidePanelColor() {
3047     return new JBColor(0xD2D6DD, 0x3C4447);
3048   }
3049
3050   /**
3051    * It is your responsibility to set correct horizontal align (left in case of UI Designer)
3052    */
3053   public static void configureNumericFormattedTextField(@NotNull JFormattedTextField textField) {
3054     NumberFormat format = NumberFormat.getIntegerInstance();
3055     format.setParseIntegerOnly(true);
3056     format.setGroupingUsed(false);
3057     NumberFormatter numberFormatter = new NumberFormatter(format);
3058     numberFormatter.setMinimum(0);
3059     textField.setFormatterFactory(new DefaultFormatterFactory(numberFormatter));
3060     textField.setHorizontalAlignment(SwingConstants.TRAILING);
3061
3062     textField.setColumns(4);
3063   }
3064 }