/*
* Copyright 2000-2016 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.ide.BrowserUtil;
import com.intellij.ide.ui.AntialiasingType;
import com.intellij.ide.ui.UISettings;
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.editor.colors.ModifiableFontPreferences;
import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.FontComboBox;
import com.intellij.ui.FontInfoRenderer;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.ui.components.panels.HorizontalLayout;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.*;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class FontOptions extends JPanel implements OptionsPanel{
private static final FontInfoRenderer RENDERER = new FontInfoRenderer() {
@Override
protected AntialiasingType getAntialiasingType() {
return UISettings.getShadowInstance().EDITOR_AA_TYPE;
}
};
private static final String HELP_URL = "https://confluence.jetbrains.com/display/IDEADEV/Support+for+Ligatures+in+Editor";
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 FontComboBox myPrimaryCombo = new FontComboBox();
private final JCheckBox myUseSecondaryFontCheckbox = new JCheckBox(ApplicationBundle.message("secondary.font"));
private final JCheckBox myEnableLigaturesCheckbox = new JCheckBox(ApplicationBundle.message("use.ligatures"));
private final JLabel myLigaturesInfoLinkLabel;
private final FontComboBox mySecondaryCombo = new FontComboBox();
@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) {
super(new VerticalFlowLayout(VerticalFlowLayout.TOP, true, false));
Insets borderInsets =
JBUI.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);
JPanel primaryFontPanel = new JPanel(new HorizontalLayout(JBUI.scale(5)));
primaryFontPanel.setBorder(JBUI.Borders.emptyLeft(20));
add(primaryFontPanel);
primaryFontPanel.add(LabeledComponent.left(myPrimaryCombo, ApplicationBundle.message("primary.font")));
primaryFontPanel.add(LabeledComponent.left(myEditorFontSizeField, ApplicationBundle.message("editbox.font.size")));
primaryFontPanel.add(LabeledComponent.left(myLineSpacingField, ApplicationBundle.message("editbox.line.spacing")));
add(new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description"), MessageType.INFO.getDefaultIcon(), SwingConstants.LEFT));
JPanel secondFontPanel = new JPanel(new HorizontalLayout(JBUI.scale(5)));
add(secondFontPanel);
secondFontPanel.add(myUseSecondaryFontCheckbox);
secondFontPanel.add(mySecondaryCombo);
JPanel ligaturesPanel = new JPanel(new HorizontalLayout(JBUI.scale(5)));
myEnableLigaturesCheckbox.setBorder(null);
ligaturesPanel.add(myEnableLigaturesCheckbox);
myLigaturesInfoLinkLabel = new LinkLabel<>(ApplicationBundle.message("ligatures.more.info"), null, new LinkListener<String>() {
@Override
public void linkSelected(LinkLabel aSource, String aLinkData) {
BrowserUtil.browse(HELP_URL);
}
});
myLigaturesInfoLinkLabel.setBorder(JBUI.Borders.emptyLeft(5));
ligaturesPanel.add(myLigaturesInfoLinkLabel);
add(ligaturesPanel);
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.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
}
});
myPrimaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
myPrimaryCombo.setRenderer(RENDERER);
mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
mySecondaryCombo.setRenderer(RENDERER);
myUseSecondaryFontCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mySecondaryCombo.setEnabled(myUseSecondaryFontCheckbox.isSelected());
FontOptions.this.syncFontFamilies();
}
});
ItemListener itemListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
FontOptions.this.syncFontFamilies();
}
}
};
myPrimaryCombo.addItemListener(itemListener);
mySecondaryCombo.addItemListener(itemListener);
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FontOptions.this.syncFontFamilies();
}
};
myPrimaryCombo.addActionListener(actionListener);
mySecondaryCombo.addActionListener(actionListener);
myEditorFontSizeField.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void textChanged(DocumentEvent event) {
if (myIsInSchemeChange || !SwingUtilities.isEventDispatchThread()) return;
String selectedFont = myPrimaryCombo.getFontName();
if (selectedFont != null) {
ModifiableFontPreferences fontPreferences = getFontPreferences();
fontPreferences.register(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) {
}
}
});
myEnableLigaturesCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
FontOptions.this.getFontPreferences().setUseLigatures(myEnableLigaturesCheckbox.isSelected());
}
});
}
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;
}
ModifiableFontPreferences fontPreferences = getFontPreferences();
fontPreferences.clearFonts();
String primaryFontFamily = myPrimaryCombo.getFontName();
String secondaryFontFamily = mySecondaryCombo.isEnabled() ? mySecondaryCombo.getFontName() : null;
int fontSize = getFontSizeFromField();
if (primaryFontFamily != null ) {
if (!FontPreferences.DEFAULT_FONT_NAME.equals(primaryFontFamily)) {
fontPreferences.addFontFamily(primaryFontFamily);
}
fontPreferences.register(primaryFontFamily, JBUI.scale(fontSize));
}
if (secondaryFontFamily != null) {
if (!FontPreferences.DEFAULT_FONT_NAME.equals(secondaryFontFamily)){
fontPreferences.addFontFamily(secondaryFontFamily);
}
fontPreferences.register(secondaryFontFamily, JBUI.scale(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.setFontName(fontPreferences.getFontFamily());
boolean isThereSecondaryFont = fontFamilies.size() > 1;
myUseSecondaryFontCheckbox.setSelected(isThereSecondaryFont);
mySecondaryCombo.setFontName(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.setEnabled(!readOnly);
myUseSecondaryFontCheckbox.setEnabled(!readOnly);
myEnableLigaturesCheckbox.setEnabled(!readOnly);
myLigaturesInfoLinkLabel.setEnabled(!readOnly);
myEnableLigaturesCheckbox.setSelected(fontPreferences.useLigatures());
myIsInSchemeChange = false;
}
@NotNull
protected ModifiableFontPreferences getFontPreferences() {
return (ModifiableFontPreferences)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();
}
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>();
}
}