Java tutorial
/* * 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; } } }