/*
* Copyright 2000-2017 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.openapi.editor.colors.impl;
import com.intellij.application.options.EditorFontsConstants;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.FontPreferences;
import com.intellij.openapi.editor.colors.ModifiableFontPreferences;
import com.intellij.util.containers.ContainerUtilRt;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Utility class which holds collection of font families and theirs sizes.
* <p/>
* The basic idea is to allow end-user to configure not a single font but fonts list instead - every time particular font is unable
* to display particular char, next font is tried. This is an improvement over an old approach when it was possible to configure
* only a single font family. Fallback fonts were chosen randomly when that font family was unable to display particular char then.
*
* @author Denis Zhdanov
* @since 12/20/12 9:37 PM
*/
public class FontPreferencesImpl implements ModifiableFontPreferences {
@NotNull private final TObjectIntHashMap<String> myFontSizes = new TObjectIntHashMap<>();
@NotNull private final List<String> myEffectiveFontFamilies = ContainerUtilRt.newArrayList();
@NotNull private final List<String> myRealFontFamilies = ContainerUtilRt.newArrayList();
private boolean myUseLigatures;
private float myLineSpacing = DEFAULT_LINE_SPACING;
@Nullable private Runnable myChangeListener;
/**
* Font size to use by default. Default value is {@link #DEFAULT_FONT_SIZE}.
*/
private int myTemplateFontSize = DEFAULT_FONT_SIZE;
public void setChangeListener(@Nullable Runnable changeListener) {
myChangeListener = changeListener;
}
@Nullable
public Runnable getChangeListener() {
return myChangeListener;
}
@Override
public void clear() {
myEffectiveFontFamilies.clear();
myRealFontFamilies.clear();
myFontSizes.clear();
if (myChangeListener != null) {
myChangeListener.run();
}
}
@Override
public void clearFonts() {
myEffectiveFontFamilies.clear();
myRealFontFamilies.clear();
if (myChangeListener != null) {
myChangeListener.run();
}
}
@Override
public boolean hasSize(@NotNull String fontName) {
return myFontSizes.containsKey(fontName);
}
@Override
public float getLineSpacing() {
return myLineSpacing;
}
@Override
public void setLineSpacing(float lineSpacing) {
myLineSpacing = EditorFontsConstants.checkAndFixEditorLineSpacing(lineSpacing);
}
public int getSize(@NotNull String fontFamily) {
int result = myFontSizes.get(fontFamily);
if (result <= 0) {
result = myTemplateFontSize;
}
return result > 0 ? result : DEFAULT_FONT_SIZE;
}
public void setSize(@NotNull String fontFamily, int size) {
myFontSizes.put(fontFamily, size);
myTemplateFontSize = size;
if (myChangeListener != null) {
myChangeListener.run();
}
}
/**
* This method might return results different from {@link #getRealFontFamilies()} when
* {@link #getFallbackName(String, int, EditorColorsScheme) a font family unavailable at current environment}
* has been {@link #register(String, int) registered} at the current font preferences object.
* <p/>
* Effective fonts will hold fallback values for such font families then (exposed by the current method), 'real fonts' will
* be available via {@link #getRealFontFamilies()}.
*
* @return effective font families to use
*/
@Override
@NotNull
public List<String> getEffectiveFontFamilies() {
return myEffectiveFontFamilies;
}
/**
* @return 'real' font families
* @see #getEffectiveFontFamilies()
*/
@Override
@NotNull
public List<String> getRealFontFamilies() {
return myRealFontFamilies;
}
@Override
public void register(@NotNull String fontFamily, int size) {
String fallbackFontFamily = FontPreferences.getFallbackName(fontFamily, size, null);
if (!myRealFontFamilies.contains(fontFamily)) {
myRealFontFamilies.add(fontFamily);
}
String effectiveFontFamily = fallbackFontFamily == null ? fontFamily : fallbackFontFamily;
if (!myEffectiveFontFamilies.contains(effectiveFontFamily)) {
myEffectiveFontFamilies.add(effectiveFontFamily);
}
setSize(fontFamily, size);
}
/**
* @return first element of the {@link #getEffectiveFontFamilies() registered font families} (if any);
* {@link #DEFAULT_FONT_NAME} otherwise
*/
@Override
@NotNull
public String getFontFamily() {
return myEffectiveFontFamilies.isEmpty() ? DEFAULT_FONT_NAME : myEffectiveFontFamilies.get(0);
}
@Override
public void addFontFamily(@NotNull String fontFamily) {
String fallbackFontFamily = FontPreferences.getFallbackName(fontFamily, DEFAULT_FONT_SIZE, null);
if (!myRealFontFamilies.contains(fontFamily)) {
myRealFontFamilies.add(fontFamily);
}
String effectiveFontFamily = fallbackFontFamily == null ? fontFamily : fallbackFontFamily;
if (!myEffectiveFontFamilies.contains(effectiveFontFamily)) {
myEffectiveFontFamilies.add(effectiveFontFamily);
}
if (myChangeListener != null) {
myChangeListener.run();
}
}
@Override
public void copyTo(@NotNull final FontPreferences preferences) {
if (preferences instanceof ModifiableFontPreferences) {
ModifiableFontPreferences modifiablePreferences = (ModifiableFontPreferences)preferences;
modifiablePreferences.setEffectiveFontFamilies(myEffectiveFontFamilies);
modifiablePreferences.setRealFontFamilies(myRealFontFamilies);
modifiablePreferences.setTemplateFontSize(myTemplateFontSize);
modifiablePreferences.resetFontSizes();
for (String fontFamily : myRealFontFamilies) {
if (myFontSizes.containsKey(fontFamily)) {
modifiablePreferences.setFontSize(fontFamily, myFontSizes.get(fontFamily));
}
}
modifiablePreferences.setUseLigatures(myUseLigatures);
modifiablePreferences.setLineSpacing(myLineSpacing);
}
}
@Override
public void resetFontSizes() {
myFontSizes.clear();
}
@Override
public void setFontSize(@NotNull String fontFamily, int size) {
myFontSizes.put(fontFamily, size);
}
@Override
public void setTemplateFontSize(int size) {
myTemplateFontSize = size;
}
@Override
public void setEffectiveFontFamilies(@NotNull List<String> fontFamilies) {
myEffectiveFontFamilies.clear();
myEffectiveFontFamilies.addAll(fontFamilies);
}
@Override
public void setRealFontFamilies(@NotNull List<String> fontFamilies) {
myRealFontFamilies.clear();
myRealFontFamilies.addAll(fontFamilies);
}
@Override
public int hashCode() {
return myRealFontFamilies.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FontPreferencesImpl that = (FontPreferencesImpl)o;
if (!myRealFontFamilies.equals(that.myRealFontFamilies)) return false;
for (String fontFamily : myRealFontFamilies) {
if (myFontSizes.get(fontFamily) != that.myFontSizes.get(fontFamily)) {
return false;
}
}
if (myUseLigatures != that.myUseLigatures) return false;
if (myLineSpacing != that.myLineSpacing) return false;
return true;
}
@Override
public boolean useLigatures() {
return myUseLigatures;
}
@Override
public void setUseLigatures(boolean useLigatures) {
if (useLigatures != myUseLigatures) {
myUseLigatures = useLigatures;
if (myChangeListener != null) {
myChangeListener.run();
}
}
}
@Override
public String toString() {
return "Effective font families: " + myEffectiveFontFamilies;
}
}