IDEA-251030 Icons are not shown in Database toolwindow popup menu
[idea/community.git] / platform / util / ui / src / com / intellij / util / ui / UIUtil.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.util.ui;
3
4 import com.intellij.BundleBase;
5 import com.intellij.diagnostic.LoadingState;
6 import com.intellij.icons.AllIcons;
7 import com.intellij.openapi.Disposable;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.ui.GraphicsConfig;
10 import com.intellij.openapi.util.*;
11 import com.intellij.openapi.util.registry.Registry;
12 import com.intellij.openapi.util.text.StringUtil;
13 import com.intellij.ui.*;
14 import com.intellij.ui.mac.foundation.Foundation;
15 import com.intellij.ui.paint.LinePainter2D;
16 import com.intellij.ui.paint.PaintUtil.RoundingMode;
17 import com.intellij.ui.scale.JBUIScale;
18 import com.intellij.ui.scale.ScaleContext;
19 import com.intellij.util.*;
20 import com.intellij.util.concurrency.Semaphore;
21 import com.intellij.util.containers.ContainerUtil;
22 import com.intellij.util.containers.JBIterable;
23 import com.intellij.util.containers.JBTreeTraverser;
24 import org.intellij.lang.annotations.JdkConstants;
25 import org.intellij.lang.annotations.Language;
26 import org.jetbrains.annotations.*;
27
28 import javax.sound.sampled.AudioInputStream;
29 import javax.sound.sampled.AudioSystem;
30 import javax.sound.sampled.Clip;
31 import javax.swing.Timer;
32 import javax.swing.*;
33 import javax.swing.border.AbstractBorder;
34 import javax.swing.border.Border;
35 import javax.swing.border.LineBorder;
36 import javax.swing.event.DocumentEvent;
37 import javax.swing.event.UndoableEditListener;
38 import javax.swing.plaf.ButtonUI;
39 import javax.swing.plaf.ComboBoxUI;
40 import javax.swing.plaf.FontUIResource;
41 import javax.swing.plaf.UIResource;
42 import javax.swing.plaf.basic.BasicComboBoxUI;
43 import javax.swing.plaf.basic.BasicRadioButtonUI;
44 import javax.swing.plaf.basic.ComboPopup;
45 import javax.swing.text.*;
46 import javax.swing.text.html.HTMLDocument;
47 import javax.swing.text.html.HTMLEditorKit;
48 import javax.swing.text.html.ParagraphView;
49 import javax.swing.text.html.StyleSheet;
50 import javax.swing.undo.UndoManager;
51 import java.awt.*;
52 import java.awt.event.*;
53 import java.awt.font.FontRenderContext;
54 import java.awt.font.GlyphVector;
55 import java.awt.geom.AffineTransform;
56 import java.awt.geom.Rectangle2D;
57 import java.awt.geom.RoundRectangle2D;
58 import java.awt.image.BufferedImage;
59 import java.awt.image.BufferedImageOp;
60 import java.awt.image.ImageObserver;
61 import java.awt.image.RGBImageFilter;
62 import java.awt.print.PrinterGraphics;
63 import java.beans.PropertyChangeListener;
64 import java.io.BufferedInputStream;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.io.InputStreamReader;
68 import java.lang.ref.WeakReference;
69 import java.lang.reflect.InvocationTargetException;
70 import java.lang.reflect.Method;
71 import java.net.URL;
72 import java.nio.charset.StandardCharsets;
73 import java.text.NumberFormat;
74 import java.util.List;
75 import java.util.*;
76 import java.util.regex.Pattern;
77
78 @SuppressWarnings("StaticMethodOnlyUsedInOneClass")
79 public final class UIUtil {
80   static {
81     LoadingState.LAF_INITIALIZED.checkOccurred();
82   }
83
84   public static final String BORDER_LINE = "<hr size=1 noshade>";
85   @NonNls public static final String BR = "<br/>";
86
87   public static final Key<Boolean> LAF_WITH_THEME_KEY = Key.create("Laf.with.ui.theme");
88   public static final Key<String> PLUGGABLE_LAF_KEY = Key.create("Pluggable.laf.name");
89
90   // cannot be static because logging maybe not configured yet
91   private static @NotNull Logger getLogger() {
92     return Logger.getInstance(UIUtil.class);
93   }
94
95   public static void decorateWindowHeader(JRootPane pane) {
96     if (pane != null && SystemInfo.isMacOSMojave) {
97       pane.putClientProperty("jetbrains.awt.windowDarkAppearance", StartupUiUtil.isUnderDarcula());
98     }
99   }
100
101   public static void setCustomTitleBar(@NotNull Window window, @NotNull JRootPane rootPane, java.util.function.Consumer<Runnable> onDispose) {
102     if (!SystemInfo.isMac || !Registry.is("ide.mac.transparentTitleBarAppearance", false)) {
103       return;
104     }
105
106     JBInsets topWindowInset = JBUI.insetsTop(24);
107     rootPane.putClientProperty("jetbrains.awt.transparentTitleBarAppearance", true);
108     AbstractBorder customDecorationBorder = new AbstractBorder() {
109       @Override
110       public Insets getBorderInsets(Component c) {
111         return topWindowInset;
112       }
113
114       @Override
115       public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
116         Graphics2D graphics = (Graphics2D)g.create();
117         try {
118           Rectangle headerRectangle = new Rectangle(0, 0, c.getWidth(), topWindowInset.top);
119           graphics.setColor(getPanelBackground());
120           graphics.fill(headerRectangle);
121           Color color = window.isActive()
122                         ? JBColor.black
123                         : JBColor.gray;
124           graphics.setColor(color);
125           int controlButtonsWidth = 70;
126           String windowTitle = getWindowTitle(window);
127           double widthToFit = controlButtonsWidth * 2 + GraphicsUtil.stringWidth(windowTitle, g.getFont()) - c.getWidth();
128           if (widthToFit <= 0) {
129             drawCenteredString(graphics, headerRectangle, windowTitle);
130           } else {
131             FontMetrics fm = graphics.getFontMetrics();
132             Rectangle2D stringBounds = fm.getStringBounds(windowTitle, graphics);
133             Rectangle bounds =
134               AffineTransform.getTranslateInstance(controlButtonsWidth, fm.getAscent() + (headerRectangle.height - stringBounds.getHeight()) / 2).createTransformedShape(stringBounds).getBounds();
135             drawCenteredString(graphics, bounds, windowTitle, false, true);
136           }
137         }
138         finally {
139           graphics.dispose();
140         }
141       }
142     };
143     rootPane.setBorder(customDecorationBorder);
144
145     WindowAdapter windowAdapter = new WindowAdapter() {
146       @Override
147       public void windowActivated(WindowEvent e) {
148         rootPane.repaint();
149       }
150
151       @Override
152       public void windowDeactivated(WindowEvent e) {
153         rootPane.repaint();
154       }
155     };
156     PropertyChangeListener propertyChangeListener = e -> rootPane.repaint();
157     window.addPropertyChangeListener("title", propertyChangeListener);
158     onDispose.accept(() -> {
159       window.removeWindowListener(windowAdapter);
160       window.removePropertyChangeListener("title", propertyChangeListener);
161     });
162   }
163
164   private static String getWindowTitle(Window window) {
165     return window instanceof JDialog ? ((JDialog)window).getTitle() : ((JFrame)window).getTitle() ;
166   }
167
168   // Here we setup window to be checked in IdeEventQueue and reset typeahead state when the window finally appears and gets focus
169   public static void markAsTypeAheadAware(Window window) {
170     putWindowClientProperty(window, "TypeAheadAwareWindow", Boolean.TRUE);
171   }
172
173   public static boolean isTypeAheadAware(Window window) {
174     return isWindowClientPropertyTrue(window, "TypeAheadAwareWindow");
175   }
176
177   // Here we setup dialog to be suggested in OwnerOptional as owner even if the dialog is not modal
178   public static void markAsPossibleOwner(Dialog dialog) {
179     putWindowClientProperty(dialog, "PossibleOwner", Boolean.TRUE);
180   }
181
182   public static boolean isPossibleOwner(@NotNull Dialog dialog) {
183     return isWindowClientPropertyTrue(dialog, "PossibleOwner");
184   }
185
186   public static int getMultiClickInterval() {
187     Object property = Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
188     if (property instanceof Integer) {
189       return (Integer)property;
190     }
191     return 500;
192   }
193
194   private static final AtomicNotNullLazyValue<Boolean> X_RENDER_ACTIVE = new AtomicNotNullLazyValue<Boolean>() {
195     @Override
196     protected @NotNull Boolean compute() {
197       if (!SystemInfo.isXWindow) {
198         return false;
199       }
200       try {
201         final Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("sun.awt.X11GraphicsEnvironment");
202         final Method method = clazz.getMethod("isXRenderAvailable");
203         return (Boolean)method.invoke(null);
204       }
205       catch (Throwable e) {
206         return false;
207       }
208     }
209   };
210
211   private static final String[] STANDARD_FONT_SIZES =
212     {"8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72"};
213
214   public static void applyStyle(@NotNull ComponentStyle componentStyle, @NotNull Component comp) {
215     if (!(comp instanceof JComponent)) return;
216
217     JComponent c = (JComponent)comp;
218
219     if (isUnderAquaBasedLookAndFeel()) {
220       c.putClientProperty("JComponent.sizeVariant", StringUtil.toLowerCase(componentStyle.name()));
221     }
222     FontSize fontSize = componentStyle == ComponentStyle.MINI
223                         ? FontSize.MINI
224                         : componentStyle == ComponentStyle.SMALL
225                           ? FontSize.SMALL
226                           : FontSize.NORMAL;
227     c.setFont(getFont(fontSize, c.getFont()));
228     Container p = c.getParent();
229     if (p != null) {
230       SwingUtilities.updateComponentTreeUI(p);
231     }
232   }
233
234   public static void setMonospaced(@NotNull Component component) {
235     Font font = component.getFont();
236     component.setFont(new FontUIResource(Font.MONOSPACED, font.getStyle(), font.getSize()));
237   }
238
239   public static @NotNull Cursor getTextCursor(@NotNull Color backgroundColor) {
240     return SystemInfo.isMac && ColorUtil.isDark(backgroundColor) ?
241            MacUIUtil.getInvertedTextCursor() : Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
242   }
243
244   public static @Nullable Cursor cursorIfNotDefault(@Nullable Cursor cursorToSet) {
245     return cursorToSet != null && cursorToSet.getType() != Cursor.DEFAULT_CURSOR ? cursorToSet : null;
246   }
247
248   public static @NotNull RGBImageFilter getGrayFilter() {
249     return GrayFilter.namedFilter("grayFilter", new GrayFilter(33, -35, 100));
250   }
251
252   public static @NotNull RGBImageFilter getTextGrayFilter() {
253     return GrayFilter.namedFilter("text.grayFilter", new GrayFilter(20, 0, 100));
254   }
255
256   @ApiStatus.Experimental
257   public static class GrayFilter extends RGBImageFilter {
258     private float brightness;
259     private float contrast;
260     private int alpha;
261
262     private int origContrast;
263     private int origBrightness;
264
265     /**
266      * @param brightness in range [-100..100] where 0 has no effect
267      * @param contrast in range [-100..100] where 0 has no effect
268      * @param alpha in range [0..100] where 0 is transparent, 100 has no effect
269      */
270     public GrayFilter(int brightness, int contrast, int alpha) {
271       setBrightness(brightness);
272       setContrast(contrast);
273       setAlpha(alpha);
274     }
275
276     public GrayFilter() {
277       this(0, 0, 100);
278     }
279
280     private void setBrightness(int brightness) {
281       origBrightness = Math.max(-100, Math.min(100, brightness));
282       this.brightness = (float)(Math.pow(origBrightness, 3) / (100f * 100f)); // cubic in [0..100]
283     }
284
285     public int getBrightness() {
286       return origBrightness;
287     }
288
289     private void setContrast(int contrast) {
290       origContrast = Math.max(-100, Math.min(100, contrast));
291       this.contrast = origContrast / 100f;
292     }
293
294     public int getContrast() {
295       return origContrast;
296     }
297
298     private void setAlpha(int alpha) {
299       this.alpha = Math.max(0, Math.min(100, alpha));
300     }
301
302     public int getAlpha() {
303       return alpha;
304     }
305
306     @Override
307     @SuppressWarnings("AssignmentReplaceableWithOperatorAssignment")
308     public int filterRGB(int x, int y, int rgb) {
309       // Use NTSC conversion formula.
310       int gray = (int)(0.30 * (rgb >> 16 & 0xff) +
311                        0.59 * (rgb >> 8 & 0xff) +
312                        0.11 * (rgb & 0xff));
313
314       if (brightness >= 0) {
315         gray = (int)((gray + brightness * 255) / (1 + brightness));
316       }
317       else {
318         gray = (int)(gray / (1 - brightness));
319       }
320
321       if (contrast >= 0) {
322         if (gray >= 127) {
323           gray = (int)(gray + (255 - gray) * contrast);
324         }
325         else {
326           gray = (int)(gray - gray * contrast);
327         }
328       }
329       else {
330         gray = (int)(127 + (gray - 127) * (contrast + 1));
331       }
332
333       int a = ((rgb >> 24) & 0xff) * alpha / 100;
334
335       return (a << 24) | (gray << 16) | (gray << 8) | gray;
336     }
337
338     public @NotNull GrayFilterUIResource asUIResource() {
339       return new GrayFilterUIResource(this);
340     }
341
342     public static class GrayFilterUIResource extends GrayFilter implements UIResource {
343       public GrayFilterUIResource(@NotNull GrayFilter filter) {
344         super(filter.origBrightness, filter.origContrast, filter.alpha);
345       }
346     }
347
348     public static @NotNull GrayFilter namedFilter(@NotNull String resourceName, @NotNull GrayFilter defaultFilter) {
349       return ObjectUtils.notNull((GrayFilter)UIManager.get(resourceName), defaultFilter);
350     }
351   }
352
353   /** @deprecated use {@link JBUIScale} instead */
354   @Deprecated
355   @ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
356   public static boolean isAppleRetina() {
357     return false;
358   }
359
360   public static @NotNull Couple<Color> getCellColors(@NotNull JTable table, boolean isSel, int row, int column) {
361     return Couple.of(isSel ? table.getSelectionForeground() : table.getForeground(),
362                      isSel ? table.getSelectionBackground() : table.getBackground());
363   }
364
365   public static void fixOSXEditorBackground(@NotNull JTable table) {
366     if (!SystemInfo.isMac) return;
367
368     if (table.isEditing()) {
369       int column = table.getEditingColumn();
370       int row = table.getEditingRow();
371       Component renderer = column>=0 && row >= 0 ? table.getCellRenderer(row, column)
372         .getTableCellRendererComponent(table, table.getValueAt(row, column), true, table.hasFocus(), row, column) : null;
373       Component component = table.getEditorComponent();
374       if (component != null && renderer != null) {
375         changeBackGround(component, renderer.getBackground());
376       }
377     }
378   }
379
380   public enum FontSize {NORMAL, SMALL, MINI}
381
382   public enum ComponentStyle {LARGE, REGULAR, SMALL, MINI}
383
384   public enum FontColor {NORMAL, BRIGHTER}
385
386   public static final char MNEMONIC = BundleBase.MNEMONIC;
387   @NonNls public static final String HTML_MIME = "text/html";
388   @NonNls public static final String JSLIDER_ISFILLED = "JSlider.isFilled";
389   @NonNls public static final String TABLE_FOCUS_CELL_BACKGROUND_PROPERTY = "Table.focusCellBackground";
390   /**
391    * Prevent component DataContext from returning parent editor
392    * Useful for components that are manually painted over the editor to prevent shortcuts from falling-through to editor
393    *
394    * Usage: {@code component.putClientProperty(HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, Boolean.TRUE)}
395    */
396   @NonNls public static final String HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY = "AuxEditorComponent";
397   @NonNls public static final String CENTER_TOOLTIP_DEFAULT = "ToCenterTooltip";
398   @NonNls public static final String CENTER_TOOLTIP_STRICT = "ToCenterTooltip.default";
399
400   private static final Pattern CLOSE_TAG_PATTERN = Pattern.compile("<\\s*([^<>/ ]+)([^<>]*)/\\s*>", Pattern.CASE_INSENSITIVE);
401
402   @NonNls private static final String FOCUS_PROXY_KEY = "isFocusProxy";
403
404   public static final Key<Integer> KEEP_BORDER_SIDES = Key.create("keepBorderSides");
405   private static final Key<UndoManager> UNDO_MANAGER = Key.create("undoManager");
406   /**
407    * Alt+click does copy text from tooltip or balloon to clipboard.
408    * We collect this text from components recursively and this generic approach might 'grab' unexpected text fragments.
409    * To provide more accurate text scope you should mark dedicated component with putClientProperty(TEXT_COPY_ROOT, Boolean.TRUE)
410    * Note, main(root) components of BalloonImpl and AbstractPopup are already marked with this key
411    */
412   public static final Key<Boolean> TEXT_COPY_ROOT = Key.create("TEXT_COPY_ROOT");
413
414   private static final AbstractAction REDO_ACTION = new AbstractAction() {
415     @Override
416     public void actionPerformed(@NotNull ActionEvent e) {
417       UndoManager manager = getClientProperty(e.getSource(), UNDO_MANAGER);
418       if (manager != null && manager.canRedo()) {
419         manager.redo();
420       }
421     }
422   };
423   private static final AbstractAction UNDO_ACTION = new AbstractAction() {
424     @Override
425     public void actionPerformed(@NotNull ActionEvent e) {
426       UndoManager manager = getClientProperty(e.getSource(), UNDO_MANAGER);
427       if (manager != null && manager.canUndo()) {
428         manager.undo();
429       }
430     }
431   };
432
433   private static final Color ACTIVE_HEADER_COLOR = JBColor.namedColor("HeaderColor.active", 0xa0bad5);
434   private static final Color INACTIVE_HEADER_COLOR = JBColor.namedColor("HeaderColor.inactive", Gray._128);
435
436   public static final Color CONTRAST_BORDER_COLOR = JBColor.namedColor("Borders.ContrastBorderColor", new JBColor(0xC9C9C9, 0x323232));
437
438   public static final Color SIDE_PANEL_BACKGROUND = JBColor.namedColor("SidePanel.background", new JBColor(0xE6EBF0, 0x3E434C));
439
440   public static final Color AQUA_SEPARATOR_BACKGROUND_COLOR = new JBColor(Gray._240, Gray.x51);
441   public static final Color TRANSPARENT_COLOR = Gray.TRANSPARENT;
442
443   public static final int DEFAULT_HGAP = 10;
444   public static final int DEFAULT_VGAP = 4;
445   public static final int LARGE_VGAP = 12;
446
447   private static final int REGULAR_PANEL_TOP_BOTTOM_INSET = 8;
448   private static final int REGULAR_PANEL_LEFT_RIGHT_INSET = 12;
449
450   public static final Insets PANEL_REGULAR_INSETS = getRegularPanelInsets();
451
452   public static final Insets PANEL_SMALL_INSETS = JBInsets.create(5, 8);
453
454   @NonNls private static final String ROOT_PANE = "JRootPane.future";
455
456   private static final Ref<Boolean> ourRetina = Ref.create(SystemInfo.isMac ? null : false);
457
458   private UIUtil() {
459   }
460
461   public static boolean isRetina(@NotNull Graphics2D graphics) {
462     return SystemInfo.isMac ? DetectRetinaKit.isMacRetina(graphics) : isRetina();
463   }
464
465   //public static boolean isMacRetina(Graphics2D g) {
466   //  return DetectRetinaKit.isMacRetina(g);
467   //}
468
469   public static boolean isRetina() {
470     if (GraphicsEnvironment.isHeadless()) return false;
471
472     //Temporary workaround for HiDPI on Windows/Linux
473     if ("true".equalsIgnoreCase(System.getProperty("is.hidpi"))) {
474       return true;
475     }
476
477     if (Registry.is("new.retina.detection", false)) {
478       return DetectRetinaKit.isRetina();
479     }
480     else {
481       synchronized (ourRetina) {
482         if (ourRetina.isNull()) {
483           ourRetina.set(false); // in case HiDPIScaledImage.drawIntoImage is not called for some reason
484
485           try {
486             GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
487             final GraphicsDevice device = env.getDefaultScreenDevice();
488             Integer scale = ReflectionUtil.getField(device.getClass(), device, int.class, "scale");
489             if (scale != null && scale.intValue() == 2) {
490               ourRetina.set(true);
491               return true;
492             }
493           }
494           catch (AWTError | Exception ignore) { }
495           ourRetina.set(false);
496         }
497
498         return ourRetina.get();
499       }
500     }
501   }
502
503   public static boolean isWindowClientPropertyTrue(Window window, @NotNull Object key) {
504     return Boolean.TRUE.equals(getWindowClientProperty(window, key));
505   }
506
507   public static Object getWindowClientProperty(Window window, @NotNull Object key) {
508     if (window instanceof RootPaneContainer) {
509       JRootPane pane = ((RootPaneContainer)window).getRootPane();
510       if (pane != null) {
511         return pane.getClientProperty(key);
512       }
513     }
514     return null;
515   }
516
517   public static void putWindowClientProperty(Window window, @NotNull Object key, Object value) {
518     if (window instanceof RootPaneContainer) {
519       JRootPane pane = ((RootPaneContainer)window).getRootPane();
520       if (pane != null) {
521         pane.putClientProperty(key, value);
522       }
523     }
524   }
525
526   /**
527    * @param component a Swing component that may hold a client property value
528    * @param key       the client property key
529    * @return {@code true} if the property of the specified component is set to {@code true}
530    */
531   public static boolean isClientPropertyTrue(Object component, @NotNull Object key) {
532     return Boolean.TRUE.equals(getClientProperty(component, key));
533   }
534
535   /**
536    * @param component a Swing component that may hold a client property value
537    * @param key       the client property key that specifies a return type
538    * @return the property value from the specified component or {@code null}
539    */
540   public static Object getClientProperty(Object component, @NotNull @NonNls Object key) {
541     return component instanceof JComponent ? ((JComponent)component).getClientProperty(key) : null;
542   }
543
544   /**
545    * @param component a Swing component that may hold a client property value
546    * @return the property value from the specified component or {@code null}
547    */
548   public static <T> T getClientProperty(Object component, @NotNull Class<T> type) {
549     return ObjectUtils.tryCast(getClientProperty(component, (Object)type), type);
550   }
551
552   /**
553    * @param component a Swing component that may hold a client property value
554    * @param key       the client property key that specifies a return type
555    * @return the property value from the specified component or {@code null}
556    */
557   public static <T> T getClientProperty(Object component, @NotNull Key<T> key) {
558     //noinspection unchecked
559     return (T)getClientProperty(component, (Object)key);
560   }
561
562   public static <T> void putClientProperty(@NotNull JComponent component, @NotNull Key<T> key, T value) {
563     ComponentUtil.putClientProperty(component, key, value);
564   }
565
566   public static @NotNull String getHtmlBody(@NotNull String text) {
567     int htmlIndex = 6 + text.indexOf("<html>");
568     if (htmlIndex < 6) {
569       return text.replaceAll("\n", "<br>");
570     }
571     int htmlCloseIndex = text.indexOf("</html>", htmlIndex);
572     if (htmlCloseIndex < 0) {
573       htmlCloseIndex = text.length();
574     }
575     int bodyIndex = 6 + text.indexOf("<body>", htmlIndex);
576     if (bodyIndex < 6) {
577       return text.substring(htmlIndex, htmlCloseIndex);
578     }
579     int bodyCloseIndex = text.indexOf("</body>", bodyIndex);
580     if (bodyCloseIndex < 0) {
581       bodyCloseIndex = text.length();
582     }
583     return text.substring(bodyIndex, Math.min(bodyCloseIndex, htmlCloseIndex));
584   }
585
586   public static @NotNull String getHtmlBody(@NotNull Html html) {
587     String result = getHtmlBody(html.getText());
588     return html.isKeepFont() ? result : result.replaceAll("<font(.*?)>", "").replaceAll("</font>", "");
589   }
590
591   public static void drawLinePickedOut(@NotNull Graphics graphics, int x, int y, int x1, int y1) {
592     if (x == x1) {
593       int minY = Math.min(y, y1);
594       int maxY = Math.max(y, y1);
595       LinePainter2D.paint((Graphics2D)graphics, x, minY + 1, x1, maxY - 1);
596     }
597     else if (y == y1) {
598       int minX = Math.min(x, x1);
599       int maxX = Math.max(x, x1);
600       LinePainter2D.paint((Graphics2D)graphics, minX + 1, y, maxX - 1, y1);
601     }
602     else {
603       LinePainter2D.paint((Graphics2D)graphics, x, y, x1, y1);
604     }
605   }
606
607   public static boolean isReallyTypedEvent(@NotNull KeyEvent e) {
608     char c = e.getKeyChar();
609     if (c == KeyEvent.CHAR_UNDEFINED) return false; // ignore CHAR_UNDEFINED, like Swing text components do
610     if (c < 0x20 || c == 0x7F) return false;
611
612     // Allow input of special characters on Windows in Persian keyboard layout using Ctrl+Shift+1..4
613     if (SystemInfo.isWindows && c >= 0x200C && c <= 0x200F) return true;
614
615     if (SystemInfo.isMac) {
616       return !e.isMetaDown() && !e.isControlDown();
617     }
618
619     return !e.isAltDown() && !e.isControlDown();
620   }
621
622   public static int getStringY(final @NotNull String string, final @NotNull Rectangle bounds, final @NotNull Graphics2D g) {
623     final int centerY = bounds.height / 2;
624     final Font font = g.getFont();
625     final FontRenderContext frc = g.getFontRenderContext();
626     final Rectangle stringBounds = font.getStringBounds(string.isEmpty() ? " " : string, frc).getBounds();
627
628     return (int)(centerY - stringBounds.height / 2.0 - stringBounds.y);
629   }
630
631   public static void drawLabelDottedRectangle(final @NotNull JLabel label, final @NotNull Graphics g) {
632     drawLabelDottedRectangle(label, g, null);
633   }
634
635   public static void drawLabelDottedRectangle(final @NotNull JLabel label, final @NotNull Graphics g, @Nullable Rectangle bounds) {
636     if (bounds == null) {
637       bounds = getLabelTextBounds(label);
638     }
639     // JLabel draws the text relative to the baseline. So, we must ensure
640     // we draw the dotted rectangle relative to that same baseline.
641     FontMetrics fm = label.getFontMetrics(label.getFont());
642     int baseLine = label.getUI().getBaseline(label, label.getWidth(), label.getHeight());
643     int textY = baseLine - fm.getLeading() - fm.getAscent();
644     int textHeight = fm.getHeight();
645     drawDottedRectangle(g, bounds.x, textY, bounds.x + bounds.width - 1, textY + textHeight - 1);
646   }
647
648   public static @NotNull Rectangle getLabelTextBounds(final @NotNull JLabel label) {
649     final Dimension size = label.getPreferredSize();
650     Icon icon = label.getIcon();
651     final Point point = new Point(0, 0);
652     final Insets insets = label.getInsets();
653     if (icon != null) {
654       if (label.getHorizontalTextPosition() == SwingConstants.TRAILING) {
655         point.x += label.getIconTextGap();
656         point.x += icon.getIconWidth();
657       } else if (label.getHorizontalTextPosition() == SwingConstants.LEADING) {
658         size.width -= icon.getIconWidth();
659       }
660     }
661     point.x += insets.left;
662     point.y += insets.top;
663     size.width -= point.x;
664     size.width -= insets.right;
665     size.height -= insets.bottom;
666
667     return new Rectangle(point, size);
668   }
669
670   /**
671    * @param string {@code String} to examine
672    * @param font {@code Font} that is used to render the string
673    * @param graphics {@link Graphics} that should be used to render the string
674    * @return height of the tallest glyph in a string. If string is empty, returns 0
675    */
676   public static int getHighestGlyphHeight(@NotNull String string, @NotNull Font font, @NotNull Graphics graphics) {
677     FontRenderContext frc = ((Graphics2D)graphics).getFontRenderContext();
678     GlyphVector gv = font.createGlyphVector(frc, string);
679     int maxHeight = 0;
680     for (int i = 0; i < string.length(); i ++) {
681       maxHeight = Math.max(maxHeight, (int)gv.getGlyphMetrics(i).getBounds2D().getHeight());
682     }
683     return maxHeight;
684   }
685
686   public static void setEnabled(@NotNull Component component, boolean enabled, boolean recursively) {
687     setEnabled(component, enabled, recursively, false);
688   }
689
690   public static void setEnabled(@NotNull Component component, boolean enabled, boolean recursively, final boolean visibleOnly) {
691     JBIterable<Component> all = recursively ? uiTraverser(component).expandAndFilter(
692       visibleOnly ? Component::isVisible : Conditions.alwaysTrue()).traverse() : JBIterable.of(component);
693     Color fg = enabled ? getLabelForeground() : getLabelDisabledForeground();
694     for (Component c : all) {
695       c.setEnabled(enabled);
696       if (c instanceof JLabel) {
697         c.setForeground(fg);
698       }
699     }
700   }
701
702   /**
703    * @deprecated Use {@link LinePainter2D#paint(Graphics2D, double, double, double, double)} instead.
704    */
705   @Deprecated
706   public static void drawLine(@NotNull Graphics g, int x1, int y1, int x2, int y2) {
707     LinePainter2D.paint((Graphics2D)g, x1, y1, x2, y2);
708   }
709
710   public static void drawLine(@NotNull Graphics2D g, int x1, int y1, int x2, int y2, @Nullable Color bgColor, @Nullable Color fgColor) {
711     Color oldFg = g.getColor();
712     Color oldBg = g.getBackground();
713     if (fgColor != null) {
714       g.setColor(fgColor);
715     }
716     if (bgColor != null) {
717       g.setBackground(bgColor);
718     }
719     LinePainter2D.paint(g, x1, y1, x2, y2);
720     if (fgColor != null) {
721       g.setColor(oldFg);
722     }
723     if (bgColor != null) {
724       g.setBackground(oldBg);
725     }
726   }
727
728   public static void drawWave(@NotNull Graphics2D g, @NotNull Rectangle rectangle) {
729     WavePainter.forColor(g.getColor()).paint(g, (int)rectangle.getMinX(), (int) rectangle.getMaxX(), (int) rectangle.getMaxY());
730   }
731
732   public static String @NotNull [] splitText(@NotNull String text, @NotNull FontMetrics fontMetrics, int widthLimit, char separator) {
733     List<String> lines = new ArrayList<>();
734     StringBuilder currentLine = new StringBuilder();
735     StringBuilder currentAtom = new StringBuilder();
736
737     for (int i = 0; i < text.length(); i++) {
738       char ch = text.charAt(i);
739       currentAtom.append(ch);
740
741       if (ch == separator) {
742         currentLine.append(currentAtom);
743         currentAtom.setLength(0);
744       }
745
746       String s = currentLine.toString() + currentAtom;
747       int width = fontMetrics.stringWidth(s);
748
749       if (width >= widthLimit - fontMetrics.charWidth('w')) {
750         if (currentLine.length() > 0) {
751           lines.add(currentLine.toString());
752           currentLine = new StringBuilder();
753         }
754         else {
755           lines.add(currentAtom.toString());
756           currentAtom.setLength(0);
757         }
758       }
759     }
760
761     String s = currentLine.toString() + currentAtom;
762     if (!s.isEmpty()) {
763       lines.add(s);
764     }
765
766     return ArrayUtilRt.toStringArray(lines);
767   }
768
769   public static void setActionNameAndMnemonic(@NotNull String text, @NotNull Action action) {
770     assignMnemonic(text, action);
771
772     text = text.replaceAll("&", "");
773     action.putValue(Action.NAME, text);
774   }
775   public static void assignMnemonic(@NotNull String text, @NotNull Action action) {
776     int mnemoPos = text.indexOf('&');
777     if (mnemoPos >= 0 && mnemoPos < text.length() - 2) {
778       String mnemoChar = text.substring(mnemoPos + 1, mnemoPos + 2).trim();
779       if (mnemoChar.length() == 1) {
780         action.putValue(Action.MNEMONIC_KEY, Integer.valueOf(mnemoChar.charAt(0)));
781       }
782     }
783   }
784
785
786   public static @NotNull Font getLabelFont(@NotNull FontSize size) {
787     return getFont(size, null);
788   }
789
790   public static @NotNull Font getFont(@NotNull FontSize size, @Nullable Font base) {
791     if (base == null) base = StartupUiUtil.getLabelFont();
792
793     return base.deriveFont(getFontSize(size));
794   }
795
796   public static float getFontSize(@NotNull FontSize size) {
797     int defSize = StartupUiUtil.getLabelFont().getSize();
798     switch (size) {
799       case SMALL:
800         return Math.max(defSize - JBUIScale.scale(2f), JBUIScale.scale(11f));
801       case MINI:
802         return Math.max(defSize - JBUIScale.scale(4f), JBUIScale.scale(9f));
803       default:
804         return defSize;
805     }
806   }
807
808   public static @NotNull Color getLabelFontColor(@NotNull FontColor fontColor) {
809     Color defColor = getLabelForeground();
810     if (fontColor == FontColor.BRIGHTER) {
811       return new JBColor(new Color(Math.min(defColor.getRed() + 50, 255), Math.min(defColor.getGreen() + 50, 255), Math.min(
812         defColor.getBlue() + 50, 255)), defColor.darker());
813     }
814     return defColor;
815   }
816
817   public static int getCheckBoxTextHorizontalOffset(@NotNull JCheckBox cb) {
818     // logic copied from javax.swing.plaf.basic.BasicRadioButtonUI.paint
819     ButtonUI ui = cb.getUI();
820     String text = cb.getText();
821
822     Icon buttonIcon = cb.getIcon();
823     if (buttonIcon == null && ui != null) {
824       if (ui instanceof BasicRadioButtonUI) {
825         buttonIcon = ((BasicRadioButtonUI)ui).getDefaultIcon();
826       }
827     }
828
829     Dimension size = new Dimension();
830     Rectangle viewRect = new Rectangle();
831     Rectangle iconRect = new Rectangle();
832     Rectangle textRect = new Rectangle();
833
834     Insets i = cb.getInsets();
835
836     size = cb.getSize(size);
837     viewRect.x = i.left;
838     viewRect.y = i.top;
839     viewRect.width = size.width - (i.right + viewRect.x);
840     viewRect.height = size.height - (i.bottom + viewRect.y);
841     iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
842     textRect.x = textRect.y = textRect.width = textRect.height = 0;
843
844     SwingUtilities.layoutCompoundLabel(
845       cb, cb.getFontMetrics(cb.getFont()), text, buttonIcon,
846       cb.getVerticalAlignment(), cb.getHorizontalAlignment(),
847       cb.getVerticalTextPosition(), cb.getHorizontalTextPosition(),
848       viewRect, iconRect, textRect,
849       text == null ? 0 : cb.getIconTextGap());
850
851     return textRect.x;
852   }
853
854   public static int getScrollBarWidth() {
855     return UIManager.getInt("ScrollBar.width");
856   }
857
858   public static Color getLabelBackground() {
859     return UIManager.getColor("Label.background");
860   }
861
862   public static @NotNull Color getLabelForeground() {
863     return JBColor.namedColor("Label.foreground", new JBColor(Gray._0, Gray.xBB));
864   }
865
866   public static Color getErrorForeground() {
867     return JBColor.namedColor("Label.errorForeground", new JBColor(new Color(0xC7222D), JBColor.RED));
868   }
869
870   public static @NotNull Color getLabelDisabledForeground() {
871     return JBColor.namedColor("Label.disabledForeground", JBColor.GRAY);
872   }
873
874   public static @NotNull Color getContextHelpForeground() {
875     return JBColor.namedColor("Label.infoForeground", new JBColor(Gray.x78, Gray.x8C));
876   }
877
878   public static @Nls @NotNull String removeMnemonic(@Nls @NotNull String s) {
879     if (s.indexOf('&') != -1) {
880       s = StringUtil.replace(s, "&", "");
881     }
882     if (s.indexOf('_') != -1) {
883       s = StringUtil.replace(s, "_", "");
884     }
885     if (s.indexOf(MNEMONIC) != -1) {
886       s = StringUtil.replace(s, String.valueOf(MNEMONIC), "");
887     }
888     return s;
889   }
890
891   public static int getDisplayMnemonicIndex(@NotNull String s) {
892     int idx = s.indexOf('&');
893     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf('&')) return idx;
894
895     idx = s.indexOf(MNEMONIC);
896     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf(MNEMONIC)) return idx;
897
898     return -1;
899   }
900
901   public static @Nls String replaceMnemonicAmpersand(@Nls String value) {
902     return BundleBase.replaceMnemonicAmpersand(value);
903   }
904
905   /**
906    * @deprecated use {@link #getTreeForeground()}
907    */
908   @Deprecated
909   public static @NotNull Color getTreeTextForeground() {
910     return getTreeForeground();
911   }
912
913   /**
914    * @deprecated use {@link #getTreeBackground()}
915    */
916   @Deprecated
917   public static @NotNull Color getTreeTextBackground() {
918     return getTreeBackground();
919   }
920
921   public static Color getFieldForegroundColor() {
922     return UIManager.getColor("field.foreground");
923   }
924
925   public static Color getActiveTextColor() {
926     return UIManager.getColor("textActiveText");
927   }
928
929   public static @NotNull Color getInactiveTextColor() {
930     return JBColor.namedColor("Component.infoForeground", new JBColor(Gray.x99, Gray.x78));
931   }
932
933   /**
934    * @deprecated use {@link UIUtil#getTextFieldBackground()} instead
935    */
936   @Deprecated
937   public static Color getActiveTextFieldBackgroundColor() {
938     return getTextFieldBackground();
939   }
940
941   public static Color getInactiveTextFieldBackgroundColor() {
942     return UIManager.getColor("TextField.inactiveBackground");
943   }
944
945   /**
946    * @deprecated use {@link UIUtil#getInactiveTextColor()} instead
947    */
948   @Deprecated
949   public static @NotNull Color getTextInactiveTextColor() {
950     return getInactiveTextColor();
951   }
952
953   public static Color getTreeSelectionBorderColor() {
954     return UIManager.getColor("Tree.selectionBorderColor");
955   }
956
957   public static int getTreeRightChildIndent() {
958     return UIManager.getInt("Tree.rightChildIndent");
959   }
960
961   public static int getTreeLeftChildIndent() {
962     return UIManager.getInt("Tree.leftChildIndent");
963   }
964
965   public static @NotNull Color getToolTipBackground() {
966     return JBColor.namedColor("ToolTip.background", new JBColor(Gray.xF2, new Color(0x3c3f41)));
967   }
968
969   public static @NotNull Color getToolTipActionBackground() {
970     return JBColor.namedColor("ToolTip.Actions.background", new JBColor(Gray.xEB, new Color(0x43474a)));
971   }
972
973   public static @NotNull Color getToolTipForeground() {
974     return JBColor.namedColor("ToolTip.foreground", new JBColor(Gray.x00, Gray.xBF));
975   }
976
977   public static Color getComboBoxDisabledForeground() {
978     return UIManager.getColor("ComboBox.disabledForeground");
979   }
980
981   public static Color getComboBoxDisabledBackground() {
982     return UIManager.getColor("ComboBox.disabledBackground");
983   }
984
985   public static Color getButtonSelectColor() {
986     return UIManager.getColor("Button.select");
987   }
988
989   public static Integer getPropertyMaxGutterIconWidth(@NotNull String propertyPrefix) {
990     return (Integer)UIManager.get(propertyPrefix + ".maxGutterIconWidth");
991   }
992
993   public static Color getMenuItemDisabledForeground() {
994     return UIManager.getColor("MenuItem.disabledForeground");
995   }
996
997   public static Object getMenuItemDisabledForegroundObject() {
998     return UIManager.get("MenuItem.disabledForeground");
999   }
1000
1001   public static Object getTabbedPanePaintContentBorder(final @NotNull JComponent c) {
1002     return c.getClientProperty("TabbedPane.paintContentBorder");
1003   }
1004
1005   public static Color getTableGridColor() {
1006     return UIManager.getColor("Table.gridColor");
1007   }
1008
1009   public static @NotNull Color getPanelBackground() {
1010     return JBColor.PanelBackground;
1011   }
1012
1013   public static Color getEditorPaneBackground() {
1014     return UIManager.getColor("EditorPane.background");
1015   }
1016
1017   public static Color getTableFocusCellBackground() {
1018     return UIManager.getColor(TABLE_FOCUS_CELL_BACKGROUND_PROPERTY);
1019   }
1020
1021   public static Color getTextFieldForeground() {
1022     return UIManager.getColor("TextField.foreground");
1023   }
1024
1025   public static Color getTextFieldBackground() {
1026     return UIManager.getColor("TextField.background");
1027   }
1028
1029   public static Font getButtonFont() {
1030     return UIManager.getFont("Button.font");
1031   }
1032
1033   public static Font getToolTipFont() {
1034     return UIManager.getFont("ToolTip.font");
1035   }
1036
1037   public static void setSliderIsFilled(final @NotNull JSlider slider, final boolean value) {
1038     slider.putClientProperty("JSlider.isFilled", value);
1039   }
1040
1041   public static Color getLabelTextForeground() {
1042     return UIManager.getColor("Label.textForeground");
1043   }
1044
1045   public static Color getControlColor() {
1046     return UIManager.getColor("control");
1047   }
1048
1049   public static Font getOptionPaneMessageFont() {
1050     return UIManager.getFont("OptionPane.messageFont");
1051   }
1052
1053   public static Font getMenuFont() {
1054     return UIManager.getFont("Menu.font");
1055   }
1056
1057   /**
1058    * @deprecated use {@link JBUI.CurrentTheme.CustomFrameDecorations#separatorForeground()}
1059    */
1060   @Deprecated
1061   public static @NotNull Color getSeparatorForeground() {
1062     return JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground();
1063   }
1064
1065   public static Color getSeparatorShadow() {
1066     return UIManager.getColor("Separator.shadow");
1067   }
1068
1069   @SuppressWarnings("MissingDeprecatedAnnotation")
1070   @Deprecated
1071   public static Color getSeparatorHighlight() {
1072     return UIManager.getColor("Separator.highlight");
1073   }
1074
1075   /**
1076    * @deprecated use {@link JBUI.CurrentTheme.CustomFrameDecorations#separatorForeground()}
1077    */
1078   @Deprecated
1079   public static @NotNull Color getSeparatorColor() {
1080     return JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground();
1081   }
1082
1083   public static Border getTableFocusCellHighlightBorder() {
1084     return UIManager.getBorder("Table.focusCellHighlightBorder");
1085   }
1086
1087   /**
1088    * @deprecated unsupported UI feature
1089    */
1090   @Deprecated
1091   public static void setLineStyleAngled(@SuppressWarnings("unused") @NotNull JTree component) {
1092   }
1093
1094   public static Color getTableFocusCellForeground() {
1095     return UIManager.getColor("Table.focusCellForeground");
1096   }
1097
1098   public static Border getTextFieldBorder() {
1099     return UIManager.getBorder("TextField.border");
1100   }
1101
1102   public static @NotNull Icon getErrorIcon() {
1103     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.errorIcon"), AllIcons.General.ErrorDialog);
1104   }
1105
1106   public static @NotNull Icon getInformationIcon() {
1107     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.informationIcon"), AllIcons.General.InformationDialog);
1108   }
1109
1110   public static @NotNull Icon getQuestionIcon() {
1111     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.questionIcon"), AllIcons.General.QuestionDialog);
1112   }
1113
1114   public static @NotNull Icon getWarningIcon() {
1115     return ObjectUtils.notNull(UIManager.getIcon("OptionPane.warningIcon"), AllIcons.General.WarningDialog);
1116   }
1117
1118   public static @NotNull Icon getBalloonInformationIcon() {
1119     return AllIcons.General.BalloonInformation;
1120   }
1121
1122   public static @NotNull Icon getBalloonWarningIcon() {
1123     return AllIcons.General.BalloonWarning;
1124   }
1125
1126   public static @NotNull Icon getBalloonErrorIcon() {
1127     return AllIcons.General.BalloonError;
1128   }
1129
1130   @SuppressWarnings("MissingDeprecatedAnnotation")
1131   @Deprecated
1132   public static Icon getRadioButtonIcon() {
1133     return UIManager.getIcon("RadioButton.icon");
1134   }
1135
1136   public static @NotNull Icon getTreeNodeIcon(boolean expanded, boolean selected, boolean focused) {
1137     boolean white = selected && focused || StartupUiUtil.isUnderDarcula();
1138
1139     Icon expandedDefault = getTreeExpandedIcon();
1140     Icon collapsedDefault = getTreeCollapsedIcon();
1141     Icon expandedSelected = getTreeSelectedExpandedIcon();
1142     Icon collapsedSelected = getTreeSelectedCollapsedIcon();
1143
1144     int width = Math.max(
1145       Math.max(expandedDefault.getIconWidth(), collapsedDefault.getIconWidth()),
1146       Math.max(expandedSelected.getIconWidth(), collapsedSelected.getIconWidth()));
1147     int height = Math.max(
1148       Math.max(expandedDefault.getIconHeight(), collapsedDefault.getIconHeight()),
1149       Math.max(expandedSelected.getIconHeight(), collapsedSelected.getIconHeight()));
1150
1151     return new CenteredIcon(!white
1152                             ? expanded ? expandedDefault : collapsedDefault
1153                             : expanded ? expandedSelected : collapsedSelected,
1154                             width, height, false);
1155   }
1156
1157   public static @NotNull Icon getTreeCollapsedIcon() {
1158     return UIManager.getIcon("Tree.collapsedIcon");
1159   }
1160
1161   public static @NotNull Icon getTreeExpandedIcon() {
1162     return UIManager.getIcon("Tree.expandedIcon");
1163   }
1164
1165   /**
1166    * @deprecated use {@link #getTreeExpandedIcon()} and {@link #getTreeCollapsedIcon()}
1167    */
1168   @Deprecated
1169   public static Icon getTreeIcon(boolean expanded) {
1170     return expanded ? getTreeExpandedIcon() : getTreeCollapsedIcon();
1171   }
1172
1173   public static @NotNull Icon getTreeSelectedCollapsedIcon() {
1174     Icon icon = UIManager.getIcon("Tree.collapsedSelectedIcon");
1175     return icon != null ? icon : getTreeCollapsedIcon();
1176   }
1177
1178   public static @NotNull Icon getTreeSelectedExpandedIcon() {
1179     Icon icon = UIManager.getIcon("Tree.expandedSelectedIcon");
1180     return icon != null ? icon : getTreeExpandedIcon();
1181   }
1182
1183   @SuppressWarnings("MissingDeprecatedAnnotation")
1184   @Deprecated
1185   public static Border getTableHeaderCellBorder() {
1186     return UIManager.getBorder("TableHeader.cellBorder");
1187   }
1188
1189   public static Color getWindowColor() {
1190     return UIManager.getColor("window");
1191   }
1192
1193   public static Color getTextAreaForeground() {
1194     return UIManager.getColor("TextArea.foreground");
1195   }
1196
1197   public static Color getOptionPaneBackground() {
1198     return UIManager.getColor("OptionPane.background");
1199   }
1200
1201   /**
1202    * @deprecated Aqua Look-n-Feel is not supported anymore
1203    */
1204   @Deprecated
1205   @ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
1206   public static boolean isUnderAquaLookAndFeel() {
1207     return SystemInfo.isMac && UIManager.getLookAndFeel().getName().contains("Mac OS X");
1208   }
1209
1210   /**
1211    * @deprecated Nimbus Look-n-Feel is deprecated and not supported anymore
1212    */
1213   @Deprecated
1214   @ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
1215   public static boolean isUnderNimbusLookAndFeel() {
1216     return false;
1217   }
1218
1219   public static boolean isUnderAquaBasedLookAndFeel() {
1220     return SystemInfo.isMac && (StartupUiUtil.isUnderDarcula() || isUnderIntelliJLaF());
1221   }
1222
1223   public static boolean isUnderDefaultMacTheme() {
1224     LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
1225     if (SystemInfo.isMac && lookAndFeel instanceof UserDataHolder) {
1226       UserDataHolder dh = (UserDataHolder)lookAndFeel;
1227
1228       return Boolean.TRUE != dh.getUserData(LAF_WITH_THEME_KEY) &&
1229              StringUtil.equals(dh.getUserData(PLUGGABLE_LAF_KEY), "macOS Light");
1230     }
1231     return false;
1232   }
1233
1234   public static boolean isUnderWin10LookAndFeel() {
1235     LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
1236     if (SystemInfo.isWindows && lookAndFeel instanceof UserDataHolder) {
1237       UserDataHolder dh = (UserDataHolder)lookAndFeel;
1238
1239       return Boolean.TRUE != dh.getUserData(LAF_WITH_THEME_KEY) &&
1240              StringUtil.equals(dh.getUserData(PLUGGABLE_LAF_KEY), "Windows 10 Light");
1241     }
1242     return false;
1243   }
1244
1245   public static boolean isUnderDarcula() {
1246     return StartupUiUtil.isUnderDarcula();
1247   }
1248
1249   public static boolean isUnderIntelliJLaF() {
1250     return UIManager.getLookAndFeel().getName().contains("IntelliJ") || isUnderDefaultMacTheme() || isUnderWin10LookAndFeel();
1251   }
1252
1253   @Deprecated
1254   @ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
1255   public static boolean isUnderGTKLookAndFeel() {
1256     return SystemInfo.isXWindow && UIManager.getLookAndFeel().getName().contains("GTK");
1257   }
1258
1259   public static boolean isGraphite() {
1260     if (!SystemInfo.isMac) return false;
1261     try {
1262       // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSCell_Class/index.html#//apple_ref/doc/c_ref/NSGraphiteControlTint
1263       // NSGraphiteControlTint = 6
1264       return Foundation.invoke("NSColor", "currentControlTint").intValue() == 6;
1265     } catch (Exception e) {
1266       return false;
1267     }
1268   }
1269
1270   public static @NotNull Font getToolbarFont() {
1271     return SystemInfo.isMac ? getLabelFont(UIUtil.FontSize.SMALL) : StartupUiUtil.getLabelFont();
1272   }
1273
1274   public static @NotNull Color shade(@NotNull Color c, final double factor, final double alphaFactor) {
1275     assert factor >= 0 : factor;
1276     //noinspection UseJBColor
1277     return new Color(
1278       Math.min((int)Math.round(c.getRed() * factor), 255),
1279       Math.min((int)Math.round(c.getGreen() * factor), 255),
1280       Math.min((int)Math.round(c.getBlue() * factor), 255),
1281       Math.min((int)Math.round(c.getAlpha() * alphaFactor), 255)
1282     );
1283   }
1284
1285   public static @NotNull Color mix(@NotNull Color c1, final Color c2, final double factor) {
1286     assert 0 <= factor && factor <= 1.0 : factor;
1287     final double backFactor = 1.0 - factor;
1288     //noinspection UseJBColor
1289     return new Color(
1290       Math.min((int)Math.round(c1.getRed() * backFactor + c2.getRed() * factor), 255),
1291       Math.min((int)Math.round(c1.getGreen() * backFactor + c2.getGreen() * factor), 255),
1292       Math.min((int)Math.round(c1.getBlue() * backFactor + c2.getBlue() * factor), 255)
1293     );
1294   }
1295
1296   public static boolean isFullRowSelectionLAF() {
1297     return false;
1298   }
1299
1300   public static boolean isUnderNativeMacLookAndFeel() {
1301     return StartupUiUtil.isUnderDarcula();
1302   }
1303
1304   public static int getListCellHPadding() {
1305     return isUnderDefaultMacTheme() ? 8 :
1306            isUnderWin10LookAndFeel() ? 2 :
1307            7;
1308   }
1309
1310   public static int getListCellVPadding() {
1311     return 1;
1312   }
1313
1314   public static @NotNull JBInsets getRegularPanelInsets() {
1315     return JBInsets.create(REGULAR_PANEL_TOP_BOTTOM_INSET, REGULAR_PANEL_LEFT_RIGHT_INSET);
1316   }
1317
1318   public static @NotNull Insets getListCellPadding() {
1319     return JBInsets.create(getListCellVPadding(), getListCellHPadding());
1320   }
1321
1322   public static @NotNull Insets getListViewportPadding() {
1323     return isUnderNativeMacLookAndFeel() ? JBInsets.create(1, 0) : JBUI.emptyInsets();
1324   }
1325
1326   public static boolean isToUseDottedCellBorder() {
1327     return !isUnderNativeMacLookAndFeel();
1328   }
1329
1330   public static boolean isControlKeyDown(@NotNull MouseEvent mouseEvent) {
1331     return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
1332   }
1333
1334   public static String @NotNull [] getValidFontNames(final boolean familyName) {
1335     Set<String> result = new TreeSet<>();
1336
1337     // adds fonts that can display symbols at [A, Z] + [a, z] + [0, 9]
1338     for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
1339       try {
1340         if (FontUtil.isValidFont(font)) {
1341           result.add(familyName ? font.getFamily() : font.getName());
1342         }
1343       }
1344       catch (Exception ignore) {
1345         // JRE has problems working with the font. Just skip.
1346       }
1347     }
1348
1349     // add label font (if isn't listed among above)
1350     Font labelFont = StartupUiUtil.getLabelFont();
1351     if (labelFont != null && FontUtil.isValidFont(labelFont)) {
1352       result.add(familyName ? labelFont.getFamily() : labelFont.getName());
1353     }
1354
1355     return ArrayUtilRt.toStringArray(result);
1356   }
1357
1358   public static String @NotNull [] getStandardFontSizes() {
1359     return STANDARD_FONT_SIZES;
1360   }
1361
1362   public static void setupEnclosingDialogBounds(final @NotNull JComponent component) {
1363     component.revalidate();
1364     component.repaint();
1365     final Window window = SwingUtilities.windowForComponent(component);
1366     if (window != null &&
1367         (window.getSize().height < window.getMinimumSize().height || window.getSize().width < window.getMinimumSize().width)) {
1368       window.pack();
1369     }
1370   }
1371
1372   public static @NotNull String displayPropertiesToCSS(Font font, Color fg) {
1373     @NonNls StringBuilder rule = new StringBuilder("body {");
1374     if (font != null) {
1375       rule.append(" font-family: ");
1376       rule.append(font.getFamily());
1377       rule.append(" ; ");
1378       rule.append(" font-size: ");
1379       rule.append(font.getSize());
1380       rule.append("pt ;");
1381       if (font.isBold()) {
1382         rule.append(" font-weight: 700 ; ");
1383       }
1384       if (font.isItalic()) {
1385         rule.append(" font-style: italic ; ");
1386       }
1387     }
1388     if (fg != null) {
1389       rule.append(" color: #");
1390       appendColor(fg, rule);
1391       rule.append(" ; ");
1392     }
1393     rule.append(" }");
1394     return rule.toString();
1395   }
1396
1397   public static void appendColor(final @NotNull Color color, @NotNull StringBuilder sb) {
1398     if (color.getRed() < 16) sb.append('0');
1399     sb.append(Integer.toHexString(color.getRed()));
1400     if (color.getGreen() < 16) sb.append('0');
1401     sb.append(Integer.toHexString(color.getGreen()));
1402     if (color.getBlue() < 16) sb.append('0');
1403     sb.append(Integer.toHexString(color.getBlue()));
1404   }
1405
1406   public static void drawDottedRectangle(@NotNull Graphics g, @NotNull Rectangle r) {
1407     drawDottedRectangle(g, r.x, r.y, r.x + r.width, r.y + r.height);
1408   }
1409
1410   /**
1411    * @param g  graphics.
1412    * @param x  top left X coordinate.
1413    * @param y  top left Y coordinate.
1414    * @param x1 right bottom X coordinate.
1415    * @param y1 right bottom Y coordinate.
1416    */
1417   public static void drawDottedRectangle(@NotNull Graphics g, int x, int y, int x1, int y1) {
1418     int i1;
1419     for (i1 = x; i1 <= x1; i1 += 2) {
1420       LinePainter2D.paint((Graphics2D)g, i1, y, i1, y);
1421     }
1422
1423     for (i1 = y + (i1 != x1 + 1 ? 2 : 1); i1 <= y1; i1 += 2) {
1424       LinePainter2D.paint((Graphics2D)g, x1, i1, x1, i1);
1425     }
1426
1427     for (i1 = x1 - (i1 != y1 + 1 ? 2 : 1); i1 >= x; i1 -= 2) {
1428       LinePainter2D.paint((Graphics2D)g, i1, y1, i1, y1);
1429     }
1430
1431     for (i1 = y1 - (i1 != x - 1 ? 2 : 1); i1 >= y; i1 -= 2) {
1432       LinePainter2D.paint((Graphics2D)g, x, i1, x, i1);
1433     }
1434   }
1435
1436   /**
1437    * Should be invoked only in EDT.
1438    *
1439    * @param g       Graphics surface
1440    * @param startX  Line start X coordinate
1441    * @param endX    Line end X coordinate
1442    * @param lineY   Line Y coordinate
1443    * @param bgColor Background color (optional)
1444    * @param fgColor Foreground color (optional)
1445    * @param opaque  If opaque the image will be dr
1446    */
1447   public static void drawBoldDottedLine(@NotNull Graphics2D g,
1448                                         final int startX,
1449                                         final int endX,
1450                                         final int lineY,
1451                                         final Color bgColor,
1452                                         final Color fgColor,
1453                                         final boolean opaque) {
1454     if (SystemInfo.isMac && !isRetina() || SystemInfo.isLinux) {
1455       drawAppleDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
1456     }
1457     else {
1458       drawBoringDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
1459     }
1460   }
1461
1462   @SuppressWarnings("UnregisteredNamedColor")
1463   public static void drawSearchMatch(@NotNull Graphics2D g,
1464                                      final float startX,
1465                                      final float endX,
1466                                      final int height) {
1467     Color c1 = JBColor.namedColor("SearchMatch.startBackground", JBColor.namedColor("SearchMatch.startColor", 0xffeaa2));
1468     Color c2 = JBColor.namedColor("SearchMatch.endBackground", JBColor.namedColor("SearchMatch.endColor", 0xffd042));
1469     drawSearchMatch(g, startX, endX, height, c1, c2);
1470   }
1471
1472   public static void drawSearchMatch(@NotNull Graphics2D g, float startXf, float endXf, int height, Color c1, Color c2) {
1473     GraphicsConfig config = new GraphicsConfig(g);
1474     float alpha = JBUI.getInt("SearchMatch.transparency", 70) / 100f;
1475     alpha = alpha < 0 || alpha > 1 ? 0.7f : alpha;
1476     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
1477     g.setPaint(getGradientPaint(startXf, 2, c1, startXf, height - 5, c2));
1478
1479     if (JreHiDpiUtil.isJreHiDPI(g)) {
1480       GraphicsConfig c = GraphicsUtil.setupRoundedBorderAntialiasing(g);
1481       g.fill(new RoundRectangle2D.Float(startXf, 2, endXf - startXf, height - 4, 5, 5));
1482       c.restore();
1483       config.restore();
1484       return;
1485     }
1486
1487     int startX = (int)startXf;
1488     int endX = (int)endXf;
1489
1490     g.fillRect(startX, 3, endX - startX, height - 5);
1491
1492     final boolean drawRound = endXf - startXf > 4;
1493     if (drawRound) {
1494       LinePainter2D.paint(g, startX - 1, 4, startX - 1, height - 4);
1495       LinePainter2D.paint(g, endX, 4, endX, height - 4);
1496
1497       g.setColor(new Color(100, 100, 100, 50));
1498       LinePainter2D.paint(g, startX - 1, 4, startX - 1, height - 4);
1499       LinePainter2D.paint(g, endX, 4, endX, height - 4);
1500
1501       LinePainter2D.paint(g, startX, 3, endX - 1, 3);
1502       LinePainter2D.paint(g, startX, height - 3, endX - 1, height - 3);
1503     }
1504
1505     config.restore();
1506   }
1507
1508   private static void drawBoringDottedLine(final @NotNull Graphics2D g,
1509                                            final int startX,
1510                                            final int endX,
1511                                            final int lineY,
1512                                            final Color bgColor,
1513                                            final Color fgColor,
1514                                            final boolean opaque) {
1515     final Color oldColor = g.getColor();
1516
1517     // Fill 2 lines with background color
1518     if (opaque && bgColor != null) {
1519       g.setColor(bgColor);
1520
1521       LinePainter2D.paint(g, startX, lineY, endX, lineY);
1522       LinePainter2D.paint(g, startX, lineY + 1, endX, lineY + 1);
1523     }
1524
1525     // Draw dotted line:
1526     //
1527     // CCC CCC CCC ...
1528     // CCC CCC CCC ...
1529     //
1530     // (where "C" - colored pixel, " " - white pixel)
1531
1532     final int step = 4;
1533     final int startPosCorrection = startX % step < 3 ? 0 : 1;
1534
1535     g.setColor(fgColor != null ? fgColor : oldColor);
1536     // Now draw bold line segments
1537     for (int dotXi = (startX / step + startPosCorrection) * step; dotXi < endX; dotXi += step) {
1538       LinePainter2D.paint(g, dotXi, lineY, dotXi + 1, lineY);
1539       LinePainter2D.paint(g, dotXi, lineY + 1, dotXi + 1, lineY + 1);
1540     }
1541
1542     // restore color
1543     g.setColor(oldColor);
1544   }
1545
1546   public static void drawGradientHToolbarBackground(@NotNull Graphics g, final int width, final int height) {
1547     final Graphics2D g2d = (Graphics2D)g;
1548     g2d.setPaint(getGradientPaint(0, 0, Gray._215, 0, height, Gray._200));
1549     g2d.fillRect(0, 0, width, height);
1550   }
1551
1552   public static void drawHeader(@NotNull Graphics g, int x, int width, int height, boolean active, boolean drawTopLine) {
1553     drawHeader(g, x, width, height, active, false, drawTopLine, true);
1554   }
1555
1556   public static void drawHeader(@NotNull Graphics g,
1557                                 int x,
1558                                 int width,
1559                                 int height,
1560                                 boolean active,
1561                                 boolean toolWindow,
1562                                 boolean drawTopLine,
1563                                 boolean drawBottomLine) {
1564     GraphicsConfig config = GraphicsUtil.disableAAPainting(g);
1565     try {
1566       g.setColor(JBUI.CurrentTheme.ToolWindow.headerBackground(active));
1567       g.fillRect(x, 0, width, height);
1568
1569       g.setColor(JBUI.CurrentTheme.ToolWindow.headerBorderBackground());
1570       if (drawTopLine) LinePainter2D.paint((Graphics2D)g, x, 0, width, 0);
1571       if (drawBottomLine) LinePainter2D.paint((Graphics2D)g, x, height - 1, width, height - 1);
1572
1573     }
1574     finally {
1575       config.restore();
1576     }
1577   }
1578
1579   public static void drawDoubleSpaceDottedLine(final @NotNull Graphics2D g,
1580                                                final int start,
1581                                                final int end,
1582                                                final int xOrY,
1583                                                final Color fgColor,
1584                                                boolean horizontal) {
1585
1586     g.setColor(fgColor);
1587     for (int dot = start; dot < end; dot += 3) {
1588       if (horizontal) {
1589         LinePainter2D.paint(g, dot, xOrY, dot, xOrY);
1590       }
1591       else {
1592         LinePainter2D.paint(g, xOrY, dot, xOrY, dot);
1593       }
1594     }
1595   }
1596
1597   private static void drawAppleDottedLine(final @NotNull Graphics2D g,
1598                                           final int startX,
1599                                           final int endX,
1600                                           final int lineY,
1601                                           final Color bgColor,
1602                                           final Color fgColor,
1603                                           final boolean opaque) {
1604     final Color oldColor = g.getColor();
1605
1606     // Fill 3 lines with background color
1607     if (opaque && bgColor != null) {
1608       g.setColor(bgColor);
1609
1610       LinePainter2D.paint(g, startX, lineY, endX, lineY);
1611       LinePainter2D.paint(g, startX, lineY + 1, endX, lineY + 1);
1612       LinePainter2D.paint(g, startX, lineY + 2, endX, lineY + 2);
1613     }
1614
1615     AppleBoldDottedPainter painter = AppleBoldDottedPainter.forColor(ObjectUtils.notNull(fgColor, oldColor));
1616     painter.paint(g, startX, endX, lineY);
1617   }
1618
1619   @Deprecated
1620   public static void applyRenderingHints(@NotNull Graphics g) {
1621     GraphicsUtil.applyRenderingHints((Graphics2D)g);
1622   }
1623
1624   /**
1625    * @deprecated Use {@link ImageUtil#createImage(int, int, int)}
1626    */
1627   @Deprecated
1628   public static @NotNull BufferedImage createImage(int width, int height, int type) {
1629     return ImageUtil.createImage(width, height, type);
1630   }
1631
1632   /**
1633    * @deprecated Use {@link ImageUtil#createImage(GraphicsConfiguration, int, int, int)}
1634    */
1635   @Deprecated
1636   public static @NotNull BufferedImage createImage(@Nullable GraphicsConfiguration gc, int width, int height, int type) {
1637     return ImageUtil.createImage(gc, width, height, type);
1638   }
1639
1640   /**
1641    * Creates a HiDPI-aware BufferedImage in the graphics config scale.
1642    *
1643    * @param gc the graphics config
1644    * @param width the width in user coordinate space
1645    * @param height the height in user coordinate space
1646    * @param type the type of the image
1647    * @param rm the rounding mode to apply to width/height (for a HiDPI-aware image, the rounding is applied in the device space)
1648    *
1649    * @return a HiDPI-aware BufferedImage in the graphics scale
1650    * @throws IllegalArgumentException if {@code width} or {@code height} is not greater than 0
1651    */
1652   public static @NotNull BufferedImage createImage(GraphicsConfiguration gc, double width, double height, int type, @NotNull RoundingMode rm) {
1653     if (JreHiDpiUtil.isJreHiDPI(gc)) {
1654       return RetinaImage.create(gc, width, height, type, rm);
1655     }
1656     //noinspection UndesirableClassUsage
1657     return new BufferedImage(rm.round(width), rm.round(height), type);
1658   }
1659
1660   /**
1661    * @see #createImage(GraphicsConfiguration, double, double, int, RoundingMode)
1662    * @throws IllegalArgumentException if {@code width} or {@code height} is not greater than 0
1663    */
1664   public static @NotNull BufferedImage createImage(ScaleContext ctx, double width, double height, int type, @NotNull RoundingMode rm) {
1665     if (StartupUiUtil.isJreHiDPI(ctx)) {
1666       return RetinaImage.create(ctx, width, height, type, rm);
1667     }
1668     //noinspection UndesirableClassUsage
1669     return new BufferedImage(rm.round(width), rm.round(height), type);
1670   }
1671
1672   /**
1673    * @deprecated Use {@link ImageUtil#createImage(Graphics, int, int, int)}
1674    */
1675   @Deprecated
1676   public static @NotNull BufferedImage createImage(Graphics g, int width, int height, int type) {
1677     return ImageUtil.createImage(g, width, height, type);
1678   }
1679
1680   /**
1681    * @deprecated Use {@link ImageUtil#createImage(Graphics, double, double, int, RoundingMode)}
1682    */
1683   @Deprecated
1684   public static @NotNull BufferedImage createImage(Graphics g, double width, double height, int type, @NotNull RoundingMode rm) {
1685     return ImageUtil.createImage(g, width, height, type, rm);
1686   }
1687
1688   /**
1689    * Creates a HiDPI-aware BufferedImage in the component scale.
1690    *
1691    * @param comp the component associated with the target graphics device
1692    * @param width the width in user coordinate space
1693    * @param height the height in user coordinate space
1694    * @param type the type of the image
1695    *
1696    * @return a HiDPI-aware BufferedImage in the component scale
1697    * @throws IllegalArgumentException if {@code width} or {@code height} is not greater than 0
1698    */
1699   public static @NotNull BufferedImage createImage(Component comp, int width, int height, int type) {
1700     return comp != null ?
1701            ImageUtil.createImage(comp.getGraphicsConfiguration(), width, height, type) :
1702            ImageUtil.createImage(width, height, type);
1703   }
1704
1705   /**
1706    * @deprecated use {@link #createImage(Graphics, int, int, int)}
1707    */
1708   @Deprecated
1709   public static @NotNull BufferedImage createImageForGraphics(Graphics2D g, int width, int height, int type) {
1710     return ImageUtil.createImage(g, width, height, type);
1711   }
1712
1713   /**
1714    * Configures composite to use for drawing text with the given graphics container.
1715    * <p/>
1716    * The whole idea is that <a href="http://en.wikipedia.org/wiki/X_Rendering_Extension">XRender-based</a> pipeline doesn't support
1717    * {@link AlphaComposite#SRC} and we should use {@link AlphaComposite#SRC_OVER} instead.
1718    *
1719    * @param g target graphics container
1720    */
1721   public static void setupComposite(@NotNull Graphics2D g) {
1722     g.setComposite(X_RENDER_ACTIVE.getValue() ? AlphaComposite.SrcOver : AlphaComposite.Src);
1723   }
1724
1725   private static final Method dispatchEventMethod =
1726     Objects.requireNonNull(ReflectionUtil.getDeclaredMethod(EventQueue.class, "dispatchEvent", AWTEvent.class));
1727   /**
1728    * Dispatch all pending invocation events (if any) in the {@link com.intellij.ide.IdeEventQueue}, ignores and removes all other events from the queue.
1729    * In tests, consider using {@link com.intellij.testFramework.PlatformTestUtil#dispatchAllInvocationEventsInIdeEventQueue()}
1730    * @see #pump()
1731    */
1732   @TestOnly
1733   public static void dispatchAllInvocationEvents() {
1734     assert EdtInvocationManager.getInstance().isEventDispatchThread() : Thread.currentThread() + "; EDT: "+getEventQueueThread();
1735     EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1736     for (int i = 1; ; i++) {
1737       AWTEvent event = eventQueue.peekEvent();
1738       if (event == null) break;
1739       try {
1740         event = eventQueue.getNextEvent();
1741         if (event instanceof InvocationEvent) {
1742           dispatchEventMethod.invoke(eventQueue, event);
1743         }
1744       }
1745       catch (InvocationTargetException e) {
1746         ExceptionUtil.rethrowAllAsUnchecked(e.getCause());
1747       }
1748       catch (Exception e) {
1749         ExceptionUtil.rethrow(e);
1750       }
1751
1752       if (i % 10000 == 0) {
1753         //noinspection UseOfSystemOutOrSystemErr
1754         System.out.println("Suspiciously many (" + i + ") AWT events, last dispatched " + event);
1755       }
1756     }
1757   }
1758
1759   private static @NotNull Thread getEventQueueThread() {
1760     EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1761     try {
1762       Method method = ReflectionUtil.getDeclaredMethod(EventQueue.class, "getDispatchThread");
1763       //noinspection ConstantConditions
1764       return (Thread)method.invoke(eventQueue);
1765     }
1766     catch (Exception e) {
1767       throw new RuntimeException(e);
1768     }
1769   }
1770
1771   public static void addAwtListener(final @NotNull AWTEventListener listener, long mask, @NotNull Disposable parent) {
1772     Toolkit.getDefaultToolkit().addAWTEventListener(listener, mask);
1773     Disposer.register(parent, () -> Toolkit.getDefaultToolkit().removeAWTEventListener(listener));
1774   }
1775
1776   public static void addParentChangeListener(@NotNull Component component, @NotNull PropertyChangeListener listener) {
1777     component.addPropertyChangeListener("ancestor", listener);
1778   }
1779
1780   public static void removeParentChangeListener(@NotNull Component component, @NotNull PropertyChangeListener listener) {
1781     component.removePropertyChangeListener("ancestor", listener);
1782   }
1783
1784   public static void drawVDottedLine(@NotNull Graphics2D g, int lineX, int startY, int endY, final @Nullable Color bgColor, final Color fgColor) {
1785     if (bgColor != null) {
1786       g.setColor(bgColor);
1787       LinePainter2D.paint(g, lineX, startY, lineX, endY);
1788     }
1789
1790     g.setColor(fgColor);
1791     for (int i = startY / 2 * 2; i < endY; i += 2) {
1792       g.drawRect(lineX, i, 0, 0);
1793     }
1794   }
1795
1796   public static void drawHDottedLine(@NotNull Graphics2D g, int startX, int endX, int lineY, final @Nullable Color bgColor, final Color fgColor) {
1797     if (bgColor != null) {
1798       g.setColor(bgColor);
1799       LinePainter2D.paint(g, startX, lineY, endX, lineY);
1800     }
1801
1802     g.setColor(fgColor);
1803
1804     for (int i = startX / 2 * 2; i < endX; i += 2) {
1805       g.drawRect(i, lineY, 0, 0);
1806     }
1807   }
1808
1809   public static void drawDottedLine(@NotNull Graphics2D g, int x1, int y1, int x2, int y2, final @Nullable Color bgColor, final Color fgColor) {
1810     if (x1 == x2) {
1811       drawVDottedLine(g, x1, y1, y2, bgColor, fgColor);
1812     }
1813     else if (y1 == y2) {
1814       drawHDottedLine(g, x1, x2, y1, bgColor, fgColor);
1815     }
1816     else {
1817       throw new IllegalArgumentException("Only vertical or horizontal lines are supported");
1818     }
1819   }
1820
1821   public static void drawStringWithHighlighting(@NotNull Graphics g, @NotNull String s, int x, int y, Color foreground, Color highlighting) {
1822     g.setColor(highlighting);
1823     boolean isRetina = JreHiDpiUtil.isJreHiDPI((Graphics2D)g);
1824     float scale = 1 / JBUIScale.sysScale((Graphics2D)g);
1825     for (float i = x - 1; i <= x + 1; i += isRetina ? scale : 1) {
1826       for (float j = y - 1; j <= y + 1; j += isRetina ? scale : 1) {
1827         ((Graphics2D)g).drawString(s, i, j);
1828       }
1829     }
1830     g.setColor(foreground);
1831     g.drawString(s, x, y);
1832   }
1833
1834   /**
1835    * Draws a centered string in the passed rectangle.
1836    * @param g the {@link Graphics} instance to draw to
1837    * @param rect the {@link Rectangle} to use as bounding box
1838    * @param str the string to draw
1839    * @param horzCentered if true, the string will be centered horizontally
1840    * @param vertCentered if true, the string will be centered vertically
1841    */
1842   public static void drawCenteredString(@NotNull Graphics2D g, @NotNull Rectangle rect, @NotNull String str, boolean horzCentered, boolean vertCentered) {
1843     FontMetrics fm = g.getFontMetrics(g.getFont());
1844     int textWidth = fm.stringWidth(str) - 1;
1845     int x = horzCentered ? Math.max(rect.x, rect.x + (rect.width - textWidth) / 2) : rect.x;
1846     int y = vertCentered ? Math.max(rect.y, rect.y + rect.height / 2 + fm.getAscent() * 2 / 5) : rect.y;
1847     Shape oldClip = g.getClip();
1848     g.clip(rect);
1849     g.drawString(str, x, y);
1850     g.setClip(oldClip);
1851   }
1852
1853   /**
1854    * Draws a centered string in the passed rectangle.
1855    * @param g the {@link Graphics} instance to draw to
1856    * @param rect the {@link Rectangle} to use as bounding box
1857    * @param str the string to draw
1858    */
1859   public static void drawCenteredString(@NotNull Graphics2D g, @NotNull Rectangle rect, @NotNull String str) {
1860     drawCenteredString(g, rect, str, true, true);
1861   }
1862
1863   /**
1864    * @param component to check whether it has focus within its component hierarchy
1865    * @return {@code true} if component or one of its children has focus
1866    * @see Component#isFocusOwner()
1867    */
1868   public static boolean isFocusAncestor(@NotNull Component component) {
1869     Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1870     if (owner == null) return false;
1871     if (owner == component) return true;
1872     return SwingUtilities.isDescendingFrom(owner, component);
1873   }
1874
1875   public static boolean isCloseClick(@NotNull MouseEvent e) {
1876     return isCloseClick(e, MouseEvent.MOUSE_PRESSED);
1877   }
1878
1879   public static boolean isCloseClick(@NotNull MouseEvent e, int effectiveType) {
1880     if (e.isPopupTrigger() || e.getID() != effectiveType) return false;
1881     return e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown();
1882   }
1883
1884   public static boolean isActionClick(@NotNull MouseEvent e) {
1885     return isActionClick(e, MouseEvent.MOUSE_PRESSED);
1886   }
1887
1888   public static boolean isActionClick(@NotNull MouseEvent e, int effectiveType) {
1889     return isActionClick(e, effectiveType, false);
1890   }
1891
1892   public static boolean isActionClick(@NotNull MouseEvent e, int effectiveType, boolean allowShift) {
1893     if (!allowShift && isCloseClick(e) || e.isPopupTrigger() || e.getID() != effectiveType) return false;
1894     return e.getButton() == MouseEvent.BUTTON1;
1895   }
1896
1897   public static @NotNull Color getBgFillColor(@NotNull Component c) {
1898     final Component parent = findNearestOpaque(c);
1899     return parent == null ? c.getBackground() : parent.getBackground();
1900   }
1901
1902   public static @Nullable Component findNearestOpaque(Component c) {
1903     return ComponentUtil.findParentByCondition(c, Component::isOpaque);
1904   }
1905
1906   /**
1907    * @deprecated use {@link ComponentUtil#findParentByCondition(Component, java.util.function.Predicate)}
1908    */
1909   @Deprecated
1910   public static Component findParentByCondition(@Nullable Component c, @NotNull Condition<? super Component> condition) {
1911     return ComponentUtil.findParentByCondition(c, it -> condition.value(it));
1912   }
1913
1914   //x and y should be from {0, 0} to {parent.getWidth(), parent.getHeight()}
1915   public static @Nullable Component getDeepestComponentAt(@NotNull Component parent, int x, int y) {
1916     Component component = SwingUtilities.getDeepestComponentAt(parent, x, y);
1917     if (component != null && component.getParent() instanceof JRootPane) { // GlassPane case
1918       JRootPane rootPane = (JRootPane)component.getParent();
1919       component = getDeepestComponentAtForComponent(parent, x, y, rootPane.getLayeredPane());
1920       if (component == null) {
1921         component = getDeepestComponentAtForComponent(parent, x, y, rootPane.getContentPane());
1922       }
1923     }
1924     if (component != null && component.getParent() instanceof JLayeredPane) { // Handle LoadingDecorator
1925       Component[] components = ((JLayeredPane)component.getParent()).getComponentsInLayer(JLayeredPane.DEFAULT_LAYER);
1926       if (components.length == 1 && ArrayUtilRt.indexOf(components, component, 0, components.length) == -1) {
1927         component = getDeepestComponentAtForComponent(parent, x, y, components[0]);
1928       }
1929     }
1930     return component;
1931   }
1932
1933   private static Component getDeepestComponentAtForComponent(@NotNull Component parent, int x, int y, @NotNull Component component) {
1934     Point point = SwingUtilities.convertPoint(parent, new Point(x, y), component);
1935     return SwingUtilities.getDeepestComponentAt(component, point.x, point.y);
1936   }
1937
1938   public static void layoutRecursively(@NotNull Component component) {
1939     if (!(component instanceof JComponent)) {
1940       return;
1941     }
1942     forEachComponentInHierarchy(component, Component::doLayout);
1943   }
1944
1945   @Language("HTML")
1946   public static @NotNull String getCssFontDeclaration(@NotNull Font font) {
1947     return getCssFontDeclaration(font, getLabelForeground(), JBUI.CurrentTheme.Link.linkColor(), null);
1948   }
1949
1950   @Language("HTML")
1951   public static @NotNull String getCssFontDeclaration(@NotNull Font font, @Nullable Color fgColor, @Nullable Color linkColor, @Nullable String liImg) {
1952     @Language("HTML")
1953     String familyAndSize = "font-family:'" + font.getFamily() + "'; font-size:" + font.getSize() + "pt;";
1954     return "<style>\n"
1955     +"body, div, td, p {" + familyAndSize
1956     + (fgColor != null ? " color:#" + ColorUtil.toHex(fgColor)+';' : "")
1957     +"}\n"
1958     +"a {" + familyAndSize
1959     + (linkColor != null ? " color:#"+ColorUtil.toHex(linkColor)+';' : "")
1960     +"}\n"
1961     +"code {font-size:"+font.getSize()+"pt;}\n"
1962     +"ul {list-style:disc; margin-left:15px;}\n"
1963     +"</style>";
1964   }
1965
1966   public static @NotNull Color getFocusedFillColor() {
1967     return toAlpha(getListSelectionBackground(true), 100);
1968   }
1969
1970   public static @NotNull Color getFocusedBoundsColor() {
1971     return getBoundsColor();
1972   }
1973
1974   public static @NotNull Color getBoundsColor() {
1975     return JBColor.border();
1976   }
1977
1978   public static @NotNull Color getBoundsColor(boolean focused) {
1979     return focused ? getFocusedBoundsColor() : getBoundsColor();
1980   }
1981
1982   public static @NotNull Color toAlpha(final Color color, final int alpha) {
1983     Color actual = color != null ? color : Color.black;
1984     return new Color(actual.getRed(), actual.getGreen(), actual.getBlue(), alpha);
1985   }
1986
1987   /**
1988    * @param component to check whether it can be focused or not
1989    * @return {@code true} if component is not {@code null} and can be focused
1990    * @see Component#isRequestFocusAccepted(boolean, boolean, sun.awt.CausedFocusEvent.Cause)
1991    */
1992   public static boolean isFocusable(@Nullable Component component) {
1993     return component != null && component.isFocusable() && component.isEnabled() && component.isShowing();
1994   }
1995
1996   /**
1997    * @deprecated use {@link com.intellij.openapi.wm.IdeFocusManager}
1998    */
1999   @Deprecated
2000   public static void requestFocus(final @NotNull JComponent c) {
2001     if (c.isShowing()) {
2002       c.requestFocus();
2003     }
2004     else {
2005       SwingUtilities.invokeLater(c::requestFocus);
2006     }
2007   }
2008
2009   //Whitelist for component types that provide obvious 'focused' view
2010   public static boolean canDisplayFocusedState(@NotNull Component component) {
2011     return component instanceof JTextComponent || component instanceof AbstractButton || component instanceof JComboBox;
2012   }
2013
2014   //todo maybe should do for all kind of listeners via the AWTEventMulticaster class
2015
2016   public static void dispose(final Component c) {
2017     if (c == null) return;
2018
2019     final MouseListener[] mouseListeners = c.getMouseListeners();
2020     for (MouseListener each : mouseListeners) {
2021       c.removeMouseListener(each);
2022     }
2023
2024     final MouseMotionListener[] motionListeners = c.getMouseMotionListeners();
2025     for (MouseMotionListener each : motionListeners) {
2026       c.removeMouseMotionListener(each);
2027     }
2028
2029     final MouseWheelListener[] mouseWheelListeners = c.getMouseWheelListeners();
2030     for (MouseWheelListener each : mouseWheelListeners) {
2031       c.removeMouseWheelListener(each);
2032     }
2033
2034     if (c instanceof AbstractButton) {
2035       final ActionListener[] listeners = ((AbstractButton)c).getActionListeners();
2036       for (ActionListener listener : listeners) {
2037         ((AbstractButton)c).removeActionListener(listener);
2038       }
2039     }
2040   }
2041
2042   public static void disposeProgress(final @NotNull JProgressBar progress) {
2043     if (!isUnderNativeMacLookAndFeel()) return;
2044
2045     SwingUtilities.invokeLater(() -> progress.setUI(null));
2046   }
2047
2048   public static @Nullable Component findUltimateParent(@Nullable Component c) {
2049     return c == null ? null : ComponentUtil.findUltimateParent(c);
2050   }
2051
2052   public static @NotNull Color getHeaderActiveColor() {
2053     return ACTIVE_HEADER_COLOR;
2054   }
2055
2056   public static @NotNull Color getFocusedBorderColor() {
2057     return JBUI.CurrentTheme.Focus.focusColor();
2058   }
2059
2060   public static @NotNull Color getHeaderInactiveColor() {
2061     return INACTIVE_HEADER_COLOR;
2062   }
2063
2064   public static @NotNull Font getTitledBorderFont() {
2065     return StartupUiUtil.getLabelFont();
2066   }
2067
2068   /**
2069    * @deprecated use getBorderColor instead
2070    */
2071   @Deprecated
2072   public static @NotNull Color getBorderInactiveColor() {
2073     return JBColor.border();
2074   }
2075
2076   /**
2077    * @deprecated use getBorderColor instead
2078    */
2079   @Deprecated
2080   public static @NotNull Color getBorderActiveColor() {
2081     return JBColor.border();
2082   }
2083
2084   /**
2085    * @deprecated use getBorderColor instead
2086    */
2087   @Deprecated
2088   public static @NotNull Color getBorderSeparatorColor() {
2089     return JBColor.border();
2090   }
2091
2092   public static @Nullable StyleSheet loadStyleSheet(@Nullable URL url) {
2093     if (url == null) return null;
2094     try {
2095       StyleSheet styleSheet = new StyleSheet();
2096       styleSheet.loadRules(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8), url);
2097       return styleSheet;
2098     }
2099     catch (IOException e) {
2100       getLogger().warn(url + " loading failed", e);
2101       return null;
2102     }
2103   }
2104
2105   public static @NotNull HTMLEditorKit getHTMLEditorKit() {
2106     return getHTMLEditorKit(true);
2107   }
2108
2109   public static @NotNull HTMLEditorKit getHTMLEditorKit(boolean noGapsBetweenParagraphs) {
2110     return new JBHtmlEditorKit(noGapsBetweenParagraphs);
2111   }
2112
2113   public static final class JBWordWrapHtmlEditorKit extends JBHtmlEditorKit {
2114     private final HTMLFactory myFactory = new HTMLFactory() {
2115       @Override
2116       public View create(Element e) {
2117         View view = super.create(e);
2118         if (view instanceof javax.swing.text.html.ParagraphView) {
2119           // wrap too long words, for example: ATEST_TABLE_SIGNLE_ROW_UPDATE_AUTOCOMMIT_A_FIK
2120           return new ParagraphView(e) {
2121             @Override
2122             protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
2123               if (r == null) {
2124                 r = new SizeRequirements();
2125               }
2126               r.minimum = (int)layoutPool.getMinimumSpan(axis);
2127               r.preferred = Math.max(r.minimum, (int)layoutPool.getPreferredSpan(axis));
2128               r.maximum = Integer.MAX_VALUE;
2129               r.alignment = 0.5f;
2130               return r;
2131             }
2132           };
2133         }
2134         return view;
2135       }
2136     };
2137
2138     @Override
2139     public ViewFactory getViewFactory() {
2140       return myFactory;
2141     }
2142   }
2143
2144   public static @NotNull Font getFontWithFallbackIfNeeded(@NotNull Font font, @NotNull String text) {
2145     if (font.canDisplayUpTo(text) != -1) {
2146       return getFontWithFallback(font);
2147     }
2148     else {
2149       return font;
2150     }
2151   }
2152
2153   public static @NotNull FontUIResource getFontWithFallback(@NotNull Font font) {
2154     return getFontWithFallback(font.getFamily(), font.getStyle(), font.getSize());
2155   }
2156
2157   public static @NotNull FontUIResource getFontWithFallback(@Nullable String familyName, @JdkConstants.FontStyle int style, int size) {
2158     // On macOS font fallback is implemented in JDK by default
2159     // (except for explicitly registered fonts, e.g. the fonts we bundle with IDE, for them we don't have a solution now)
2160     Font fontWithFallback = SystemInfo.isMac ? new Font(familyName, style, size) : new StyleContext().getFont(familyName, style, size);
2161     return fontWithFallback instanceof FontUIResource ? (FontUIResource)fontWithFallback : new FontUIResource(fontWithFallback);
2162   }
2163
2164   //Escape error-prone HTML data (if any) when we use it in renderers, see IDEA-170768
2165   public static <T> T htmlInjectionGuard(T toRender) {
2166     if (toRender instanceof String && StringUtil.toLowerCase((String)toRender).startsWith("<html>")) {
2167       //noinspection unchecked
2168       return (T) ("<html>" + StringUtil.escapeXmlEntities((String)toRender));
2169     }
2170     return toRender;
2171   }
2172
2173   /**
2174    * @deprecated This method is a hack. Please avoid it and create borderless {@code JScrollPane} manually using
2175    * {@link com.intellij.ui.ScrollPaneFactory#createScrollPane(Component, boolean)}.
2176    */
2177   @Deprecated
2178   public static void removeScrollBorder(final Component c) {
2179     JBIterable<JScrollPane> scrollPanes = uiTraverser(c)
2180       .expand(o -> o == c || o instanceof JPanel || o instanceof JLayeredPane)
2181       .filter(JScrollPane.class);
2182     for (JScrollPane scrollPane : scrollPanes) {
2183       Integer keepBorderSides = ComponentUtil.getClientProperty(scrollPane, KEEP_BORDER_SIDES);
2184       if (keepBorderSides != null) {
2185         if (scrollPane.getBorder() instanceof LineBorder) {
2186           Color color = ((LineBorder)scrollPane.getBorder()).getLineColor();
2187           scrollPane.setBorder(new SideBorder(color, keepBorderSides.intValue()));
2188         }
2189         else {
2190           scrollPane.setBorder(new SideBorder(getBoundsColor(), keepBorderSides.intValue()));
2191         }
2192       }
2193       else {
2194         scrollPane.setBorder(new SideBorder(getBoundsColor(), SideBorder.NONE));
2195       }
2196     }
2197   }
2198
2199   public static @NotNull @NlsSafe String toHtml(@NotNull @Nls String html) {
2200     return toHtml(html, 0);
2201   }
2202
2203   @NonNls
2204   public static @NotNull @NlsSafe String toHtml(@NotNull @Nls String html, final int hPadding) {
2205     @NlsSafe final String withClosedTag = CLOSE_TAG_PATTERN.matcher(html).replaceAll("<$1$2></$1>");
2206     Font font = StartupUiUtil.getLabelFont();
2207     @NonNls String family = font != null ? font.getFamily() : "Tahoma";
2208     int size = font != null ? font.getSize() : JBUIScale.scale(11);
2209     return "<html><style>body { font-family: "
2210            + family + "; font-size: "
2211            + size + ";} ul li {list-style-type:circle;}</style>"
2212            + addPadding(withClosedTag, hPadding) + "</html>";
2213   }
2214
2215   public static @NotNull String addPadding(@NotNull String html, int hPadding) {
2216     return String.format("<p style=\"margin: 0 %dpx 0 %dpx;\">%s</p>", hPadding, hPadding, html);
2217   }
2218
2219   public static @NotNull String convertSpace2Nbsp(@NotNull String html) {
2220     @NonNls StringBuilder result = new StringBuilder();
2221     int currentPos = 0;
2222     int braces = 0;
2223     while (currentPos < html.length()) {
2224       String each = html.substring(currentPos, currentPos + 1);
2225       if ("<".equals(each)) {
2226         braces++;
2227       }
2228       else if (">".equals(each)) {
2229         braces--;
2230       }
2231
2232       if (" ".equals(each) && braces == 0) {
2233         result.append("&nbsp;");
2234       }
2235       else {
2236         result.append(each);
2237       }
2238       currentPos++;
2239     }
2240
2241     return result.toString();
2242   }
2243
2244   /**
2245    * Please use Application.invokeLater() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2246    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings. For those, use GuiUtils, application.invoke* or TransactionGuard methods.<p/>
2247    *
2248    * On AWT thread, invoked runnable immediately, otherwise do {@link SwingUtilities#invokeLater(Runnable)} on it.
2249    */
2250   public static void invokeLaterIfNeeded(@NotNull Runnable runnable) {
2251     EdtInvocationManager edtInvocationManager = EdtInvocationManager.getInstance();
2252     if (edtInvocationManager.isEventDispatchThread()) {
2253       runnable.run();
2254     }
2255     else {
2256       edtInvocationManager.invokeLater(runnable);
2257     }
2258   }
2259
2260   /**
2261    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2262    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2263    *
2264    * Invoke and wait in the event dispatch thread
2265    * or in the current thread if the current thread
2266    * is event queue thread.
2267    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2268    *
2269    * @param runnable a runnable to invoke
2270    * @see #invokeAndWaitIfNeeded(ThrowableRunnable)
2271    */
2272   public static void invokeAndWaitIfNeeded(@NotNull Runnable runnable) {
2273     EdtInvocationManager.getInstance().invokeAndWaitIfNeeded(runnable);
2274   }
2275
2276   /**
2277    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2278    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2279    *
2280    * Invoke and wait in the event dispatch thread
2281    * or in the current thread if the current thread
2282    * is event queue thread.
2283    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2284    *
2285    * @param computable a runnable to invoke
2286    * @see #invokeAndWaitIfNeeded(ThrowableRunnable)
2287    */
2288   public static <T> T invokeAndWaitIfNeeded(final @NotNull Computable<T> computable) {
2289     final Ref<T> result = Ref.create();
2290     invokeAndWaitIfNeeded((Runnable)() -> result.set(computable.compute()));
2291     return result.get();
2292   }
2293
2294   /**
2295    * Please use Application.invokeAndWait() with a modality state (or GuiUtils, or TransactionGuard methods), unless you work with Swings internals
2296    * and 'runnable' deals with Swings components only and doesn't access any PSI, VirtualFiles, project/module model or other project settings.<p/>
2297    *
2298    * Invoke and wait in the event dispatch thread
2299    * or in the current thread if the current thread
2300    * is event queue thread.
2301    * DO NOT INVOKE THIS METHOD FROM UNDER READ ACTION.
2302    *
2303    * @param runnable a runnable to invoke
2304    */
2305   public static void invokeAndWaitIfNeeded(final @NotNull ThrowableRunnable<?> runnable) throws Throwable {
2306     if (EdtInvocationManager.getInstance().isEventDispatchThread()) {
2307       runnable.run();
2308     }
2309     else {
2310       final Ref<Throwable> ref = Ref.create();
2311       EdtInvocationManager.getInstance().invokeAndWait(() -> {
2312         try {
2313           runnable.run();
2314         }
2315         catch (Throwable throwable) {
2316           ref.set(throwable);
2317         }
2318       });
2319       if (!ref.isNull()) throw ref.get();
2320     }
2321   }
2322
2323   public static boolean isFocusProxy(@Nullable Component c) {
2324     return c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty(FOCUS_PROXY_KEY));
2325   }
2326
2327   public static void maybeInstall(@NotNull InputMap map, String action, KeyStroke stroke) {
2328     if (map.get(stroke) == null) {
2329       map.put(stroke, action);
2330     }
2331   }
2332
2333   /**
2334    * Avoid blinking while changing background.
2335    *
2336    * @param component  component.
2337    * @param background new background.
2338    */
2339   public static void changeBackGround(final @NotNull Component component, final Color background) {
2340     final Color oldBackGround = component.getBackground();
2341     if (background == null || !background.equals(oldBackGround)) {
2342       component.setBackground(background);
2343     }
2344   }
2345
2346   public static @Nullable ComboPopup getComboBoxPopup(@NotNull JComboBox<?> comboBox) {
2347     final ComboBoxUI ui = comboBox.getUI();
2348     if (ui instanceof BasicComboBoxUI) {
2349       return ReflectionUtil.getField(BasicComboBoxUI.class, ui, ComboPopup.class, "popup");
2350     }
2351
2352     return null;
2353   }
2354
2355   public static void fixFormattedField(@NotNull JFormattedTextField field) {
2356     if (SystemInfo.isMac) {
2357       final int commandKeyMask;
2358       try {
2359         commandKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
2360       }
2361       catch (HeadlessException e) {
2362         return;
2363       }
2364       final InputMap inputMap = field.getInputMap();
2365       final KeyStroke copyKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, commandKeyMask);
2366       inputMap.put(copyKeyStroke, "copy-to-clipboard");
2367       final KeyStroke pasteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, commandKeyMask);
2368       inputMap.put(pasteKeyStroke, "paste-from-clipboard");
2369       final KeyStroke cutKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, commandKeyMask);
2370       inputMap.put(cutKeyStroke, "cut-to-clipboard");
2371     }
2372   }
2373
2374   public static boolean isPrinting(Graphics g) {
2375     return g instanceof PrintGraphics || g instanceof PrinterGraphics;
2376   }
2377
2378   public static int getSelectedButton(@NotNull ButtonGroup group) {
2379     Enumeration<AbstractButton> enumeration = group.getElements();
2380     int i = 0;
2381     while (enumeration.hasMoreElements()) {
2382       AbstractButton button = enumeration.nextElement();
2383       if (group.isSelected(button.getModel())) {
2384         return i;
2385       }
2386       i++;
2387     }
2388     return -1;
2389   }
2390
2391   public static void setSelectedButton(@NotNull ButtonGroup group, int index) {
2392     Enumeration<AbstractButton> enumeration = group.getElements();
2393     int i = 0;
2394     while (enumeration.hasMoreElements()) {
2395       AbstractButton button = enumeration.nextElement();
2396       group.setSelected(button.getModel(), index == i);
2397       i++;
2398     }
2399   }
2400
2401   public static boolean isSelectionButtonDown(@NotNull MouseEvent e) {
2402     return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
2403   }
2404
2405   public static boolean isToggleListSelectionEvent(@NotNull MouseEvent e) {
2406     return SwingUtilities.isLeftMouseButton(e) && (SystemInfo.isMac ? e.isMetaDown() : e.isControlDown()) && !e.isPopupTrigger();
2407   }
2408
2409   @SuppressWarnings("deprecation")
2410   public static void setComboBoxEditorBounds(int x, int y, int width, int height, @NotNull JComponent editor) {
2411     editor.reshape(x, y, width, height);
2412   }
2413
2414   /**
2415    * @deprecated the method was used to fix Aqua Look-n-Feel problems. Now it does not make sense
2416    */
2417   @Deprecated
2418   @ApiStatus.ScheduledForRemoval(inVersion = "2020.3")
2419   public static int fixComboBoxHeight(final int height) {
2420     return height;
2421   }
2422
2423   public static final int LIST_FIXED_CELL_HEIGHT = 20;
2424
2425   /**
2426    * The main difference from javax.swing.SwingUtilities#isDescendingFrom(Component, Component) is that this method
2427    * uses getInvoker() instead of getParent() when it meets JPopupMenu
2428    * @param child child component
2429    * @param parent parent component
2430    * @return true if parent if a top parent of child, false otherwise
2431    *
2432    * @see SwingUtilities#isDescendingFrom(Component, Component)
2433    */
2434   public static boolean isDescendingFrom(@Nullable Component child, @NotNull Component parent) {
2435     while (child != null && child != parent) {
2436       child = child instanceof JPopupMenu ? ((JPopupMenu)child).getInvoker()
2437                                           : child.getParent();
2438     }
2439     return child == parent;
2440   }
2441
2442   /**
2443    * Searches above in the component hierarchy starting from the specified component.
2444    * Note that the initial component is also checked.
2445    *
2446    * @param type      expected class
2447    * @param component initial component
2448    * @return a component of the specified type, or {@code null} if the search is failed
2449    * @see SwingUtilities#getAncestorOfClass
2450    */
2451   @Contract(pure = true)
2452   public static @Nullable <T> T getParentOfType(@NotNull Class<? extends T> type, Component component) {
2453     return ComponentUtil.getParentOfType(type, component);
2454   }
2455
2456   public static @NotNull JBIterable<Component> uiParents(@Nullable Component c, boolean strict) {
2457     return strict ? JBIterable.generate(c, c1 -> c1.getParent()).skip(1) : JBIterable.generate(c, c1 -> c1.getParent());
2458   }
2459
2460   public static @NotNull JBIterable<Component> uiChildren(@Nullable Component component) {
2461     if (!(component instanceof Container)) return JBIterable.empty();
2462     Container container = (Container)component;
2463     return JBIterable.of(container.getComponents());
2464   }
2465
2466   public static @NotNull JBTreeTraverser<Component> uiTraverser(@Nullable Component component) {
2467     return UI_TRAVERSER.withRoot(component).expandAndFilter(o -> !(o instanceof CellRendererPane));
2468   }
2469
2470   public static final Key<Iterable<? extends Component>> NOT_IN_HIERARCHY_COMPONENTS = Key.create("NOT_IN_HIERARCHY_COMPONENTS");
2471
2472   private static final JBTreeTraverser<Component> UI_TRAVERSER = JBTreeTraverser.from((Function<Component, JBIterable<Component>>)c -> {
2473     JBIterable<Component> result;
2474     if (c instanceof JMenu) {
2475       result = JBIterable.of(((JMenu)c).getMenuComponents());
2476     }
2477     else {
2478       result = uiChildren(c);
2479     }
2480     if (c instanceof JComponent) {
2481       JComponent jc = (JComponent)c;
2482       Iterable<? extends Component> orphans = ComponentUtil.getClientProperty(jc, NOT_IN_HIERARCHY_COMPONENTS);
2483       if (orphans != null) {
2484         result = result.append(orphans);
2485       }
2486       JPopupMenu jpm = jc.getComponentPopupMenu();
2487       if (jpm != null && jpm.isVisible() && jpm.getInvoker() == jc) {
2488         result = result.append(Collections.singletonList(jpm));
2489       }
2490     }
2491     return result;
2492   });
2493
2494   public static void scrollListToVisibleIfNeeded(final @NotNull JList<?> list) {
2495     SwingUtilities.invokeLater(() -> {
2496       final int selectedIndex = list.getSelectedIndex();
2497       if (selectedIndex >= 0) {
2498         final Rectangle visibleRect = list.getVisibleRect();
2499         final Rectangle cellBounds = list.getCellBounds(selectedIndex, selectedIndex);
2500         if (!visibleRect.contains(cellBounds)) {
2501           list.scrollRectToVisible(cellBounds);
2502         }
2503       }
2504     });
2505   }
2506
2507   public static @Nullable <T extends JComponent> T findComponentOfType(JComponent parent, Class<T> cls) {
2508     if (parent == null || cls.isInstance(parent)) {
2509       return cls.cast(parent);
2510     }
2511     for (Component component : parent.getComponents()) {
2512       if (component instanceof JComponent) {
2513         T comp = findComponentOfType((JComponent)component, cls);
2514         if (comp != null) return comp;
2515       }
2516     }
2517     return null;
2518   }
2519
2520   public static @NotNull <T extends JComponent> List<T> findComponentsOfType(JComponent parent, @NotNull Class<? extends T> cls) {
2521     final ArrayList<T> result = new ArrayList<>();
2522     findComponentsOfType(parent, cls, result);
2523     return result;
2524   }
2525
2526   private static <T extends JComponent> void findComponentsOfType(JComponent parent, @NotNull Class<T> cls, @NotNull List<? super T> result) {
2527     if (parent == null) return;
2528     if (cls.isAssignableFrom(parent.getClass())) {
2529       @SuppressWarnings("unchecked") final T t = (T)parent;
2530       result.add(t);
2531     }
2532     for (Component c : parent.getComponents()) {
2533       if (c instanceof JComponent) {
2534         findComponentsOfType((JComponent)c, cls, result);
2535       }
2536     }
2537   }
2538
2539   public static class TextPainter {
2540     private final List<String> myLines = new ArrayList<>();
2541     private boolean myDrawShadow;
2542     private Color myShadowColor;
2543     private float myLineSpacing;
2544     private Font myFont;
2545     private Color myColor;
2546
2547     public TextPainter() {
2548       myDrawShadow = StartupUiUtil.isUnderDarcula();
2549       myShadowColor = StartupUiUtil.isUnderDarcula() ? Gray._0.withAlpha(100) : Gray._220;
2550       myLineSpacing = 1.0f;
2551     }
2552
2553     public @NotNull TextPainter withShadow(boolean drawShadow, Color shadowColor) {
2554       myDrawShadow = drawShadow;
2555       myShadowColor = shadowColor;
2556       return this;
2557     }
2558
2559     public @NotNull TextPainter withLineSpacing(float lineSpacing) {
2560       myLineSpacing = lineSpacing;
2561       return this;
2562     }
2563
2564     public @NotNull TextPainter withColor(Color color) {
2565       myColor = color;
2566       return this;
2567     }
2568
2569     public @NotNull TextPainter withFont(Font font) {
2570       myFont = font;
2571       return this;
2572     }
2573
2574     public @NotNull TextPainter appendLine(String text) {
2575       if (text == null || text.isEmpty()) return this;
2576       myLines.add(text);
2577       return this;
2578     }
2579
2580     /**
2581      * _position(block width, block height) => (x, y) of the block
2582      */
2583     public void draw(final @NotNull Graphics g, @NotNull PairFunction<? super Integer, ? super Integer, ? extends Couple<Integer>> _position) {
2584       Font oldFont = null;
2585       if (myFont != null) {
2586         oldFont = g.getFont();
2587         g.setFont(myFont);
2588       }
2589       Color oldColor = null;
2590       if (myColor != null) {
2591         oldColor = g.getColor();
2592         g.setColor(myColor);
2593       }
2594       try {
2595         final int[] maxWidth = {0};
2596         final int[] height = {0};
2597         ContainerUtil.process(myLines, text -> {
2598           FontMetrics fm = g.getFontMetrics();
2599           maxWidth[0] = Math.max(fm.stringWidth(text.replace("<shortcut>", "").replace("</shortcut>", "")), maxWidth[0]);
2600           height[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2601           return true;
2602         });
2603
2604         final Couple<Integer> position = _position.fun(maxWidth[0] + 20, height[0]);
2605         assert position != null;
2606
2607         final int[] yOffset = {position.getSecond()};
2608         ContainerUtil.process(myLines, text -> {
2609           String shortcut = "";
2610           if (text.contains("<shortcut>")) {
2611             shortcut = text.substring(text.indexOf("<shortcut>") + "<shortcut>".length(), text.indexOf("</shortcut>"));
2612             text = text.substring(0, text.indexOf("<shortcut>"));
2613           }
2614
2615           int x = position.getFirst() + 10;
2616
2617           FontMetrics fm = g.getFontMetrics();
2618
2619           if (myDrawShadow) {
2620             int xOff = StartupUiUtil.isUnderDarcula() ? 1 : 0;
2621             Color oldColor1 = g.getColor();
2622             g.setColor(myShadowColor);
2623
2624             int yOff = 1;
2625
2626             g.drawString(text, x + xOff, yOffset[0] + yOff);
2627             g.setColor(oldColor1);
2628           }
2629
2630           g.drawString(text, x, yOffset[0]);
2631           if (!StringUtil.isEmpty(shortcut)) {
2632             Color oldColor1 = g.getColor();
2633             g.setColor(JBColor.namedColor("Editor.shortcutForeground", new JBColor(new Color(82, 99, 155), new Color(88, 157, 246))));
2634             g.drawString(shortcut, x + fm.stringWidth(text + (StartupUiUtil.isUnderDarcula() ? " " : "")), yOffset[0]);
2635             g.setColor(oldColor1);
2636           }
2637
2638           yOffset[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2639
2640           return true;
2641         });
2642       }
2643       finally {
2644         if (oldFont != null) g.setFont(oldFont);
2645         if (oldColor != null) g.setColor(oldColor);
2646       }
2647     }
2648   }
2649
2650   public static @Nullable JRootPane getRootPane(Component c) {
2651     JRootPane root = ComponentUtil.getParentOfType((Class<? extends JRootPane>)JRootPane.class, c);
2652     if (root != null) return root;
2653     Component eachParent = c;
2654     while (eachParent != null) {
2655       if (eachParent instanceof JComponent) {
2656         @SuppressWarnings("unchecked") WeakReference<JRootPane> pane =
2657           (WeakReference<JRootPane>)((JComponent)eachParent).getClientProperty(ROOT_PANE);
2658         if (pane != null) return pane.get();
2659       }
2660       eachParent = eachParent.getParent();
2661     }
2662
2663     return null;
2664   }
2665
2666   public static void setFutureRootPane(@NotNull JComponent c, @NotNull JRootPane pane) {
2667     c.putClientProperty(ROOT_PANE, new WeakReference<>(pane));
2668   }
2669
2670   public static boolean isMeaninglessFocusOwner(@Nullable Component c) {
2671     if (c == null || !c.isShowing()) return true;
2672
2673     return c instanceof JFrame || c instanceof JDialog || c instanceof JWindow || c instanceof JRootPane || isFocusProxy(c);
2674   }
2675
2676   /**
2677    * @deprecated Use {@link TimerUtil#createNamedTimer(String, int, ActionListener)}
2678    */
2679   @Deprecated
2680   public static @NotNull Timer createNamedTimer(@NonNls @NotNull String name, int delay, @NotNull ActionListener listener) {
2681     return TimerUtil.createNamedTimer(name, delay, listener);
2682   }
2683
2684   /**
2685    * @deprecated Use {@link TimerUtil#createNamedTimer(String, int)}
2686    */
2687   @Deprecated
2688   public static @NotNull Timer createNamedTimer(@NonNls @NotNull String name, int delay) {
2689     return TimerUtil.createNamedTimer(name, delay);
2690   }
2691
2692   public static boolean isDialogRootPane(JRootPane rootPane) {
2693     if (rootPane != null) {
2694       final Object isDialog = rootPane.getClientProperty("DIALOG_ROOT_PANE");
2695       return isDialog instanceof Boolean && ((Boolean)isDialog).booleanValue();
2696     }
2697     return false;
2698   }
2699
2700   public static @Nullable JComponent mergeComponentsWithAnchor(PanelWithAnchor @NotNull ... panels) {
2701     return mergeComponentsWithAnchor(Arrays.asList(panels));
2702   }
2703
2704   public static @Nullable JComponent mergeComponentsWithAnchor(@NotNull Collection<? extends PanelWithAnchor> panels) {
2705     JComponent maxWidthAnchor = null;
2706     int maxWidth = 0;
2707     for (PanelWithAnchor panel : panels) {
2708       JComponent anchor = panel != null ? panel.getAnchor() : null;
2709       if (anchor != null) {
2710         int anchorWidth = anchor.getPreferredSize().width;
2711         if (maxWidth < anchorWidth) {
2712           maxWidth = anchorWidth;
2713           maxWidthAnchor = anchor;
2714         }
2715       }
2716     }
2717     for (PanelWithAnchor panel : panels) {
2718       if (panel != null) {
2719         panel.setAnchor(maxWidthAnchor);
2720       }
2721     }
2722     return maxWidthAnchor;
2723   }
2724
2725   public static void setNotOpaqueRecursively(@NotNull Component component) {
2726     setOpaqueRecursively(component, false);
2727   }
2728
2729   public static void setOpaqueRecursively(@NotNull Component component, boolean opaque) {
2730     if (!(component instanceof JComponent)) {
2731       return;
2732     }
2733     forEachComponentInHierarchy(component, c -> {
2734       if (c instanceof JComponent) {
2735         ((JComponent)c).setOpaque(opaque);
2736       }
2737     });
2738   }
2739
2740   public static void setBackgroundRecursively(@NotNull Component component, @NotNull Color bg) {
2741     forEachComponentInHierarchy(component, c -> c.setBackground(bg));
2742   }
2743
2744   public static void setForegroundRecursively(@NotNull Component component, @NotNull Color bg) {
2745     forEachComponentInHierarchy(component, c -> c.setForeground(bg));
2746   }
2747
2748   private static void forEachComponentInHierarchy(@NotNull Component component, @NotNull Consumer<? super Component> action) {
2749     action.consume(component);
2750     if (component instanceof Container) {
2751       for (Component c : ((Container)component).getComponents()) {
2752         forEachComponentInHierarchy(c, action);
2753       }
2754     }
2755   }
2756
2757   /**
2758    * Adds an empty border with the specified insets to the specified component.
2759    * If the component already has a border it will be preserved.
2760    *
2761    * @param component the component to which border added
2762    * @param top       the inset from the top
2763    * @param left      the inset from the left
2764    * @param bottom    the inset from the bottom
2765    * @param right     the inset from the right
2766    */
2767   public static void addInsets(@NotNull JComponent component, int top, int left, int bottom, int right) {
2768     addBorder(component, BorderFactory.createEmptyBorder(top, left, bottom, right));
2769   }
2770
2771   /**
2772    * Adds an empty border with the specified insets to the specified component.
2773    * If the component already has a border it will be preserved.
2774    *
2775    * @param component the component to which border added
2776    * @param insets    the top, left, bottom, and right insets
2777    */
2778   public static void addInsets(@NotNull JComponent component, @NotNull Insets insets) {
2779     addInsets(component, insets.top, insets.left, insets.bottom, insets.right);
2780   }
2781
2782   public static void adjustWindowToMinimumSize(final Window window) {
2783     if (window == null) return;
2784     final Dimension minSize = window.getMinimumSize();
2785     final Dimension size = window.getSize();
2786     final Dimension newSize = new Dimension(Math.max(size.width, minSize.width), Math.max(size.height, minSize.height));
2787
2788     if (!newSize.equals(size)) {
2789       //noinspection SSBasedInspection
2790       SwingUtilities.invokeLater(() -> {
2791         if (window.isShowing()) {
2792           window.setSize(newSize);
2793         }
2794       });
2795     }
2796   }
2797
2798   public static int getLcdContrastValue() {
2799     int lcdContrastValue  = Registry.intValue("lcd.contrast.value", 0);
2800     if (lcdContrastValue == 0) {
2801       return StartupUiUtil.doGetLcdContrastValueForSplash(StartupUiUtil.isUnderDarcula());
2802     }
2803     else {
2804       return StartupUiUtil.normalizeLcdContrastValue(lcdContrastValue);
2805     }
2806   }
2807
2808   /**
2809    * Adds the specified border to the specified component.
2810    * If the component already has a border it will be preserved.
2811    * If component or border is not specified nothing happens.
2812    *
2813    * @param component the component to which border added
2814    * @param border    the border to add to the component
2815    */
2816   public static void addBorder(JComponent component, Border border) {
2817     if (component != null && border != null) {
2818       Border old = component.getBorder();
2819       if (old != null) {
2820         border = BorderFactory.createCompoundBorder(border, old);
2821       }
2822       component.setBorder(border);
2823     }
2824   }
2825
2826   private static final Color DECORATED_ROW_BG_COLOR = new JBColor(new Color(242, 245, 249), new Color(65, 69, 71));
2827
2828   public static @NotNull Color getDecoratedRowColor() {
2829     return JBColor.namedColor("Table.stripeColor", DECORATED_ROW_BG_COLOR);
2830   }
2831
2832   public static @NotNull Paint getGradientPaint(float x1, float y1, @NotNull Color c1, float x2, float y2, @NotNull Color c2) {
2833     return Registry.is("ui.no.bangs.and.whistles", false) ? ColorUtil.mix(c1, c2, .5) : new GradientPaint(x1, y1, c1, x2, y2, c2);
2834   }
2835
2836   public static @Nullable Point getLocationOnScreen(@NotNull JComponent component) {
2837     int dx = 0;
2838     int dy = 0;
2839     for (Container c = component; c != null; c = c.getParent()) {
2840       if (c.isShowing()) {
2841         Point locationOnScreen = c.getLocationOnScreen();
2842         locationOnScreen.translate(dx, dy);
2843         return locationOnScreen;
2844       }
2845       else {
2846         Point location = c.getLocation();
2847         dx += location.x;
2848         dy += location.y;
2849       }
2850     }
2851     return null;
2852   }
2853
2854   public static void setAutoRequestFocus(@NotNull Window window, boolean value) {
2855     if (!SystemInfo.isMac) {
2856       window.setAutoRequestFocus(value);
2857     }
2858   }
2859
2860   public static void runWhenWindowOpened(@NotNull Window window, @NotNull Runnable runnable) {
2861     window.addWindowListener(new WindowAdapter() {
2862       @Override
2863       public void windowOpened(WindowEvent e) {
2864         e.getWindow().removeWindowListener(this);
2865         runnable.run();
2866       }
2867     });
2868   }
2869
2870   public static void runWhenWindowClosed(@NotNull Window window, @NotNull Runnable runnable) {
2871     window.addWindowListener(new WindowAdapter() {
2872       @Override
2873       public void windowClosed(WindowEvent e) {
2874         e.getWindow().removeWindowListener(this);
2875         runnable.run();
2876       }
2877     });
2878   }
2879
2880   //May have no usages but it's useful in runtime (Debugger "watches", some logging etc.)
2881   public static @NotNull String getDebugText(@NotNull Component c) {
2882     StringBuilder builder  = new StringBuilder();
2883     getAllTextsRecursively(c, builder);
2884     return builder.toString();
2885   }
2886
2887   private static void getAllTextsRecursively(@NotNull Component component, @NotNull StringBuilder builder) {
2888     String candidate = "";
2889     if (component instanceof JLabel) candidate = ((JLabel)component).getText();
2890     if (component instanceof JTextComponent) candidate = ((JTextComponent)component).getText();
2891     if (component instanceof AbstractButton) candidate = ((AbstractButton)component).getText();
2892     if (StringUtil.isNotEmpty(candidate)) {
2893       candidate = candidate.replaceAll("<a href=\"#inspection/[^)]+\\)", "");
2894       if (builder.length() > 0) builder.append(' ');
2895       builder.append(StringUtil.removeHtmlTags(candidate).trim());
2896     }
2897     if (component instanceof Container) {
2898       Component[] components = ((Container)component).getComponents();
2899       for (Component child : components) {
2900         getAllTextsRecursively(child, builder);
2901       }
2902     }
2903   }
2904
2905   public static boolean isAncestor(@NotNull Component ancestor, @Nullable Component descendant) {
2906     while (descendant != null) {
2907       if (descendant == ancestor) {
2908         return true;
2909       }
2910       descendant = descendant.getParent();
2911     }
2912     return false;
2913   }
2914
2915   public static void resetUndoRedoActions(@NotNull JTextComponent textComponent) {
2916     UndoManager undoManager = ComponentUtil.getClientProperty(textComponent, UNDO_MANAGER);
2917     if (undoManager != null) {
2918       undoManager.discardAllEdits();
2919     }
2920   }
2921
2922   private static final DocumentAdapter SET_TEXT_CHECKER = new DocumentAdapter() {
2923     @Override
2924     protected void textChanged(@NotNull DocumentEvent e) {
2925       Document document = e.getDocument();
2926       if (document instanceof AbstractDocument) {
2927         StackTraceElement[] stackTrace = new Throwable().getStackTrace();
2928         for (StackTraceElement element : stackTrace) {
2929           if (!element.getClassName().equals(JTextComponent.class.getName()) || !element.getMethodName().equals("setText")) continue;
2930           UndoableEditListener[] undoableEditListeners = ((AbstractDocument)document).getUndoableEditListeners();
2931           for (final UndoableEditListener listener : undoableEditListeners) {
2932             if (listener instanceof UndoManager) {
2933               Runnable runnable = ((UndoManager)listener)::discardAllEdits;
2934               //noinspection SSBasedInspection
2935               SwingUtilities.invokeLater(runnable);
2936               return;
2937             }
2938           }
2939         }
2940       }
2941     }
2942   };
2943
2944   public static void addUndoRedoActions(final @NotNull JTextComponent textComponent) {
2945     if (textComponent.getClientProperty(UNDO_MANAGER) instanceof UndoManager) {
2946       return;
2947     }
2948     UndoManager undoManager = new UndoManager();
2949     textComponent.putClientProperty(UNDO_MANAGER, undoManager);
2950     textComponent.getDocument().addUndoableEditListener(undoManager);
2951     textComponent.getDocument().addDocumentListener(SET_TEXT_CHECKER);
2952     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK), "undoKeystroke");
2953     textComponent.getActionMap().put("undoKeystroke", UNDO_ACTION);
2954     textComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, (SystemInfo.isMac
2955                                                                            ? InputEvent.META_MASK : InputEvent.CTRL_MASK) | InputEvent.SHIFT_MASK), "redoKeystroke");
2956     textComponent.getActionMap().put("redoKeystroke", REDO_ACTION);
2957   }
2958
2959   public static @Nullable UndoManager getUndoManager(Component component) {
2960     if (component instanceof JTextComponent) {
2961       Object o = ((JTextComponent)component).getClientProperty(UNDO_MANAGER);
2962       if (o instanceof UndoManager) return (UndoManager)o;
2963     }
2964     return null;
2965   }
2966
2967   public static void playSoundFromResource(@NotNull String resourceName) {
2968     Class<?> callerClass = ReflectionUtil.getGrandCallerClass();
2969     if (callerClass == null) {
2970       return;
2971     }
2972     playSoundFromStream(() -> callerClass.getResourceAsStream(resourceName));
2973   }
2974
2975   public static void playSoundFromStream(final @NotNull Factory<? extends InputStream> streamProducer) {
2976     // The wrapper thread is unnecessary, unless it blocks on the
2977     // Clip finishing; see comments.
2978     new Thread(() -> {
2979       try {
2980         Clip clip = AudioSystem.getClip();
2981         InputStream stream = streamProducer.create();
2982         if (!stream.markSupported()) stream = new BufferedInputStream(stream);
2983         AudioInputStream inputStream = AudioSystem.getAudioInputStream(stream);
2984         clip.open(inputStream);
2985
2986         clip.start();
2987       }
2988       catch (Exception e) {
2989         getLogger().info(e);
2990       }
2991     }, "play sound").start();
2992   }
2993
2994   public static @NotNull String leftArrow() {
2995     return FontUtil.leftArrow(StartupUiUtil.getLabelFont());
2996   }
2997
2998   public static @NotNull String rightArrow() {
2999     return FontUtil.rightArrow(StartupUiUtil.getLabelFont());
3000   }
3001
3002   public static @NotNull String upArrow(@NotNull String defaultValue) {
3003     return FontUtil.upArrow(StartupUiUtil.getLabelFont(), defaultValue);
3004   }
3005
3006   /**
3007    * It is your responsibility to set correct horizontal align (left in case of UI Designer)
3008    */
3009   public static void configureNumericFormattedTextField(@NotNull JFormattedTextField textField) {
3010     NumberFormat format = NumberFormat.getIntegerInstance();
3011     format.setParseIntegerOnly(true);
3012     format.setGroupingUsed(false);
3013     NumberFormatter numberFormatter = new NumberFormatter(format);
3014     numberFormatter.setMinimum(0);
3015     textField.setFormatterFactory(new DefaultFormatterFactory(numberFormatter));
3016     textField.setHorizontalAlignment(SwingConstants.TRAILING);
3017
3018     textField.setColumns(4);
3019   }
3020
3021   /**
3022    * Returns the first window ancestor of the component.
3023    * Note that this method returns the component itself if it is a window.
3024    *
3025    * @param component the component used to find corresponding window
3026    * @return the first window ancestor of the component; or {@code null}
3027    *         if the component is not a window and is not contained inside a window
3028    */
3029   public static @Nullable Window getWindow(@Nullable Component component) {
3030     return ComponentUtil.getWindow(component);
3031   }
3032
3033   /**
3034    * Places the specified window at the top of the stacking order and shows it in front of any other windows.
3035    * If the window is iconified it will be shown anyway.
3036    *
3037    * @param window the window to activate
3038    */
3039   public static void toFront(@Nullable Window window) {
3040     if (window instanceof Frame) {
3041       ((Frame)window).setState(Frame.NORMAL);
3042     }
3043     if (window != null) {
3044       window.toFront();
3045     }
3046   }
3047
3048   /**
3049    * Indicates whether the specified component is scrollable or it contains a scrollable content.
3050    */
3051   public static boolean hasScrollPane(@NotNull Component component) {
3052     return hasComponentOfType(component, JScrollPane.class);
3053   }
3054
3055   /**
3056    * Indicates whether the specified component is instance of one of the specified types
3057    * or it contains an instance of one of the specified types.
3058    */
3059   public static boolean hasComponentOfType(@NotNull Component component, Class<?> @NotNull ... types) {
3060     for (Class<?> type : types) {
3061       if (type.isAssignableFrom(component.getClass())) {
3062         return true;
3063       }
3064     }
3065     if (component instanceof Container) {
3066       Container container = (Container)component;
3067       for (int i = 0; i < container.getComponentCount(); i++) {
3068         if (hasComponentOfType(container.getComponent(i), types)) {
3069           return true;
3070         }
3071       }
3072     }
3073     return false;
3074   }
3075
3076   public static void setColumns(JTextComponent textComponent, int columns) {
3077     if (textComponent instanceof JTextField) {
3078       ((JTextField)textComponent).setColumns(columns);
3079     }
3080     if (textComponent instanceof JTextArea) {
3081       ((JTextArea)textComponent).setColumns(columns);
3082     }
3083   }
3084
3085   public static int getLineHeight(@NotNull JTextComponent textComponent) {
3086     return textComponent.getFontMetrics(textComponent.getFont()).getHeight();
3087   }
3088
3089   /**
3090    * Returns the first focusable component in the specified container.
3091    * This method returns {@code null} if container is {@code null},
3092    * or if focus traversal policy cannot be determined,
3093    * or if found focusable component is not a {@link JComponent}.
3094    *
3095    * @param container a container whose first focusable component is to be returned
3096    * @return the first focusable component or {@code null} if it cannot be found
3097    */
3098   public static JComponent getPreferredFocusedComponent(Container container) {
3099     Container parent = container;
3100     if (parent == null) return null;
3101     FocusTraversalPolicy policy = parent.getFocusTraversalPolicy();
3102     while (policy == null) {
3103       parent = parent.getParent();
3104       if (parent == null) return null;
3105       policy = parent.getFocusTraversalPolicy();
3106     }
3107     Component component = policy.getFirstComponent(container);
3108     return component instanceof JComponent ? (JComponent)component : null;
3109   }
3110
3111   /**
3112    * Calculates a component style from the corresponding client property.
3113    * The key "JComponent.sizeVariant" is used by Apple's L&F to scale components.
3114    *
3115    * @param component a component to process
3116    * @return a component style of the specified component
3117    */
3118   public static @NotNull ComponentStyle getComponentStyle(Component component) {
3119     if (component instanceof JComponent) {
3120       Object property = ((JComponent)component).getClientProperty("JComponent.sizeVariant");
3121       if ("large".equals(property)) return ComponentStyle.LARGE;
3122       if ("small".equals(property)) return ComponentStyle.SMALL;
3123       if ("mini".equals(property)) return ComponentStyle.MINI;
3124     }
3125     return ComponentStyle.REGULAR;
3126   }
3127
3128   public static final String CHECKBOX_ROLLOVER_PROPERTY = "JCheckBox.rollOver.rectangle";
3129   public static final String CHECKBOX_PRESSED_PROPERTY = "JCheckBox.pressed.rectangle";
3130
3131   public static void repaintViewport(@NotNull JComponent c) {
3132     if (!c.isDisplayable() || !c.isVisible()) return;
3133
3134     Container p = c.getParent();
3135     if (p instanceof JViewport) {
3136       p.repaint();
3137     }
3138   }
3139
3140   public static void setCursor(@NotNull Component component, Cursor cursor) {
3141     // cursor is updated by native code even if component has the same cursor, causing performance problems (IDEA-167733)
3142     if(component.isCursorSet() && component.getCursor() == cursor) return;
3143     component.setCursor(cursor);
3144   }
3145
3146   public static boolean haveCommonOwner(Component c1, Component c2) {
3147     if (c1 == null || c2 == null) return false;
3148     Window c1Ancestor = findWindowAncestor(c1);
3149     Window c2Ancestor = findWindowAncestor(c2);
3150
3151     Set <Window> ownerSet = new HashSet<>();
3152
3153     Window owner = c1Ancestor;
3154
3155     while (owner != null && !(owner instanceof JDialog || owner instanceof JFrame)) {
3156       ownerSet.add(owner);
3157       owner = owner.getOwner();
3158     }
3159
3160     owner = c2Ancestor;
3161
3162     while (owner != null && !(owner instanceof JDialog || owner instanceof JFrame)) {
3163       if (ownerSet.contains(owner)) return true;
3164       owner = owner.getOwner();
3165     }
3166
3167     return false;
3168   }
3169
3170   private static Window findWindowAncestor(@NotNull Component c) {
3171     return c instanceof Window ? (Window)c : SwingUtilities.getWindowAncestor(c);
3172   }
3173
3174   public static boolean isHelpButton(Component button) {
3175     return button instanceof JButton && "help".equals(((JComponent)button).getClientProperty("JButton.buttonType"));
3176   }
3177
3178   public static boolean isRetina(@NotNull GraphicsDevice device) {
3179     return DetectRetinaKit.isOracleMacRetinaDevice(device);
3180   }
3181
3182   /** Employs a common pattern to use {@code Graphics}. This is a non-distractive approach
3183    * all modifications on {@code Graphics} are metter only inside the {@code Consumer} block
3184    *
3185    * @param originGraphics graphics to work with
3186    * @param drawingConsumer you can use the Graphics2D object here safely
3187    */
3188   public static void useSafely(@NotNull Graphics originGraphics, @NotNull Consumer<? super Graphics2D> drawingConsumer) {
3189     Graphics2D graphics = (Graphics2D)originGraphics.create();
3190     try {
3191       drawingConsumer.consume(graphics);
3192     }
3193     finally {
3194       graphics.dispose();
3195     }
3196   }
3197
3198
3199   private static final Color BACKGROUND = new JBColor(0xFFFFFF, 0x3C3F41);
3200   private static final Color LIST_BACKGROUND = JBColor.namedColor("List.background", BACKGROUND);
3201   private static final Color TREE_BACKGROUND = JBColor.namedColor("Tree.background", BACKGROUND);
3202   private static final Color TABLE_BACKGROUND = JBColor.namedColor("Table.background", BACKGROUND);
3203
3204   private static final class FocusedSelection {
3205     private static final Color BACKGROUND = new JBColor(0x3875D6, 0x2F65CA);
3206     private static final Color TREE_BACKGROUND = JBColor.namedColor("Tree.selectionBackground", BACKGROUND);
3207     private static final Color TABLE_BACKGROUND = JBColor.namedColor("Table.selectionBackground", BACKGROUND);
3208   }
3209
3210   private static final class UnfocusedSelection {
3211     private static final Color BACKGROUND = new JBColor(0xD4D4D4, 0x0D293E);
3212     private static final Color LIST_BACKGROUND = JBColor.namedColor("List.selectionInactiveBackground", BACKGROUND);
3213     private static final Color TREE_BACKGROUND = JBColor.namedColor("Tree.selectionInactiveBackground", BACKGROUND);
3214     private static final Color TABLE_BACKGROUND = JBColor.namedColor("Table.selectionInactiveBackground", BACKGROUND);
3215   }
3216
3217
3218   // List
3219
3220   public static @NotNull Font getListFont() {
3221     Font font = UIManager.getFont("List.font");
3222     return font != null ? font : StartupUiUtil.getLabelFont();
3223   }
3224
3225   // background
3226
3227   public static @NotNull Color getListBackground() {
3228     return LIST_BACKGROUND;
3229   }
3230
3231   private static final JBValue SELECTED_ITEM_ALPHA = new JBValue.UIInteger("List.selectedItemAlpha", 75);
3232
3233   public static @NotNull Color getListSelectionBackground(boolean focused) {
3234     if (!focused) return UnfocusedSelection.LIST_BACKGROUND;
3235     Color color = UIManager.getColor("List.selectionBackground");
3236     double alpha = SELECTED_ITEM_ALPHA.getFloat() / 100.0;
3237     //noinspection UseJBColor
3238     return isUnderDefaultMacTheme() && alpha >= 0 && alpha <= 1.0 ? ColorUtil.mix(Color.WHITE, color, alpha) : color;
3239   }
3240
3241   public static @NotNull Dimension updateListRowHeight(@NotNull Dimension size) {
3242     size.height = Math.max(size.height, UIManager.getInt("List.rowHeight"));
3243     return size;
3244   }
3245
3246   public static @NotNull Color getListBackground(boolean selected, boolean focused) {
3247     return !selected ? getListBackground() : getListSelectionBackground(focused);
3248   }
3249
3250   /**
3251    * @deprecated use {@link #getListBackground(boolean, boolean)}
3252    */
3253   @Deprecated
3254   public static @NotNull Color getListBackground(boolean selected) {
3255     return getListBackground(selected, true);
3256   }
3257
3258   /**
3259    * @deprecated use {@link #getListSelectionBackground(boolean)}
3260    */
3261   @Deprecated
3262   public static @NotNull Color getListSelectionBackground() {
3263     return getListSelectionBackground(true);
3264   }
3265
3266   /**
3267    * @deprecated use {@link #getListSelectionBackground(boolean)}
3268    */
3269   @Deprecated
3270   public static @NotNull Color getListUnfocusedSelectionBackground() {
3271     return getListSelectionBackground(false);
3272   }
3273
3274   // foreground
3275
3276   public static @NotNull Color getListForeground() {
3277     return UIManager.getColor("List.foreground");
3278   }
3279
3280   public static @NotNull Color getListSelectionForeground(boolean focused) {
3281     Color foreground = UIManager.getColor(focused ? "List.selectionForeground" : "List.selectionInactiveForeground");
3282     if (focused && foreground == null) foreground = UIManager.getColor("List[Selected].textForeground");  // Nimbus
3283     return foreground != null ? foreground : getListForeground();
3284   }
3285
3286   public static @NotNull Color getListForeground(boolean selected, boolean focused) {
3287     return !selected ? getListForeground() : getListSelectionForeground(focused);
3288   }
3289
3290   /**
3291    * @deprecated use {@link #getListForeground(boolean, boolean)}
3292    */
3293   @Deprecated
3294   public static @NotNull Color getListForeground(boolean selected) {
3295     return getListForeground(selected, true);
3296   }
3297
3298   /**
3299    * @deprecated use {@link #getListSelectionForeground(boolean)}
3300    */
3301   @Deprecated
3302   public static @NotNull Color getListSelectionForeground() {
3303     return getListSelectionForeground(true);
3304   }
3305
3306
3307   // Tree
3308
3309   public static @NotNull Font getTreeFont() {
3310     Font font = UIManager.getFont("Tree.font");
3311     return font != null ? font : StartupUiUtil.getLabelFont();
3312   }
3313
3314   // background
3315
3316   public static @NotNull Color getTreeBackground() {
3317     return TREE_BACKGROUND;
3318   }
3319
3320   public static @NotNull Color getTreeSelectionBackground(boolean focused) {
3321     return focused ? FocusedSelection.TREE_BACKGROUND : UnfocusedSelection.TREE_BACKGROUND;
3322   }
3323
3324   public static @NotNull Color getTreeBackground(boolean selected, boolean focused) {
3325     return !selected ? getTreeBackground() : getTreeSelectionBackground(focused);
3326   }
3327
3328   /**
3329    * @deprecated use {@link #getTreeSelectionBackground(boolean)}
3330    */
3331   @Deprecated
3332   public static @NotNull Color getTreeSelectionBackground() {
3333     return getTreeSelectionBackground(true);
3334   }
3335
3336   /**
3337    * @deprecated use {@link #getTreeSelectionBackground(boolean)}
3338    */
3339   @Deprecated
3340   public static @NotNull Color getTreeUnfocusedSelectionBackground() {
3341     return getTreeSelectionBackground(false);
3342   }
3343
3344   // foreground
3345
3346   public static @NotNull Color getTreeForeground() {
3347     return UIManager.getColor("Tree.foreground");
3348   }
3349
3350   public static @NotNull Color getTreeSelectionForeground(boolean focused) {
3351     Color foreground = UIManager.getColor(focused ? "Tree.selectionForeground" : "Tree.selectionInactiveForeground");
3352     return foreground != null ? foreground : getTreeForeground();
3353   }
3354
3355   public static @NotNull Color getTreeForeground(boolean selected, boolean focused) {
3356     return !selected ? getTreeForeground() : getTreeSelectionForeground(focused);
3357   }
3358
3359   /**
3360    * @deprecated use {@link #getTreeSelectionForeground(boolean)}
3361    */
3362   @Deprecated
3363   public static @NotNull Color getTreeSelectionForeground() {
3364     return getTreeSelectionForeground(true);
3365   }
3366
3367   public static @NotNull Color getTableBackground() {
3368     return TABLE_BACKGROUND;
3369   }
3370
3371   public static @NotNull Color getTableSelectionBackground(boolean focused) {
3372     return focused ? FocusedSelection.TABLE_BACKGROUND : UnfocusedSelection.TABLE_BACKGROUND;
3373   }
3374
3375   public static @NotNull Color getTableBackground(boolean selected, boolean focused) {
3376     return !selected ? getTableBackground() : getTableSelectionBackground(focused);
3377   }
3378
3379   /**
3380    * @deprecated use {@link #getTableBackground(boolean, boolean)}
3381    */
3382   @Deprecated
3383   public static @NotNull Color getTableBackground(boolean selected) {
3384     return getTableBackground(selected, true);
3385   }
3386
3387   /**
3388    * @deprecated use {@link #getTableSelectionBackground(boolean)}
3389    */
3390   @Deprecated
3391   public static @NotNull Color getTableSelectionBackground() {
3392     return getTableSelectionBackground(true);
3393   }
3394
3395   /**
3396    * @deprecated use {@link #getTableSelectionBackground(boolean)}
3397    */
3398   @Deprecated
3399   public static @NotNull Color getTableUnfocusedSelectionBackground() {
3400     return getTableSelectionBackground(false);
3401   }
3402
3403   // foreground
3404
3405   public static @NotNull Color getTableForeground() {
3406     return UIManager.getColor("Table.foreground");
3407   }
3408
3409   public static @NotNull Color getTableSelectionForeground(boolean focused) {
3410     Color foreground = UIManager.getColor(focused ? "Table.selectionForeground" : "Table.selectionInactiveForeground");
3411     return foreground != null ? foreground : getTreeForeground();
3412   }
3413
3414   public static @NotNull Color getTableForeground(boolean selected, boolean focused) {
3415     return !selected ? getTableForeground() : getTableSelectionForeground(focused);
3416   }
3417
3418   /**
3419    * @deprecated use {@link #getTableForeground(boolean, boolean)}
3420    */
3421   @Deprecated
3422   public static @NotNull Color getTableForeground(boolean selected) {
3423     return getTableForeground(selected, true);
3424   }
3425
3426   /**
3427    * @deprecated use {@link #getTableSelectionForeground(boolean)}