blob: cf2172ef2cf029650544b4256a21a99c84eb7326 [file] [log] [blame]
/*
* Copyright 2000-2009 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.application.options.colors;
import com.intellij.application.options.OptionsConstants;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.FontPreferences;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.ui.UIUtil;
import net.miginfocom.swing.MigLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
public class FontOptions extends JPanel implements OptionsPanel{
private static List<String> myFontNames;
private static List<String> myMonospacedFontNames;
private final EventDispatcher<ColorAndFontSettingsListener> myDispatcher = EventDispatcher.create(ColorAndFontSettingsListener.class);
@NotNull private final ColorAndFontOptions myOptions;
@NotNull private final JTextField myEditorFontSizeField = new JTextField(4);
@NotNull private final JTextField myLineSpacingField = new JTextField(4);
private final FontNameCombo myPrimaryCombo = new FontNameCombo(null);
private final JCheckBox myUseSecondaryFontCheckbox = new JCheckBox(ApplicationBundle.message("secondary.font"));
private final FontNameCombo mySecondaryCombo = new FontNameCombo(null);
@NotNull private final JBCheckBox myOnlyMonospacedCheckBox =
new JBCheckBox(ApplicationBundle.message("checkbox.show.only.monospaced.fonts"));
private boolean myIsInSchemeChange;
public FontOptions(ColorAndFontOptions options) {
this(options, ApplicationBundle.message("group.editor.font"));
}
protected FontOptions(@NotNull ColorAndFontOptions options, final String title) {
setLayout(new MigLayout("ins 0, gap 5, flowx"));
Insets borderInsets = new Insets(IdeBorderFactory.TITLED_BORDER_TOP_INSET,
IdeBorderFactory.TITLED_BORDER_LEFT_INSET,
0,
IdeBorderFactory.TITLED_BORDER_RIGHT_INSET);
setBorder(IdeBorderFactory.createTitledBorder(title, false, borderInsets));
myOptions = options;
add(myOnlyMonospacedCheckBox, "sgx b, sx 2");
add(new JLabel(ApplicationBundle.message("primary.font")), "newline, ax right");
add(myPrimaryCombo, "sgx b");
add(new JLabel(ApplicationBundle.message("editbox.font.size")), "gapleft 20");
add(myEditorFontSizeField);
add(new JLabel(ApplicationBundle.message("editbox.line.spacing")), "gapleft 20");
add(myLineSpacingField);
add(new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description"),
MessageType.INFO.getDefaultIcon(),
SwingConstants.LEFT), "newline, sx 5");
add(myUseSecondaryFontCheckbox, "newline, ax right");
add(mySecondaryCombo, "sgx b");
myOnlyMonospacedCheckBox.setBorder(null);
myUseSecondaryFontCheckbox.setBorder(null);
mySecondaryCombo.setEnabled(false);
myOnlyMonospacedCheckBox.setSelected(EditorColorsManager.getInstance().isUseOnlyMonospacedFonts());
myOnlyMonospacedCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
EditorColorsManager.getInstance().setUseOnlyMonospacedFonts(myOnlyMonospacedCheckBox.isSelected());
myPrimaryCombo.updateModel();
mySecondaryCombo.updateModel();
}
});
myUseSecondaryFontCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mySecondaryCombo.setEnabled(myUseSecondaryFontCheckbox.isSelected());
syncFontFamilies();
}
});
ItemListener itemListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
syncFontFamilies();
}
}
};
myPrimaryCombo.addItemListener(itemListener);
mySecondaryCombo.addItemListener(itemListener);
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
syncFontFamilies();
}
};
myPrimaryCombo.addActionListener(actionListener);
mySecondaryCombo.addActionListener(actionListener);
myEditorFontSizeField.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void textChanged(DocumentEvent event) {
if (myIsInSchemeChange || !SwingUtilities.isEventDispatchThread()) return;
Object selectedFont = myPrimaryCombo.getSelectedItem();
if (selectedFont instanceof String) {
FontPreferences fontPreferences = getFontPreferences();
fontPreferences.register((String)selectedFont, getFontSizeFromField());
}
updateDescription(true);
}
});
myEditorFontSizeField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN) return;
boolean up = e.getKeyCode() == KeyEvent.VK_UP;
try {
int value = Integer.parseInt(myEditorFontSizeField.getText());
value += (up ? 1 : -1);
value = Math.min(OptionsConstants.MAX_EDITOR_FONT_SIZE, Math.max(OptionsConstants.MIN_EDITOR_FONT_SIZE, value));
myEditorFontSizeField.setText(String.valueOf(value));
}
catch (NumberFormatException ignored) {
}
}
});
myLineSpacingField.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void textChanged(DocumentEvent event) {
if (myIsInSchemeChange) return;
float lineSpacing = getLineSpacingFromField();
if (getLineSpacing() != lineSpacing) {
setCurrentLineSpacing(lineSpacing);
}
updateDescription(true);
}
});
myLineSpacingField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN) return;
boolean up = e.getKeyCode() == KeyEvent.VK_UP;
try {
float value = Float.parseFloat(myLineSpacingField.getText());
value += (up ? 1 : -1) * .1F;
value = Math.min(OptionsConstants.MAX_EDITOR_LINE_SPACING, Math.max(OptionsConstants.MIN_EDITOR_LINE_SPACING, value));
myLineSpacingField.setText(String.format(Locale.ENGLISH, "%.1f", value));
}
catch (NumberFormatException ignored) {
}
}
});
}
private int getFontSizeFromField() {
try {
return Math.min(OptionsConstants.MAX_EDITOR_FONT_SIZE, Math.max(OptionsConstants.MIN_EDITOR_FONT_SIZE, Integer.parseInt(myEditorFontSizeField.getText())));
}
catch (NumberFormatException e) {
return OptionsConstants.DEFAULT_EDITOR_FONT_SIZE;
}
}
private float getLineSpacingFromField() {
try {
return Math.min(OptionsConstants.MAX_EDITOR_LINE_SPACING, Math.max(OptionsConstants.MIN_EDITOR_LINE_SPACING, Float.parseFloat(myLineSpacingField.getText())));
} catch (NumberFormatException e){
return OptionsConstants.DEFAULT_EDITOR_LINE_SPACING;
}
}
private void syncFontFamilies() {
if (myIsInSchemeChange) {
return;
}
FontPreferences fontPreferences = getFontPreferences();
fontPreferences.clearFonts();
String primaryFontFamily = (String)myPrimaryCombo.getSelectedItem();
String secondaryFontFamily = mySecondaryCombo.isEnabled() ? (String)mySecondaryCombo.getSelectedItem() : null;
int fontSize = getFontSizeFromField();
if (primaryFontFamily != null ) {
if (!FontPreferences.DEFAULT_FONT_NAME.equals(primaryFontFamily)) {
fontPreferences.addFontFamily(primaryFontFamily);
}
fontPreferences.register(primaryFontFamily, fontSize);
}
if (secondaryFontFamily != null) {
if (!FontPreferences.DEFAULT_FONT_NAME.equals(secondaryFontFamily)){
fontPreferences.addFontFamily(secondaryFontFamily);
}
fontPreferences.register(secondaryFontFamily, fontSize);
}
updateDescription(true);
}
public static void showReadOnlyMessage(JComponent parent, final boolean sharedScheme) {
if (!sharedScheme) {
Messages.showMessageDialog(
parent,
ApplicationBundle.message("error.readonly.scheme.cannot.be.modified"),
ApplicationBundle.message("title.cannot.modify.readonly.scheme"),
Messages.getInformationIcon()
);
}
else {
Messages.showMessageDialog(
parent,
ApplicationBundle.message("error.shared.scheme.cannot.be.modified"),
ApplicationBundle.message("title.cannot.modify.readonly.scheme"),
Messages.getInformationIcon()
);
}
}
@Override
public void updateOptionsList() {
myIsInSchemeChange = true;
myLineSpacingField.setText(Float.toString(getLineSpacing()));
FontPreferences fontPreferences = getFontPreferences();
List<String> fontFamilies = fontPreferences.getEffectiveFontFamilies();
myPrimaryCombo.setSelectedItem(fontPreferences.getFontFamily());
boolean isThereSecondaryFont = fontFamilies.size() > 1;
myUseSecondaryFontCheckbox.setSelected(isThereSecondaryFont);
mySecondaryCombo.setSelectedItem(isThereSecondaryFont ? fontFamilies.get(1) : null);
myEditorFontSizeField.setText(String.valueOf(fontPreferences.getSize(fontPreferences.getFontFamily())));
boolean readOnly = ColorAndFontOptions.isReadOnly(myOptions.getSelectedScheme());
myPrimaryCombo.setEnabled(!readOnly);
mySecondaryCombo.setEnabled(isThereSecondaryFont && !readOnly);
myOnlyMonospacedCheckBox.setEnabled(!readOnly);
myLineSpacingField.setEnabled(!readOnly);
myEditorFontSizeField.setEditable(!readOnly);
myUseSecondaryFontCheckbox.setEnabled(!readOnly);
myIsInSchemeChange = false;
}
@NotNull
protected FontPreferences getFontPreferences() {
return getCurrentScheme().getFontPreferences();
}
protected float getLineSpacing() {
return getCurrentScheme().getLineSpacing();
}
protected void setCurrentLineSpacing(float lineSpacing) {
getCurrentScheme().setLineSpacing(lineSpacing);
}
@Override
@Nullable
public Runnable showOption(final String option) {
return null;
}
@Override
public void applyChangesToScheme() {
}
@Override
public void selectOption(final String typeToSelect) {
}
protected EditorColorsScheme getCurrentScheme() {
return myOptions.getSelectedScheme();
}
@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
private void initFontTables(FontNameCombo popupCallback) {
if (myFontNames == null) {
myFontNames = new ArrayList<String>();
myMonospacedFontNames = new ArrayList<String>();
ProgressManager.getInstance().runProcessWithProgressSynchronously(new InitFontsRunnable(popupCallback), ApplicationBundle.message("progress.analyzing.fonts"), false, null);
}
}
public boolean updateDescription(boolean modified) {
EditorColorsScheme scheme = myOptions.getSelectedScheme();
if (modified && (ColorAndFontOptions.isReadOnly(scheme) || ColorSettingsUtil.isSharedScheme(scheme))) {
showReadOnlyMessage(this, ColorSettingsUtil.isSharedScheme(scheme));
return false;
}
myDispatcher.getMulticaster().fontChanged();
return true;
}
@Override
public void addListener(ColorAndFontSettingsListener listener) {
myDispatcher.addListener(listener);
}
@Override
public JPanel getPanel() {
return this;
}
@Override
public Set<String> processListOptions() {
return new HashSet<String>();
}
private class InitFontsRunnable implements Runnable {
private final FontNameCombo myPopupCallback;
private InitFontsRunnable(FontNameCombo popupCallback) {
myPopupCallback = popupCallback;
}
@Override
public void run() {
ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontNames = graphicsEnvironment.getAvailableFontFamilyNames();
for (final String fontName : fontNames) {
//noinspection HardCodedStringLiteral
if (fontName.endsWith(".bold") || fontName.endsWith(".italic")) {
continue;
}
try {
Font plainFont = new Font(fontName, Font.PLAIN, OptionsConstants.DEFAULT_EDITOR_FONT_SIZE);
if (plainFont.canDisplay('W')) {
Font boldFont = plainFont.deriveFont(Font.BOLD);
if (progress != null) {
progress.setText(ApplicationBundle.message("progress.analysing.font", fontName));
}
FontMetrics plainMetrics = getFontMetrics(plainFont);
FontMetrics boldMetrics = getFontMetrics(boldFont);
if (plainMetrics.getDescent() < 0 ||
boldMetrics.getDescent() < 0 ||
plainMetrics.getAscent() < 0 ||
boldMetrics.getAscent() < 0) {
continue;
}
int plainL = plainMetrics.charWidth('l');
int boldL = boldMetrics.charWidth('l');
int plainW = plainMetrics.charWidth('W');
int boldW = boldMetrics.charWidth('W');
int plainSpace = plainMetrics.charWidth(' ');
int boldSpace = boldMetrics.charWidth(' ');
if (plainL <= 0 || boldL <= 0 || plainW <= 0 || boldW <= 0 || plainSpace <= 0 || boldSpace <= 0) {
continue;
}
myFontNames.add(fontName);
if (plainL == plainW && plainL == boldL && plainW == boldW && plainSpace == boldSpace) {
myMonospacedFontNames.add(fontName);
}
}
}
catch (Throwable e) {
// JRE has problems working with the font. Just skip.
}
}
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myPrimaryCombo.updateModel();
mySecondaryCombo.updateModel();
myPopupCallback.showPopup();
}
});
}
}
private class FontNameCombo extends JComboBox {
private final DefaultComboBoxModel myModel;
private Boolean myMonospacedOnly = null;
private FontNameCombo(String selectedName) {
setModel(myModel = new DefaultComboBoxModel());
updateModel();
setSelectedItem(selectedName);
}
private void updateModel() {
if (myFontNames == null || myMonospacedFontNames == null) return;
if (myMonospacedOnly == null || myMonospacedOnly.booleanValue() != EditorColorsManager.getInstance().isUseOnlyMonospacedFonts()) {
myMonospacedOnly = EditorColorsManager.getInstance().isUseOnlyMonospacedFonts();
Object tmp = getSelectedItem();
myModel.removeAllElements();
List toAdd = myMonospacedOnly ? myMonospacedFontNames : myFontNames;
for (Object o : toAdd) {
myModel.addElement(o);
}
if (myModel.getIndexOf(tmp) != -1) {
setSelectedItem(tmp);
} else {
setSelectedItem(FontPreferences.DEFAULT_FONT_NAME);
}
fireActionEvent();
revalidate();
repaint();
}
}
@Override
public void setSelectedItem(Object anObject) {
if (myModel.getSize() == 0 && anObject != null) {
myModel.addElement(anObject);
}
super.setSelectedItem(anObject);
}
@Nullable
private JList getPopupList() {
ComboPopup popup = ReflectionUtil.getField(getUI().getClass(), getUI(), ComboPopup.class, "popup");
return (popup != null) ? popup.getList() : null;
}
@Override
public void firePopupMenuWillBecomeVisible() {
super.firePopupMenuWillBecomeVisible();
if (myFontNames == null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
initFontTables(FontNameCombo.this);
}
});
}
final JList list = getPopupList();
if (list != null && !(list.getCellRenderer() instanceof MyListCellRenderer)) {
list.setCellRenderer(new MyListCellRenderer());
}
}
}
private static class MyListCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof String) {
c.setFont(new Font((String) value, Font.PLAIN, 14));
}
return c;
}
}
}