blob: d082503118a64a7895d69da1916391fffa2b7d7e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Red Hat Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Mickael Istria (Red Hat Inc.) - 469918 Zoom In/Out
*******************************************************************************/
package org.eclipse.ui.texteditor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISources;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.part.AbstractMultiEditor;
import org.eclipse.ui.part.MultiPageEditorPart;
/**
* Abstract handler to change the default font size on Text editors.
*/
abstract class AbstractTextZoomHandler extends AbstractHandler {
private static Map<String, String> fgFontToDefault;
private static Map<String, Set<String>> fgDefaultToFonts;
private int fFontSizeOffset;
/**
* Implementations of this class have to specify in the constructor how much the font would
* change when the handler is executed.
*
* @param fontSizeOffset how many points the default font will change. The offset can be
* negative, to reduce font size (zoom out) or positive to increase font size (zoom
* in)
*/
protected AbstractTextZoomHandler(int fontSizeOffset) {
fFontSizeOffset= fontSizeOffset;
}
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
AbstractTextEditor textEditor= getActiveTextEditor(event);
if (textEditor == null) {
return null;
}
FontRegistry fontRegistry= textEditor.getSite().getWorkbenchWindow().getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
String fontProperty= textEditor.getSymbolicFontName();
if (fontProperty == null) {
fontProperty= JFaceResources.TEXT_FONT;
}
Set<String> fontsToSet= getAffectedFontNames(fontProperty, fontRegistry);
FontData[] initialFontData= null;
String currentFontName= fontProperty;
while (currentFontName != null && (initialFontData= fontRegistry.getFontData(currentFontName)) == null) {
currentFontName= fgFontToDefault.get(currentFontName);
}
FontData[] newFontData= createFontDescriptor(initialFontData).getFontData();
if (newFontData != null) {
fontsToSet.stream().forEach(fontName -> fontRegistry.put(fontName, newFontData));
}
return Status.OK_STATUS;
}
private FontDescriptor createFontDescriptor(FontData[] initialFontData) {
int destFontSize= initialFontData[0].getHeight() + fFontSizeOffset;
if (destFontSize <= 0) {
return FontDescriptor.createFrom(initialFontData);
}
return FontDescriptor.createFrom(initialFontData).setHeight(destFontSize);
}
private AbstractTextEditor getActiveTextEditor(ExecutionEvent event) {
return getActiveTextEditor(HandlerUtil.getActiveEditor(event));
}
private AbstractTextEditor getActiveTextEditor(IEditorPart part) {
if (part instanceof AbstractTextEditor) {
return (AbstractTextEditor)part;
} else if ((part instanceof AbstractMultiEditor) && ((AbstractMultiEditor)part).getActiveEditor() instanceof AbstractTextEditor) {
return (AbstractTextEditor)((AbstractMultiEditor)part).getActiveEditor();
} else if ((part instanceof MultiPageEditorPart) && ((MultiPageEditorPart)part).getSelectedPage() instanceof AbstractTextEditor) {
return (AbstractTextEditor)((MultiPageEditorPart)part).getSelectedPage();
}
return part != null ? part.getAdapter(AbstractTextEditor.class) : null;
}
@Override
public void setEnabled(Object evaluationContext) {
boolean enabled = false;
if (evaluationContext instanceof IEvaluationContext) {
Object activeEditor = ((IEvaluationContext) evaluationContext).getVariable(ISources.ACTIVE_EDITOR_NAME);
if (activeEditor instanceof IEditorPart) {
enabled = getActiveTextEditor((IEditorPart) activeEditor) != null;
}
}
setBaseEnabled(enabled);
}
/**
*
* @param referenceFontName the font name on which change is initially requested
* @param fontRegistry the font registry
* @return the names of the fonts that should be modified together with the referenceFontName.
* Those are parent fonts from which the reference font inherit, or children font that
* are set to default or inherit from reference font or a common parent that is affected
* too.
*/
private Set<String> getAffectedFontNames(String referenceFontName, FontRegistry fontRegistry) {
synchronized (AbstractTextZoomHandler.class) {
if (fgFontToDefault == null) {
// TODO: This should rely on ThemeRegistry and IThemeElementDefinition,
// but those aren't visible at that time. So we're recreating the font hierarchy
fgFontToDefault= new HashMap<>();
fgDefaultToFonts= new HashMap<>();
IConfigurationElement[] themeElements= Platform.getExtensionRegistry().getConfigurationElementsFor("org.eclipse.ui.themes"); //$NON-NLS-1$
for (int i= 0; i < themeElements.length; i++) {
IConfigurationElement extension= themeElements[i];
if ("fontDefinition".equals(extension.getName())) { //$NON-NLS-1$
String fontId= extension.getAttribute("id"); //$NON-NLS-1$
String defaultsTo= extension.getAttribute("defaultsTo"); //$NON-NLS-1$
if (defaultsTo != null) {
fgFontToDefault.put(fontId, defaultsTo);
if (!fgDefaultToFonts.containsKey(defaultsTo)) {
fgDefaultToFonts.put(defaultsTo, new HashSet<>());
}
fgDefaultToFonts.get(defaultsTo).add(fontId);
}
}
}
}
}
Set<String> res= new HashSet<>();
FontData[] referenceFontData= fontRegistry.getFontData(referenceFontName);
if (fontRegistry.hasValueFor(referenceFontName)) {
res.add(referenceFontName);
}
String currentFontName= referenceFontName;
String rootFontName= referenceFontName;
// identify "root" font to change
do {
currentFontName= fgFontToDefault.get(currentFontName);
if (currentFontName != null && Arrays.equals(referenceFontData, fontRegistry.getFontData(currentFontName))) {
rootFontName= currentFontName;
}
} while (currentFontName != null);
LinkedList<String> fontsToProcess= new LinkedList<>();
fontsToProcess.add(rootFontName);
// propage to "children" fonts
Set<String> alreadyProcessed= new HashSet<>();
while (!fontsToProcess.isEmpty()) {
currentFontName= fontsToProcess.get(0);
fontsToProcess.remove(0);
// with recent Java, use currentFOntName = fontsToProcess.poll instead of the 2 lines above
if (!alreadyProcessed.contains(currentFontName)) { // avoid infinite loop
alreadyProcessed.add(currentFontName);
FontData[] currentFontData= fontRegistry.getFontData(currentFontName);
if (currentFontData == null || Arrays.equals(referenceFontData, currentFontData)) {
if (fontRegistry.hasValueFor(currentFontName)) {
res.add(currentFontName);
}
Set<String> children= fgDefaultToFonts.get(currentFontName);
if (children != null) {
fontsToProcess.addAll(children);
}
}
}
}
return res;
}
}