7d19d9138691dad01d40fc0b0b576c5be86ce6d1
[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     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);
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());
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) {
2394     if (styleSheet == null) {
2395       return;
2396     }
2397     // 'baseFontSize' equals to javax.swing.text.html.StyleSheet.sizeMapDefault[3],
2398     // where '3' == javax.swing.text.html.CSS.baseFontSizeIndex
2399     // See javax.swing.text.html.StyleSheet.rebaseSizeMap()
2400     int baseFontSize = 14;
2401     int scaledBaseFontSize = JBUI.scaleFontSize(baseFontSize);
2402     if (baseFontSize != scaledBaseFontSize) {
2403       styleSheet.addRule("BASE_SIZE " + scaledBaseFontSize);
2404     }
2405   }
2406
2407   public static void removeScrollBorder(final Component c) {
2408     for (JScrollPane scrollPane : uiTraverser(c).filter(JScrollPane.class)) {
2409       if (!uiParents(scrollPane, true)
2410         .takeWhile(Conditions.notEqualTo(c))
2411         .filter(Conditions.not(Conditions.instanceOf(JPanel.class, JLayeredPane.class)))
2412         .isEmpty()) continue;
2413
2414       Integer keepBorderSides = getClientProperty(scrollPane, KEEP_BORDER_SIDES);
2415       if (keepBorderSides != null) {
2416         if (scrollPane.getBorder() instanceof LineBorder) {
2417           Color color = ((LineBorder)scrollPane.getBorder()).getLineColor();
2418           scrollPane.setBorder(new SideBorder(color, keepBorderSides.intValue()));
2419         }
2420         else {
2421           scrollPane.setBorder(new SideBorder(getBoundsColor(), keepBorderSides.intValue()));
2422         }
2423       }
2424       else {
2425         scrollPane.setBorder(new SideBorder(getBoundsColor(), SideBorder.NONE));
2426       }
2427     }
2428   }
2429
2430   public static Point getCenterPoint(Dimension container, Dimension child) {
2431     return getCenterPoint(new Rectangle(container), child);
2432   }
2433
2434   public static Point getCenterPoint(Rectangle container, Dimension child) {
2435     return new Point(
2436       container.x + (container.width - child.width) / 2,
2437       container.y + (container.height - child.height) / 2
2438     );
2439   }
2440
2441   public static String toHtml(String html) {
2442     return toHtml(html, 0);
2443   }
2444
2445   @NonNls
2446   public static String toHtml(String html, final int hPadding) {
2447     html = CLOSE_TAG_PATTERN.matcher(html).replaceAll("<$1$2></$1>");
2448     Font font = getLabelFont();
2449     @NonNls String family = font != null ? font.getFamily() : "Tahoma";
2450     int size = font != null ? font.getSize() : JBUI.scale(11);
2451     return "<html><style>body { font-family: "
2452            + family + "; font-size: "
2453            + size + ";} ul li {list-style-type:circle;}</style>"
2454            + addPadding(html, hPadding) + "</html>";
2455   }
2456
2457   public static String addPadding(final String html, int hPadding) {
2458     return String.format("<p style=\"margin: 0 %dpx 0 %dpx;\">%s</p>", hPadding, hPadding, html);
2459   }
2460
2461   @NotNull
2462   public static String convertSpace2Nbsp(@NotNull String html) {
2463     @NonNls StringBuilder result = new StringBuilder();
2464     int currentPos = 0;
2465     int braces = 0;
2466     while (currentPos < html.length()) {
2467       String each = html.substring(currentPos, currentPos + 1);
2468       if ("<".equals(each)) {
2469         braces++;
2470       }
2471       else if (">".equals(each)) {
2472         braces--;
2473       }
2474
2475       if (" ".equals(each) && braces == 0) {
2476         result.append("&nbsp;");
2477       }
2478       else {
2479         result.append(each);
2480       }
2481       currentPos++;
2482     }
2483
2484     return result.toString();
2485   }
2486
2487   /**
2488    * Please use Application.invokeLater() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2489    * 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/>
2490    *
2491    * On AWT thread, invoked runnable immediately, otherwise do {@link SwingUtilities#invokeLater(Runnable)} on it.
2492    */
2493   public static void invokeLaterIfNeeded(@NotNull Runnable runnable) {
2494     if (EdtInvocationManager.getInstance().isEventDispatchThread()) {
2495       runnable.run();
2496     }
2497     else {
2498       EdtInvocationManager.getInstance().invokeLater(runnable);
2499     }
2500   }
2501
2502   /**
2503    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2504    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2505    *
2506    * Invoke and wait in the event dispatch thread
2507    * or in the current thread if the current thread
2508    * is event queue thread.
2509    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2510    *
2511    * @param runnable a runnable to invoke
2512    * @see #invokeAndWaitIfNeeded(ThrowableRunnable)
2513    */
2514   public static void invokeAndWaitIfNeeded(@NotNull Runnable runnable) {
2515     if (EdtInvocationManager.getInstance().isEventDispatchThread()) {
2516       runnable.run();
2517     }
2518     else {
2519       try {
2520         EdtInvocationManager.getInstance().invokeAndWait(runnable);
2521       }
2522       catch (Exception e) {
2523         LOG.error(e);
2524       }
2525     }
2526   }
2527
2528   /**
2529    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2530    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2531    *
2532    * Invoke and wait in the event dispatch thread
2533    * or in the current thread if the current thread
2534    * is event queue thread.
2535    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2536    *
2537    * @param computable a runnable to invoke
2538    * @see #invokeAndWaitIfNeeded(ThrowableRunnable)
2539    */
2540   public static <T> T invokeAndWaitIfNeeded(@NotNull final Computable<T> computable) {
2541     final Ref<T> result = Ref.create();
2542     invokeAndWaitIfNeeded(new Runnable() {
2543       @Override
2544       public void run() {
2545         result.set(computable.compute());
2546       }
2547     });
2548     return result.get();
2549   }
2550
2551   /**
2552    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2553    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2554    *
2555    * Invoke and wait in the event dispatch thread
2556    * or in the current thread if the current thread
2557    * is event queue thread.
2558    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2559    *
2560    * @param runnable a runnable to invoke
2561    * @see #invokeAndWaitIfNeeded(ThrowableRunnable)
2562    */
2563   public static void invokeAndWaitIfNeeded(@NotNull final ThrowableRunnable runnable) throws Throwable {
2564     if (EdtInvocationManager.getInstance().isEventDispatchThread()) {
2565       runnable.run();
2566     }
2567     else {
2568       final Ref<Throwable> ref = Ref.create();
2569       EdtInvocationManager.getInstance().invokeAndWait(new Runnable() {
2570         @Override
2571         public void run() {
2572           try {
2573             runnable.run();
2574           }
2575           catch (Throwable throwable) {
2576             ref.set(throwable);
2577           }
2578         }
2579       });
2580       if (!ref.isNull()) throw ref.get();
2581     }
2582   }
2583
2584   public static boolean isFocusProxy(@Nullable Component c) {
2585     return c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty(FOCUS_PROXY_KEY));
2586   }
2587
2588   public static void setFocusProxy(JComponent c, boolean isProxy) {
2589     c.putClientProperty(FOCUS_PROXY_KEY, isProxy ? Boolean.TRUE : null);
2590   }
2591
2592   public static void maybeInstall(InputMap map, String action, KeyStroke stroke) {
2593     if (map.get(stroke) == null) {
2594       map.put(stroke, action);
2595     }
2596   }
2597
2598   /**
2599    * Avoid blinking while changing background.
2600    *
2601    * @param component  component.
2602    * @param background new background.
2603    */
2604   public static void changeBackGround(final Component component, final Color background) {
2605     final Color oldBackGround = component.getBackground();
2606     if (background == null || !background.equals(oldBackGround)) {
2607       component.setBackground(background);
2608     }
2609   }
2610
2611   private static String systemLaFClassName;
2612
2613   public static String getSystemLookAndFeelClassName() {
2614     if (systemLaFClassName != null) {
2615       return systemLaFClassName;
2616     }
2617     else if (SystemInfo.isLinux) {
2618       // Normally, GTK LaF is considered "system" when:
2619       // 1) Gnome session is run
2620       // 2) gtk lib is available
2621       // Here we weaken the requirements to only 2) and force GTK LaF
2622       // installation in order to let it properly scale default font
2623       // based on Xft.dpi value.
2624       try {
2625         String name = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
2626         Class cls = Class.forName(name);
2627         LookAndFeel laf = (LookAndFeel)cls.newInstance();
2628         if (laf.isSupportedLookAndFeel()) { // if gtk lib is available
2629           return systemLaFClassName = name;
2630         }
2631       }
2632       catch (Exception ignore) {
2633       }
2634     }
2635     return systemLaFClassName = UIManager.getSystemLookAndFeelClassName();
2636   }
2637
2638   public static void initDefaultLAF() {
2639     try {
2640       UIManager.setLookAndFeel(getSystemLookAndFeelClassName());
2641       initSystemFontData();
2642     }
2643     catch (Exception ignore) {}
2644   }
2645
2646   public static void initSystemFontData() {
2647     if (ourSystemFontData != null) return;
2648
2649     // With JB Linux JDK the label font comes properly scaled based on Xft.dpi settings.
2650     Font font = getLabelFont();
2651
2652     Float forcedScale = null;
2653     if (Registry.is("ide.ui.scale.override")) {
2654       forcedScale = Float.valueOf((float)Registry.get("ide.ui.scale").asDouble());
2655     }
2656     else if (SystemInfo.isLinux && !SystemInfo.isJetbrainsJvm) {
2657       // With Oracle JDK: derive scale from X server DPI
2658       float scale = getScreenScale();
2659       if (scale > 1f) {
2660         forcedScale = Float.valueOf(scale);
2661       }
2662       // Or otherwise leave the detected font. It's undetermined if it's scaled or not.
2663       // If it is (likely with GTK DE), then the UI scale will be derived from it,
2664       // if it's not, then IDEA will start unscaled. This lets the users of GTK DEs
2665       // not to bother about X server DPI settings. Users of other DEs (like KDE)
2666       // will have to set X server DPI to meet their display.
2667     }
2668     else if (SystemInfo.isWindows) {
2669       //noinspection HardCodedStringLiteral
2670       Font winFont = (Font)Toolkit.getDefaultToolkit().getDesktopProperty("win.messagebox.font");
2671       if (winFont != null) {
2672         font = winFont; // comes scaled
2673       }
2674     }
2675     if (forcedScale != null) {
2676       // With forced scale, we derive font from a hard-coded value as we cannot be sure
2677       // the system font comes unscaled.
2678       font = font.deriveFont(DEF_SYSTEM_FONT_SIZE * forcedScale.floatValue());
2679     }
2680     ourSystemFontData = Pair.create(font.getName(), font.getSize());
2681   }
2682
2683   @Nullable
2684   public static Pair<String, Integer> getSystemFontData() {
2685     return ourSystemFontData;
2686   }
2687
2688   private static float getScreenScale() {
2689     int dpi = 96;
2690     try {
2691       dpi = Toolkit.getDefaultToolkit().getScreenResolution();
2692     } catch (HeadlessException e) {
2693     }
2694     float scale = 1f;
2695     if (dpi < 120) scale = 1f;
2696     else if (dpi < 144) scale = 1.25f;
2697     else if (dpi < 168) scale = 1.5f;
2698     else if (dpi < 192) scale = 1.75f;
2699     else scale = 2f;
2700
2701     return scale;
2702   }
2703
2704   public static void addKeyboardShortcut(final JComponent target, final AbstractButton button, final KeyStroke keyStroke) {
2705     target.registerKeyboardAction(
2706       new ActionListener() {
2707         @Override
2708         public void actionPerformed(ActionEvent e) {
2709           if (button.isEnabled()) {
2710             button.doClick();
2711           }
2712         }
2713       },
2714       keyStroke,
2715       JComponent.WHEN_FOCUSED
2716     );
2717   }
2718
2719   public static void installComboBoxCopyAction(JComboBox comboBox) {
2720     final ComboBoxEditor editor = comboBox.getEditor();
2721     final Component editorComponent = editor != null ? editor.getEditorComponent() : null;
2722     if (!(editorComponent instanceof JTextComponent)) return;
2723     final InputMap inputMap = ((JTextComponent)editorComponent).getInputMap();
2724     for (KeyStroke keyStroke : inputMap.allKeys()) {
2725       if (DefaultEditorKit.copyAction.equals(inputMap.get(keyStroke))) {
2726         comboBox.getInputMap().put(keyStroke, DefaultEditorKit.copyAction);
2727       }
2728     }
2729     comboBox.getActionMap().put(DefaultEditorKit.copyAction, new AbstractAction() {
2730       @Override
2731       public void actionPerformed(final ActionEvent e) {
2732         if (!(e.getSource() instanceof JComboBox)) return;
2733         final JComboBox comboBox = (JComboBox)e.getSource();
2734         final String text;
2735         final Object selectedItem = comboBox.getSelectedItem();
2736         if (selectedItem instanceof String) {
2737           text = (String)selectedItem;
2738         }
2739         else {
2740           final Component component =
2741             comboBox.getRenderer().getListCellRendererComponent(new JList(), selectedItem, 0, false, false);
2742           if (component instanceof JLabel) {
2743             text = ((JLabel)component).getText();
2744           }
2745           else if (component != null) {
2746             final String str = component.toString();
2747             // skip default Component.toString and handle SimpleColoredComponent case
2748             text = str == null || str.startsWith(component.getClass().getName() + "[") ? null : str;
2749           }
2750           else {
2751             text = null;
2752           }
2753         }
2754         if (text != null) {
2755           final JTextField textField = new JTextField(text);
2756           textField.selectAll();
2757           textField.copy();
2758         }
2759       }
2760     });
2761   }
2762
2763   @Nullable
2764   public static ComboPopup getComboBoxPopup(@NotNull JComboBox comboBox) {
2765     final ComboBoxUI ui = comboBox.getUI();
2766     if (ui instanceof BasicComboBoxUI) {
2767       return ReflectionUtil.getField(BasicComboBoxUI.class, ui, ComboPopup.class, "popup");
2768     }
2769
2770     return null;
2771   }
2772
2773   @SuppressWarnings({"HardCodedStringLiteral"})
2774   public static void fixFormattedField(JFormattedTextField field) {
2775     if (SystemInfo.isMac) {
2776       final Toolkit toolkit = Toolkit.getDefaultToolkit();
2777       final int commandKeyMask = toolkit.getMenuShortcutKeyMask();
2778       final InputMap inputMap = field.getInputMap();
2779       final KeyStroke copyKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, commandKeyMask);
2780       inputMap.put(copyKeyStroke, "copy-to-clipboard");
2781       final KeyStroke pasteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, commandKeyMask);
2782       inputMap.put(pasteKeyStroke, "paste-from-clipboard");
2783       final KeyStroke cutKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, commandKeyMask);
2784       inputMap.put(cutKeyStroke, "cut-to-clipboard");
2785     }
2786   }
2787
2788   public static boolean isPrinting(Graphics g) {
2789     return g instanceof PrintGraphics;
2790   }
2791
2792   public static int getSelectedButton(ButtonGroup group) {
2793     Enumeration<AbstractButton> enumeration = group.getElements();
2794     int i = 0;
2795     while (enumeration.hasMoreElements()) {
2796       AbstractButton button = enumeration.nextElement();
2797       if (group.isSelected(button.getModel())) {
2798         return i;
2799       }
2800       i++;
2801     }
2802     return -1;
2803   }
2804
2805   public static void setSelectedButton(ButtonGroup group, int index) {
2806     Enumeration<AbstractButton> enumeration = group.getElements();
2807     int i = 0;
2808     while (enumeration.hasMoreElements()) {
2809       AbstractButton button = enumeration.nextElement();
2810       group.setSelected(button.getModel(), index == i);
2811       i++;
2812     }
2813   }
2814
2815   public static boolean isSelectionButtonDown(MouseEvent e) {
2816     return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
2817   }
2818
2819   @SuppressWarnings("deprecation")
2820   public static void setComboBoxEditorBounds(int x, int y, int width, int height, JComponent editor) {
2821     if (SystemInfo.isMac && isUnderAquaLookAndFeel()) {
2822       // fix for too wide combobox editor, see AquaComboBoxUI.layoutContainer:
2823       // it adds +4 pixels to editor width. WTF?!
2824       editor.reshape(x, y, width - 4, height - 1);
2825     }
2826     else {
2827       editor.reshape(x, y, width, height);
2828     }
2829   }
2830
2831   public static int fixComboBoxHeight(final int height) {
2832     return SystemInfo.isMac && isUnderAquaLookAndFeel() ? 28 : height;
2833   }
2834
2835   public static final int LIST_FIXED_CELL_HEIGHT = 20;
2836
2837   /**
2838    * The main difference from javax.swing.SwingUtilities#isDescendingFrom(Component, Component) is that this method
2839    * uses getInvoker() instead of getParent() when it meets JPopupMenu
2840    * @param child child component
2841    * @param parent parent component
2842    * @return true if parent if a top parent of child, false otherwise
2843    *
2844    * @see SwingUtilities#isDescendingFrom(Component, Component)
2845    */
2846   public static boolean isDescendingFrom(@Nullable Component child, @NotNull Component parent) {
2847     while (child != null && child != parent) {
2848       child =  child instanceof JPopupMenu  ? ((JPopupMenu)child).getInvoker()
2849                                             : child.getParent();
2850     }
2851     return child == parent;
2852   }
2853
2854   /**
2855    * Searches above in the component hierarchy starting from the specified component.
2856    * Note that the initial component is also checked.
2857    *
2858    * @param type      expected class
2859    * @param component initial component
2860    * @return a component of the specified type, or {@code null} if the search is failed
2861    * @see SwingUtilities#getAncestorOfClass
2862    */
2863   @Nullable
2864   public static <T> T getParentOfType(@NotNull Class<? extends T> type, Component component) {
2865     while (component != null) {
2866       if (type.isInstance(component)) {
2867         //noinspection unchecked
2868         return (T)component;
2869       }
2870       component = component.getParent();
2871     }
2872     return null;
2873   }
2874
2875   @NotNull
2876   public static JBIterable<Component> uiParents(@Nullable Component c, boolean strict) {
2877     return strict ? JBIterable.generate(c, COMPONENT_PARENT).skip(1) : JBIterable.generate(c, COMPONENT_PARENT);
2878   }
2879
2880   @NotNull
2881   public static JBIterable<Component> uiChildren(@Nullable Component component) {
2882     if (!(component instanceof Container)) return JBIterable.empty();
2883     Container container = (Container)component;
2884     return JBIterable.of(container.getComponents());
2885   }
2886
2887   @NotNull
2888   public static JBTreeTraverser<Component> uiTraverser(@Nullable Component component) {
2889     return new JBTreeTraverser<Component>(COMPONENT_CHILDREN).withRoot(component);
2890   }
2891
2892   public static final Key<Iterable<? extends Component>> NOT_IN_HIERARCHY_COMPONENTS = Key.create("NOT_IN_HIERARCHY_COMPONENTS");
2893
2894   private static final Function<Component, JBIterable<Component>> COMPONENT_CHILDREN = new Function<Component, JBIterable<Component>>() {
2895     @Override
2896     public JBIterable<Component> fun(@NotNull Component c) {
2897       JBIterable<Component> result;
2898       if (c instanceof JMenu) {
2899         result = JBIterable.of(((JMenu)c).getMenuComponents());
2900       }
2901       else if (c instanceof JComboBox && isUnderAquaLookAndFeel()) {
2902         // On Mac JComboBox instances have children: com.apple.laf.AquaComboBoxButton and javax.swing.CellRendererPane.
2903         // Disabling these children results in ugly UI: WEB-10733
2904         result = JBIterable.empty();
2905       }
2906       else {
2907         result = uiChildren(c);
2908       }
2909       if (c instanceof JComponent) {
2910         JComponent jc = (JComponent)c;
2911         Iterable<? extends Component> orphans = getClientProperty(jc, NOT_IN_HIERARCHY_COMPONENTS);
2912         if (orphans != null) {
2913           result = result.append(orphans);
2914         }
2915         JPopupMenu jpm = jc.getComponentPopupMenu();
2916         if (jpm != null && jpm.isVisible() && jpm.getInvoker() == jc) {
2917           result = result.append(Collections.singletonList(jpm));
2918         }
2919       }
2920       return result;
2921     }
2922   };
2923
2924   private static final Function.Mono<Component> COMPONENT_PARENT = new Function.Mono<Component>() {
2925     @Override
2926     public Component fun(Component c) {
2927       return c.getParent();
2928     }
2929   };
2930
2931
2932   public static void scrollListToVisibleIfNeeded(@NotNull final JList list) {
2933     SwingUtilities.invokeLater(new Runnable() {
2934       @Override
2935       public void run() {
2936         final int selectedIndex = list.getSelectedIndex();
2937         if (selectedIndex >= 0) {
2938           final Rectangle visibleRect = list.getVisibleRect();
2939           final Rectangle cellBounds = list.getCellBounds(selectedIndex, selectedIndex);
2940           if (!visibleRect.contains(cellBounds)) {
2941             list.scrollRectToVisible(cellBounds);
2942           }
2943         }
2944       }
2945     });
2946   }
2947
2948   @Nullable
2949   public static <T extends JComponent> T findComponentOfType(JComponent parent, Class<T> cls) {
2950     if (parent == null || cls.isAssignableFrom(parent.getClass())) {
2951       @SuppressWarnings({"unchecked"}) final T t = (T)parent;
2952       return t;
2953     }
2954     for (Component component : parent.getComponents()) {
2955       if (component instanceof JComponent) {
2956         T comp = findComponentOfType((JComponent)component, cls);
2957         if (comp != null) return comp;
2958       }
2959     }
2960     return null;
2961   }
2962
2963   public static <T extends JComponent> List<T> findComponentsOfType(JComponent parent, Class<T> cls) {
2964     final ArrayList<T> result = new ArrayList<T>();
2965     findComponentsOfType(parent, cls, result);
2966     return result;
2967   }
2968
2969   private static <T extends JComponent> void findComponentsOfType(JComponent parent, Class<T> cls, ArrayList<T> result) {
2970     if (parent == null) return;
2971     if (cls.isAssignableFrom(parent.getClass())) {
2972       @SuppressWarnings({"unchecked"}) final T t = (T)parent;
2973       result.add(t);
2974     }
2975     for (Component c : parent.getComponents()) {
2976       if (c instanceof JComponent) {
2977         findComponentsOfType((JComponent)c, cls, result);
2978       }
2979     }
2980   }
2981
2982   public static class TextPainter {
2983     private final List<Pair<String, LineInfo>> myLines = new ArrayList<Pair<String, LineInfo>>();
2984     private boolean myDrawShadow;
2985     private Color myShadowColor;
2986     private float myLineSpacing;
2987
2988     public TextPainter() {
2989       myDrawShadow = /*isUnderAquaLookAndFeel() ||*/ isUnderDarcula();
2990       myShadowColor = isUnderDarcula() ? Gray._0.withAlpha(100) : Gray._220;
2991       myLineSpacing = 1.0f;
2992     }
2993
2994     public TextPainter withShadow(final boolean drawShadow) {
2995       myDrawShadow = drawShadow;
2996       return this;
2997     }
2998
2999     public TextPainter withShadow(final boolean drawShadow, final Color shadowColor) {
3000       myDrawShadow = drawShadow;
3001       myShadowColor = shadowColor;
3002       return this;
3003     }
3004
3005     public TextPainter withLineSpacing(final float lineSpacing) {
3006       myLineSpacing = lineSpacing;
3007       return this;
3008     }
3009
3010     public TextPainter appendLine(final String text) {
3011       if (text == null || text.isEmpty()) return this;
3012       myLines.add(Pair.create(text, new LineInfo()));
3013       return this;
3014     }
3015
3016     public TextPainter underlined(@Nullable final Color color) {
3017       if (!myLines.isEmpty()) {
3018         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
3019         info.underlined = true;
3020         info.underlineColor = color;
3021       }
3022
3023       return this;
3024     }
3025
3026     public TextPainter withBullet(final char c) {
3027       if (!myLines.isEmpty()) {
3028         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
3029         info.withBullet = true;
3030         info.bulletChar = c;
3031       }
3032
3033       return this;
3034     }
3035
3036     public TextPainter withBullet() {
3037       return withBullet('\u2022');
3038     }
3039
3040     public TextPainter underlined() {
3041       return underlined(null);
3042     }
3043
3044     public TextPainter smaller() {
3045       if (!myLines.isEmpty()) {
3046         myLines.get(myLines.size() - 1).getSecond().smaller = true;
3047       }
3048
3049       return this;
3050     }
3051
3052     public TextPainter center() {
3053       if (!myLines.isEmpty()) {
3054         myLines.get(myLines.size() - 1).getSecond().center = true;
3055       }
3056
3057       return this;
3058     }
3059
3060     /**
3061      * _position(block width, block height) => (x, y) of the block
3062      */
3063     public void draw(@NotNull final Graphics g, final PairFunction<Integer, Integer, Couple<Integer>> _position) {
3064       final int[] maxWidth = {0};
3065       final int[] height = {0};
3066       final int[] maxBulletWidth = {0};
3067       GraphicsUtil.setupAntialiasing(g, true, true);
3068       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
3069         @Override
3070         public boolean process(final Pair<String, LineInfo> pair) {
3071           final LineInfo info = pair.getSecond();
3072           Font old = null;
3073           if (info.smaller) {
3074             old = g.getFont();
3075             g.setFont(old.deriveFont(old.getSize() * 0.70f));
3076           }
3077
3078           final FontMetrics fm = g.getFontMetrics();
3079
3080           final int bulletWidth = info.withBullet ? fm.stringWidth(" " + info.bulletChar) : 0;
3081           maxBulletWidth[0] = Math.max(maxBulletWidth[0], bulletWidth);
3082
3083           maxWidth[0] = Math.max(fm.stringWidth(pair.getFirst().replace("<shortcut>", "").replace("</shortcut>", "") + bulletWidth), maxWidth[0]);
3084           height[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
3085
3086           if (old != null) {
3087             g.setFont(old);
3088           }
3089
3090           return true;
3091         }
3092       });
3093
3094       final Couple<Integer> position = _position.fun(maxWidth[0] + 20, height[0]);
3095       assert position != null;
3096
3097       final int[] yOffset = {position.getSecond()};
3098       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
3099         @Override
3100         public boolean process(final Pair<String, LineInfo> pair) {
3101           final LineInfo info = pair.getSecond();
3102           String text = pair.first;
3103           String shortcut = "";
3104           if (pair.first.contains("<shortcut>")) {
3105             shortcut = text.substring(text.indexOf("<shortcut>") + "<shortcut>".length(), text.indexOf("</shortcut>"));
3106             text = text.substring(0, text.indexOf("<shortcut>"));
3107           }
3108
3109           Font old = null;
3110           if (info.smaller) {
3111             old = g.getFont();
3112             g.setFont(old.deriveFont(old.getSize() * 0.70f));
3113           }
3114
3115           final int x = position.getFirst() + maxBulletWidth[0] + 10;
3116
3117           final FontMetrics fm = g.getFontMetrics();
3118           int xOffset = x;
3119           if (info.center) {
3120             xOffset = x + (maxWidth[0] - fm.stringWidth(text)) / 2;
3121           }
3122
3123           if (myDrawShadow) {
3124             int xOff = isUnderDarcula() ? 1 : 0;
3125             int yOff = 1;
3126             Color oldColor = g.getColor();
3127             g.setColor(myShadowColor);
3128
3129             if (info.withBullet) {
3130               g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar) + xOff, yOffset[0] + yOff);
3131             }
3132
3133             g.drawString(text, xOffset + xOff, yOffset[0] + yOff);
3134             g.setColor(oldColor);
3135           }
3136
3137           if (info.withBullet) {
3138             g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar), yOffset[0]);
3139           }
3140
3141           g.drawString(text, xOffset, yOffset[0]);
3142           if (!StringUtil.isEmpty(shortcut)) {
3143             Color oldColor = g.getColor();
3144             g.setColor(new JBColor(new Color(82, 99, 155),
3145                                    new Color(88, 157, 246)));
3146             g.drawString(shortcut, xOffset + fm.stringWidth(text + (isUnderDarcula() ? " " : "")), yOffset[0]);
3147             g.setColor(oldColor);
3148           }
3149
3150           if (info.underlined) {
3151             Color c = null;
3152             if (info.underlineColor != null) {
3153               c = g.getColor();
3154               g.setColor(info.underlineColor);
3155             }
3156
3157             g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent(), x + maxWidth[0] + 10, yOffset[0] + fm.getDescent());
3158             if (c != null) {
3159               g.setColor(c);
3160
3161             }
3162
3163             if (myDrawShadow) {
3164               c = g.getColor();
3165               g.setColor(myShadowColor);
3166               g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent() + 1, x + maxWidth[0] + 10,
3167                          yOffset[0] + fm.getDescent() + 1);
3168               g.setColor(c);
3169             }
3170           }
3171
3172           yOffset[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
3173
3174           if (old != null) {
3175             g.setFont(old);
3176           }
3177
3178           return true;
3179         }
3180       });
3181     }
3182
3183     private static class LineInfo {
3184       private boolean underlined;
3185       private boolean withBullet;
3186       private char bulletChar;
3187       private Color underlineColor;
3188       private boolean smaller;
3189       private boolean center;
3190     }
3191   }
3192
3193   @Nullable
3194   public static JRootPane getRootPane(Component c) {
3195     JRootPane root = getParentOfType(JRootPane.class, c);
3196     if (root != null) return root;
3197     Component eachParent = c;
3198     while (eachParent != null) {
3199       if (eachParent instanceof JComponent) {
3200         @SuppressWarnings({"unchecked"}) WeakReference<JRootPane> pane =
3201           (WeakReference<JRootPane>)((JComponent)eachParent).getClientProperty(ROOT_PANE);
3202         if (pane != null) return pane.get();
3203       }
3204       eachParent = eachParent.getParent();
3205     }
3206
3207     return null;
3208   }
3209
3210   public static void setFutureRootPane(JComponent c, JRootPane pane) {
3211     c.putClientProperty(ROOT_PANE, new WeakReference<JRootPane>(pane));
3212   }
3213
3214   public static boolean isMeaninglessFocusOwner(@Nullable Component c) {
3215     if (c == null || !c.isShowing()) return true;
3216
3217     return c instanceof JFrame || c instanceof JDialog || c instanceof JWindow || c instanceof JRootPane || isFocusProxy(c);
3218   }
3219
3220   @NotNull
3221   public static Timer createNamedTimer(@NonNls @NotNull final String name, int delay, @NotNull ActionListener listener) {
3222     return new Timer(delay, listener) {
3223       @Override
3224       public String toString() {
3225         return name;
3226       }
3227     };
3228   }
3229   @NotNull
3230   public static Timer createNamedTimer(@NonNls @NotNull final String name, int delay) {
3231     return new Timer(delay, null) {
3232       @Override
3233       public String toString() {
3234         return name;
3235       }
3236     };
3237   }
3238
3239   public static boolean isDialogRootPane(JRootPane rootPane) {
3240     if (rootPane != null) {
3241       final Object isDialog = rootPane.getClientProperty("DIALOG_ROOT_PANE");
3242       return isDialog instanceof Boolean && ((Boolean)isDialog).booleanValue();
3243     }
3244     return false;
3245   }
3246
3247   @Nullable
3248   public static JComponent mergeComponentsWithAnchor(PanelWithAnchor... panels) {
3249     return mergeComponentsWithAnchor(Arrays.asList(panels));
3250   }
3251
3252   @Nullable
3253   public static JComponent mergeComponentsWithAnchor(Collection<? extends PanelWithAnchor> panels) {
3254     JComponent maxWidthAnchor = null;
3255     int maxWidth = 0;
3256     for (PanelWithAnchor panel : panels) {
3257       JComponent anchor = panel != null ? panel.getAnchor() : null;
3258       if (anchor != null) {
3259         int anchorWidth = anchor.getPreferredSize().width;
3260         if (maxWidth < anchorWidth) {
3261           maxWidth = anchorWidth;
3262           maxWidthAnchor = anchor;
3263         }
3264       }
3265     }
3266     for (PanelWithAnchor panel : panels) {
3267       if (panel != null) {
3268         panel.setAnchor(maxWidthAnchor);
3269       }
3270     }
3271     return maxWidthAnchor;
3272   }
3273
3274   public static void setNotOpaqueRecursively(@NotNull Component component) {
3275     if (!isUnderAquaLookAndFeel()) return;
3276
3277     if (component.getBackground().equals(getPanelBackground())
3278         || component instanceof JScrollPane
3279         || component instanceof JViewport
3280         || component instanceof JLayeredPane) {
3281       if (component instanceof JComponent) {
3282         ((JComponent)component).setOpaque(false);
3283       }
3284       if (component instanceof Container) {
3285         for (Component c : ((Container)component).getComponents()) {
3286           setNotOpaqueRecursively(c);
3287         }
3288       }
3289     }
3290   }
3291
3292   public static void setBackgroundRecursively(@NotNull Component component, @NotNull Color bg) {
3293     component.setBackground(bg);
3294     if (component instanceof Container) {
3295       for (Component c : ((Container)component).getComponents()) {
3296         setBackgroundRecursively(c, bg);
3297       }
3298     }
3299   }
3300
3301   /**
3302    * Adds an empty border with the specified insets to the specified component.
3303    * If the component already has a border it will be preserved.
3304    *
3305    * @param component the component to which border added
3306    * @param top       the inset from the top
3307    * @param left      the inset from the left
3308    * @param bottom    the inset from the bottom
3309    * @param right     the inset from the right
3310    */
3311   public static void addInsets(@NotNull JComponent component, int top, int left, int bottom, int right) {
3312     addBorder(component, BorderFactory.createEmptyBorder(top, left, bottom, right));
3313   }
3314
3315   /**
3316    * Adds an empty border with the specified insets to the specified component.
3317    * If the component already has a border it will be preserved.
3318    *
3319    * @param component the component to which border added
3320    * @param insets    the top, left, bottom, and right insets
3321    */
3322   public static void addInsets(@NotNull JComponent component, @NotNull Insets insets) {
3323     addInsets(component, insets.top, insets.left, insets.bottom, insets.right);
3324   }
3325
3326   public static void adjustWindowToMinimumSize(final Window window) {
3327     if (window == null) return;
3328     final Dimension minSize = window.getMinimumSize();
3329     final Dimension size = window.getSize();
3330     final Dimension newSize = new Dimension(Math.max(size.width, minSize.width), Math.max(size.height, minSize.height));
3331
3332     if (!newSize.equals(size)) {
3333       //noinspection SSBasedInspection
3334       SwingUtilities.invokeLater(new Runnable() {
3335         @Override
3336         public void run() {
3337           if (window.isShowing()) {
3338             window.setSize(newSize);
3339           }
3340         }
3341       });
3342     }
3343   }
3344
3345   @Nullable
3346   public static Color getColorAt(final Icon icon, final int x, final int y) {
3347     if (0 <= x && x < icon.getIconWidth() && 0 <= y && y < icon.getIconHeight()) {
3348       final BufferedImage image = createImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_RGB);
3349       icon.paintIcon(null, image.getGraphics(), 0, 0);
3350
3351       final int[] pixels = new int[1];
3352       final PixelGrabber pixelGrabber = new PixelGrabber(image, x, y, 1, 1, pixels, 0, 1);
3353       try {
3354         pixelGrabber.grabPixels();
3355         return new Color(pixels[0]);
3356       }
3357       catch (InterruptedException ignored) {
3358       }
3359     }
3360
3361     return null;
3362   }
3363
3364   public static int getLcdContrastValue() {
3365     int lcdContrastValue  = Registry.get("lcd.contrast.value").asInteger();
3366
3367     // Evaluate the value depending on our current theme
3368     if (lcdContrastValue == 0) {
3369       if (SystemInfo.isMacIntel64) {
3370         lcdContrastValue = isUnderDarcula() ? 140 : 230;
3371       } else {
3372         Map map = (Map)Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
3373
3374         if (map == null) {
3375           lcdContrastValue = 140;
3376         } else {
3377           Object o = map.get(RenderingHints.KEY_TEXT_LCD_CONTRAST);
3378           lcdContrastValue = (o == null) ? 140 : ((Integer)o);
3379         }
3380       }
3381     }
3382
3383     if (lcdContrastValue < 100 || lcdContrastValue > 250) {
3384       // the default value
3385       lcdContrastValue = 140;
3386     }
3387
3388     return lcdContrastValue;
3389   }
3390
3391   /**
3392    * Adds the specified border to the specified component.
3393    * If the component already has a border it will be preserved.
3394    * If component or border is not specified nothing happens.
3395    *
3396    * @param component the component to which border added
3397    * @param border    the border to add to the component
3398    */
3399   public static void addBorder(JComponent component, Border border) {
3400     if (component != null && border != null) {
3401       Border old = component.getBorder();
3402       if (old != null) {
3403         border = BorderFactory.createCompoundBorder(border, old);
3404       }
3405       component.setBorder(border);
3406     }
3407   }
3408
3409   private static final Color DECORATED_ROW_BG_COLOR = new JBColor(new Color(242, 245, 249), new Color(65, 69, 71));
3410
3411   public static Color getDecoratedRowColor() {
3412     return DECORATED_ROW_BG_COLOR;
3413   }
3414
3415   @NotNull
3416   public static Paint getGradientPaint(float x1, float y1, @NotNull Color c1, float x2, float y2, @NotNull Color c2) {
3417     return (Registry.is("ui.no.bangs.and.whistles")) ? ColorUtil.mix(c1, c2, .5) : new GradientPaint(x1, y1, c1, x2, y2, c2);
3418   }
3419
3420   @Nullable
3421   public static Point getLocationOnScreen(@NotNull JComponent component) {
3422     int dx = 0;
3423     int dy = 0;
3424     for (Container c = component; c != null; c = c.getParent()) {
3425       if (c.isShowing()) {
3426         Point locationOnScreen = c.getLocationOnScreen();
3427         locationOnScreen.translate(dx, dy);
3428         return locationOnScreen;
3429       }
3430       else {
3431         Point location = c.getLocation();
3432         dx += location.x;
3433         dy += location.y;
3434       }
3435     }
3436     return null;
3437   }
3438
3439   @NotNull
3440   public static Window getActiveWindow() {
3441     Window[] windows = Window.getWindows();
3442     for (Window each : windows) {
3443       if (each.isVisible() && each.isActive()) return each;
3444     }
3445     return JOptionPane.getRootFrame();
3446   }
3447
3448   public static void suppressFocusStealing (Window window) {
3449     // Focus stealing is not a problem on Mac
3450     if (SystemInfo.isMac) return;
3451     if (Registry.is("suppress.focus.stealing")) {
3452       setAutoRequestFocus(window, false);
3453     }
3454   }
3455
3456   public static void setAutoRequestFocus (final Window onWindow, final boolean set){
3457     if (SystemInfo.isMac) return;
3458     if (SystemInfo.isJavaVersionAtLeast("1.7")) {
3459       try {
3460         Method setAutoRequestFocusMethod  = onWindow.getClass().getMethod("setAutoRequestFocus", boolean.class);
3461         setAutoRequestFocusMethod.invoke(onWindow, set);
3462       }
3463       catch (NoSuchMethodException e) { LOG.debug(e); }
3464       catch (InvocationTargetException e) { LOG.debug(e); }
3465       catch (IllegalAccessException e) { LOG.debug(e); }
3466     }
3467   }
3468
3469   //May have no usages but it's useful in runtime (Debugger "watches", some logging etc.)
3470   public static String getDebugText(Component c) {
3471     StringBuilder builder  = new StringBuilder();
3472     getAllTextsRecursivelyImpl(c, builder);
3473     return builder.toString();
3474   }
3475
3476   private static void getAllTextsRecursivelyImpl(Component component, StringBuilder builder) {
3477     String candidate = "";
3478     int limit = builder.length() > 60 ? 20 : 40;
3479     if (component instanceof JLabel) candidate = ((JLabel)component).getText();
3480     if (component instanceof JTextComponent) candidate = ((JTextComponent)component).getText();
3481     if (component instanceof AbstractButton) candidate = ((AbstractButton)component).getText();
3482     if (StringUtil.isNotEmpty(candidate)) {
3483       builder.append(candidate.length() > limit ? (candidate.substring(0, limit - 3) + "...") : candidate).append('|');
3484     }
3485     if (component instanceof Container) {
3486       Component[] components = ((Container)component).getComponents();
3487       for (Component child : components) {
3488         getAllTextsRecursivelyImpl(child, builder);
3489       }
3490     }
3491   }
3492
3493   public static boolean isAncestor(@NotNull Component ancestor, @Nullable Component descendant) {
3494     while (descendant != null) {
3495       if (descendant == ancestor) {
3496         return true;
3497       }
3498       descendant = descendant.getParent();
3499     }
3500     return false;
3501   }
3502
3503   public static void resetUndoRedoActions(@NotNull JTextComponent textComponent) {
3504     UndoManager undoManager = getClientProperty(textComponent, UNDO_MANAGER);
3505     if (undoManager != null) {
3506       undoManager.discardAllEdits();
3507     }
3508   }
3509
3510   private static final DocumentAdapter SET_TEXT_CHECKER = new DocumentAdapter() {
3511     @Override
3512     protected void textChanged(DocumentEvent e) {
3513       Document document = e.getDocument();
3514       if (document instanceof AbstractDocument) {
3515         StackTraceElement[] stackTrace = new Throwable().getStackTrace();
3516         for (StackTraceElement element : stackTrace) {
3517           if (!element.getClassName().equals(JTextComponent.class.getName()) || !element.getMethodName().equals("setText")) continue;
3518           UndoableEditListener[] undoableEditListeners = ((AbstractDocument)document).getUndoableEditListeners();
3519           for (final UndoableEditListener listener : undoableEditListeners) {
3520             if (listener instanceof UndoManager) {
3521               Runnable runnable = new Runnable() {
3522                 public void run() {
3523                   ((UndoManager)listener).discardAllEdits();
3524                 }
3525               };
3526               //noinspection SSBasedInspection
3527               SwingUtilities.invokeLater(runnable);
3528               return;
3529             }
3530           }
3531         }
3532       }
3533     }
3534   };
3535
3536   public static void addUndoRedoActions(@NotNull final JTextComponent textComponent) {
3537     if (textComponent.getClientProperty(UNDO_MANAGER) instanceof UndoManager) {
3538       return;
3539     }
3540     UndoManager undoManager = new UndoManager();
3541     textComponent.putClientProperty(UNDO_MANAGER, undoManager);
3542     textComponent.getDocument().addUndoableEditListener(undoManager);
3543     textComponent.getDocument().addDocumentListener(SET_TEXT_CHECKER);
3544     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, SystemInfo.isMac? InputEvent.META_MASK : InputEvent.CTRL_MASK), "undoKeystroke");
3545     textComponent.getActionMap().put("undoKeystroke", UNDO_ACTION);
3546     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, (SystemInfo.isMac? InputEvent.META_MASK : InputEvent.CTRL_MASK) | InputEvent.SHIFT_MASK), "redoKeystroke");
3547     textComponent.getActionMap().put("redoKeystroke", REDO_ACTION);
3548   }
3549
3550   public static void playSoundFromResource(final String resourceName) {
3551     final Class callerClass = ReflectionUtil.getGrandCallerClass();
3552     if (callerClass == null) return;
3553     playSoundFromStream(new Factory<InputStream>() {
3554       @Override
3555       public InputStream create() {
3556         return callerClass.getResourceAsStream(resourceName);
3557       }
3558     });
3559   }
3560
3561   public static void playSoundFromStream(final Factory<InputStream> streamProducer) {
3562     new Thread(new Runnable() {
3563       // The wrapper thread is unnecessary, unless it blocks on the
3564       // Clip finishing; see comments.