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