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