com.intellij.application.options.colors.FontOptions.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.application.options.colors.FontOptions.java

Source

/*
 * 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 {
        @Override
        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;
        }
    }
}