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