blob: 7e00dc3a9841deddaf970fdafba62ef4284e338c [file] [log] [blame]
/*
* Copyright 2000-2014 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.icons.AllIcons;
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.DarculaInstaller;
import com.intellij.ide.ui.laf.darcula.DarculaLaf;
import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo;
import com.intellij.idea.StartupUtil;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
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.util.registry.Registry;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
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.ui.popup.OurHeavyWeightPopup;
import com.intellij.util.IJSwingUtilities;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.security.action.GetPropertyAction;
import javax.swing.*;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.ColorUIResource;
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.security.AccessController;
import java.util.*;
import java.util.List;
/**
* @author Eugene Belyaev
* @author Vladimir Kondratyev
*/
@State(
name = "LafManager",
storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/options.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"};
private static final String[] ourAlloyComponentsToPatchSelection = {"Tree", "MenuItem", "Menu", "List",
"ComboBox", "Table", "TextArea", "EditorPane", "TextPane", "FormattedTextField", "PasswordField",
"TextField", "RadioButtonMenuItem", "CheckBoxMenuItem"};
private final EventListenerList myListenerList;
private final UIManager.LookAndFeelInfo[] myLaFs;
private UIManager.LookAndFeelInfo myCurrentLaf;
private final Map<UIManager.LookAndFeelInfo, HashMap<String, Object>> myStoredDefaults = ContainerUtil.newHashMap();
private final UISettings myUiSettings;
private String myLastWarning = null;
private PropertyChangeListener myThemeChangeListener = null;
private static final Map<String, String> ourLafClassesAliases = ContainerUtil.newHashMap();
static {
ourLafClassesAliases.put("idea.dark.laf.classname", DarculaLookAndFeelInfo.CLASS_NAME);
}
/**
* Invoked via reflection.
*/
LafManagerImpl(UISettings uiSettings) {
myUiSettings = uiSettings;
myListenerList = new EventListenerList();
List<UIManager.LookAndFeelInfo> lafList = ContainerUtil.newArrayList();
if (SystemInfo.isMac) {
lafList.add(new UIManager.LookAndFeelInfo("Default", UIManager.getSystemLookAndFeelClassName()));
}
else {
if (isIntelliJLafEnabled()) {
lafList.add(new IntelliJLookAndFeelInfo());
}
else {
lafList.add(new IdeaLookAndFeelInfo());
}
for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
String name = laf.getName();
if (!"Metal".equalsIgnoreCase(name)
&& !"CDE/Motif".equalsIgnoreCase(name)
&& !"Nimbus".equalsIgnoreCase(name)
&& !"Windows Classic".equalsIgnoreCase(name)
&& !name.startsWith("JGoodies")) {
lafList.add(laf);
}
}
}
if (Registry.is("dark.laf.available")) {
lafList.add(new DarculaLookAndFeelInfo());
}
myLaFs = lafList.toArray(new UIManager.LookAndFeelInfo[lafList.size()]);
if (!SystemInfo.isMac) {
// do not sort LaFs on mac - the order is determined as Default, Darcula.
// when we leave only system LaFs on other OSes, the order also should be determined as Default, Darcula
Arrays.sort(myLaFs, new Comparator<UIManager.LookAndFeelInfo>() {
@Override
public int compare(UIManager.LookAndFeelInfo obj1, UIManager.LookAndFeelInfo obj2) {
String name1 = obj1.getName();
String name2 = obj2.getName();
return name1.compareToIgnoreCase(name2);
}
});
}
myCurrentLaf = getDefaultLaf();
}
private static boolean isIntelliJLafEnabled() {
return !Registry.is("idea.4.5.laf.enabled");
}
/**
* 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) {
boolean needUninstall = UIUtil.isUnderDarcula();
setCurrentLookAndFeel(laf); // setup default LAF or one specified by readExternal.
if (StartupUtil.getWizardLAF() != null) {
if (UIUtil.isUnderDarcula()) {
DarculaInstaller.install();
}
else if (needUninstall) {
DarculaInstaller.uninstall();
}
}
}
}
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);
if (className != null && ourLafClassesAliases.containsKey(className)) {
className = ourLafClassesAliases.get(className);
}
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() {
if (StartupUtil.getWizardLAF() != null) {
UIManager.LookAndFeelInfo laf = findLaf(StartupUtil.getWizardLAF());
LOG.assertTrue(laf != null);
return laf;
}
final String systemLafClassName = UIManager.getSystemLookAndFeelClassName();
if (SystemInfo.isMac) {
UIManager.LookAndFeelInfo laf = findLaf(systemLafClassName);
LOG.assertTrue(laf != null);
return laf;
}
if (PlatformUtils.isRubyMine() || PlatformUtils.isPyCharm()) {
final String desktop = AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
if ("gnome".equals(desktop)) {
UIManager.LookAndFeelInfo laf = findLaf(systemLafClassName);
if (laf != null) {
return laf;
}
LOG.info("Could not find system look and feel: " + systemLafClassName);
}
}
// Default
final String defaultLafName = StartupUtil.getDefaultLAF();
if (defaultLafName != null) {
UIManager.LookAndFeelInfo defaultLaf = findLaf(defaultLafName);
if (defaultLaf != null) {
return defaultLaf;
}
}
UIManager.LookAndFeelInfo ideaLaf = findLaf(isIntelliJLafEnabled() ? IntelliJLaf.class.getName() : IdeaLookAndFeelInfo.CLASS_NAME);
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;
}
// Set L&F
if (IdeaLookAndFeelInfo.CLASS_NAME.equals(lookAndFeelInfo.getClassName())) { // that is IDEA default LAF
IdeaLaf laf = new IdeaLaf();
MetalLookAndFeel.setCurrentTheme(new IdeaBlueMetalTheme());
try {
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;
}
}
else if (DarculaLookAndFeelInfo.CLASS_NAME.equals(lookAndFeelInfo.getClassName())) {
DarculaLaf laf = new DarculaLaf();
try {
UIManager.setLookAndFeel(laf);
JBColor.setDark(true);
IconLoader.setUseDarkIcons(true);
}
catch (Exception e) {
Messages.showMessageDialog(
IdeBundle.message("error.cannot.set.look.and.feel", lookAndFeelInfo.getName(), e.getMessage()),
CommonBundle.getErrorTitle(),
Messages.getErrorIcon()
);
return;
}
}
else { // non default LAF
try {
LookAndFeel laf = ((LookAndFeel)Class.forName(lookAndFeelInfo.getClassName()).newInstance());
if (laf instanceof MetalLookAndFeel) {
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
}
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 = ObjectUtils.chooseNotNull(findLaf(lookAndFeelInfo.getClassName()), lookAndFeelInfo);
checkLookAndFeel(lookAndFeelInfo, false);
}
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 == Messages.OK) {
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();
fixTreeWideSelection(uiDefaults);
fixMenuIssues(uiDefaults);
if (UIUtil.isUnderAquaLookAndFeel()) {
uiDefaults.put("Panel.opaque", Boolean.TRUE);
}
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()) {
// OSX/Aqua fix: Some image caching components like ToolWindowHeader use
// com.apple.laf.AquaNativeResources$CColorPaintUIResource
// a Java wrapper for ObjC MagicBackgroundColor class (Java RGB values ignored).
// MagicBackgroundColor always reports current Frame background.
// So we need to set frames background to exact and correct value.
if (SystemInfo.isMac) {
//noinspection UseJBColor
frame.setBackground(new Color(UIUtil.getPanelBackground().getRGB()));
}
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());
}
else if (UIUtil.isUnderJGoodiesLookAndFeel()) {
uiDefaults.put("Menu.opaque", true);
uiDefaults.put("MenuItem.opaque", true);
}
uiDefaults.put("MenuItem.background", UIManager.getColor("Menu.background"));
}
private static void fixTreeWideSelection(UIDefaults uiDefaults) {
if (UIUtil.isUnderAlloyIDEALookAndFeel() || UIUtil.isUnderJGoodiesLookAndFeel()) {
final Color bg = new ColorUIResource(56, 117, 215);
final Color fg = new ColorUIResource(255, 255, 255);
uiDefaults.put("info", bg);
uiDefaults.put("textHighlight", bg);
for (String key : ourAlloyComponentsToPatchSelection) {
uiDefaults.put(key + ".selectionBackground", bg);
uiDefaults.put(key + ".selectionForeground", fg);
}
}
}
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(Locale.ENGLISH).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(UIDefaults defaults) {
if (!UIUtil.isUnderGTKLookAndFeel()) return;
Map<String, Icon> map = ContainerUtil.newHashMap(
Arrays.asList("OptionPane.errorIcon", "OptionPane.informationIcon", "OptionPane.warningIcon", "OptionPane.questionIcon"),
Arrays.asList(AllIcons.General.ErrorDialog, AllIcons.General.InformationDialog, AllIcons.General.WarningDialog, AllIcons.General.QuestionDialog));
// GTK+ L&F keeps icons hidden in style
SynthStyle style = SynthLookAndFeel.getStyle(new JOptionPane(""), Region.DESKTOP_ICON);
for (String key : map.keySet()) {
if (defaults.get(key) != null) continue;
Object icon = style == null ? null : style.get(null, key);
defaults.put(key, icon instanceof Icon ? icon : map.get(key));
}
}
private void patchLafFonts(UIDefaults uiDefaults) {
if (UISettings.getInstance().OVERRIDE_NONIDEA_LAF_FONTS) {
storeOriginalFontDefaults(uiDefaults);
initFontDefaults(uiDefaults, myUiSettings.FONT_FACE, myUiSettings.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<String, Object>();
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"})
static void initFontDefaults(UIDefaults defaults, String fontFace, int fontSize) {
defaults.put("Tree.ancestorInputMap", null);
FontUIResource uiFont = new FontUIResource(fontFace, Font.PLAIN, fontSize);
FontUIResource textFont = new FontUIResource("Serif", Font.PLAIN, fontSize);
FontUIResource monoFont = new FontUIResource("Monospaced", Font.PLAIN, fontSize);
for (String fontResource : ourPatchableFontResources) {
defaults.put(fontResource, uiFont);
}
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 == WEIGHT_HEAVY && OurHeavyWeightPopup.isEnabled()) {
return new OurHeavyWeightPopup(owner, contents, point.x, point.y);
}
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) {
}
}
}
}
}