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