replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / ui / laf / LafManagerImpl.java
1 /*
2  * Copyright 2000-2017 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.ide.ui.laf;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.WelcomeWizardUtil;
22 import com.intellij.ide.ui.LafManager;
23 import com.intellij.ide.ui.LafManagerListener;
24 import com.intellij.ide.ui.UISettings;
25 import com.intellij.ide.ui.laf.darcula.DarculaInstaller;
26 import com.intellij.ide.ui.laf.darcula.DarculaLaf;
27 import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.components.*;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.project.ProjectManager;
33 import com.intellij.openapi.ui.DialogWrapper;
34 import com.intellij.openapi.ui.JBPopupMenu;
35 import com.intellij.openapi.ui.Messages;
36 import com.intellij.openapi.ui.popup.util.PopupUtil;
37 import com.intellij.openapi.util.Comparing;
38 import com.intellij.openapi.util.Disposer;
39 import com.intellij.openapi.util.IconLoader;
40 import com.intellij.openapi.util.SystemInfo;
41 import com.intellij.openapi.util.registry.Registry;
42 import com.intellij.openapi.wm.ToolWindow;
43 import com.intellij.openapi.wm.ToolWindowManager;
44 import com.intellij.ui.JBColor;
45 import com.intellij.ui.ScreenUtil;
46 import com.intellij.ui.content.Content;
47 import com.intellij.ui.mac.MacPopupMenuUI;
48 import com.intellij.ui.popup.OurHeavyWeightPopup;
49 import com.intellij.util.IJSwingUtilities;
50 import com.intellij.util.IconUtil;
51 import com.intellij.util.ObjectUtils;
52 import com.intellij.util.ReflectionUtil;
53 import com.intellij.util.containers.ContainerUtil;
54 import com.intellij.util.ui.JBInsets;
55 import com.intellij.util.ui.JBUI;
56 import com.intellij.util.ui.UIUtil;
57 import org.jdom.Element;
58 import org.jetbrains.annotations.NonNls;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
61
62 import javax.swing.*;
63 import javax.swing.event.EventListenerList;
64 import javax.swing.plaf.ColorUIResource;
65 import javax.swing.plaf.FontUIResource;
66 import javax.swing.plaf.UIResource;
67 import javax.swing.plaf.metal.DefaultMetalTheme;
68 import javax.swing.plaf.metal.MetalLookAndFeel;
69 import javax.swing.plaf.synth.Region;
70 import javax.swing.plaf.synth.SynthLookAndFeel;
71 import javax.swing.plaf.synth.SynthStyle;
72 import javax.swing.plaf.synth.SynthStyleFactory;
73 import javax.swing.text.DefaultEditorKit;
74 import java.awt.*;
75 import java.awt.event.InputEvent;
76 import java.awt.event.KeyEvent;
77 import java.awt.event.WindowAdapter;
78 import java.awt.event.WindowEvent;
79 import java.beans.PropertyChangeEvent;
80 import java.beans.PropertyChangeListener;
81 import java.lang.reflect.InvocationTargetException;
82 import java.lang.reflect.Method;
83 import java.util.*;
84 import java.util.List;
85
86 @State(
87   name = "LafManager",
88   storages = {
89     @Storage(value = "laf.xml", roamingType = RoamingType.PER_OS),
90     @Storage(value = "options.xml", deprecated = true)
91   }
92 )
93 public final class LafManagerImpl extends LafManager implements PersistentStateComponent<Element>, Disposable, ApplicationComponent {
94   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.ui.LafManager");
95
96   @NonNls private static final String ELEMENT_LAF = "laf";
97   @NonNls private static final String ATTRIBUTE_CLASS_NAME = "class-name";
98   @NonNls private static final String GNOME_THEME_PROPERTY_NAME = "gnome.Net/ThemeName";
99
100   @NonNls private static final String[] ourPatchableFontResources = {"Button.font", "ToggleButton.font", "RadioButton.font",
101     "CheckBox.font", "ColorChooser.font", "ComboBox.font", "Label.font", "List.font", "MenuBar.font", "MenuItem.font",
102     "MenuItem.acceleratorFont", "RadioButtonMenuItem.font", "CheckBoxMenuItem.font", "Menu.font", "PopupMenu.font", "OptionPane.font",
103     "Panel.font", "ProgressBar.font", "ScrollPane.font", "Viewport.font", "TabbedPane.font", "Table.font", "TableHeader.font",
104     "TextField.font", "FormattedTextField.font", "Spinner.font", "PasswordField.font", "TextArea.font", "TextPane.font", "EditorPane.font",
105     "TitledBorder.font", "ToolBar.font", "ToolTip.font", "Tree.font"};
106
107   @NonNls private static final String[] ourFileChooserTextKeys = {"FileChooser.viewMenuLabelText", "FileChooser.newFolderActionLabelText",
108     "FileChooser.listViewActionLabelText", "FileChooser.detailsViewActionLabelText", "FileChooser.refreshActionLabelText"};
109
110   private static final String[] ourAlloyComponentsToPatchSelection = {"Tree", "MenuItem", "Menu", "List",
111     "ComboBox", "Table", "TextArea", "EditorPane", "TextPane", "FormattedTextField", "PasswordField",
112     "TextField", "RadioButtonMenuItem", "CheckBoxMenuItem"};
113
114   private final EventListenerList myListenerList;
115   private final UIManager.LookAndFeelInfo[] myLaFs;
116   private UIManager.LookAndFeelInfo myCurrentLaf;
117   private final Map<UIManager.LookAndFeelInfo, HashMap<String, Object>> myStoredDefaults = ContainerUtil.newHashMap();
118
119   private static final Map<String, String> ourLafClassesAliases = ContainerUtil.newHashMap();
120   static {
121     ourLafClassesAliases.put("idea.dark.laf.classname", DarculaLookAndFeelInfo.CLASS_NAME);
122   }
123
124   public static boolean useIntelliJInsteadOfAqua() {
125     return Registry.is("ide.mac.yosemite.laf") && isIntelliJLafEnabled() && SystemInfo.isJavaVersionAtLeast("1.8") && SystemInfo.isMacOSYosemite;
126   }
127   /**
128    * Invoked via reflection.
129    */
130   LafManagerImpl() {
131     myListenerList = new EventListenerList();
132
133     List<UIManager.LookAndFeelInfo> lafList = ContainerUtil.newArrayList();
134
135     if (SystemInfo.isMac) {
136       if (useIntelliJInsteadOfAqua()) {
137         lafList.add(new UIManager.LookAndFeelInfo("Default", IntelliJLaf.class.getName()));
138       } else {
139         lafList.add(new UIManager.LookAndFeelInfo("Default", UIManager.getSystemLookAndFeelClassName()));
140       }
141     }
142     else {
143       if (isIntelliJLafEnabled()) {
144         lafList.add(new IntelliJLookAndFeelInfo());
145       }
146       else {
147         lafList.add(new IdeaLookAndFeelInfo());
148       }
149       for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
150         String name = laf.getName();
151         if (!"Metal".equalsIgnoreCase(name)
152             && !"CDE/Motif".equalsIgnoreCase(name)
153             && !"Nimbus".equalsIgnoreCase(name)
154             && !"Windows Classic".equalsIgnoreCase(name)
155             && !name.startsWith("JGoodies")) {
156           lafList.add(laf);
157         }
158       }
159     }
160
161     lafList.add(new DarculaLookAndFeelInfo());
162
163     myLaFs = lafList.toArray(new UIManager.LookAndFeelInfo[lafList.size()]);
164
165     if (!SystemInfo.isMac) {
166       // do not sort LaFs on mac - the order is determined as Default, Darcula.
167       // when we leave only system LaFs on other OSes, the order also should be determined as Default, Darcula
168
169       Arrays.sort(myLaFs, (obj1, obj2) -> {
170         String name1 = obj1.getName();
171         String name2 = obj2.getName();
172         return name1.compareToIgnoreCase(name2);
173       });
174     }
175
176     myCurrentLaf = getDefaultLaf();
177   }
178
179   private static boolean isIntelliJLafEnabled() {
180     return !Registry.is("idea.4.5.laf.enabled");
181   }
182
183   /**
184    * Adds specified listener
185    */
186   @Override
187   public void addLafManagerListener(@NotNull final LafManagerListener l) {
188     myListenerList.add(LafManagerListener.class, l);
189   }
190
191   /**
192    * Removes specified listener
193    */
194   @Override
195   public void removeLafManagerListener(@NotNull final LafManagerListener l) {
196     myListenerList.remove(LafManagerListener.class, l);
197   }
198
199   private void fireLookAndFeelChanged() {
200     LafManagerListener[] listeners = myListenerList.getListeners(LafManagerListener.class);
201     for (LafManagerListener listener : listeners) {
202       listener.lookAndFeelChanged(this);
203     }
204   }
205
206   @Override
207   public void initComponent() {
208     if (myCurrentLaf != null) {
209       final UIManager.LookAndFeelInfo laf = findLaf(myCurrentLaf.getClassName());
210       if (laf != null) {
211         boolean needUninstall = UIUtil.isUnderDarcula();
212         setCurrentLookAndFeel(laf); // setup default LAF or one specified by readExternal.
213         updateWizardLAF(needUninstall);
214       }
215     }
216
217     updateUI();
218
219     if (SystemInfo.isXWindow) {
220       PropertyChangeListener themeChangeListener = new PropertyChangeListener() {
221         @Override
222         public void propertyChange(final PropertyChangeEvent evt) {
223           //noinspection SSBasedInspection
224           SwingUtilities.invokeLater(() -> {
225             fixGtkPopupStyle();
226             patchGtkDefaults(UIManager.getLookAndFeelDefaults());
227           });
228         }
229       };
230       Toolkit.getDefaultToolkit().addPropertyChangeListener(GNOME_THEME_PROPERTY_NAME, themeChangeListener);
231       Disposer.register(this, new Disposable() {
232         @Override
233         public void dispose() {
234           Toolkit.getDefaultToolkit().removePropertyChangeListener(GNOME_THEME_PROPERTY_NAME, themeChangeListener);
235         }
236       });
237     }
238   }
239
240   public void updateWizardLAF(boolean wasUnderDarcula) {
241     if (WelcomeWizardUtil.getWizardLAF() != null) {
242       if (UIUtil.isUnderDarcula()) {
243         DarculaInstaller.install();
244       }
245       else if (wasUnderDarcula) {
246         DarculaInstaller.uninstall();
247       }
248       WelcomeWizardUtil.setWizardLAF(null);
249     }
250   }
251
252   @Override
253   public void dispose() {
254   }
255
256   @Override
257   public void loadState(final Element element) {
258     String className = null;
259     Element lafElement = element.getChild(ELEMENT_LAF);
260     if (lafElement != null) {
261       className = lafElement.getAttributeValue(ATTRIBUTE_CLASS_NAME);
262       if (className != null && ourLafClassesAliases.containsKey(className)) {
263         className = ourLafClassesAliases.get(className);
264       }
265     }
266
267     UIManager.LookAndFeelInfo laf = findLaf(className);
268     // If LAF is undefined (wrong class name or something else) we have set default LAF anyway.
269     if (laf == null) {
270       laf = getDefaultLaf();
271     }
272
273     if (myCurrentLaf != null && !laf.getClassName().equals(myCurrentLaf.getClassName())) {
274       setCurrentLookAndFeel(laf);
275       updateUI();
276     }
277
278     myCurrentLaf = laf;
279   }
280
281   @Override
282   public Element getState() {
283     Element element = new Element("state");
284     if (myCurrentLaf != null) {
285       String className = myCurrentLaf.getClassName();
286       if (className != null) {
287         Element child = new Element(ELEMENT_LAF);
288         child.setAttribute(ATTRIBUTE_CLASS_NAME, className);
289         element.addContent(child);
290       }
291     }
292     return element;
293   }
294
295   @Override
296   public UIManager.LookAndFeelInfo[] getInstalledLookAndFeels() {
297     return myLaFs.clone();
298   }
299
300   @Override
301   public UIManager.LookAndFeelInfo getCurrentLookAndFeel() {
302     return myCurrentLaf;
303   }
304
305   public UIManager.LookAndFeelInfo getDefaultLaf() {
306     String wizardLafName = WelcomeWizardUtil.getWizardLAF();
307     if (wizardLafName != null) {
308       UIManager.LookAndFeelInfo laf = findLaf(wizardLafName);
309       if (laf != null) return laf;
310       LOG.error("Could not find wizard L&F: " + wizardLafName);
311     }
312
313     if (SystemInfo.isMac) {
314       String className = useIntelliJInsteadOfAqua() ? IntelliJLaf.class.getName() : UIManager.getSystemLookAndFeelClassName();
315       UIManager.LookAndFeelInfo laf = findLaf(className);
316       if (laf != null) return laf;
317       LOG.error("Could not find OS X L&F: " + className);
318     }
319
320     String appLafName = WelcomeWizardUtil.getDefaultLAF();
321     if (appLafName != null) {
322       UIManager.LookAndFeelInfo laf = findLaf(appLafName);
323       if (laf != null) return laf;
324       LOG.error("Could not find app L&F: " + appLafName);
325     }
326
327     String defaultLafName = isIntelliJLafEnabled() ? IntelliJLaf.class.getName() : IdeaLookAndFeelInfo.CLASS_NAME;
328     UIManager.LookAndFeelInfo laf = findLaf(defaultLafName);
329     if (laf != null) return laf;
330     throw new IllegalStateException("No default L&F found: " + defaultLafName);
331   }
332
333   @Nullable
334   private UIManager.LookAndFeelInfo findLaf(@Nullable String className) {
335     if (className == null) {
336       return null;
337     }
338     for (UIManager.LookAndFeelInfo laf : myLaFs) {
339       if (Comparing.equal(laf.getClassName(), className)) {
340         return laf;
341       }
342     }
343     return null;
344   }
345
346   /**
347    * Sets current LAF. The method doesn't update component hierarchy.
348    */
349   @Override
350   public void setCurrentLookAndFeel(UIManager.LookAndFeelInfo lookAndFeelInfo) {
351     if (findLaf(lookAndFeelInfo.getClassName()) == null) {
352       LOG.error("unknown LookAndFeel : " + lookAndFeelInfo);
353       return;
354     }
355     // Set L&F
356     if (IdeaLookAndFeelInfo.CLASS_NAME.equals(lookAndFeelInfo.getClassName())) { // that is IDEA default LAF
357       IdeaLaf laf = new IdeaLaf();
358       MetalLookAndFeel.setCurrentTheme(new IdeaBlueMetalTheme());
359       try {
360         UIManager.setLookAndFeel(laf);
361       }
362       catch (Exception e) {
363         Messages.showMessageDialog(
364           IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName(), e.getMessage()),
365           CommonBundle.getErrorTitle(),
366           Messages.getErrorIcon()
367         );
368         return;
369       }
370     }
371     else if (DarculaLookAndFeelInfo.CLASS_NAME.equals(lookAndFeelInfo.getClassName())) {
372       DarculaLaf laf = new DarculaLaf();
373       try {
374         UIManager.setLookAndFeel(laf);
375         JBColor.setDark(true);
376         IconLoader.setUseDarkIcons(true);
377       }
378       catch (Exception e) {
379         Messages.showMessageDialog(
380           IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName(), e.getMessage()),
381           CommonBundle.getErrorTitle(),
382           Messages.getErrorIcon()
383         );
384         return;
385       }
386     }
387     else { // non default LAF
388       try {
389         LookAndFeel laf = ((LookAndFeel)Class.forName(lookAndFeelInfo.getClassName()).newInstance());
390         if (laf instanceof MetalLookAndFeel) {
391           MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
392         }
393         UIManager.setLookAndFeel(laf);
394       }
395       catch (Exception e) {
396         Messages.showMessageDialog(
397           IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName(), e.getMessage()),
398           CommonBundle.getErrorTitle(),
399           Messages.getErrorIcon()
400         );
401         return;
402       }
403     }
404     myCurrentLaf = ObjectUtils.chooseNotNull(findLaf(lookAndFeelInfo.getClassName()), lookAndFeelInfo);
405   }
406
407   public void setLookAndFeelAfterRestart(UIManager.LookAndFeelInfo lookAndFeelInfo) {
408     myCurrentLaf = lookAndFeelInfo;
409   }
410
411   @Nullable
412   private static Icon getAquaMenuDisabledIcon() {
413     final Icon arrowIcon = (Icon)UIManager.get("Menu.arrowIcon");
414     if (arrowIcon != null) {
415       return IconLoader.getDisabledIcon(arrowIcon);
416     }
417
418     return null;
419   }
420
421   @Nullable
422   private static Icon getAquaMenuInvertedIcon() {
423     if (UIUtil.isUnderAquaLookAndFeel() || (SystemInfo.isMac && UIUtil.isUnderIntelliJLaF())) {
424       final Icon arrow = (Icon)UIManager.get("Menu.arrowIcon");
425       if (arrow == null) return null;
426
427       try {
428         final Method method = ReflectionUtil.getMethod(arrow.getClass(), "getInvertedIcon");
429         if (method != null) {
430           return (Icon)method.invoke(arrow);
431         }
432         return null;
433       }
434       catch (InvocationTargetException | IllegalAccessException e1) {
435         return null;
436       }
437     }
438     return null;
439   }
440
441   /**
442    * Updates LAF of all windows. The method also updates font of components
443    * as it's configured in {@code UISettings}.
444    */
445   @Override
446   public void updateUI() {
447     final UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults();
448
449     fixPopupWeight();
450
451     fixGtkPopupStyle();
452
453     fixTreeWideSelection(uiDefaults);
454
455     fixMenuIssues(uiDefaults);
456
457     if (UIUtil.isUnderAquaLookAndFeel()) {
458       uiDefaults.put("Panel.opaque", Boolean.TRUE);
459     }
460     else if (UIUtil.isWinLafOnVista()) {
461       uiDefaults.put("ComboBox.border", null);
462     }
463
464     initInputMapDefaults(uiDefaults);
465
466     uiDefaults.put("Button.defaultButtonFollowsFocus", Boolean.FALSE);
467     uiDefaults.put("Balloon.error.textInsets", new JBInsets(3, 8, 3, 8).asUIResource());
468
469     patchFileChooserStrings(uiDefaults);
470
471     patchLafFonts(uiDefaults);
472
473     patchHiDPI(uiDefaults);
474
475     patchGtkDefaults(uiDefaults);
476
477     fixSeparatorColor(uiDefaults);
478
479     updateToolWindows();
480
481     for (Frame frame : Frame.getFrames()) {
482       // OSX/Aqua fix: Some image caching components like ToolWindowHeader use
483       // com.apple.laf.AquaNativeResources$CColorPaintUIResource
484       // a Java wrapper for ObjC MagicBackgroundColor class (Java RGB values ignored).
485       // MagicBackgroundColor always reports current Frame background.
486       // So we need to set frames background to exact and correct value.
487       if (SystemInfo.isMac) {
488         //noinspection UseJBColor
489         frame.setBackground(new Color(UIUtil.getPanelBackground().getRGB()));
490       }
491
492       updateUI(frame);
493     }
494     fireLookAndFeelChanged();
495   }
496
497   private static void patchHiDPI(UIDefaults defaults) {
498     Object prevScaleVal = defaults.get("hidpi.scaleFactor");
499     // used to normalize previously patched values
500     float prevScale = prevScaleVal != null ? (Float)prevScaleVal : 1f;
501
502     if (prevScale == JBUI.scale(1f) && prevScaleVal != null) return;
503
504     List<String> myIntKeys = Arrays.asList("Tree.leftChildIndent",
505                                            "Tree.rightChildIndent",
506                                            "Tree.rowHeight");
507
508     List<String> myDimensionKeys = Arrays.asList("Slider.horizontalSize",
509                                                  "Slider.verticalSize",
510                                                  "Slider.minimumHorizontalSize",
511                                                  "Slider.minimumVerticalSize");
512
513     for (Map.Entry<Object, Object> entry : defaults.entrySet()) {
514       Object value = entry.getValue();
515       String key = entry.getKey().toString();
516       if (value instanceof Dimension) {
517         if (value instanceof UIResource || myDimensionKeys.contains(key)) {
518           entry.setValue(JBUI.size((Dimension)value).asUIResource());
519         }
520       }
521       else if (value instanceof Insets) {
522         if (value instanceof UIResource) {
523           entry.setValue(JBUI.insets(((Insets)value)).asUIResource());
524         }
525       }
526       else if (value instanceof Integer) {
527         if (key.endsWith(".maxGutterIconWidth") || myIntKeys.contains(key)) {
528           int normValue = (int)((Integer)value / prevScale);
529           entry.setValue(Integer.valueOf(JBUI.scale(normValue)));
530         }
531       }
532     }
533     defaults.put("hidpi.scaleFactor", JBUI.scale(1f));
534   }
535
536   public static void updateToolWindows() {
537     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
538       final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
539       for (String id : toolWindowManager.getToolWindowIds()) {
540         final ToolWindow toolWindow = toolWindowManager.getToolWindow(id);
541         for (Content content : toolWindow.getContentManager().getContents()) {
542           final JComponent component = content.getComponent();
543           if (component != null) {
544             IJSwingUtilities.updateComponentTreeUI(component);
545           }
546         }
547         final JComponent c = toolWindow.getComponent();
548         if (c != null) {
549           IJSwingUtilities.updateComponentTreeUI(c);
550         }
551       }
552     }
553   }
554
555   private static void fixMenuIssues(UIDefaults uiDefaults) {
556     if (UIUtil.isUnderAquaLookAndFeel() || (SystemInfo.isMac && UIUtil.isUnderIntelliJLaF())) {
557       // update ui for popup menu to get round corners
558       uiDefaults.put("PopupMenuUI", MacPopupMenuUI.class.getCanonicalName());
559       uiDefaults.put("Menu.invertedArrowIcon", getAquaMenuInvertedIcon());
560       uiDefaults.put("Menu.disabledArrowIcon", getAquaMenuDisabledIcon());
561     }
562     else if (UIUtil.isUnderJGoodiesLookAndFeel()) {
563       uiDefaults.put("Menu.opaque", true);
564       uiDefaults.put("MenuItem.opaque", true);
565     }
566
567     if ((SystemInfo.isLinux || SystemInfo.isWindows) && (UIUtil.isUnderIntelliJLaF() || UIUtil.isUnderDarcula())) {
568       uiDefaults.put("Menu.arrowIcon", new MenuArrowIcon(AllIcons.Actions.Right));
569     }
570
571     uiDefaults.put("MenuItem.background", UIManager.getColor("Menu.background"));
572   }
573
574   private static void fixTreeWideSelection(UIDefaults uiDefaults) {
575     if (UIUtil.isUnderAlloyIDEALookAndFeel() || UIUtil.isUnderJGoodiesLookAndFeel()) {
576       final Color bg = new ColorUIResource(56, 117, 215);
577       final Color fg = new ColorUIResource(255, 255, 255);
578       uiDefaults.put("info", bg);
579       uiDefaults.put("textHighlight", bg);
580       for (String key : ourAlloyComponentsToPatchSelection) {
581         uiDefaults.put(key + ".selectionBackground", bg);
582         uiDefaults.put(key + ".selectionForeground", fg);
583       }
584     }
585   }
586
587   private static void fixSeparatorColor(UIDefaults uiDefaults) {
588     if (UIUtil.isUnderAquaLookAndFeel()) {
589       uiDefaults.put("Separator.background", UIUtil.AQUA_SEPARATOR_BACKGROUND_COLOR);
590       uiDefaults.put("Separator.foreground", UIUtil.AQUA_SEPARATOR_FOREGROUND_COLOR);
591     }
592   }
593
594   /**
595    * The following code is a trick! By default Swing uses lightweight and "medium" weight
596    * popups to show JPopupMenu. The code below force the creation of real heavyweight menus -
597    * this increases speed of popups and allows to get rid of some drawing artifacts.
598    */
599   private static void fixPopupWeight() {
600     int popupWeight = OurPopupFactory.WEIGHT_MEDIUM;
601     String property = System.getProperty("idea.popup.weight");
602     if (property != null) property = property.toLowerCase(Locale.ENGLISH).trim();
603     if (SystemInfo.isMacOSLeopard) {
604       // force heavy weight popups under Leopard, otherwise they don't have shadow or any kind of border.
605       popupWeight = OurPopupFactory.WEIGHT_HEAVY;
606     }
607     else if (property == null) {
608       // use defaults if popup weight isn't specified
609       if (SystemInfo.isWindows) {
610         popupWeight = OurPopupFactory.WEIGHT_HEAVY;
611       }
612     }
613     else {
614       if ("light".equals(property)) {
615         popupWeight = OurPopupFactory.WEIGHT_LIGHT;
616       }
617       else if ("medium".equals(property)) {
618         popupWeight = OurPopupFactory.WEIGHT_MEDIUM;
619       }
620       else if ("heavy".equals(property)) {
621         popupWeight = OurPopupFactory.WEIGHT_HEAVY;
622       }
623       else {
624         LOG.error("Illegal value of property \"idea.popup.weight\": " + property);
625       }
626     }
627
628     PopupFactory factory = PopupFactory.getSharedInstance();
629     if (!(factory instanceof OurPopupFactory)) {
630       factory = new OurPopupFactory(factory);
631       PopupFactory.setSharedInstance(factory);
632     }
633     PopupUtil.setPopupType(factory, popupWeight);
634   }
635
636   private static void fixGtkPopupStyle() {
637     if (!UIUtil.isUnderGTKLookAndFeel()) return;
638
639     final SynthStyleFactory original = SynthLookAndFeel.getStyleFactory();
640
641     SynthLookAndFeel.setStyleFactory(new SynthStyleFactory() {
642       @Override
643       public SynthStyle getStyle(final JComponent c, final Region id) {
644         final SynthStyle style = original.getStyle(c, id);
645         if (id == Region.POPUP_MENU) {
646           final Integer x = ReflectionUtil.getField(style.getClass(), style, int.class, "xThickness");
647           if (x != null && x == 0) {
648             // workaround for Sun bug #6636964
649             ReflectionUtil.setField(style.getClass(), style, int.class, "xThickness", 1);
650             ReflectionUtil.setField(style.getClass(), style, int.class, "yThickness", 3);
651           }
652         }
653         return style;
654       }
655     });
656
657     new JBPopupMenu();  // invokes updateUI() -> updateStyle()
658
659     SynthLookAndFeel.setStyleFactory(original);
660   }
661
662   private static void patchFileChooserStrings(final UIDefaults defaults) {
663     if (!defaults.containsKey(ourFileChooserTextKeys[0])) {
664       // Alloy L&F does not define strings for names of context menu actions, so we have to patch them in here
665       for (String key : ourFileChooserTextKeys) {
666         defaults.put(key, IdeBundle.message(key));
667       }
668     }
669   }
670
671   private static void patchGtkDefaults(UIDefaults defaults) {
672     if (!UIUtil.isUnderGTKLookAndFeel()) return;
673
674     Map<String, Icon> map = ContainerUtil.newHashMap(
675       Arrays.asList("OptionPane.errorIcon", "OptionPane.informationIcon", "OptionPane.warningIcon", "OptionPane.questionIcon"),
676       Arrays.asList(AllIcons.General.ErrorDialog, AllIcons.General.InformationDialog, AllIcons.General.WarningDialog, AllIcons.General.QuestionDialog));
677     // GTK+ L&F keeps icons hidden in style
678     SynthStyle style = SynthLookAndFeel.getStyle(new JOptionPane(""), Region.DESKTOP_ICON);
679     for (String key : map.keySet()) {
680       if (defaults.get(key) != null) continue;
681
682       Object icon = style == null ? null : style.get(null, key);
683       defaults.put(key, icon instanceof Icon ? icon : map.get(key));
684     }
685
686     Color fg = defaults.getColor("Label.foreground");
687     Color bg = defaults.getColor("Label.background");
688     if (fg != null && bg != null) {
689       defaults.put("Label.disabledForeground", UIUtil.mix(fg, bg, 0.5));
690     }
691   }
692
693   private void patchLafFonts(UIDefaults uiDefaults) {
694     //if (JBUI.isHiDPI()) {
695     //  HashMap<Object, Font> newFonts = new HashMap<Object, Font>();
696     //  for (Object key : uiDefaults.keySet().toArray()) {
697     //    Object val = uiDefaults.get(key);
698     //    if (val instanceof Font) {
699     //      newFonts.put(key, JBFont.create((Font)val));
700     //    }
701     //  }
702     //  for (Map.Entry<Object, Font> entry : newFonts.entrySet()) {
703     //    uiDefaults.put(entry.getKey(), entry.getValue());
704     //  }
705     //} else
706     UISettings uiSettings = UISettings.getInstance();
707     if (uiSettings.getOverrideLafFonts()) {
708       storeOriginalFontDefaults(uiDefaults);
709       initFontDefaults(uiDefaults, uiSettings.getFontSize(), new FontUIResource(uiSettings.getFontFace(), Font.PLAIN, uiSettings.getFontSize()));
710       JBUI.setUserScaleFactor(JBUI.getFontScale(uiSettings.getFontSize()));
711     }
712     else {
713       restoreOriginalFontDefaults(uiDefaults);
714     }
715   }
716
717   private void restoreOriginalFontDefaults(UIDefaults defaults) {
718     UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel();
719     HashMap<String, Object> lfDefaults = myStoredDefaults.get(lf);
720     if (lfDefaults != null) {
721       for (String resource : ourPatchableFontResources) {
722         defaults.put(resource, lfDefaults.get(resource));
723       }
724     }
725     JBUI.setUserScaleFactor(JBUI.getFontScale(JBUI.Fonts.label().getSize()));
726   }
727
728   private void storeOriginalFontDefaults(UIDefaults defaults) {
729     UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel();
730     HashMap<String, Object> lfDefaults = myStoredDefaults.get(lf);
731     if (lfDefaults == null) {
732       lfDefaults = new HashMap<>();
733       for (String resource : ourPatchableFontResources) {
734         lfDefaults.put(resource, defaults.get(resource));
735       }
736       myStoredDefaults.put(lf, lfDefaults);
737     }
738   }
739
740   private static void updateUI(Window window) {
741     IJSwingUtilities.updateComponentTreeUI(window);
742     Window[] children = window.getOwnedWindows();
743     for (Window w : children) {
744       IJSwingUtilities.updateComponentTreeUI(w);
745     }
746   }
747
748   /**
749    * Repaints all displayable window.
750    */
751   @Override
752   public void repaintUI() {
753     Frame[] frames = Frame.getFrames();
754     for (Frame frame : frames) {
755       repaintUI(frame);
756     }
757   }
758
759   private static void repaintUI(Window window) {
760     if (!window.isDisplayable()) {
761       return;
762     }
763     window.repaint();
764     Window[] children = window.getOwnedWindows();
765     for (Window aChildren : children) {
766       repaintUI(aChildren);
767     }
768   }
769
770   private static void installCutCopyPasteShortcuts(InputMap inputMap, boolean useSimpleActionKeys) {
771     String copyActionKey = useSimpleActionKeys ? "copy" : DefaultEditorKit.copyAction;
772     String pasteActionKey = useSimpleActionKeys ? "paste" : DefaultEditorKit.pasteAction;
773     String cutActionKey = useSimpleActionKeys ? "cut" : DefaultEditorKit.cutAction;
774     // Ctrl+Ins, Shift+Ins, Shift+Del
775     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), copyActionKey);
776     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.SHIFT_MASK | InputEvent.SHIFT_DOWN_MASK), pasteActionKey);
777     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_MASK | InputEvent.SHIFT_DOWN_MASK), cutActionKey);
778     // Ctrl+C, Ctrl+V, Ctrl+X
779     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), copyActionKey);
780     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), pasteActionKey);
781     inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), DefaultEditorKit.cutAction);
782   }
783
784   @SuppressWarnings({"HardCodedStringLiteral"})
785   public static void initInputMapDefaults(UIDefaults defaults) {
786     // Make ENTER work in JTrees
787     InputMap treeInputMap = (InputMap)defaults.get("Tree.focusInputMap");
788     if (treeInputMap != null) { // it's really possible. For example,  GTK+ doesn't have such map
789       treeInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "toggle");
790     }
791     // Cut/Copy/Paste in JTextAreas
792     InputMap textAreaInputMap = (InputMap)defaults.get("TextArea.focusInputMap");
793     if (textAreaInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem)
794       installCutCopyPasteShortcuts(textAreaInputMap, false);
795     }
796     // Cut/Copy/Paste in JTextFields
797     InputMap textFieldInputMap = (InputMap)defaults.get("TextField.focusInputMap");
798     if (textFieldInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem)
799       installCutCopyPasteShortcuts(textFieldInputMap, false);
800     }
801     // Cut/Copy/Paste in JPasswordField
802     InputMap passwordFieldInputMap = (InputMap)defaults.get("PasswordField.focusInputMap");
803     if (passwordFieldInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem)
804       installCutCopyPasteShortcuts(passwordFieldInputMap, false);
805     }
806     // Cut/Copy/Paste in JTables
807     InputMap tableInputMap = (InputMap)defaults.get("Table.ancestorInputMap");
808     if (tableInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem)
809       installCutCopyPasteShortcuts(tableInputMap, true);
810     }
811   }
812
813   @SuppressWarnings({"HardCodedStringLiteral"})
814   public static void initFontDefaults(UIDefaults defaults, int fontSize, FontUIResource uiFont) {
815     defaults.put("Tree.ancestorInputMap", null);
816     FontUIResource textFont = new FontUIResource("Serif", Font.PLAIN, fontSize);
817     FontUIResource monoFont = new FontUIResource("Monospaced", Font.PLAIN, fontSize);
818
819     for (String fontResource : ourPatchableFontResources) {
820       defaults.put(fontResource, uiFont);
821     }
822
823     if (!SystemInfo.isMac) {
824       defaults.put("PasswordField.font", monoFont);
825     }
826     defaults.put("TextArea.font", monoFont);
827     defaults.put("TextPane.font", textFont);
828     defaults.put("EditorPane.font", textFont);
829   }
830
831
832   private static class OurPopupFactory extends PopupFactory {
833     public static final int WEIGHT_LIGHT = 0;
834     public static final int WEIGHT_MEDIUM = 1;
835     public static final int WEIGHT_HEAVY = 2;
836
837     private final PopupFactory myDelegate;
838
839     public OurPopupFactory(final PopupFactory delegate) {
840       myDelegate = delegate;
841     }
842
843     @Override
844     public Popup getPopup(final Component owner, final Component contents, final int x, final int y) throws IllegalArgumentException {
845       final Point point = fixPopupLocation(contents, x, y);
846
847       final int popupType = UIUtil.isUnderGTKLookAndFeel() ? WEIGHT_HEAVY : PopupUtil.getPopupType(this);
848       if (popupType == WEIGHT_HEAVY && OurHeavyWeightPopup.isEnabled()) {
849         return new OurHeavyWeightPopup(owner, contents, point.x, point.y);
850       }
851       if (popupType >= 0) {
852         PopupUtil.setPopupType(myDelegate, popupType);
853       }
854
855       Popup popup = myDelegate.getPopup(owner, contents, point.x, point.y);
856       Window window = UIUtil.getWindow(contents);
857       String cleanupKey = "LafManagerImpl.rootPaneCleanup";
858       if (window instanceof RootPaneContainer && window != UIUtil.getWindow(owner) &&
859           ((RootPaneContainer)window).getRootPane().getClientProperty(cleanupKey) == null) {
860         ((RootPaneContainer)window).getRootPane().putClientProperty(cleanupKey, cleanupKey);
861         window.addWindowListener(new WindowAdapter() {
862           @Override
863           public void windowOpened(WindowEvent e) {
864             // cleanup will be handled by AbstractPopup wrapper
865             if (PopupUtil.getPopupContainerFor(((RootPaneContainer)window).getRootPane()) != null) {
866               window.removeWindowListener(this);
867               ((RootPaneContainer)window).getRootPane().putClientProperty(cleanupKey, null);
868             }
869           }
870
871           @Override
872           public void windowClosed(WindowEvent e) {
873             window.removeWindowListener(this);
874             ((RootPaneContainer)window).getRootPane().putClientProperty(cleanupKey, null);
875             DialogWrapper.cleanupRootPane(((RootPaneContainer)window).getRootPane());
876             DialogWrapper.cleanupWindowListeners(window);
877           }
878         });
879       }
880       fixPopupSize(popup, contents);
881       return popup;
882     }
883
884     private static Point fixPopupLocation(final Component contents, final int x, final int y) {
885       if (!(contents instanceof JToolTip)) return new Point(x, y);
886
887       final PointerInfo info;
888       try {
889         info = MouseInfo.getPointerInfo();
890       }
891       catch (InternalError e) {
892         // http://www.jetbrains.net/jira/browse/IDEADEV-21390
893         // may happen under Mac OSX 10.5
894         return new Point(x, y);
895       }
896       int deltaY = 0;
897
898       if (info != null) {
899         final Point mouse = info.getLocation();
900         deltaY = mouse.y - y;
901       }
902
903       final Dimension size = contents.getPreferredSize();
904       final Rectangle rec = new Rectangle(new Point(x, y), size);
905       ScreenUtil.moveRectangleToFitTheScreen(rec);
906
907       if (rec.y < y) {
908         rec.y += deltaY;
909       }
910
911       return rec.getLocation();
912     }
913
914     private static void fixPopupSize(final Popup popup, final Component contents) {
915       if (!UIUtil.isUnderGTKLookAndFeel() || !(contents instanceof JPopupMenu)) return;
916
917       for (Class<?> aClass = popup.getClass(); aClass != null && Popup.class.isAssignableFrom(aClass); aClass = aClass.getSuperclass()) {
918         try {
919           final Method getComponent = aClass.getDeclaredMethod("getComponent");
920           getComponent.setAccessible(true);
921           final Object component = getComponent.invoke(popup);
922           if (component instanceof JWindow) {
923             ((JWindow)component).setSize(new Dimension(0, 0));
924           }
925           break;
926         }
927         catch (Exception ignored) {
928         }
929       }
930     }
931   }
932
933   private static class MenuArrowIcon implements Icon, UIResource {
934     private final Icon icon;
935     private final Icon selectedIcon;
936     private final Icon grayIcon;
937
938     private MenuArrowIcon(Icon icon) {
939       boolean invert = UIUtil.isUnderDarcula();
940       this.icon = invert ? IconUtil.brighter(icon, 2) : IconUtil.darker(icon, 2);
941       this.grayIcon = invert ? IconUtil.darker(icon, 2) : IconUtil.brighter(icon, 2);
942       this.selectedIcon = IconUtil.brighter(icon, 8);
943     }
944
945     @Override public void paintIcon(Component c, Graphics g, int x, int y) {
946       JMenuItem b = (JMenuItem) c;
947       ButtonModel model = b.getModel();
948
949       if (!model.isEnabled()) {
950         grayIcon.paintIcon(c, g, x, y);
951       } else if (model.isArmed() || ( c instanceof JMenu && model.isSelected())) {
952         selectedIcon.paintIcon(c, g, x, y);
953       }
954       else {
955         icon.paintIcon(c, g, x, y);
956       }
957     }
958
959     @Override public int getIconWidth() {
960       return icon.getIconWidth();
961     }
962
963     @Override public int getIconHeight() {
964       return icon.getIconHeight();
965     }
966   }
967 }