org.eclipse.ui.texteditor.AbstractTextZoomHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ui.texteditor.AbstractTextZoomHandler.java

Source

/*******************************************************************************
 * 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.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.IWorkbenchPart;
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) {
        IWorkbenchPart part = HandlerUtil.getActiveEditor(event);
        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 null;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

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

}