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