Merge branch 'appcode10' into merge_appcode10
[idea/community.git] / platform / util / src / com / intellij / util / ui / UIUtil.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.util.ui;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.*;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.ui.ColorUtil;
23 import com.intellij.ui.PanelWithAnchor;
24 import com.intellij.ui.SideBorder;
25 import com.intellij.util.ArrayUtil;
26 import com.intellij.util.PairFunction;
27 import com.intellij.util.Processor;
28 import com.intellij.util.ReflectionUtil;
29 import com.intellij.util.containers.ContainerUtil;
30 import org.intellij.lang.annotations.Language;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 import org.jetbrains.annotations.TestOnly;
35
36 import javax.swing.*;
37 import javax.swing.Timer;
38 import javax.swing.border.Border;
39 import javax.swing.border.CompoundBorder;
40 import javax.swing.border.EmptyBorder;
41 import javax.swing.border.LineBorder;
42 import javax.swing.plaf.ComboBoxUI;
43 import javax.swing.plaf.ProgressBarUI;
44 import javax.swing.plaf.UIResource;
45 import javax.swing.plaf.basic.BasicComboBoxUI;
46 import javax.swing.plaf.basic.BasicTreeUI;
47 import javax.swing.plaf.basic.ComboPopup;
48 import javax.swing.text.DefaultEditorKit;
49 import javax.swing.text.JTextComponent;
50 import javax.swing.text.html.HTMLEditorKit;
51 import javax.swing.text.html.StyleSheet;
52 import javax.swing.tree.TreePath;
53 import java.awt.*;
54 import java.awt.event.*;
55 import java.awt.font.FontRenderContext;
56 import java.awt.image.BufferedImage;
57 import java.lang.ref.WeakReference;
58 import java.lang.reflect.Field;
59 import java.lang.reflect.Method;
60 import java.net.URL;
61 import java.util.*;
62 import java.util.List;
63 import java.util.concurrent.BlockingQueue;
64 import java.util.concurrent.LinkedBlockingQueue;
65 import java.util.regex.Pattern;
66
67 /**
68  * @author max
69  */
70 @SuppressWarnings("StaticMethodOnlyUsedInOneClass")
71 public class UIUtil {
72
73   private static final String TABLE_DECORATION_KEY = "TABLE_DECORATION_KEY";
74   private static final Color DECORATED_ROW_BG_COLOR = new Color(242, 245, 249);
75
76   public static void applyStyle(@NotNull ComponentStyle componentStyle, @NotNull Component comp) {
77     if (!(comp instanceof JComponent)) return;
78
79     JComponent c = (JComponent)comp;
80
81     if (isUnderAquaLookAndFeel()) {
82       c.putClientProperty("JComponent.sizeVariant",
83                           componentStyle == ComponentStyle.REGULAR ? "regular" : componentStyle == ComponentStyle.SMALL ? "small" : "mini");
84     } else {
85       c.setFont(getFont(
86         componentStyle == ComponentStyle.REGULAR ? FontSize.NORMAL : componentStyle == ComponentStyle.SMALL ? FontSize.SMALL : FontSize.MINI, c.getFont()));
87     }
88     Container p = c.getParent();
89     if (p != null) {
90       SwingUtilities.updateComponentTreeUI(p);
91     }
92   }
93
94   public static Cursor getTextCursor(final Color backgroundColor) {
95     return SystemInfo.isMac && ColorUtil.isDark(backgroundColor) ?
96                                 MacUIUtil.getInvertedTextCursor(): Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
97   }
98
99   public enum FontSize {NORMAL, TREE, SMALL, MINI}
100   public enum ComponentStyle {REGULAR, SMALL, MINI}
101   public enum FontColor {NORMAL, BRIGHTER}
102
103   public static final char MNEMONIC = 0x1B;
104   @NonNls public static final String HTML_MIME = "text/html";
105   @NonNls public static final String JSLIDER_ISFILLED = "JSlider.isFilled";
106   @NonNls public static final String ARIAL_FONT_NAME = "Arial";
107   @NonNls public static final String TABLE_FOCUS_CELL_BACKGROUND_PROPERTY = "Table.focusCellBackground";
108   @NonNls public static final String CENTER_TOOLTIP_DEFAULT = "ToCenterTooltip";
109   @NonNls public static final String CENTER_TOOLTIP_STRICT = "ToCenterTooltip.default";
110
111   public static final Pattern CLOSE_TAG_PATTERN = Pattern.compile("<\\s*([^<>/ ]+)([^<>]*)/\\s*>", Pattern.CASE_INSENSITIVE);
112
113   @NonNls public static final String FOCUS_PROXY_KEY = "isFocusProxy";
114
115   public static Key<Integer> KEEP_BORDER_SIDES = Key.create("keepBorderSides");
116
117   private static final Logger LOG = Logger.getInstance("#com.intellij.util.ui.UIUtil");
118
119   private static final Color UNFOCUSED_SELECTION_COLOR = new Color(212, 212, 212);
120   private static final Color ACTIVE_HEADER_COLOR = new Color(160, 186, 213);
121   private static final Color INACTIVE_HEADER_COLOR = new Color(128, 128, 128);
122   private static final Color BORDER_COLOR = Color.LIGHT_GRAY;
123
124   public static final Color AQUA_SEPARATOR_FOREGROUND_COLOR = new Color(190, 190, 190);
125   public static final Color AQUA_SEPARATOR_BACKGROUND_COLOR = new Color(240, 240, 240);
126   public static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);
127
128   public static final int DEFAULT_HGAP = 8;
129   public static final int DEFAULT_VGAP = 4;
130   public static final int LARGE_HGAP = 30;
131   public static final int LARGE_VGAP = 20;
132
133   public static final Insets PANEL_REGULAR_INSETS = new Insets(8, 12, 8, 12);
134   public static final Insets PANEL_SMALL_INSETS = new Insets(5, 8, 5, 8);
135
136   // accessed only from EDT
137   private static final HashMap<Color, BufferedImage> ourAppleDotSamples = new HashMap<Color, BufferedImage>();
138
139   @NonNls private static final String ROOT_PANE = "JRootPane.future";
140
141   private UIUtil() { }
142
143   public static String getHtmlBody(String text) {
144     return getHtmlBody(new Html(text));
145   }
146
147   public static String getHtmlBody(Html html) {
148     String text = html.getText();
149     String result;
150     if (!text.startsWith("<html>")) {
151       result = text.replaceAll("\n", "<br>");
152     }
153     else {
154       final int bodyIdx = text.indexOf("<body>");
155       final int closedBodyIdx = text.indexOf("</body>");
156       if (bodyIdx != -1 && closedBodyIdx != -1) {
157         result = text.substring(bodyIdx + "<body>".length(), closedBodyIdx);
158       }
159       else {
160         text = StringUtil.trimStart(text, "<html>").trim();
161         text = StringUtil.trimEnd(text, "</html>").trim();
162         text = StringUtil.trimStart(text, "<body>").trim();
163         text = StringUtil.trimEnd(text, "</body>").trim();
164         result = text;
165       }
166     }
167
168     return html.isKeepFont() ? result : result.replaceAll("<font(.*?)>", "").replaceAll("</font>", "");
169   }
170
171   public static void drawLinePickedOut(Graphics graphics, int x, int y, int x1, int y1) {
172     if (x == x1) {
173       int minY = Math.min(y, y1);
174       int maxY = Math.max(y,  y1);
175       graphics.drawLine(x,  minY+1, x1, maxY-1);
176     } else if (y == y1) {
177       int minX = Math.min(x, x1);
178       int maxX = Math.max(x, x1);
179       graphics.drawLine(minX+1, y,  maxX-1, y1);
180     } else {
181       drawLine(graphics, x, y, x1, y1);
182     }
183   }
184
185   public static boolean isReallyTypedEvent(KeyEvent e) {
186     char c = e.getKeyChar();
187     if (!(c >= 0x20 && c != 0x7F)) return false;
188
189     int modifiers = e.getModifiers();
190     if (SystemInfo.isMac) {
191       return !e.isMetaDown() && !e.isControlDown();
192     }
193
194     return (modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK);
195   }
196
197   public static int getStringY(@NotNull final String string, @NotNull final Rectangle bounds, @NotNull final Graphics2D g) {
198     final int centerY = bounds.height / 2;
199     final Font font = g.getFont();
200     final FontRenderContext frc = g.getFontRenderContext();
201     final Rectangle stringBounds = font.getStringBounds(string, frc).getBounds();
202
203     return (int)(centerY - stringBounds.height / 2.0 - stringBounds.y);
204   }
205
206   public static void setEnabled(Component component, boolean enabled, boolean recursively) {
207     component.setEnabled(enabled);
208     if (component instanceof JLabel) {
209       Color color = enabled ? getLabelForeground() : UIManager.getColor("Label.disabledForeground");
210       if (color != null) {
211         component.setForeground(color);
212       }
213     }
214     if (recursively && enabled == component.isEnabled()) {
215       if (component instanceof Container) {
216         final Container container = (Container)component;
217         final int subComponentCount = container.getComponentCount();
218         for (int i = 0; i < subComponentCount; i++) {
219           setEnabled(container.getComponent(i), enabled, recursively);
220         }
221
222       }
223     }
224   }
225
226   public static void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
227     g.drawLine(x1, y1, x2, y2);
228   }
229
230   @NotNull
231   public static String[] splitText(String text, FontMetrics fontMetrics, int widthLimit, char separator) {
232     ArrayList<String> lines = new ArrayList<String>();
233     String currentLine = "";
234     StringBuilder currentAtom = new StringBuilder();
235
236     for (int i = 0; i < text.length(); i++) {
237       char ch = text.charAt(i);
238       currentAtom.append(ch);
239
240       if (ch == separator) {
241         currentLine += currentAtom.toString();
242         currentAtom.setLength(0);
243       }
244
245       String s = currentLine + currentAtom.toString();
246       int width = fontMetrics.stringWidth(s);
247
248       if (width >= widthLimit - fontMetrics.charWidth('w')) {
249         if (currentLine.length() > 0) {
250           lines.add(currentLine);
251           currentLine = "";
252         }
253         else {
254           lines.add(currentAtom.toString());
255           currentAtom.setLength(0);
256         }
257       }
258     }
259
260     String s = currentLine + currentAtom.toString();
261     if (s.length() > 0) {
262       lines.add(s);
263     }
264
265     return ArrayUtil.toStringArray(lines);
266   }
267
268   public static void setActionNameAndMnemonic(String text, Action action) {
269     int mnemoPos = text.indexOf('&');
270     if (mnemoPos >= 0 && mnemoPos < text.length() - 2) {
271       String mnemoChar = text.substring(mnemoPos + 1, mnemoPos + 2).trim();
272       if (mnemoChar.length() == 1) {
273         action.putValue(Action.MNEMONIC_KEY, Integer.valueOf((int)mnemoChar.charAt(0)));
274       }
275     }
276
277     text = text.replaceAll("&", "");
278     action.putValue(Action.NAME, text);
279   }
280
281   public static Font getLabelFont(@NotNull FontSize size) {
282     return getFont(size, null);
283   }
284
285   @NotNull
286   public static Font getFont(@NotNull FontSize size, @Nullable Font base) {
287     if (base == null) base = getLabelFont();
288
289     return base.deriveFont(getFontSize(size));
290   }
291
292   public static float getFontSize(FontSize size) {
293     int defSize = getLabelFont().getSize();
294     switch (size) {
295       case TREE:
296         return Math.max(defSize - 2f, 12f);
297       case SMALL:
298         return Math.max(defSize - 2f, 11f);
299       case MINI:
300         return Math.max(defSize - 4f, 9f);
301       default:
302         return defSize;
303     }
304   }
305
306   public static Color getLabelFontColor(FontColor fontColor) {
307     Color defColor = getLabelForeground();
308     if (fontColor == FontColor.BRIGHTER) {
309       return new Color(Math.min(defColor.getRed() + 50, 255), Math.min(defColor.getGreen() + 50, 255), Math.min(defColor.getBlue() + 50, 255));
310     }
311     return defColor;
312   }
313
314   public static Font getLabelFont() {
315     return UIManager.getFont("Label.font");
316   }
317
318   public static Color getLabelBackground() {
319     return UIManager.getColor("Label.background");
320   }
321
322   public static Color getLabelForeground() {
323     return UIManager.getColor("Label.foreground");
324   }
325
326   public static Icon getOptionPanelWarningIcon() {
327     return UIManager.getIcon("OptionPane.warningIcon");
328   }
329
330   /**
331    * @deprecated use com.intellij.util.ui.UIUtil#getQuestionIcon()
332    */
333   public static Icon getOptionPanelQuestionIcon() {
334     return getQuestionIcon();
335   }
336
337   @NotNull
338   public static String removeMnemonic(@NotNull String s) {
339     if (s.indexOf('&') != -1) {
340       s = StringUtil.replace(s, "&", "");
341     }
342     if (s.indexOf('_') != -1) {
343       s = StringUtil.replace(s, "_", "");
344     }
345     if (s.indexOf(MNEMONIC) != -1) {
346       s = StringUtil.replace(s, String.valueOf(MNEMONIC), "");
347     }
348     return s;
349   }
350
351   public static int getDisplayMnemonicIndex(@NotNull String s) {
352     int idx = s.indexOf('&');
353     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf('&')) return idx;
354
355     idx = s.indexOf(MNEMONIC);
356     if (idx >= 0 && idx != s.length() - 1 && idx == s.lastIndexOf(MNEMONIC)) return idx;
357
358     return -1;
359   }
360
361   public static String replaceMnemonicAmpersand(final String value) {
362     if (value.indexOf('&') >= 0) {
363       boolean useMacMnemonic = value.contains("&&");
364       StringBuilder realValue = new StringBuilder();
365       int i = 0;
366       while (i < value.length()) {
367         char c = value.charAt(i);
368         if (c == '\\') {
369           if (i < value.length() - 1 && value.charAt(i + 1) == '&') {
370             realValue.append('&');
371             i++;
372           }
373           else {
374             realValue.append(c);
375           }
376         }
377         else if (c == '&') {
378           if (i < value.length() - 1 && value.charAt(i + 1) == '&') {
379             if (SystemInfo.isMac) {
380               realValue.append(MNEMONIC);
381             }
382             i++;
383           }
384           else {
385             if (!SystemInfo.isMac || !useMacMnemonic) {
386               realValue.append(MNEMONIC);
387             }
388           }
389         }
390         else {
391           realValue.append(c);
392         }
393         i++;
394       }
395
396       return realValue.toString();
397     }
398     return value;
399   }
400
401   public static Color getTableHeaderBackground() {
402     return UIManager.getColor("TableHeader.background");
403   }
404
405   public static Color getTreeTextForeground() {
406     return UIManager.getColor("Tree.textForeground");
407   }
408
409   public static Color getTreeSelectionBackground() {
410     return UIManager.getColor("Tree.selectionBackground");
411   }
412
413   public static Color getTreeTextBackground() {
414     return UIManager.getColor("Tree.textBackground");
415   }
416
417   public static Color getListSelectionForeground() {
418     final Color color = UIManager.getColor("List.selectionForeground");
419     if (color == null) {
420       return UIManager.getColor("List[Selected].textForeground");  // Nimbus
421     }
422     return color;
423   }
424
425   public static Color getFieldForegroundColor() {
426     return UIManager.getColor("field.foreground");
427   }
428
429   public static Color getTableSelectionBackground() {
430     return UIManager.getColor("Table.selectionBackground");
431   }
432
433   public static Color getActiveTextColor() {
434     return UIManager.getColor("textActiveText");
435   }
436
437   public static Color getInactiveTextColor() {
438     return UIManager.getColor("textInactiveText");
439   }
440
441   /**
442    * @deprecated use com.intellij.util.ui.UIUtil#getTextFieldBackground()
443    */
444   public static Color getActiveTextFieldBackgroundColor() {
445     return getTextFieldBackground();
446   }
447
448   public static Color getInactiveTextFieldBackgroundColor() {
449     return UIManager.getColor("TextField.inactiveBackground");
450   }
451
452   public static Font getTreeFont() {
453     return UIManager.getFont("Tree.font");
454   }
455
456   public static Font getListFont() {
457     return UIManager.getFont("List.font");
458   }
459
460   public static Color getTreeSelectionForeground() {
461     return UIManager.getColor("Tree.selectionForeground");
462   }
463
464   /**
465    * @deprecated use com.intellij.util.ui.UIUtil#getInactiveTextColor()
466    */
467   public static Color getTextInactiveTextColor() {
468     return getInactiveTextColor();
469   }
470
471   public static void installPopupMenuColorAndFonts(final JComponent contentPane) {
472     LookAndFeel.installColorsAndFont(contentPane, "PopupMenu.background", "PopupMenu.foreground", "PopupMenu.font");
473   }
474
475   public static void installPopupMenuBorder(final JComponent contentPane) {
476     LookAndFeel.installBorder(contentPane, "PopupMenu.border");
477   }
478
479   public static boolean isMotifLookAndFeel() {
480     return "Motif".equals(UIManager.getLookAndFeel().getID());
481   }
482
483   public static Color getTreeSelectionBorderColor() {
484     return UIManager.getColor("Tree.selectionBorderColor");
485   }
486
487   public static int getTreeRightChildIndent() {
488     return UIManager.getInt("Tree.rightChildIndent");
489   }
490
491   public static int getTreeLeftChildIndent() {
492     return UIManager.getInt("Tree.leftChildIndent");
493   }
494
495   public static Color getToolTipBackground() {
496     return UIManager.getColor("ToolTip.background");
497   }
498
499   public static Color getToolTipForeground() {
500     return UIManager.getColor("ToolTip.foreground");
501   }
502
503   public static Color getComboBoxDisabledForeground() {
504     return UIManager.getColor("ComboBox.disabledForeground");
505   }
506
507   public static Color getComboBoxDisabledBackground() {
508     return UIManager.getColor("ComboBox.disabledBackground");
509   }
510
511   public static Color getButtonSelectColor() {
512     return UIManager.getColor("Button.select");
513   }
514
515   public static Integer getPropertyMaxGutterIconWidth(final String propertyPrefix) {
516     return (Integer)UIManager.get(propertyPrefix + ".maxGutterIconWidth");
517   }
518
519   public static Color getMenuItemDisabledForeground() {
520     return UIManager.getColor("MenuItem.disabledForeground");
521   }
522
523   public static Object getMenuItemDisabledForegroundObject() {
524     return UIManager.get("MenuItem.disabledForeground");
525   }
526
527   public static Object getTabbedPanePaintContentBorder(final JComponent c) {
528     return c.getClientProperty("TabbedPane.paintContentBorder");
529   }
530
531   public static boolean isMenuCrossMenuMnemonics() {
532     return UIManager.getBoolean("Menu.crossMenuMnemonic");
533   }
534
535   public static Color getTableBackground() {
536     return UIManager.getColor("Table.background");
537   }
538
539   public static Color getTableSelectionForeground() {
540     return UIManager.getColor("Table.selectionForeground");
541   }
542
543   public static Color getTableForeground() {
544     return UIManager.getColor("Table.foreground");
545   }
546
547   public static Color getTableGridColor() {
548     return UIManager.getColor("Table.gridColor");
549   }
550
551   public static Color getListBackground() {
552     // Fixes most of the GTK+ L&F glitches
553     return isUnderGTKLookAndFeel() ? getTreeTextBackground() : UIManager.getColor("List.background");
554   }
555
556   public static Color getListBackground(boolean isSelected) {
557     return isSelected ? getListSelectionBackground() : getListBackground();
558   }
559
560   public static Color getListForeground() {
561     return UIManager.getColor("List.foreground");
562   }
563
564   public static Color getListForeground(boolean isSelected) {
565     return isSelected ? getListSelectionForeground() : getListForeground();
566   }
567
568   public static Color getPanelBackground() {
569     return UIManager.getColor("Panel.background");
570   }
571
572   public static Color getTreeForeground() {
573     return UIManager.getColor("Tree.foreground");
574   }
575
576   public static Color getTableFocusCellBackground() {
577     return UIManager.getColor(TABLE_FOCUS_CELL_BACKGROUND_PROPERTY);
578   }
579
580   public static Color getListSelectionBackground() {
581     final Color color = UIManager.getColor("List.selectionBackground");
582     if (color == null) {
583       return UIManager.getColor("List[Selected].textBackground");  // Nimbus
584     }
585     return color;
586   }
587
588   public static Color getListUnfocusedSelectionBackground() {
589     return UNFOCUSED_SELECTION_COLOR;
590   }
591
592   public static Color getTextFieldForeground() {
593     return UIManager.getColor("TextField.foreground");
594   }
595
596   public static Color getTextFieldBackground() {
597     return isUnderGTKLookAndFeel() ? UIManager.getColor("EditorPane.background") : UIManager.getColor("TextField.background");
598   }
599
600   public static Font getButtonFont() {
601     return UIManager.getFont("Button.font");
602   }
603
604   public static Font getToolTipFont() {
605     return UIManager.getFont("ToolTip.font");
606   }
607
608   public static Color getTabbedPaneBackground() {
609     return UIManager.getColor("TabbedPane.background");
610   }
611
612   public static void setSliderIsFilled(final JSlider slider, final boolean value) {
613     slider.putClientProperty("JSlider.isFilled", Boolean.valueOf(value));
614   }
615
616   public static Color getLabelTextForeground() {
617     return UIManager.getColor("Label.textForeground");
618   }
619
620   public static Color getControlColor() {
621     return UIManager.getColor("control");
622   }
623
624   public static Font getOptionPaneMessageFont() {
625     return UIManager.getFont("OptionPane.messageFont");
626   }
627
628   public static Font getMenuFont() {
629     return UIManager.getFont("Menu.font");
630   }
631
632   public static Color getSeparatorForeground() {
633     return UIManager.getColor("Separator.foreground");
634   }
635
636   public static Color getSeparatorBackground() {
637     return UIManager.getColor("Separator.background");
638   }
639
640   public static Color getSeparatorShadow() {
641     return UIManager.getColor("Separator.shadow");
642   }
643
644   public static Color getSeparatorHighlight() {
645     return UIManager.getColor("Separator.highlight");
646   }
647
648   public static Color getSeparatorColorUnderNimbus() {
649     return UIManager.getColor("nimbusBlueGrey");
650   }
651
652   public static Border getTableFocusCellHighlightBorder() {
653     return UIManager.getBorder("Table.focusCellHighlightBorder");
654   }
655
656   public static void setLineStyleAngled(final ClientPropertyHolder component) {
657     component.putClientProperty("JTree.lineStyle", "Angled");
658   }
659
660   public static void setLineStyleAngled(final JTree component) {
661     component.putClientProperty("JTree.lineStyle", "Angled");
662   }
663
664   public static Color getTableFocusCellForeground() {
665   return UIManager.getColor("Table.focusCellForeground");
666   }
667
668   /**
669    * @deprecated  use com.intellij.util.ui.UIUtil#getPanelBackground() instead
670    */
671   public static Color getPanelBackgound() {
672     return getPanelBackground();
673   }
674
675   public static Border getTextFieldBorder() {
676     return UIManager.getBorder("TextField.border");
677   }
678
679   public static Border getButtonBorder() {
680     return UIManager.getBorder("Button.border");
681   }
682
683   public static Icon getErrorIcon() {
684     return UIManager.getIcon("OptionPane.errorIcon");
685   }
686
687   public static Icon getInformationIcon() {
688     return UIManager.getIcon("OptionPane.informationIcon");
689   }
690
691   public static Icon getQuestionIcon() {
692     return UIManager.getIcon("OptionPane.questionIcon");
693   }
694
695   public static Icon getWarningIcon() {
696     return UIManager.getIcon("OptionPane.warningIcon");
697   }
698
699   public static Icon getBalloonInformationIcon() {
700     return IconLoader.getIcon("/general/balloonInformation.png");
701   }
702
703   public static Icon getBalloonWarningIcon() {
704     return IconLoader.getIcon("/general/balloonWarning.png");
705   }
706
707   public static Icon getBalloonErrorIcon() {
708     return IconLoader.getIcon("/general/balloonError.png");
709   }
710
711   public static Icon getRadioButtonIcon() {
712     return UIManager.getIcon("RadioButton.icon");
713   }
714
715   public static Icon getTreeCollapsedIcon() {
716     return UIManager.getIcon("Tree.collapsedIcon");
717   }
718
719   public static Icon getTreeExpandedIcon() {
720     return UIManager.getIcon("Tree.expandedIcon");
721   }
722
723   public static Border getTableHeaderCellBorder() {
724     return UIManager.getBorder("TableHeader.cellBorder");
725   }
726
727   public static Color getWindowColor() {
728     return UIManager.getColor("window");
729   }
730
731   public static Color getTextAreaForeground() {
732     return UIManager.getColor("TextArea.foreground");
733   }
734
735   public static Color getOptionPaneBackground() {
736     return UIManager.getColor("OptionPane.background");
737   }
738
739   @SuppressWarnings({"HardCodedStringLiteral"})
740   public static boolean isUnderQuaquaLookAndFeel() {
741     return UIManager.getLookAndFeel().getName().contains("Quaqua");
742   }
743
744   @SuppressWarnings({"HardCodedStringLiteral"})
745   public static boolean isUnderAlloyLookAndFeel() {
746     return UIManager.getLookAndFeel().getName().contains("Alloy");
747   }
748
749   @SuppressWarnings({"HardCodedStringLiteral"})
750   public static boolean isUnderAlloyIDEALookAndFeel() {
751     return isUnderAlloyLookAndFeel() && UIManager.getLookAndFeel().getName().contains("IDEA");
752   }
753
754   @SuppressWarnings({"HardCodedStringLiteral"})
755   public static boolean isUnderWindowsLookAndFeel() {
756     return UIManager.getLookAndFeel().getName().contains("Windows");
757   }
758
759   @SuppressWarnings({"HardCodedStringLiteral"})
760   public static boolean isUnderMetalLookAndFeel() {
761     return UIManager.getLookAndFeel().getName().equals("Metal");
762   }
763
764   @SuppressWarnings({"HardCodedStringLiteral"})
765   public static boolean isUnderNimbusLookAndFeel() {
766     return UIManager.getLookAndFeel().getName().contains("Nimbus");
767   }
768
769   @SuppressWarnings({"HardCodedStringLiteral"})
770   public static boolean isUnderAquaLookAndFeel() {
771     return SystemInfo.isMac && UIManager.getLookAndFeel().getName().contains("Mac OS X");
772   }
773
774   @SuppressWarnings({"HardCodedStringLiteral"})
775   public static boolean isUnderGTKLookAndFeel() {
776     return UIManager.getLookAndFeel().getName().contains("GTK");
777   }
778
779   @SuppressWarnings({"HardCodedStringLiteral"})
780   @Nullable
781   public static String getGtkThemeName() {
782     final LookAndFeel laf = UIManager.getLookAndFeel();
783     if (laf != null && "GTKLookAndFeel".equals(laf.getClass().getSimpleName())) {
784       try {
785         final Method method = laf.getClass().getDeclaredMethod("getGtkThemeName");
786         method.setAccessible(true);
787         final Object theme = method.invoke(laf);
788         if (theme != null) {
789           return theme.toString();
790         }
791       }
792       catch (Exception ignored) {
793       }
794     }
795     return null;
796   }
797
798   @SuppressWarnings({"HardCodedStringLiteral"})
799   public static boolean isMurrineBasedTheme() {
800     final String gtkTheme = getGtkThemeName();
801     return "Ambiance".equalsIgnoreCase(gtkTheme) ||
802            "Radiance".equalsIgnoreCase(gtkTheme) ||
803            "Dust".equalsIgnoreCase(gtkTheme) ||
804            "Dust Sand".equalsIgnoreCase(gtkTheme);
805   }
806
807   public static Color shade(final Color c, final double factor, final double alphaFactor) {
808     assert factor >= 0 : factor;
809     return new Color(
810       Math.min((int)Math.round(c.getRed() * factor), 255),
811       Math.min((int)Math.round(c.getGreen() * factor), 255),
812       Math.min((int)Math.round(c.getBlue() * factor), 255),
813       Math.min((int)Math.round(c.getAlpha() * alphaFactor), 255)
814     );
815   }
816
817   public static Color mix(final Color c1, final Color c2, final double factor) {
818     assert 0 <= factor && factor <= 1.0 : factor;
819     final double backFactor = 1.0 - factor;
820     return new Color(
821       Math.min((int)Math.round(c1.getRed() * backFactor + c2.getRed() * factor), 255),
822       Math.min((int)Math.round(c1.getGreen() * backFactor + c2.getGreen() * factor), 255),
823       Math.min((int)Math.round(c1.getBlue() * backFactor + c2.getBlue() * factor), 255)
824     );
825   }
826
827   public static boolean isFullRowSelectionLAF() {
828     return isUnderNimbusLookAndFeel() || isUnderQuaquaLookAndFeel();
829   }
830
831   public static boolean isUnderNativeMacLookAndFeel() {
832     return isUnderAquaLookAndFeel() || isUnderQuaquaLookAndFeel();
833   }
834
835   public static int getListCellHPadding() {
836     return isUnderNativeMacLookAndFeel() ? 7 : 2;
837   }
838
839   public static int getListCellVPadding() {
840     return 1;
841   }
842
843   public static Insets getListCellPadding() {
844     return new Insets(getListCellVPadding(), getListCellHPadding(), getListCellVPadding(), getListCellHPadding());
845   }
846
847   public static Insets getListViewportPadding() {
848     return isUnderNativeMacLookAndFeel() ? new Insets(1, 0, 1, 0) : new Insets(5, 5, 5, 5);
849   }
850
851   public static boolean isToUseDottedCellBorder() {
852     return !isUnderNativeMacLookAndFeel();
853   }
854
855   @SuppressWarnings({"HardCodedStringLiteral"})
856   public static void removeQuaquaVisualMarginsIn(Component component) {
857     if (component instanceof JComponent) {
858       final JComponent jComponent = (JComponent)component;
859       final Component[] children = jComponent.getComponents();
860       for (Component child : children) {
861         removeQuaquaVisualMarginsIn(child);
862       }
863
864       jComponent.putClientProperty("Quaqua.Component.visualMargin", new Insets(0, 0, 0, 0));
865     }
866   }
867
868   public static boolean isControlKeyDown(MouseEvent mouseEvent) {
869     return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
870   }
871
872   public static String[] getValidFontNames(final boolean familyName) {
873     Set<String> result = new TreeSet<String>();
874     Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
875     for (Font font : fonts) {
876       // Adds fonts that can display symbols at [A, Z] + [a, z] + [0, 9]
877       try {
878         if (font.canDisplay('a') && font.canDisplay('z') && font.canDisplay('A') && font.canDisplay('Z') && font.canDisplay('0') &&
879             font.canDisplay('1')) {
880           result.add(familyName ? font.getFamily() : font.getName());
881         }
882       }
883       catch (Exception e) {
884         // JRE has problems working with the font. Just skip.
885       }
886     }
887     return ArrayUtil.toStringArray(result);
888   }
889
890   public static String[] getStandardFontSizes() {
891     return new String[]{"8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72"};
892   }
893
894   public static void setupEnclosingDialogBounds(final JComponent component) {
895     component.revalidate();
896     component.repaint();
897     final Window window = SwingUtilities.windowForComponent(component);
898     if (window != null &&
899         (window.getSize().height < window.getMinimumSize().height || window.getSize().width < window.getMinimumSize().width)) {
900       window.pack();
901     }
902   }
903
904   public static String displayPropertiesToCSS(Font font, Color fg) {
905     @NonNls StringBuilder rule = new StringBuilder("body {");
906     if (font != null) {
907       rule.append(" font-family: ");
908       rule.append(font.getFamily());
909       rule.append(" ; ");
910       rule.append(" font-size: ");
911       rule.append(font.getSize());
912       rule.append("pt ;");
913       if (font.isBold()) {
914         rule.append(" font-weight: 700 ; ");
915       }
916       if (font.isItalic()) {
917         rule.append(" font-style: italic ; ");
918       }
919     }
920     if (fg != null) {
921       rule.append(" color: #");
922       appendColor(fg, rule);
923       rule.append(" ; ");
924     }
925     rule.append(" }");
926     return rule.toString();
927   }
928
929   public static void appendColor(final Color color, final StringBuilder sb) {
930     if (color.getRed() < 16) sb.append('0');
931     sb.append(Integer.toHexString(color.getRed()));
932     if (color.getGreen() < 16) sb.append('0');
933     sb.append(Integer.toHexString(color.getGreen()));
934     if (color.getBlue() < 16) sb.append('0');
935     sb.append(Integer.toHexString(color.getBlue()));
936   }
937
938   /**
939    * @param g  graphics.
940    * @param x  top left X coordinate.
941    * @param y  top left Y coordinate.
942    * @param x1 right bottom X coordinate.
943    * @param y1 right bottom Y coordinate.
944    */
945   public static void drawDottedRectangle(Graphics g, int x, int y, int x1, int y1) {
946     int i1;
947     for (i1 = x; i1 <= x1; i1 += 2) {
948       drawLine(g, i1, y, i1, y);
949     }
950
951     for (i1 = i1 != x1 + 1 ? y + 2 : y + 1; i1 <= y1; i1 += 2) {
952       drawLine(g, x1, i1, x1, i1);
953     }
954
955     for (i1 = i1 != y1 + 1 ? x1 - 2 : x1 - 1; i1 >= x; i1 -= 2) {
956       drawLine(g, i1, y1, i1, y1);
957     }
958
959     for (i1 = i1 != x - 1 ? y1 - 2 : y1 - 1; i1 >= y; i1 -= 2) {
960       drawLine(g, x, i1, x, i1);
961     }
962   }
963
964   /**
965    * Should be invoked only in EDT.
966    *
967    * @param g       Graphics surface
968    * @param startX  Line start X coordinate
969    * @param endX    Line end X coordinate
970    * @param lineY   Line Y coordinate
971    * @param bgColor Background color (optional)
972    * @param fgColor Foreground color (optional)
973    * @param opaque  If opaque the image will be dr
974    */
975   public static void drawBoldDottedLine(final Graphics2D g,
976                                         final int startX,
977                                         final int endX,
978                                         final int lineY,
979                                         final Color bgColor,
980                                         final Color fgColor,
981                                         final boolean opaque) {
982     if (SystemInfo.isMac || SystemInfo.isLinux) {
983       drawAppleDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
984     }
985     else {
986       drawBoringDottedLine(g, startX, endX, lineY, bgColor, fgColor, opaque);
987     }
988   }
989
990   public static void drawSearchMatch(final Graphics2D g,
991                                         final int startX,
992                                         final int endX,
993                                         final int height) {
994     Color c1 = new Color(255, 234, 162);
995     Color c2 = new Color(255, 208, 66);
996     drawSearchMatch(g, startX, endX, height, c1, c2);
997   }
998
999   public static void drawSearchMatch(Graphics2D g, int startX, int endX, int height, Color c1, Color c2) {
1000     final boolean drawRound = endX - startX > 4;
1001
1002     final Composite oldComposite = g.getComposite();
1003     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
1004     g.setPaint(new GradientPaint(startX, 2, c1, startX, height - 5, c2));
1005     g.fillRect(startX, 3, endX - startX, height - 5);
1006
1007     if (drawRound) {
1008       g.drawLine(startX - 1, 4, startX - 1, height - 4);
1009       g.drawLine(endX, 4, endX, height - 4);
1010
1011       g.setColor(new Color(100, 100, 100, 50));
1012       g.drawLine(startX - 1, 4, startX - 1, height - 4);
1013       g.drawLine(endX, 4, endX, height - 4);
1014
1015       g.drawLine(startX, 3, endX - 1, 3);
1016       g.drawLine(startX, height - 3, endX - 1, height - 3);
1017     }
1018
1019     g.setComposite(oldComposite);
1020   }
1021
1022   public static void drawRectPickedOut(Graphics2D g, int x, int y, int w, int h) {
1023     g.drawLine(x+1, y, x+w-1, y);
1024     g.drawLine(x+w, y+1, x+w, y+h-1);
1025     g.drawLine(x+w-1, y+h, x+1, y+h);
1026     g.drawLine(x, y+1, x, y+h-1);
1027   }
1028
1029   private static void drawBoringDottedLine(final Graphics2D g,
1030                                            final int startX,
1031                                            final int endX,
1032                                            final int lineY,
1033                                            final Color bgColor,
1034                                            final Color fgColor,
1035                                            final boolean opaque) {
1036     final Color oldColor = g.getColor();
1037
1038     // Fill 2 lines with background color
1039     if (opaque && bgColor != null) {
1040       g.setColor(bgColor);
1041
1042       drawLine(g, startX, lineY, endX, lineY);
1043       drawLine(g, startX, lineY + 1, endX, lineY + 1);
1044     }
1045
1046     // Draw dotted line:
1047     //
1048     // CCC CCC CCC ...
1049     // CCC CCC CCC ...
1050     //
1051     // (where "C" - colored pixel, " " - white pixel)
1052
1053     final int step = 4;
1054     final int startPosCorrection = startX % step < 3 ? 0 : 1;
1055
1056     g.setColor(fgColor != null ? fgColor : oldColor);
1057     // Now draw bold line segments
1058     for (int dotXi = (startX / step + startPosCorrection) * step; dotXi < endX; dotXi += step) {
1059       g.drawLine(dotXi, lineY, dotXi + 1, lineY);
1060       g.drawLine(dotXi, lineY + 1, dotXi + 1, lineY + 1);
1061     }
1062
1063     // restore color
1064     g.setColor(oldColor);
1065   }
1066
1067   public static void drawGradientHToolbarBackground(final Graphics g, final int width, final int height) {
1068     final Graphics2D g2d = (Graphics2D)g;
1069     final GradientPaint gradientPaint = new GradientPaint(0, 0, new Color(220, 220, 220), 0, height, new Color(200, 200, 200));
1070     g2d.setPaint(gradientPaint);
1071     g2d.fillRect(0, 0, width, height);
1072   }
1073
1074   public static void drawDoubleSpaceDottedLine(final Graphics2D g,
1075                                           final int start,
1076                                           final int end,
1077                                           final int xOrY,
1078                                           final Color fgColor,
1079                                           boolean horizontal) {
1080
1081     g.setColor(fgColor);
1082     for (int dot = start; dot < end; dot+=3) {
1083       if (horizontal) {
1084         g.drawLine(dot, xOrY, dot, xOrY);
1085       } else {
1086         g.drawLine(xOrY, dot, xOrY, dot);
1087       }
1088     }
1089
1090   }
1091
1092   private static void drawAppleDottedLine(final Graphics2D g,
1093                                           final int startX,
1094                                           final int endX,
1095                                           final int lineY,
1096                                           final Color bgColor,
1097                                           final Color fgColor,
1098                                           final boolean opaque) {
1099     final Color oldColor = g.getColor();
1100
1101     // Fill 3 lines with background color
1102     if (opaque && bgColor != null) {
1103       g.setColor(bgColor);
1104
1105       drawLine(g, startX, lineY, endX, lineY);
1106       drawLine(g, startX, lineY + 1, endX, lineY + 1);
1107       drawLine(g, startX, lineY + 2, endX, lineY + 2);
1108     }
1109
1110     // Draw apple like dotted line:
1111     //
1112     // CCC CCC CCC ...
1113     // CCC CCC CCC ...
1114     // CCC CCC CCC ...
1115     //
1116     // (where "C" - colored pixel, " " - white pixel)
1117
1118     final int step = 4;
1119     final int startPosCorrection = startX % step < 3 ? 0 : 1;
1120
1121     // Optimization - lets draw dotted line using dot sample image.
1122
1123     // draw one dot by pixel:
1124
1125     // save old settings
1126     final Composite oldComposite = g.getComposite();
1127     // draw image "over" on top of background
1128     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
1129
1130     // sample
1131     final BufferedImage image = getAppleDotStamp(fgColor, oldColor);
1132
1133     // Now copy our dot several times
1134     final int dotX0 = (startX / step + startPosCorrection) * step;
1135     for (int dotXi = dotX0; dotXi < endX; dotXi += step) {
1136       g.drawImage(image, dotXi, lineY, null);
1137     }
1138
1139     //restore previous settings
1140     g.setComposite(oldComposite);
1141   }
1142
1143   private static BufferedImage getAppleDotStamp(final Color fgColor,
1144                                                 final Color oldColor) {
1145     final Color color = fgColor != null ? fgColor : oldColor;
1146
1147     // let's avoid of generating tons of GC and store samples for different colors
1148     BufferedImage sample = ourAppleDotSamples.get(color);
1149     if (sample == null) {
1150       sample = createAppleDotStamp(color);
1151       ourAppleDotSamples.put(color, sample);
1152     }
1153     return sample;
1154   }
1155
1156   private static BufferedImage createAppleDotStamp(final Color color) {
1157     final BufferedImage image = new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB);
1158     final Graphics2D g = image.createGraphics();
1159
1160     g.setColor(color);
1161
1162     // Each dot:
1163     // | 20%  | 50%  | 20% |
1164     // | 80%  | 80%  | 80% |
1165     // | 50%  | 100% | 50% |
1166
1167     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .2f));
1168     g.drawLine(0, 0, 0, 0);
1169     g.drawLine(2, 0, 2, 0);
1170
1171     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
1172     g.drawLine(0, 1, 2, 1);
1173
1174     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f));
1175     g.drawLine(1, 2, 1, 2);
1176
1177     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, .5f));
1178     g.drawLine(1, 0, 1, 0);
1179     g.drawLine(0, 2, 0, 2);
1180     g.drawLine(2, 2, 2, 2);
1181
1182     // dispose graphics
1183     g.dispose();
1184
1185     return image;
1186   }
1187
1188   public static void applyRenderingHints(final Graphics g) {
1189     Toolkit tk = Toolkit.getDefaultToolkit();
1190     //noinspection HardCodedStringLiteral
1191     Map map = (Map)tk.getDesktopProperty("awt.font.desktophints");
1192     if (map != null) {
1193       ((Graphics2D)g).addRenderingHints(map);
1194     }
1195   }
1196
1197   @TestOnly
1198   public static void dispatchAllInvocationEvents() {
1199     assert SwingUtilities.isEventDispatchThread() : Thread.currentThread();
1200     final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1201     while (true) {
1202       AWTEvent event = eventQueue.peekEvent();
1203       if (event == null) break;
1204       try {
1205         AWTEvent event1 = eventQueue.getNextEvent();
1206         if (event1 instanceof InvocationEvent) {
1207           ((InvocationEvent)event1).dispatch();
1208         }
1209       }
1210       catch (Exception e) {
1211         LOG.error(e); //?
1212       }
1213     }
1214   }
1215
1216   @TestOnly
1217   public static void pump() {
1218     assert !SwingUtilities.isEventDispatchThread();
1219     final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
1220     SwingUtilities.invokeLater(new Runnable() {
1221       public void run() {
1222         queue.offer(queue);
1223       }
1224     });
1225     try {
1226       queue.take();
1227     }
1228     catch (InterruptedException e) {
1229       LOG.error(e);
1230     }
1231   }
1232
1233   public static void addAwtListener(final AWTEventListener listener, long mask, Disposable parent) {
1234     Toolkit.getDefaultToolkit().addAWTEventListener(listener, mask);
1235     Disposer.register(parent, new Disposable() {
1236       public void dispose() {
1237         Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
1238       }
1239     });
1240   }
1241
1242   public static void drawVDottedLine(Graphics2D g, int lineX, int startY, int endY, @Nullable final Color bgColor, final Color fgColor) {
1243     if (bgColor != null) {
1244       g.setColor(bgColor);
1245       drawLine(g, lineX, startY, lineX, endY);
1246     }
1247
1248     g.setColor(fgColor);
1249     for (int i = (startY / 2) * 2; i < endY; i += 2) {
1250       g.drawRect(lineX, i, 0, 0);
1251     }
1252   }
1253
1254   public static void drawHDottedLine(Graphics2D g, int startX, int endX, int lineY, @Nullable final Color bgColor, final Color fgColor) {
1255     if (bgColor != null) {
1256       g.setColor(bgColor);
1257       drawLine(g, startX, lineY, endX, lineY);
1258     }
1259
1260     g.setColor(fgColor);
1261
1262     for (int i = (startX / 2) * 2; i < endX; i += 2) {
1263       g.drawRect(i, lineY, 0, 0);
1264     }
1265   }
1266
1267   public static void drawDottedLine(Graphics2D g, int x1, int y1, int x2, int y2, @Nullable final Color bgColor, final Color fgColor) {
1268     if (x1 == x2) {
1269       drawVDottedLine(g, x1, y1, y2, bgColor, fgColor);
1270     }
1271     else if (y1 == y2) {
1272       drawHDottedLine(g, x1, x2, y1, bgColor, fgColor);
1273     }
1274     else {
1275       throw new IllegalArgumentException("Only vertical or horizontal lines are supported");
1276     }
1277   }
1278
1279   public static boolean isFocusAncestor(@NotNull final JComponent component) {
1280     final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1281     if (owner == null) return false;
1282     if (owner == component) return true;
1283     return SwingUtilities.isDescendingFrom(owner, component);
1284   }
1285
1286
1287   public static boolean isCloseClick(MouseEvent e) {
1288     return isCloseClick(e, MouseEvent.MOUSE_PRESSED);
1289   }
1290
1291   public static boolean isCloseClick(MouseEvent e, int effectiveType) {
1292     if (e.isPopupTrigger() || e.getID() != effectiveType) return false;
1293     return e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON1 && e.isShiftDown();
1294   }
1295
1296   public static boolean isActionClick(MouseEvent e) {
1297     return isActionClick(e, MouseEvent.MOUSE_PRESSED);
1298   }
1299
1300   public static boolean isActionClick(MouseEvent e, int effectiveType) {
1301     return isActionClick(e, effectiveType, false);
1302   }
1303
1304   public static boolean isActionClick(MouseEvent e, int effectiveType, boolean allowShift) {
1305     if (!allowShift && isCloseClick(e) || e.isPopupTrigger() || e.getID() != effectiveType) return false;
1306     return e.getButton() == MouseEvent.BUTTON1;
1307   }
1308
1309   @NotNull
1310   public static Color getBgFillColor(@NotNull JComponent c) {
1311     final Component parent = findNearestOpaque(c);
1312     return parent == null ? c.getBackground() : parent.getBackground();
1313   }
1314
1315   @Nullable
1316   public static Component findNearestOpaque(JComponent c) {
1317     Component eachParent = c;
1318     while (eachParent != null) {
1319       if (eachParent.isOpaque()) return eachParent;
1320       eachParent = eachParent.getParent();
1321     }
1322
1323     return eachParent;
1324   }
1325
1326   @NonNls
1327   public static String getCssFontDeclaration(final Font font) {
1328     return getCssFontDeclaration(font, null, null, null);
1329   }
1330
1331   @Language("HTML")
1332   @NonNls
1333   public static String getCssFontDeclaration(final Font font, @Nullable Color fgColor, @Nullable Color linkColor, @Nullable String liImg) {
1334     URL resource = liImg != null ? SystemInfo.class.getResource(liImg) : null;
1335
1336     @NonNls String fontFamilyAndSize = "font-family:" + font.getFamily() + "; font-size:" + font.getSize() + ";";
1337     @NonNls @Language("HTML")
1338     String body = "body, div, td {" + fontFamilyAndSize + " " + (fgColor != null ? "color:" + ColorUtil.toHex(fgColor) : "") + "}";
1339     if (resource != null) {
1340       body +=  "ul {list-style-image: " + resource.toExternalForm() +"}";
1341     }
1342     @NonNls String link = linkColor != null ? "a {" + fontFamilyAndSize + " color:" + ColorUtil.toHex(linkColor) + "}" : "";
1343     return "<style> " + body + " " + link + "</style>";
1344   }
1345
1346   public static boolean isWinLafOnVista() {
1347     return (SystemInfo.isWindowsVista || SystemInfo.isWindows7) && "Windows".equals(UIManager.getLookAndFeel().getName());
1348   }
1349
1350   public static boolean isStandardMenuLAF() {
1351     return isWinLafOnVista() ||
1352            isUnderNimbusLookAndFeel() ||
1353            isUnderGTKLookAndFeel();
1354   }
1355
1356   public static Color getFocusedFillColor() {
1357     return toAlpha(getListSelectionBackground(), 100);
1358   }
1359
1360   public static Color getFocusedBoundsColor() {
1361     return getBoundsColor();
1362   }
1363
1364   public static Color getBoundsColor() {
1365     return getBorderColor();
1366   }
1367
1368   public static Color getBoundsColor(boolean focused) {
1369     return focused ? getFocusedBoundsColor() : getBoundsColor();
1370   }
1371
1372   public static Color toAlpha(final Color color, final int alpha) {
1373     Color actual = color != null ? color : Color.black;
1374     return new Color(actual.getRed(), actual.getGreen(), actual.getBlue(), alpha);
1375   }
1376
1377   public static void requestFocus(@NotNull final JComponent c) {
1378     if (c.isShowing()) {
1379       c.requestFocus();
1380     }
1381     else {
1382       SwingUtilities.invokeLater(new Runnable() {
1383         public void run() {
1384           c.requestFocus();
1385         }
1386       });
1387     }
1388   }
1389
1390   //todo maybe should do for all kind of listeners via the AWTEventMulticaster class
1391
1392   public static void dispose(final Component c) {
1393     if (c == null) return;
1394
1395     final MouseListener[] mouseListeners = c.getMouseListeners();
1396     for (MouseListener each : mouseListeners) {
1397       c.removeMouseListener(each);
1398     }
1399
1400     final MouseMotionListener[] motionListeners = c.getMouseMotionListeners();
1401     for (MouseMotionListener each : motionListeners) {
1402       c.removeMouseMotionListener(each);
1403     }
1404
1405     final MouseWheelListener[] mouseWheelListeners = c.getMouseWheelListeners();
1406     for (MouseWheelListener each : mouseWheelListeners) {
1407       c.removeMouseWheelListener(each);
1408     }
1409   }
1410
1411   public static void disposeProgress(final JProgressBar progress) {
1412     if (!isUnderNativeMacLookAndFeel()) return;
1413
1414     SwingUtilities.invokeLater(new Runnable() {
1415       public void run() {
1416         if (isToDispose(progress)) {
1417           progress.getUI().uninstallUI(progress);
1418           progress.putClientProperty("isDisposed", Boolean.TRUE);
1419         }
1420       }
1421     });
1422   }
1423
1424   private static boolean isToDispose(final JProgressBar progress) {
1425     final ProgressBarUI ui = progress.getUI();
1426
1427     if (ui == null) return false;
1428     if (Boolean.TYPE.equals(progress.getClientProperty("isDisposed"))) return false;
1429
1430     try {
1431       final Field progressBarField = ReflectionUtil.findField(ui.getClass(), JProgressBar.class, "progressBar");
1432       progressBarField.setAccessible(true);
1433       return progressBarField.get(ui) != null;
1434     }
1435     catch (NoSuchFieldException e) {
1436       return true;
1437     }
1438     catch (IllegalAccessException e) {
1439       return true;
1440     }
1441   }
1442
1443   @Nullable
1444   public static Component findUltimateParent(Component c) {
1445     if (c == null) return null;
1446
1447     Component eachParent = c;
1448     while (true) {
1449       if (eachParent.getParent() == null) return eachParent;
1450       eachParent = eachParent.getParent();
1451     }
1452   }
1453
1454   public static void setToolkitModal(final JDialog dialog) {
1455     try {
1456       final Class<?> modalityType = dialog.getClass().getClassLoader().loadClass("java.awt.Dialog$ModalityType");
1457       final Field field = modalityType.getField("TOOLKIT_MODAL");
1458       final Object value = field.get(null);
1459
1460       final Method method = dialog.getClass().getMethod("setModalityType", modalityType);
1461       method.invoke(dialog, value);
1462     }
1463     catch (Exception e) {
1464       // ignore - no JDK 6
1465     }
1466   }
1467
1468   public static void updateDialogIcon(final JDialog dialog, final List<Image> images) {
1469     try {
1470       final Method method = dialog.getClass().getMethod("setIconImages", List.class);
1471       method.invoke(dialog, images);
1472     }
1473     catch (Exception e) {
1474       // ignore - no JDK 6
1475     }
1476   }
1477
1478   public static boolean hasJdk6Dialogs() {
1479     try {
1480       UIUtil.class.getClassLoader().loadClass("java.awt.Dialog$ModalityType");
1481     }
1482     catch (Throwable e) {
1483       return false;
1484     }
1485     return true;
1486   }
1487
1488   public static Color getHeaderActiveColor() {
1489     return ACTIVE_HEADER_COLOR;
1490   }
1491
1492   public static Color getHeaderInactiveColor() {
1493     return INACTIVE_HEADER_COLOR;
1494   }
1495
1496   public static Color getBorderColor() {
1497     return BORDER_COLOR;
1498   }
1499   
1500   public static Color getTitledBorderLineColor() {
1501     return BORDER_COLOR;
1502   }
1503
1504   public static Font getTitledBorderFont() {
1505     return getLabelFont();
1506   }
1507
1508   public static Font getBorderFont(@NotNull FontSize size, boolean isBold) {
1509     Font defFont = getTitledBorderFont();
1510     if (size == FontSize.SMALL) {
1511       defFont = defFont.deriveFont(Math.max(defFont.getSize() - 2f, 11f));
1512     }
1513     if (size == FontSize.MINI) {
1514       defFont = defFont.deriveFont(Math.max(defFont.getSize() - 4f, 9f));
1515     }
1516     if (isBold) {
1517       defFont = defFont.deriveFont(Font.BOLD);
1518     }
1519     return defFont;
1520   }
1521
1522   public static Color getTitledBorderTitleColor() {
1523     return Color.BLACK;
1524   }
1525
1526   /**
1527    * @deprecated use getBorderColor instead
1528    */
1529   public static Color getBorderInactiveColor() {
1530     return getBorderColor();
1531   }
1532
1533   /**
1534    * @deprecated use getBorderColor instead
1535    */
1536   public static Color getBorderActiveColor() {
1537     return getBorderColor();
1538   }
1539
1540   /**
1541    * @deprecated use getBorderColor instead
1542    */
1543   public static Color getBorderSeparatorColor() {
1544     return getBorderColor();
1545   }
1546
1547   public static HTMLEditorKit getHTMLEditorKit() {
1548     final HTMLEditorKit kit = new HTMLEditorKit();
1549
1550     Font font = getLabelFont();
1551     @NonNls String family = font != null ? font.getFamily() : "Tahoma";
1552     int size = font != null ? font.getSize() : 11;
1553
1554     final StyleSheet styleSheet = kit.getStyleSheet();
1555     styleSheet.addRule(String.format("body, div, p { font-family: %s; font-size: %s; } p { margin-top: 0; }", family, size));
1556     kit.setStyleSheet(styleSheet);
1557
1558     return kit;
1559   }
1560
1561   public static void removeScrollBorder(final Component c) {
1562     new AwtVisitor(c) {
1563       public boolean visit(final Component component) {
1564         if (component instanceof JScrollPane) {
1565           if (!hasNonPrimitiveParents(c, component)) {
1566             final JScrollPane scrollPane = (JScrollPane)component;
1567             Integer keepBorderSides = (Integer)scrollPane.getClientProperty(KEEP_BORDER_SIDES);
1568             if (keepBorderSides != null) {
1569               if (scrollPane.getBorder() instanceof LineBorder) {
1570                 Color color = ((LineBorder)scrollPane.getBorder()).getLineColor();
1571                 scrollPane.setBorder(new SideBorder(color, keepBorderSides.intValue()));
1572               }
1573               else {
1574                 scrollPane.setBorder(new SideBorder(getBoundsColor(), keepBorderSides.intValue()));
1575               }
1576             }
1577             else {
1578               scrollPane.setBorder(new SideBorder(getBoundsColor(), SideBorder.NONE));
1579             }
1580           }
1581         }
1582         return false;
1583       }
1584     };
1585   }
1586
1587   public static boolean hasNonPrimitiveParents(Component stopParent, Component c) {
1588     Component eachParent = c.getParent();
1589     while (true) {
1590       if (eachParent == null || eachParent == stopParent) return false;
1591       if (!isPrimitive(eachParent)) return true;
1592       eachParent = eachParent.getParent();
1593     }
1594   }
1595
1596   public static boolean isPrimitive(Component c) {
1597     return c instanceof JPanel || c instanceof JLayeredPane;
1598   }
1599
1600   public static Point getCenterPoint(Dimension container, Dimension child) {
1601     return getCenterPoint(new Rectangle(new Point(), container), child);
1602   }
1603
1604   public static Point getCenterPoint(Rectangle container, Dimension child) {
1605     Point result = new Point();
1606
1607     Point containerLocation = container.getLocation();
1608     Dimension containerSize = container.getSize();
1609
1610     result.x = containerLocation.x + containerSize.width / 2 - child.width / 2;
1611     result.y = containerLocation.y + containerSize.height / 2 - child.height / 2;
1612
1613     return result;
1614   }
1615
1616   public static String toHtml(String html) {
1617     return toHtml(html, 0);
1618   }
1619
1620   @NonNls
1621   public static String toHtml(String html, final int hPadding) {
1622     html = CLOSE_TAG_PATTERN.matcher(html).replaceAll("<$1$2></$1>");
1623     Font font = getLabelFont();
1624     @NonNls String family = font != null ? font.getFamily() : "Tahoma";
1625     int size = font != null ? font.getSize() : 11;
1626     return "<html><style>body { font-family: "
1627            + family + "; font-size: "
1628            + size + ";} ul li {list-style-type:circle;}</style>"
1629            + addPadding(html, hPadding) + "</html>";
1630   }
1631
1632   public static String addPadding(final String html, int hPadding) {
1633     return String.format("<p style=\"margin: 0 %dpx 0 %dpx;\">%s</p>", hPadding, hPadding, html);
1634   }
1635
1636   public static String convertSpace2Nbsp(String html) {
1637     @NonNls StringBuilder result = new StringBuilder();
1638     int currentPos = 0;
1639     int braces = 0;
1640     while (currentPos < html.length()) {
1641       String each = html.substring(currentPos, currentPos + 1);
1642       if ("<".equals(each)) {
1643         braces++;
1644       } else if (">".equals(each)) {
1645         braces--;
1646       }
1647
1648       if (" ".equals(each) && braces == 0) {
1649         result.append("&nbsp;");
1650       } else {
1651         result.append(each);
1652       }
1653       currentPos++;
1654     }
1655
1656     return result.toString();
1657   }
1658
1659   public static void invokeLaterIfNeeded(@NotNull Runnable runnable) {
1660     if (SwingUtilities.isEventDispatchThread()) {
1661       runnable.run();
1662     }
1663     else {
1664       SwingUtilities.invokeLater(runnable);
1665     }
1666   }
1667
1668   /**
1669    * Invoke and wait in the event dispatch thread
1670    * or in the current thread if the current thread
1671    * is event queue thread.
1672    *
1673    * @param runnable a runnable to invoke
1674    */
1675   public static void invokeAndWaitIfNeeded(@NotNull Runnable runnable) {
1676     if (SwingUtilities.isEventDispatchThread()) {
1677       runnable.run();
1678     }
1679     else {
1680       try {
1681         SwingUtilities.invokeAndWait(runnable);
1682       }
1683       catch (Exception e) {
1684         LOG.error(e);
1685       }
1686     }
1687   }
1688
1689   public static boolean isFocusProxy(@Nullable Component c) {
1690     return c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty(FOCUS_PROXY_KEY));
1691   }
1692
1693   public static void setFocusProxy(JComponent c, boolean isProxy) {
1694     c.putClientProperty(FOCUS_PROXY_KEY, isProxy ? Boolean.TRUE : null);
1695   }
1696
1697   public static void maybeInstall(InputMap map, String action, KeyStroke stroke) {
1698     if (map.get(stroke) == null) {
1699       map.put(stroke, action);
1700     }
1701   }
1702
1703   /**
1704    * Avoid blinking while changing background.
1705    * @param component   component.
1706    * @param background  new background.
1707    */
1708   public static void changeBackGround(final Component component, final Color background) {
1709     final Color oldBackGround = component.getBackground();
1710     if (background == null || !background.equals(oldBackGround)){
1711       component.setBackground(background);
1712     }
1713   }
1714
1715   public static void initDefaultLAF(final String productName) {
1716     try {
1717       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1718     }
1719     catch (Exception ignored) { }
1720   }
1721
1722   public static void addKeyboardShortcut(final JComponent target, final AbstractButton button, final KeyStroke keyStroke) {
1723     target.registerKeyboardAction(
1724       new ActionListener() {
1725         public void actionPerformed(ActionEvent e) {
1726           if (button.isEnabled()) {
1727             button.doClick();
1728           }
1729         }
1730       },
1731       keyStroke,
1732       JComponent.WHEN_FOCUSED
1733     );
1734   }
1735
1736   public static void installComboBoxCopyAction(JComboBox comboBox) {
1737     final Component editorComponent = comboBox.getEditor().getEditorComponent();
1738     if (!(editorComponent instanceof JTextComponent)) return;
1739     final InputMap inputMap = ((JTextComponent)editorComponent).getInputMap();
1740     for (KeyStroke keyStroke : inputMap.allKeys()) {
1741       if (DefaultEditorKit.copyAction.equals(inputMap.get(keyStroke))) {
1742         comboBox.getInputMap().put(keyStroke, DefaultEditorKit.copyAction);
1743       }
1744     }
1745     comboBox.getActionMap().put(DefaultEditorKit.copyAction, new AbstractAction() {
1746       @Override
1747       public void actionPerformed(final ActionEvent e) {
1748         if (!(e.getSource() instanceof JComboBox)) return;
1749         final JComboBox comboBox = (JComboBox)e.getSource();
1750         final String text;
1751         final Object selectedItem = comboBox.getSelectedItem();
1752         if (selectedItem instanceof String) {
1753           text = (String)selectedItem;
1754         }
1755         else {
1756           final Component component =
1757             comboBox.getRenderer().getListCellRendererComponent(new JList(), selectedItem, 0, false, false);
1758           if (component instanceof JLabel) {
1759             text = ((JLabel)component).getText();
1760           }
1761           else if (component != null) {
1762             final String str = component.toString();
1763             // skip default Component.toString and handle SimpleColoredComponent case
1764             text = str == null || str.startsWith(component.getClass().getName()+"[")? null : str;
1765           }
1766           else {
1767             text = null;
1768           }
1769         }
1770         if (text != null) {
1771           final JTextField textField = new JTextField(text);
1772           textField.selectAll();
1773           textField.copy();
1774         }
1775       }
1776     });
1777   }
1778
1779   @Nullable
1780   public static ComboPopup getComboBoxPopup(JComboBox comboBox) {
1781     final ComboBoxUI ui = comboBox.getUI();
1782     if (ui instanceof BasicComboBoxUI) {
1783       try {
1784         final Field popup = BasicComboBoxUI.class.getDeclaredField("popup");
1785         popup.setAccessible(true);
1786         return (ComboPopup) popup.get(ui);
1787       }
1788       catch (NoSuchFieldException e) {
1789         return null;
1790       }
1791       catch (IllegalAccessException e) {
1792         return null;
1793       }
1794     }
1795
1796     return null;
1797   }
1798
1799   @SuppressWarnings({"HardCodedStringLiteral"})
1800   public static void fixFormattedField(JFormattedTextField field) {
1801     if (SystemInfo.isMac) {
1802       final Toolkit toolkit = Toolkit.getDefaultToolkit();
1803       final int commandKeyMask = toolkit.getMenuShortcutKeyMask();
1804       final InputMap inputMap = field.getInputMap();
1805       final KeyStroke copyKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C, commandKeyMask);
1806       inputMap.put(copyKeyStroke, "copy-to-clipboard");
1807       final KeyStroke pasteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, commandKeyMask);
1808       inputMap.put(pasteKeyStroke, "paste-from-clipboard");
1809       final KeyStroke cutKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, commandKeyMask);
1810       inputMap.put(cutKeyStroke, "cut-to-clipboard");
1811     }
1812   }
1813
1814   public static class MacTreeUI extends BasicTreeUI {
1815     @NonNls public static final String SOURCE_LIST_CLIENT_PROPERTY = "mac.ui.source.list";
1816     @NonNls public static final String STRIPED_CLIENT_PROPERTY = "mac.ui.striped";
1817
1818     private static final Icon TREE_COLLAPSED_ICON = getTreeCollapsedIcon();
1819     private static final Icon TREE_EXPANDED_ICON = getTreeExpandedIcon();
1820     private static final Icon TREE_SELECTED_COLLAPSED_ICON = IconLoader.getIcon("/mac/tree_white_right_arrow.png");
1821     private static final Icon TREE_SELECTED_EXPANDED_ICON = IconLoader.getIcon("/mac/tree_white_down_arrow.png");
1822
1823     private static final Border LIST_BACKGROUND_PAINTER = (Border)UIManager.get("List.sourceListBackgroundPainter");
1824     private static final Border LIST_SELECTION_BACKGROUND_PAINTER = (Border)UIManager.get("List.sourceListSelectionBackgroundPainter");
1825     private static final Border LIST_FOCUSED_SELECTION_BACKGROUND_PAINTER =
1826       (Border)UIManager.get("List.sourceListFocusedSelectionBackgroundPainter");
1827
1828     private boolean myWideSelection;
1829     private boolean myOldRepaintAllRowValue;
1830
1831     public MacTreeUI() {
1832       this(true);
1833     }
1834
1835     public MacTreeUI(final boolean wideSelection) {
1836       myWideSelection = wideSelection;
1837     }
1838
1839     private final MouseListener mySelectionListener = new MouseAdapter() {
1840       @Override
1841       public void mousePressed(@NotNull final MouseEvent e) {
1842         final JTree tree = (JTree)e.getSource();
1843         if (SwingUtilities.isLeftMouseButton(e) && !e.isPopupTrigger()) {
1844           // if we can't stop any ongoing editing, do nothing
1845           if (isEditing(tree) && tree.getInvokesStopCellEditing() && !stopEditing(tree)) {
1846             return;
1847           }
1848
1849           final TreePath pressedPath = getClosestPathForLocation(tree, e.getX(), e.getY());
1850           if (pressedPath != null) {
1851             Rectangle bounds = getPathBounds(tree, pressedPath);
1852
1853             if (e.getY() >= bounds.y + bounds.height) {
1854               return;
1855             }
1856
1857             if (bounds.contains(e.getPoint()) || isLocationInExpandControl(pressedPath, e.getX(), e.getY())) {
1858               return;
1859             }
1860
1861             if (tree.getDragEnabled() || !startEditing(pressedPath, e)) {
1862               selectPathForEvent(pressedPath, e);
1863             }
1864           }
1865         }
1866       }
1867     };
1868
1869     @Override
1870     protected void completeUIInstall() {
1871       super.completeUIInstall();
1872
1873       myOldRepaintAllRowValue = UIManager.getBoolean("Tree.repaintWholeRow");
1874       UIManager.put("Tree.repaintWholeRow", true);
1875
1876       tree.setShowsRootHandles(true);
1877       tree.addMouseListener(mySelectionListener);
1878
1879       //final InputMap inputMap = tree.getInputMap(JComponent.WHEN_FOCUSED);
1880       //inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "clearSelection");
1881     }
1882
1883     @Override
1884     public void uninstallUI(JComponent c) {
1885       super.uninstallUI(c);
1886
1887       UIManager.put("Tree.repaintWholeRow", myOldRepaintAllRowValue);
1888       c.removeMouseListener(mySelectionListener);
1889     }
1890
1891     @Override
1892     protected void installKeyboardActions() {
1893       super.installKeyboardActions();
1894
1895       if (Boolean.TRUE.equals(tree.getClientProperty("MacTreeUi.actionsInstalled"))) return;
1896
1897       tree.putClientProperty("MacTreeUi.actionsInstalled", Boolean.TRUE);
1898
1899       final InputMap inputMap = tree.getInputMap(JComponent.WHEN_FOCUSED);
1900       inputMap.put(KeyStroke.getKeyStroke("pressed LEFT"), "collapse_or_move_up");
1901       inputMap.put(KeyStroke.getKeyStroke("pressed RIGHT"), "expand");
1902
1903       final ActionMap actionMap = tree.getActionMap();
1904
1905       final Action expandAction = actionMap.get("expand");
1906       if (expandAction != null) {
1907         actionMap.put("expand", new TreeUIAction() {
1908           @Override
1909           public void actionPerformed(ActionEvent e) {
1910             final Object source = e.getSource();
1911             if (source instanceof JTree) {
1912               JTree tree = (JTree)source;
1913               int selectionRow = tree.getLeadSelectionRow();
1914               if (selectionRow != -1) {
1915                 TreePath selectionPath = tree.getPathForRow(selectionRow);
1916                 if (selectionPath != null) {
1917                   boolean leaf = tree.getModel().isLeaf(selectionPath.getLastPathComponent());
1918                   int toSelect = -1;
1919                   int toScroll = -1;
1920                   if (!leaf && tree.isExpanded(selectionRow)) {
1921                     if (selectionRow + 1 < tree.getRowCount()) {
1922                       toSelect = selectionRow + 1;
1923                       toScroll = toSelect;
1924                     }
1925                   } else if (leaf) {
1926                     toScroll = selectionRow;
1927                   }
1928
1929                   if (toSelect != -1) {
1930                     tree.setSelectionInterval(toSelect, toSelect);
1931                   }
1932
1933                   if (toScroll != -1) {
1934                     tree.scrollRowToVisible(toScroll);
1935                   }
1936
1937                   if (toSelect != -1 || toScroll != -1) return;
1938                 }
1939               }
1940             }
1941
1942
1943             expandAction.actionPerformed(e);
1944           }
1945         });
1946       }
1947
1948       actionMap.put("collapse_or_move_up", new TreeUIAction() {
1949         public void actionPerformed(final ActionEvent e) {
1950           final Object source = e.getSource();
1951           if (source instanceof JTree) {
1952             JTree tree = (JTree)source;
1953             int selectionRow = tree.getLeadSelectionRow();
1954             if (selectionRow == -1) return;
1955
1956             TreePath selectionPath = tree.getPathForRow(selectionRow);
1957             if (selectionPath == null) return;
1958
1959             if (tree.getModel().isLeaf(selectionPath.getLastPathComponent()) || tree.isCollapsed(selectionRow)) {
1960               final TreePath parentPath = tree.getPathForRow(selectionRow).getParentPath();
1961               if (parentPath != null) {
1962                 if (parentPath.getParentPath() != null || tree.isRootVisible()) {
1963                   final int parentRow = tree.getRowForPath(parentPath);
1964                   tree.scrollRowToVisible(parentRow);
1965                   tree.setSelectionInterval(parentRow, parentRow);
1966                 }
1967               }
1968             }
1969             else {
1970               tree.collapseRow(selectionRow);
1971             }
1972           }
1973         }
1974       });
1975     }
1976
1977     private abstract static class TreeUIAction extends AbstractAction implements UIResource {
1978     }
1979
1980     @Override
1981     protected void paintHorizontalPartOfLeg(final Graphics g,
1982                                             final Rectangle clipBounds,
1983                                             final Insets insets,
1984                                             final Rectangle bounds,
1985                                             final TreePath path,
1986                                             final int row,
1987                                             final boolean isExpanded,
1988                                             final boolean hasBeenExpanded,
1989                                             final boolean isLeaf) {
1990
1991     }
1992
1993     @Override
1994     protected boolean isToggleSelectionEvent(MouseEvent e) {
1995       return SwingUtilities.isLeftMouseButton(e) && (SystemInfo.isMac ? e.isMetaDown() : e.isControlDown()) && !e.isPopupTrigger();
1996     }
1997
1998     @Override
1999     protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) {
2000     }
2001
2002     @Override
2003     protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) {
2004     }
2005
2006     public boolean isWideSelection() {
2007       return myWideSelection;
2008     }
2009
2010     @Override
2011     protected void paintRow(final Graphics g,
2012                             final Rectangle clipBounds,
2013                             final Insets insets,
2014                             final Rectangle bounds,
2015                             final TreePath path,
2016                             final int row,
2017                             final boolean isExpanded,
2018                             final boolean hasBeenExpanded,
2019                             final boolean isLeaf) {
2020       final int containerWidth = tree.getParent() instanceof JViewport ? tree.getParent().getWidth() : tree.getWidth();
2021       final int xOffset = tree.getParent() instanceof JViewport ? ((JViewport)tree.getParent()).getViewPosition().x : 0;
2022
2023       if (path != null && myWideSelection) {
2024         boolean selected = tree.isPathSelected(path);
2025         Graphics2D rowGraphics = (Graphics2D)g.create();
2026         rowGraphics.setClip(clipBounds);
2027
2028         final Object sourceList = tree.getClientProperty(SOURCE_LIST_CLIENT_PROPERTY);
2029         Color background = tree.getBackground();
2030
2031         if ((row % 2) == 0 && Boolean.TRUE.equals(tree.getClientProperty(STRIPED_CLIENT_PROPERTY))) {
2032           background = UIUtil.DECORATED_ROW_BG_COLOR;
2033         }
2034         
2035         if (sourceList != null && (Boolean)sourceList) {
2036           if (selected) {
2037             if (tree.hasFocus()) {
2038               LIST_FOCUSED_SELECTION_BACKGROUND_PAINTER.paintBorder(tree, rowGraphics, xOffset, bounds.y, containerWidth, bounds.height);
2039             }
2040             else {
2041               LIST_SELECTION_BACKGROUND_PAINTER.paintBorder(tree, rowGraphics, xOffset, bounds.y, containerWidth, bounds.height);
2042             }
2043           }
2044           else {
2045             rowGraphics.setColor(background);
2046             rowGraphics.fillRect(xOffset, bounds.y, containerWidth, bounds.height);
2047           }
2048         }
2049         else {
2050           Color bg = tree.hasFocus() ? getTreeSelectionBackground() : getListUnfocusedSelectionBackground();
2051           if (!selected) {
2052             bg = background;
2053           }
2054
2055           rowGraphics.setColor(bg);
2056           rowGraphics.fillRect(xOffset, bounds.y, containerWidth, bounds.height - 1);
2057         }
2058
2059         if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) {
2060           paintExpandControl(rowGraphics, bounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
2061         }
2062
2063         super.paintRow(rowGraphics, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
2064         rowGraphics.dispose();
2065       }
2066       else {
2067         super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
2068       }
2069     }
2070
2071     @Override
2072     public void paint(Graphics g, JComponent c) {
2073       if (myWideSelection) {
2074         final int containerWidth = tree.getParent() instanceof JViewport ? tree.getParent().getWidth() : tree.getWidth();
2075         final int xOffset = tree.getParent() instanceof JViewport ? ((JViewport)tree.getParent()).getViewPosition().x : 0;
2076         final Rectangle bounds = g.getClipBounds();
2077
2078         // draw background for the given clip bounds
2079         final Object sourceList = tree.getClientProperty(SOURCE_LIST_CLIENT_PROPERTY);
2080         if (sourceList != null && (Boolean)sourceList) {
2081           Graphics2D backgroundGraphics = (Graphics2D)g.create();
2082           backgroundGraphics.setClip(xOffset, bounds.y, containerWidth, bounds.height);
2083           LIST_BACKGROUND_PAINTER.paintBorder(tree, backgroundGraphics, xOffset, bounds.y, containerWidth, bounds.height);
2084           backgroundGraphics.dispose();
2085         }
2086       }
2087
2088       super.paint(g, c);
2089     }
2090
2091     @Override
2092     protected CellRendererPane createCellRendererPane() {
2093       return new CellRendererPane() {
2094         @Override
2095         public void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate) {
2096           if (c instanceof JComponent && myWideSelection) {
2097             ((JComponent)c).setOpaque(false);
2098           }
2099
2100           super.paintComponent(g, c, p, x, y, w, h, shouldValidate);
2101         }
2102       };
2103     }
2104
2105     @Override
2106     protected void paintExpandControl(Graphics g,
2107                                       Rectangle clipBounds,
2108                                       Insets insets,
2109                                       Rectangle bounds,
2110                                       TreePath path,
2111                                       int row,
2112                                       boolean isExpanded,
2113                                       boolean hasBeenExpanded,
2114                                       boolean isLeaf) {
2115       boolean isPathSelected = tree.getSelectionModel().isPathSelected(path);
2116
2117       Icon expandIcon = isPathSelected && tree.hasFocus() ? TREE_SELECTED_EXPANDED_ICON
2118                                                           : TREE_EXPANDED_ICON;
2119       Icon collapseIcon = isPathSelected && tree.hasFocus() ? TREE_SELECTED_COLLAPSED_ICON
2120                                                             : TREE_COLLAPSED_ICON;
2121
2122
2123       if (!isLeaf(row)) {
2124         setExpandedIcon(expandIcon);
2125         setCollapsedIcon(collapseIcon);
2126       }
2127
2128       super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
2129     }
2130   }
2131
2132   public static boolean isPrinting(Graphics g) {
2133     return g instanceof PrintGraphics;
2134   }
2135
2136   public static int getSelectedButton(ButtonGroup group) {
2137     Enumeration<AbstractButton> enumeration = group.getElements();
2138     int i = 0;
2139     while (enumeration.hasMoreElements()) {
2140       AbstractButton button = enumeration.nextElement();
2141       if (group.isSelected(button.getModel())) {
2142         return i;
2143       }
2144       i++;
2145     }
2146     return -1;
2147   }
2148
2149   public static void setSelectedButton(ButtonGroup group, int index) {
2150     Enumeration<AbstractButton> enumeration = group.getElements();
2151     int i = 0;
2152     while (enumeration.hasMoreElements()) {
2153       AbstractButton button = enumeration.nextElement();
2154       group.setSelected(button.getModel(), index == i);
2155       i++;
2156     }
2157   }
2158
2159   public static boolean isSelectionButtonDown(MouseEvent e) {
2160     return e.isShiftDown() || e.isControlDown() || e.isMetaDown();
2161   }
2162
2163   public static void setComboBoxEditorBounds(int x, int y, int width, int height, JComponent editor) {
2164     if(SystemInfo.isMac && isUnderAquaLookAndFeel()) {
2165       // fix for too wide combobox editor, see AquaComboBoxUI.layoutContainer:
2166       // it adds +4 pixels to editor width. WTF?!
2167       editor.reshape(x, y, width - 4, height - 1);
2168     } else {
2169       editor.reshape(x, y, width, height);
2170     }
2171   }
2172
2173   public static int fixComboBoxHeight(final int height) {
2174     return SystemInfo.isMac && isUnderAquaLookAndFeel() ? 28 : height;
2175   }
2176
2177   @Nullable
2178   public static <T> T getParentOfType(Class<? extends T> cls, Component c) {
2179     Component eachParent = c;
2180     while (eachParent != null) {
2181       if (cls.isAssignableFrom(eachParent.getClass())) {
2182         return (T)eachParent;
2183       }
2184
2185       eachParent = eachParent.getParent();
2186     }
2187
2188     return null;
2189   }
2190
2191   public static void scrollListToVisibleIfNeeded(@NotNull final JList list) {
2192     SwingUtilities.invokeLater(new Runnable() {
2193       @Override
2194       public void run() {
2195         final int selectedIndex = list.getSelectedIndex();
2196         if (selectedIndex >= 0) {
2197           final Rectangle visibleRect = list.getVisibleRect();
2198           final Rectangle cellBounds = list.getCellBounds(selectedIndex, selectedIndex);
2199           if (!visibleRect.contains(cellBounds)) {
2200             list.scrollRectToVisible(cellBounds);
2201           }
2202         }
2203       }
2204     });
2205   }
2206
2207   @Nullable
2208   public static <T extends JComponent> T findComponentOfType(JComponent parent, Class<T> cls) {
2209     if (parent == null || cls.isAssignableFrom(parent.getClass())) return (T)parent;
2210     for (Component component : parent.getComponents()) {
2211       if (component instanceof JComponent) {
2212         T comp = findComponentOfType((JComponent)component, cls);
2213         if (comp != null) return comp;
2214       }
2215     }
2216     return null;
2217   }
2218
2219   public static <T extends JComponent> List<T> findComponentsOfType(JComponent parent, Class<T> cls) {
2220     final ArrayList<T> result = new ArrayList<T>();
2221     findComponentsOfType(parent, cls, result);
2222     return result;
2223   }
2224
2225   private static <T extends JComponent> void findComponentsOfType(JComponent parent, Class<T> cls, ArrayList<T> result) {
2226     if (parent == null) return;
2227     if (cls.isAssignableFrom(parent.getClass())) {
2228       result.add((T)parent);
2229     }
2230     for (Component c : parent.getComponents()) {
2231       if (c instanceof JComponent) {
2232         findComponentsOfType((JComponent)c, cls, result);
2233       }
2234     }
2235   }
2236
2237   public static class TextPainter {
2238     private List<Pair<String, LineInfo>> myLines = new ArrayList<Pair<String, LineInfo>>();
2239     private boolean myDrawMacShadow;
2240     private Color myMacShadowColor;
2241     private float myLineSpacing;
2242
2243
2244     public TextPainter() {
2245       this(true, new Color(220, 220, 220), 1.0f);
2246     }
2247
2248     public TextPainter(final float lineSpacing) {
2249       this(true, new Color(220, 220, 220), lineSpacing);
2250     }
2251
2252     public TextPainter(final boolean drawMacShadow, final Color shadowColor, final float lineSpacing) {
2253       myDrawMacShadow = drawMacShadow;
2254       myMacShadowColor = shadowColor;
2255       myLineSpacing = lineSpacing;
2256     }
2257
2258     public TextPainter appendLine(final String text) {
2259       if (text == null || text.length() == 0) return this;
2260       myLines.add(Pair.create(text, new LineInfo()));
2261       return this;
2262     }
2263
2264     public TextPainter underlined(final Color color) {
2265       if (!myLines.isEmpty()) {
2266         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
2267         info.underlined = true;
2268         info.underlineColor = color;
2269       }
2270
2271       return this;
2272     }
2273
2274     public TextPainter withBullet(final char c) {
2275       if (!myLines.isEmpty()) {
2276         final LineInfo info = myLines.get(myLines.size() - 1).getSecond();
2277         info.withBullet = true;
2278         info.bulletChar = c;
2279       }
2280
2281       return this;
2282     }
2283
2284     public TextPainter withBullet() {
2285       return withBullet('\u2022');
2286     }
2287
2288     public TextPainter underlined() {
2289       return underlined(null);
2290     }
2291
2292     public TextPainter smaller() {
2293       if (!myLines.isEmpty()) {
2294         myLines.get(myLines.size() - 1).getSecond().smaller = true;
2295       }
2296
2297       return this;
2298     }
2299
2300     public TextPainter center() {
2301       if (!myLines.isEmpty()) {
2302         myLines.get(myLines.size() - 1).getSecond().center = true;
2303       }
2304
2305       return this;
2306     }
2307
2308     /**
2309      * _position(block width, block height) => (x, y) of the block
2310      */
2311     public void draw(@NotNull final Graphics g, final PairFunction<Integer, Integer, Pair<Integer, Integer>> _position) {
2312       final int[] maxWidth = {0};
2313       final int[] height = {0};
2314       final int[] maxBulletWidth = {0};
2315       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
2316         @Override
2317         public boolean process(final Pair<String, LineInfo> pair) {
2318           final LineInfo info = pair.getSecond();
2319           Font old = null;
2320           if (info.smaller) {
2321             old = g.getFont();
2322             g.setFont(old.deriveFont(old.getSize() * 0.70f));
2323           }
2324
2325           final FontMetrics fm = g.getFontMetrics();
2326
2327           final int bulletWidth = info.withBullet ? fm.stringWidth(" " + info.bulletChar) : 0;
2328           maxBulletWidth[0] = Math.max(maxBulletWidth[0], bulletWidth);
2329
2330           maxWidth[0] = Math.max(fm.stringWidth(pair.getFirst() + bulletWidth), maxWidth[0]);
2331           height[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2332
2333           if (old != null) {
2334             g.setFont(old);
2335           }
2336
2337           return true;
2338         }
2339       });
2340
2341       final Pair<Integer, Integer> position = _position.fun(maxWidth[0] + 20, height[0]);
2342       assert position != null;
2343
2344       final int[] yOffset = {position.getSecond()};
2345       ContainerUtil.process(myLines, new Processor<Pair<String, LineInfo>>() {
2346         @Override
2347         public boolean process(final Pair<String, LineInfo> pair) {
2348           final LineInfo info = pair.getSecond();
2349           Font old = null;
2350           if (info.smaller) {
2351             old = g.getFont();
2352             g.setFont(old.deriveFont(old.getSize() * 0.70f));
2353           }
2354
2355           final int x = position.getFirst() + maxBulletWidth[0] + 10;
2356
2357           final FontMetrics fm = g.getFontMetrics();
2358           int xOffset = x;
2359           if (info.center) {
2360             xOffset = x + (maxWidth[0] - fm.stringWidth(pair.getFirst())) / 2;
2361           }
2362
2363           if (myDrawMacShadow && UIUtil.isUnderAquaLookAndFeel()) {
2364             final Color oldColor = g.getColor();
2365             g.setColor(myMacShadowColor);
2366
2367             if (info.withBullet) {
2368               g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar), yOffset[0] + 1);
2369             }
2370
2371             g.drawString(pair.getFirst(), xOffset, yOffset[0] + 1);
2372             g.setColor(oldColor);
2373           }
2374
2375           if (info.withBullet) {
2376             g.drawString(info.bulletChar + " ", x - fm.stringWidth(" " + info.bulletChar), yOffset[0]);
2377           }
2378
2379           g.drawString(pair.getFirst(), xOffset, yOffset[0]);
2380
2381           if (info.underlined) {
2382             Color c = null;
2383             if (info.underlineColor != null) {
2384               c = g.getColor();
2385               g.setColor(info.underlineColor);
2386             }
2387
2388             g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent(), x + maxWidth[0] + 10, yOffset[0] + fm.getDescent());
2389             if (c != null) {
2390               g.setColor(c);
2391             }
2392
2393             if (myDrawMacShadow && UIUtil.isUnderAquaLookAndFeel()) {
2394               c = g.getColor();
2395               g.setColor(myMacShadowColor);
2396               g.drawLine(x - maxBulletWidth[0] - 10, yOffset[0] + fm.getDescent() + 1, x + maxWidth[0] + 10, yOffset[0] + fm.getDescent() + 1);
2397               g.setColor(c);
2398             }
2399           }
2400
2401           yOffset[0] += (fm.getHeight() + fm.getLeading()) * myLineSpacing;
2402
2403           if (old != null) {
2404             g.setFont(old);
2405           }
2406
2407           return true;
2408         }
2409       });
2410     }
2411
2412     private static class LineInfo {
2413       boolean underlined;
2414       boolean withBullet;
2415       char bulletChar;
2416       Color underlineColor;
2417       boolean smaller;
2418       public boolean center;
2419     }
2420   }
2421
2422   public static JRootPane getRootPane(Component c) {
2423     JRootPane root = getParentOfType(JRootPane.class, c);
2424     if (root != null) return root;
2425     Component eachParent = c;
2426     while (eachParent != null) {
2427       if (eachParent instanceof JComponent) {
2428         WeakReference<JRootPane> pane = (WeakReference<JRootPane>)((JComponent)eachParent).getClientProperty(ROOT_PANE);
2429         if (pane != null) return pane.get();
2430       }
2431       eachParent = eachParent.getParent();
2432     }
2433
2434     return null;
2435   }
2436
2437   public static void setFutureRootPane(JComponent c, JRootPane pane) {
2438     c.putClientProperty(ROOT_PANE, new WeakReference<JRootPane>(pane));
2439   }
2440
2441   public static boolean isMeaninglessFocusOwner(@Nullable Component c) {
2442     if (c == null || !c.isShowing()) return true;
2443
2444     return c instanceof JFrame || c instanceof JDialog || c instanceof JWindow || c instanceof JRootPane || isFocusProxy(c);
2445   }
2446
2447   public static Timer createNamedTimer(@NonNls @NotNull final String name, int delay, @NotNull ActionListener listener) {
2448     return new Timer(delay, listener){
2449       @Override
2450       public String toString() {
2451         return name;
2452       }
2453     };
2454   }
2455
2456   public static boolean isDialogRootPane(JRootPane rootPane) {
2457     if (rootPane != null) {
2458       final Object isDialog = rootPane.getClientProperty("DIALOG_ROOT_PANE");
2459       return isDialog instanceof Boolean && ((Boolean)isDialog).booleanValue();
2460     }
2461     return false;
2462   }
2463
2464   public static void mergeComponentsWithAnchor(PanelWithAnchor c1, PanelWithAnchor c2) {
2465     if (c1 == null || c2 == null) return;
2466
2467     if (c1.getAnchor() == null) {
2468       c1.setAnchor(c2.getAnchor());
2469     } else {
2470       if (c2.getAnchor() == null) {
2471         c2.setAnchor(c1.getAnchor());
2472       } else {
2473         JComponent anchor = c1.getAnchor().getPreferredSize().getWidth() > c2.getAnchor().getPreferredSize().getWidth() ?
2474                             c1.getAnchor() : c2.getAnchor();
2475         c2.setAnchor(anchor);
2476         c1.setAnchor(anchor);
2477       }
2478     }
2479   }
2480   
2481   public static void setNotOpaqueRecursively(@NotNull Component component) {
2482     if (!isUnderAquaLookAndFeel()) return;
2483
2484     if (component.getBackground().equals(getPanelBackground()) || component instanceof JScrollPane || component instanceof JViewport) {
2485       if (component instanceof JComponent) {
2486         ((JComponent)component).setOpaque(false);
2487       }
2488       if (component instanceof Container) {
2489         for (Component c : ((Container)component).getComponents()) {
2490           setNotOpaqueRecursively(c);
2491         }
2492       }
2493     }
2494   }
2495
2496   public static void addInsets(@NotNull JComponent component, @NotNull Insets insets) {
2497     if (component.getBorder() != null) {
2498       component.setBorder(new CompoundBorder(new EmptyBorder(insets), component.getBorder()));
2499     }
2500     else {
2501       component.setBorder(new EmptyBorder(insets));
2502     }
2503   }
2504   
2505   public static Dimension addInsets(@NotNull Dimension dimension, @NotNull Insets insets) {
2506
2507     Dimension ans = new Dimension(dimension);
2508     ans.width += insets.left;
2509     ans.width += insets.right;
2510     ans.height += insets.top;
2511     ans.height += insets.bottom;
2512
2513     return ans;
2514   }
2515 }
2516