/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.ide.ui.laf; import com.intellij.CommonBundle; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.ide.IdeBundle; import com.intellij.ide.ui.LafManager; import com.intellij.ide.ui.LafManagerListener; import com.intellij.ide.ui.UISettings; import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo; import com.intellij.ide.ui.laf.intellij.IntelliJLaf; import com.intellij.ide.ui.laf.intellij.IntelliJLookAndFeelInfo; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.util.PopupUtil; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.ui.JBColor; import com.intellij.ui.ScreenUtil; import com.intellij.ui.content.Content; import com.intellij.ui.mac.MacPopupMenuUI; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import consulo.actionSystem.ex.ComboBoxButtonUI; import consulo.ide.ui.laf.MacDefaultLookAndFeelInfo; import consulo.ide.ui.laf.intellij.ActionButtonUI; import consulo.ide.ui.laf.modernDark.ModernDarkLookAndFeelInfo; import consulo.ide.ui.laf.modernWhite.ModernWhiteLookAndFeelInfo; import consulo.ide.ui.laf.modernWhite.NativeModernWhiteLookAndFeelInfo; import consulo.ui.laf.MacButtonlessScrollbarUI; import consulo.util.ui.BuildInLookAndFeel; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.EventListenerList; import javax.swing.plaf.FontUIResource; import javax.swing.plaf.metal.DefaultMetalTheme; import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.plaf.synth.Region; import javax.swing.plaf.synth.SynthLookAndFeel; import javax.swing.plaf.synth.SynthStyle; import javax.swing.plaf.synth.SynthStyleFactory; import javax.swing.text.DefaultEditorKit; import java.awt.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; /** * @author Eugene Belyaev * @author Vladimir Kondratyev */ @State(name = "LafManager", storages = { @Storage(file = StoragePathMacros.APP_CONFIG + "/options.xml", deprecated = true), @Storage(file = StoragePathMacros.APP_CONFIG + "/laf.xml", roamingType = RoamingType.PER_PLATFORM) }) public final class LafManagerImpl extends LafManager implements ApplicationComponent, PersistentStateComponent<Element> { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.ui.LafManager"); @NonNls private static final String ELEMENT_LAF = "laf"; @NonNls private static final String ATTRIBUTE_CLASS_NAME = "class-name"; @NonNls private static final String GNOME_THEME_PROPERTY_NAME = "gnome.Net/ThemeName"; @NonNls private static final String[] ourPatchableFontResources = {"Button.font", "ToggleButton.font", "RadioButton.font", "CheckBox.font", "ColorChooser.font", "ComboBox.font", "Label.font", "List.font", "MenuBar.font", "MenuItem.font", "MenuItem.acceleratorFont", "RadioButtonMenuItem.font", "CheckBoxMenuItem.font", "Menu.font", "PopupMenu.font", "OptionPane.font", "Panel.font", "ProgressBar.font", "ScrollPane.font", "Viewport.font", "TabbedPane.font", "Table.font", "TableHeader.font", "TextField.font", "PasswordField.font", "TextArea.font", "TextPane.font", "EditorPane.font", "TitledBorder.font", "ToolBar.font", "ToolTip.font", "Tree.font"}; @NonNls private static final String[] ourFileChooserTextKeys = {"FileChooser.viewMenuLabelText", "FileChooser.newFolderActionLabelText", "FileChooser.listViewActionLabelText", "FileChooser.detailsViewActionLabelText", "FileChooser.refreshActionLabelText"}; @NonNls private static final String[] ourOptionPaneIconKeys = {"OptionPane.errorIcon", "OptionPane.informationIcon", "OptionPane.warningIcon", "OptionPane.questionIcon"}; private final EventListenerList myListenerList; private final UIManager.LookAndFeelInfo[] myLaFs; private UIManager.LookAndFeelInfo myCurrentLaf; private final HashMap<UIManager.LookAndFeelInfo, HashMap<String, Object>> myStoredDefaults = new HashMap<>(); private String myLastWarning = null; private PropertyChangeListener myThemeChangeListener = null; /** * Invoked via reflection. */ LafManagerImpl() { myListenerList = new EventListenerList(); List<UIManager.LookAndFeelInfo> lafList = ContainerUtil.newArrayList(); if (SystemInfo.isMac) { lafList.add(new MacDefaultLookAndFeelInfo("Default", UIManager.getSystemLookAndFeelClassName())); } else { lafList.add(new IntelliJLookAndFeelInfo()); } if (!SystemInfo.isMac) { if (SystemInfo.isWin8OrNewer) { lafList.add(new NativeModernWhiteLookAndFeelInfo()); } lafList.add(new ModernWhiteLookAndFeelInfo()); lafList.add(new ModernDarkLookAndFeelInfo()); } lafList.add(new DarculaLookAndFeelInfo()); myLaFs = lafList.toArray(new UIManager.LookAndFeelInfo[lafList.size()]); myCurrentLaf = getDefaultLaf(); } /** * Adds specified listener */ @Override public void addLafManagerListener(@NotNull final LafManagerListener l) { myListenerList.add(LafManagerListener.class, l); } /** * Removes specified listener */ @Override public void removeLafManagerListener(@NotNull final LafManagerListener l) { myListenerList.remove(LafManagerListener.class, l); } private void fireLookAndFeelChanged() { LafManagerListener[] listeners = myListenerList.getListeners(LafManagerListener.class); for (LafManagerListener listener : listeners) { listener.lookAndFeelChanged(this); } } @Override @NotNull public String getComponentName() { return "LafManager"; } @Override public void initComponent() { if (myCurrentLaf != null) { final UIManager.LookAndFeelInfo laf = findLaf(myCurrentLaf.getClassName()); if (laf != null) { setCurrentLookAndFeel(laf); // setup default LAF or one specified by readExternal. } } updateUI(); if (SystemInfo.isXWindow) { myThemeChangeListener = new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent evt) { //noinspection SSBasedInspection SwingUtilities.invokeLater(new Runnable() { @Override public void run() { fixGtkPopupStyle(); patchOptionPaneIcons(UIManager.getLookAndFeelDefaults()); } }); } }; Toolkit.getDefaultToolkit().addPropertyChangeListener(GNOME_THEME_PROPERTY_NAME, myThemeChangeListener); } } @Override public void disposeComponent() { if (myThemeChangeListener != null) { Toolkit.getDefaultToolkit().removePropertyChangeListener(GNOME_THEME_PROPERTY_NAME, myThemeChangeListener); myThemeChangeListener = null; } } @Override public void loadState(final Element element) { String className = null; for (final Object o : element.getChildren()) { Element child = (Element)o; if (ELEMENT_LAF.equals(child.getName())) { className = child.getAttributeValue(ATTRIBUTE_CLASS_NAME); break; } } UIManager.LookAndFeelInfo laf = findLaf(className); // If LAF is undefined (wrong class name or something else) we have set default LAF anyway. if (laf == null) { laf = getDefaultLaf(); } if (myCurrentLaf != null && !laf.getClassName().equals(myCurrentLaf.getClassName())) { setCurrentLookAndFeel(laf); updateUI(); } myCurrentLaf = laf; } @Override public Element getState() { Element element = new Element("state"); if (myCurrentLaf != null) { String className = myCurrentLaf.getClassName(); if (className != null) { Element child = new Element(ELEMENT_LAF); child.setAttribute(ATTRIBUTE_CLASS_NAME, className); element.addContent(child); } } return element; } @Override public UIManager.LookAndFeelInfo[] getInstalledLookAndFeels() { return myLaFs.clone(); } @Override public UIManager.LookAndFeelInfo getCurrentLookAndFeel() { return myCurrentLaf; } /** * @return default LookAndFeelInfo for the running OS. For Win32 and * Linux the method returns Alloy LAF or IDEA LAF if first not found, for Mac OS X it returns Aqua * RubyMine uses Native L&F for linux as well */ private UIManager.LookAndFeelInfo getDefaultLaf() { final String systemLafClassName = UIManager.getSystemLookAndFeelClassName(); if (SystemInfo.isMac) { UIManager.LookAndFeelInfo laf = findLaf(systemLafClassName); LOG.assertTrue(laf != null); return laf; } // Default UIManager.LookAndFeelInfo ideaLaf = findLaf(IntelliJLaf.class.getName()); if (ideaLaf != null) { return ideaLaf; } throw new IllegalStateException("No default look&feel found"); } /** * Finds LAF by its class name. * will be returned. */ @Nullable private UIManager.LookAndFeelInfo findLaf(@Nullable String className) { if (className == null) { return null; } for (UIManager.LookAndFeelInfo laf : myLaFs) { if (Comparing.equal(laf.getClassName(), className)) { return laf; } } return null; } /** * Sets current LAF. The method doesn't update component hierarchy. */ @Override public void setCurrentLookAndFeel(UIManager.LookAndFeelInfo lookAndFeelInfo) { if (findLaf(lookAndFeelInfo.getClassName()) == null) { LOG.error("unknown LookAndFeel : " + lookAndFeelInfo); return; } try { LookAndFeel laf = ((LookAndFeel)Class.forName(lookAndFeelInfo.getClassName()).newInstance()); if (laf instanceof MetalLookAndFeel) { MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); } boolean dark = laf instanceof BuildInLookAndFeel && ((BuildInLookAndFeel)laf).isDark(); JBColor.setDark(dark); IconLoader.setUseDarkIcons(dark); fireUpdate(); UIManager.setLookAndFeel(laf); } catch (Exception e) { Messages.showMessageDialog(IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName(), e.getMessage()), CommonBundle.getErrorTitle(), Messages.getErrorIcon()); return; } myCurrentLaf = lookAndFeelInfo; checkLookAndFeel(lookAndFeelInfo, false); } private static void fireUpdate() { UISettings.getInstance().fireUISettingsChanged(); EditorFactory.getInstance().refreshAllEditors(); Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); for (Project openProject : openProjects) { FileStatusManager.getInstance(openProject).fileStatusesChanged(); DaemonCodeAnalyzer.getInstance(openProject).restart(); } for (IdeFrame frame : WindowManagerEx.getInstanceEx().getAllProjectFrames()) { if (frame instanceof IdeFrameImpl) { ((IdeFrameImpl)frame).updateView(); } } ActionToolbarImpl.updateAllToolbarsImmediately(); } public void setLookAndFeelAfterRestart(UIManager.LookAndFeelInfo lookAndFeelInfo) { myCurrentLaf = lookAndFeelInfo; } @Nullable private static Icon getAquaMenuDisabledIcon() { final Icon arrowIcon = (Icon)UIManager.get("Menu.arrowIcon"); if (arrowIcon != null) { return IconLoader.getDisabledIcon(arrowIcon); } return null; } @Nullable private static Icon getAquaMenuInvertedIcon() { if (!UIUtil.isUnderAquaLookAndFeel()) return null; final Icon arrow = (Icon)UIManager.get("Menu.arrowIcon"); if (arrow == null) return null; try { final Method method = arrow.getClass().getMethod("getInvertedIcon"); if (method != null) { method.setAccessible(true); return (Icon)method.invoke(arrow); } return null; } catch (NoSuchMethodException e1) { return null; } catch (InvocationTargetException e1) { return null; } catch (IllegalAccessException e1) { return null; } } @Override public boolean checkLookAndFeel(UIManager.LookAndFeelInfo lookAndFeelInfo) { return checkLookAndFeel(lookAndFeelInfo, true); } private boolean checkLookAndFeel(final UIManager.LookAndFeelInfo lafInfo, final boolean confirm) { String message = null; if (lafInfo.getName().contains("GTK") && SystemInfo.isXWindow && !SystemInfo.isJavaVersionAtLeast("1.6.0_12")) { message = IdeBundle.message("warning.problem.laf.1"); } if (message != null) { if (confirm) { final String[] options = {IdeBundle.message("confirm.set.look.and.feel"), CommonBundle.getCancelButtonText()}; final int result = Messages.showOkCancelDialog(message, CommonBundle.getWarningTitle(), options[0], options[1], Messages.getWarningIcon()); if (result == 0) { myLastWarning = message; return true; } return false; } if (!message.equals(myLastWarning)) { Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "L&F Manager", message, NotificationType.WARNING, NotificationListener.URL_OPENING_LISTENER)); myLastWarning = message; } } return true; } /** * Updates LAF of all windows. The method also updates font of components * as it's configured in <code>UISettings</code>. */ @Override public void updateUI() { final UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults(); fixPopupWeight(); fixGtkPopupStyle(); fixMenuIssues(uiDefaults); if (UIUtil.isUnderAquaLookAndFeel()) { uiDefaults.put("Panel.opaque", Boolean.TRUE); uiDefaults.put("ScrollBarUI", MacButtonlessScrollbarUI.class.getName()); uiDefaults.put("ComboBoxButtonUI", ComboBoxButtonUI.class.getName()); uiDefaults.put("ActionButtonUI", ActionButtonUI.class.getName()); } else if (UIUtil.isWinLafOnVista()) { uiDefaults.put("ComboBox.border", null); } initInputMapDefaults(uiDefaults); uiDefaults.put("Button.defaultButtonFollowsFocus", Boolean.FALSE); patchFileChooserStrings(uiDefaults); patchLafFonts(uiDefaults); patchOptionPaneIcons(uiDefaults); fixSeparatorColor(uiDefaults); updateToolWindows(); for (Frame frame : Frame.getFrames()) { updateUI(frame); } fireLookAndFeelChanged(); } public static void updateToolWindows() { for (Project project : ProjectManager.getInstance().getOpenProjects()) { final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); for (String id : toolWindowManager.getToolWindowIds()) { final ToolWindow toolWindow = toolWindowManager.getToolWindow(id); for (Content content : toolWindow.getContentManager().getContents()) { final JComponent component = content.getComponent(); if (component != null) { IJSwingUtilities.updateComponentTreeUI(component); } } final JComponent c = toolWindow.getComponent(); if (c != null) { IJSwingUtilities.updateComponentTreeUI(c); } } } } private static void fixMenuIssues(UIDefaults uiDefaults) { if (UIUtil.isUnderAquaLookAndFeel()) { // update ui for popup menu to get round corners uiDefaults.put("PopupMenuUI", MacPopupMenuUI.class.getCanonicalName()); uiDefaults.put("Menu.invertedArrowIcon", getAquaMenuInvertedIcon()); uiDefaults.put("Menu.disabledArrowIcon", getAquaMenuDisabledIcon()); } uiDefaults.put("MenuItem.background", UIManager.getColor("Menu.background")); } private static void fixSeparatorColor(UIDefaults uiDefaults) { if (UIUtil.isUnderAquaLookAndFeel()) { uiDefaults.put("Separator.background", UIUtil.AQUA_SEPARATOR_BACKGROUND_COLOR); uiDefaults.put("Separator.foreground", UIUtil.AQUA_SEPARATOR_FOREGROUND_COLOR); } } /** * The following code is a trick! By default Swing uses lightweight and "medium" weight * popups to show JPopupMenu. The code below force the creation of real heavyweight menus - * this increases speed of popups and allows to get rid of some drawing artifacts. */ private static void fixPopupWeight() { int popupWeight = OurPopupFactory.WEIGHT_MEDIUM; String property = System.getProperty("idea.popup.weight"); if (property != null) property = property.toLowerCase().trim(); if (SystemInfo.isMacOSLeopard) { // force heavy weight popups under Leopard, otherwise they don't have shadow or any kind of border. popupWeight = OurPopupFactory.WEIGHT_HEAVY; } else if (property == null) { // use defaults if popup weight isn't specified if (SystemInfo.isWindows) { popupWeight = OurPopupFactory.WEIGHT_HEAVY; } } else { if ("light".equals(property)) { popupWeight = OurPopupFactory.WEIGHT_LIGHT; } else if ("medium".equals(property)) { popupWeight = OurPopupFactory.WEIGHT_MEDIUM; } else if ("heavy".equals(property)) { popupWeight = OurPopupFactory.WEIGHT_HEAVY; } else { LOG.error("Illegal value of property \"idea.popup.weight\": " + property); } } PopupFactory factory = PopupFactory.getSharedInstance(); if (!(factory instanceof OurPopupFactory)) { factory = new OurPopupFactory(factory); PopupFactory.setSharedInstance(factory); } PopupUtil.setPopupType(factory, popupWeight); } private static void fixGtkPopupStyle() { if (!UIUtil.isUnderGTKLookAndFeel()) return; final SynthStyleFactory original = SynthLookAndFeel.getStyleFactory(); SynthLookAndFeel.setStyleFactory(new SynthStyleFactory() { @Override public SynthStyle getStyle(final JComponent c, final Region id) { final SynthStyle style = original.getStyle(c, id); if (id == Region.POPUP_MENU) { try { Field f = style.getClass().getDeclaredField("xThickness"); f.setAccessible(true); final Object x = f.get(style); if (x instanceof Integer && (Integer)x == 0) { // workaround for Sun bug #6636964 f.set(style, 1); f = style.getClass().getDeclaredField("yThickness"); f.setAccessible(true); f.set(style, 3); } } catch (Exception ignore) { } } return style; } }); new JBPopupMenu(); // invokes updateUI() -> updateStyle() SynthLookAndFeel.setStyleFactory(original); } private static void patchFileChooserStrings(final UIDefaults defaults) { if (!defaults.containsKey(ourFileChooserTextKeys[0])) { // Alloy L&F does not define strings for names of context menu actions, so we have to patch them in here for (String key : ourFileChooserTextKeys) { defaults.put(key, IdeBundle.message(key)); } } } private static void patchOptionPaneIcons(final UIDefaults defaults) { if (UIUtil.isUnderGTKLookAndFeel() && defaults.get(ourOptionPaneIconKeys[0]) == null) { // GTK+ L&F keeps icons hidden in style final SynthStyle style = SynthLookAndFeel.getStyle(new JOptionPane(""), Region.DESKTOP_ICON); if (style != null) { for (final String key : ourOptionPaneIconKeys) { final Object icon = style.get(null, key); if (icon != null) defaults.put(key, icon); } } } } private void patchLafFonts(UIDefaults uiDefaults) { //if (JBUI.isHiDPI()) { // HashMap<Object, Font> newFonts = new HashMap<Object, Font>(); // for (Object key : uiDefaults.keySet().toArray()) { // Object val = uiDefaults.get(key); // if (val instanceof Font) { // newFonts.put(key, JBFont.create((Font)val)); // } // } // for (Map.Entry<Object, Font> entry : newFonts.entrySet()) { // uiDefaults.put(entry.getKey(), entry.getValue()); // } //} else UISettings uiSettings = UISettings.getInstance(); if (uiSettings.OVERRIDE_NONIDEA_LAF_FONTS) { storeOriginalFontDefaults(uiDefaults); JBUI.setUserScaleFactor(uiSettings.FONT_SIZE / UIUtil.DEF_SYSTEM_FONT_SIZE); initFontDefaults(uiDefaults, uiSettings.FONT_SIZE, new FontUIResource(uiSettings.FONT_FACE, Font.PLAIN, uiSettings.FONT_SIZE)); } else { restoreOriginalFontDefaults(uiDefaults); } } private void restoreOriginalFontDefaults(UIDefaults defaults) { UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); HashMap<String, Object> lfDefaults = myStoredDefaults.get(lf); if (lfDefaults != null) { for (String resource : ourPatchableFontResources) { defaults.put(resource, lfDefaults.get(resource)); } } } private void storeOriginalFontDefaults(UIDefaults defaults) { UIManager.LookAndFeelInfo lf = getCurrentLookAndFeel(); HashMap<String, Object> lfDefaults = myStoredDefaults.get(lf); if (lfDefaults == null) { lfDefaults = new HashMap<>(); for (String resource : ourPatchableFontResources) { lfDefaults.put(resource, defaults.get(resource)); } myStoredDefaults.put(lf, lfDefaults); } } private static void updateUI(Window window) { if (!window.isDisplayable()) { return; } IJSwingUtilities.updateComponentTreeUI(window); Window[] children = window.getOwnedWindows(); for (Window aChildren : children) { updateUI(aChildren); } } /** * Repaints all displayable window. */ @Override public void repaintUI() { Frame[] frames = Frame.getFrames(); for (Frame frame : frames) { repaintUI(frame); } } private static void repaintUI(Window window) { if (!window.isDisplayable()) { return; } window.repaint(); Window[] children = window.getOwnedWindows(); for (Window aChildren : children) { repaintUI(aChildren); } } private static void installCutCopyPasteShortcuts(InputMap inputMap, boolean useSimpleActionKeys) { String copyActionKey = useSimpleActionKeys ? "copy" : DefaultEditorKit.copyAction; String pasteActionKey = useSimpleActionKeys ? "paste" : DefaultEditorKit.pasteAction; String cutActionKey = useSimpleActionKeys ? "cut" : DefaultEditorKit.cutAction; // Ctrl+Ins, Shift+Ins, Shift+Del inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), copyActionKey); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.SHIFT_MASK | InputEvent.SHIFT_DOWN_MASK), pasteActionKey); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_MASK | InputEvent.SHIFT_DOWN_MASK), cutActionKey); // Ctrl+C, Ctrl+V, Ctrl+X inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), copyActionKey); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), pasteActionKey); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK), DefaultEditorKit.cutAction); } @SuppressWarnings({"HardCodedStringLiteral"}) public static void initInputMapDefaults(UIDefaults defaults) { // Make ENTER work in JTrees InputMap treeInputMap = (InputMap)defaults.get("Tree.focusInputMap"); if (treeInputMap != null) { // it's really possible. For example, GTK+ doesn't have such map treeInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "toggle"); } // Cut/Copy/Paste in JTextAreas InputMap textAreaInputMap = (InputMap)defaults.get("TextArea.focusInputMap"); if (textAreaInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) installCutCopyPasteShortcuts(textAreaInputMap, false); } // Cut/Copy/Paste in JTextFields InputMap textFieldInputMap = (InputMap)defaults.get("TextField.focusInputMap"); if (textFieldInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) installCutCopyPasteShortcuts(textFieldInputMap, false); } // Cut/Copy/Paste in JPasswordField InputMap passwordFieldInputMap = (InputMap)defaults.get("PasswordField.focusInputMap"); if (passwordFieldInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) installCutCopyPasteShortcuts(passwordFieldInputMap, false); } // Cut/Copy/Paste in JTables InputMap tableInputMap = (InputMap)defaults.get("Table.ancestorInputMap"); if (tableInputMap != null) { // It really can be null, for example when LAF isn't properly initialized (Alloy license problem) installCutCopyPasteShortcuts(tableInputMap, true); } } @SuppressWarnings({"HardCodedStringLiteral"}) public static void initFontDefaults(UIDefaults defaults, int fontSize, FontUIResource uiFont) { defaults.put("Tree.ancestorInputMap", null); FontUIResource textFont = new FontUIResource("Serif", Font.PLAIN, fontSize); FontUIResource monoFont = new FontUIResource("Monospaced", Font.PLAIN, fontSize); for (String fontResource : ourPatchableFontResources) { defaults.put(fontResource, uiFont); } if (!SystemInfo.isMac) { defaults.put("PasswordField.font", monoFont); } defaults.put("TextArea.font", monoFont); defaults.put("TextPane.font", textFont); defaults.put("EditorPane.font", textFont); } private static class OurPopupFactory extends PopupFactory { public static final int WEIGHT_LIGHT = 0; public static final int WEIGHT_MEDIUM = 1; public static final int WEIGHT_HEAVY = 2; private final PopupFactory myDelegate; public OurPopupFactory(final PopupFactory delegate) { myDelegate = delegate; } @Override public Popup getPopup(final Component owner, final Component contents, final int x, final int y) throws IllegalArgumentException { final Point point = fixPopupLocation(contents, x, y); final int popupType = UIUtil.isUnderGTKLookAndFeel() ? WEIGHT_HEAVY : PopupUtil.getPopupType(this); if (popupType >= 0) { PopupUtil.setPopupType(myDelegate, popupType); } final Popup popup = myDelegate.getPopup(owner, contents, point.x, point.y); fixPopupSize(popup, contents); return popup; } private static Point fixPopupLocation(final Component contents, final int x, final int y) { if (!(contents instanceof JToolTip)) return new Point(x, y); final PointerInfo info; try { info = MouseInfo.getPointerInfo(); } catch (InternalError e) { // http://www.jetbrains.net/jira/browse/IDEADEV-21390 // may happen under Mac OSX 10.5 return new Point(x, y); } int deltaY = 0; if (info != null) { final Point mouse = info.getLocation(); deltaY = mouse.y - y; } final Dimension size = contents.getPreferredSize(); final Rectangle rec = new Rectangle(new Point(x, y), size); ScreenUtil.moveRectangleToFitTheScreen(rec); if (rec.y < y) { rec.y += deltaY; } return rec.getLocation(); } private static void fixPopupSize(final Popup popup, final Component contents) { if (!UIUtil.isUnderGTKLookAndFeel() || !(contents instanceof JPopupMenu)) return; for (Class<?> aClass = popup.getClass(); aClass != null && Popup.class.isAssignableFrom(aClass); aClass = aClass.getSuperclass()) { try { final Method getComponent = aClass.getDeclaredMethod("getComponent"); getComponent.setAccessible(true); final Object component = getComponent.invoke(popup); if (component instanceof JWindow) { ((JWindow)component).setSize(new Dimension(0, 0)); } break; } catch (Exception ignored) { } } } } }