| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jface.resource; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| import java.util.Set; |
| |
| import org.eclipse.jface.util.Assert; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| |
| /** |
| * A font registry maintains a mapping between symbolic font names |
| * and SWT fonts. |
| * <p> |
| * A font registry owns all of the font objects registered |
| * with it, and automatically disposes of them when the SWT Display |
| * that creates the fonts is disposed. Because of this, clients do |
| * not need to (indeed, must not attempt to) dispose of font |
| * objects themselves. |
| * </p> |
| * <p> |
| * A special constructor is provided for populating a font registry |
| * from a property files using the standard Java resource bundle mechanism. |
| * </p> |
| * <p> |
| * Methods are provided for registering listeners that will be kept |
| * apprised of changes to list of registed fonts. |
| * </p> |
| * <p> |
| * Clients may instantiate this class (it was not designed to be subclassed). |
| * </p> |
| * |
| * Since 3.0 this class extends ResourceRegistry. |
| */ |
| public class FontRegistry extends ResourceRegistry { |
| |
| /** |
| * FontRecord is a private helper class that holds onto a font |
| * and can be used to generate its bold and italic version. |
| */ |
| private class FontRecord { |
| |
| Font baseFont; |
| |
| Font boldFont; |
| |
| Font italicFont; |
| |
| FontData[] baseData; |
| |
| /** |
| * Create a new instance of the receiver based on the |
| * plain font and the data for it. |
| * @param plainFont The base looked up font. |
| * @param data The data used to look it up. |
| */ |
| FontRecord(Font plainFont, FontData[] data) { |
| baseFont = plainFont; |
| baseData = data; |
| } |
| |
| /** |
| * Dispose any of the fonts created for this record. |
| */ |
| void dispose() { |
| baseFont.dispose(); |
| if (boldFont != null) |
| boldFont.dispose(); |
| if (italicFont != null) |
| italicFont.dispose(); |
| } |
| |
| /** |
| * Return the base Font. |
| * @return Font |
| */ |
| public Font getBaseFont() { |
| return baseFont; |
| } |
| |
| /** |
| * Return the bold Font. Create a bold version |
| * of the base font to get it. |
| * @return Font |
| */ |
| public Font getBoldFont() { |
| if (boldFont != null) |
| return boldFont; |
| |
| FontData[] boldData = getModifiedFontData(SWT.BOLD); |
| boldFont = new Font(Display.getCurrent(), boldData); |
| return boldFont; |
| } |
| |
| /** |
| * Get a version of the base font data with the specified |
| * style. |
| * @param style |
| * @return |
| * @todo Generated comment |
| */ |
| private FontData[] getModifiedFontData(int style) { |
| FontData[] styleData = new FontData[baseData.length]; |
| for (int i = 0; i < styleData.length; i++) { |
| FontData base = baseData[i]; |
| styleData[i] = new FontData(base.getName(), base.getHeight(), |
| base.getStyle() | style); |
| } |
| |
| return styleData; |
| } |
| |
| /** |
| * Return the italic Font. Create an italic version of the |
| * base font to get it. |
| * @return Font |
| */ |
| public Font getItalicFont() { |
| if (italicFont != null) |
| return italicFont; |
| |
| FontData[] italicData = getModifiedFontData(SWT.ITALIC); |
| italicFont = new Font(Display.getCurrent(), italicData); |
| return italicFont; |
| } |
| |
| /** |
| * Add any fonts that were allocated for this record to the |
| * stale fonts. Anything that matches the default font will |
| * be skipped. |
| * @param defaultFont The system default. |
| */ |
| void addAllocatedFontsToStale(Font defaultFont) { |
| //Return all of the fonts allocated by the receiver. |
| //if any of them are the defaultFont then don't bother. |
| if (defaultFont != baseFont && baseFont != null) |
| staleFonts.add(baseFont); |
| if (defaultFont != boldFont && boldFont != null) |
| staleFonts.add(boldFont); |
| if (defaultFont != italicFont && italicFont != null) |
| staleFonts.add(italicFont); |
| } |
| } |
| |
| /** |
| * Table of known fonts, keyed by symbolic font name |
| * (key type: <code>String</code>, |
| * value type: <code>FontRecord</code>. |
| */ |
| private Map stringToFontRecord = new HashMap(7); |
| |
| /** |
| * Table of known font data, keyed by symbolic font name |
| * (key type: <code>String</code>, |
| * value type: <code>org.eclipse.swt.graphics.FontData[]</code>). |
| */ |
| private Map stringToFontData = new HashMap(7); |
| |
| /** |
| * Collection of Fonts that are now stale to be disposed |
| * when it is safe to do so (i.e. on shutdown). |
| * @see List |
| */ |
| private List staleFonts = new ArrayList(); |
| |
| /** |
| * Runnable that cleans up the manager on disposal of the display. |
| */ |
| protected Runnable displayRunnable = new Runnable() { |
| public void run() { |
| clearCaches(); |
| } |
| }; |
| |
| /** |
| * Creates an empty font registry. |
| * <p> |
| * There must be an SWT Display created in the current |
| * thread before calling this method. |
| * </p> |
| */ |
| public FontRegistry() { |
| this(Display.getCurrent(), true); |
| } |
| |
| /** |
| * Creates a font registry and initializes its content from |
| * a property file. |
| * <p> |
| * There must be an SWT Display created in the current |
| * thread before calling this method. |
| * </p> |
| * <p> |
| * The OS name (retrieved using <code>System.getProperty("os.name")</code>) |
| * is converted to lowercase, purged of whitespace, and appended |
| * as suffix (separated by an underscore <code>'_'</code>) to the given |
| * location string to yield the base name of a resource bundle |
| * acceptable to <code>ResourceBundle.getBundle</code>. |
| * The standard Java resource bundle mechanism is then used to locate |
| * and open the appropriate properties file, taking into account |
| * locale specific variations. |
| * </p> |
| * <p> |
| * For example, on the Windows 2000 operating system the location string |
| * <code>"com.example.myapp.Fonts"</code> yields the base name |
| * <code>"com.example.myapp.Fonts_windows2000"</code>. For the US English locale, |
| * this further elaborates to the resource bundle name |
| * <code>"com.example.myapp.Fonts_windows2000_en_us"</code>. |
| * </p> |
| * <p> |
| * If no appropriate OS-specific resource bundle is found, the |
| * process is repeated using the location as the base bundle name. |
| * </p> |
| * <p> |
| * The property file contains entries that look like this: |
| * <pre> |
| * textfont.0=MS Sans Serif-regular-10 |
| * textfont.1=Times New Roman-regular-10 |
| * |
| * titlefont.0=MS Sans Serif-regular-12 |
| * titlefont.1=Times New Roman-regular-12 |
| * </pre> |
| * Each entry maps a symbolic font names (the font registry keys) with |
| * a "<code>.<it>n</it></code> suffix to standard font names |
| * on the right. The suffix indicated order of preference: |
| * "<code>.0</code>" indicates the first choice, |
| * "<code>.1</code>" indicates the second choice, and so on. |
| * </p> |
| * The following example shows how to use the font registry: |
| * <pre> |
| * FontRegistry registry = new FontRegistry("com.example.myapp.fonts"); |
| * Font font = registry.get("textfont"); |
| * control.setFont(font); |
| * ... |
| * </pre> |
| * |
| * @param location the name of the resource bundle |
| * @param loader the ClassLoader to use to find the resource bundle |
| * @exception MissingResourceException if the resource bundle cannot be found |
| * @since 2.1 |
| */ |
| public FontRegistry(String location, ClassLoader loader) |
| throws MissingResourceException { |
| Display display = Display.getCurrent(); |
| Assert.isNotNull(display); |
| // FIXE: need to respect loader |
| //readResourceBundle(location, loader); |
| readResourceBundle(location); |
| |
| hookDisplayDispose(display); |
| } |
| |
| /** |
| * Load the FontRegistry using the ClassLoader from the PlatformUI |
| * plug-in |
| */ |
| public FontRegistry(String location) throws MissingResourceException { |
| // FIXE: |
| // this(location, WorkbenchPlugin.getDefault().getDescriptor().getPluginClassLoader()); |
| this(location, null); |
| } |
| |
| /** |
| * Read the resource bundle at location. Look for a file with the |
| * extension _os_ws first, then _os then just the name. |
| * @param location - String - the location of the file. |
| */ |
| |
| private void readResourceBundle(String location) { |
| String osname = System.getProperty("os.name").trim(); //$NON-NLS-1$ |
| String wsname = SWT.getPlatform(); |
| osname = StringConverter.removeWhiteSpaces(osname).toLowerCase(); |
| wsname = StringConverter.removeWhiteSpaces(wsname).toLowerCase(); |
| String OSLocation = location; |
| String WSLocation = location; |
| ResourceBundle bundle = null; |
| if (osname != null) { |
| OSLocation = location + "_" + osname; //$NON-NLS-1$ |
| if (wsname != null) |
| WSLocation = OSLocation + "_" + wsname; //$NON-NLS-1$ |
| } |
| |
| try { |
| bundle = ResourceBundle.getBundle(WSLocation); |
| readResourceBundle(bundle, WSLocation); |
| } catch (MissingResourceException wsException) { |
| try { |
| bundle = ResourceBundle.getBundle(OSLocation); |
| readResourceBundle(bundle, WSLocation); |
| } catch (MissingResourceException osException) { |
| if (location != OSLocation) { |
| bundle = ResourceBundle.getBundle(location); |
| readResourceBundle(bundle, WSLocation); |
| } else |
| throw osException; |
| } |
| } |
| } |
| |
| /** |
| * Creates an empty font registry. |
| * |
| * @param display the Display |
| */ |
| public FontRegistry(Display display) { |
| this(display, true); |
| } |
| |
| /** |
| * Creates an empty font registry. |
| * |
| * @param display |
| * the <code>Display</code> |
| * @param cleanOnDisplayDisposal |
| * whether all fonts allocated by this <code>FontRegistry</code> |
| * should be disposed when the display is disposed |
| * @since 3.1 |
| */ |
| public FontRegistry(Display display, boolean cleanOnDisplayDisposal) { |
| Assert.isNotNull(display); |
| if (cleanOnDisplayDisposal) |
| hookDisplayDispose(display); |
| } |
| |
| /** |
| * Find the first valid fontData in the provided list. If none are valid |
| * return the first one regardless. If the list is empty return null. Return |
| * null if one cannot be found. |
| * |
| * @deprecated use bestDataArray in order to support Motif multiple entry |
| * fonts. |
| */ |
| public FontData bestData(FontData[] fonts, Display display) { |
| for (int i = 0; i < fonts.length; i++) { |
| FontData fd = fonts[i]; |
| |
| if (fd == null) |
| break; |
| |
| FontData[] fixedFonts = display.getFontList(fd.getName(), false); |
| if (isFixedFont(fixedFonts, fd)) { |
| return fd; |
| } |
| |
| FontData[] scalableFonts = display.getFontList(fd.getName(), true); |
| if (scalableFonts.length > 0) { |
| return fd; |
| } |
| } |
| |
| //None of the provided datas are valid. Return the |
| //first one as it is at least the first choice. |
| if (fonts.length > 0) |
| return fonts[0]; |
| |
| //Nothing specified |
| return null; |
| } |
| |
| /** |
| * Find the first valid fontData in the provided list. |
| * If none are valid return the first one regardless. |
| * If the list is empty return null. |
| * @deprecated use filterData in order to preserve |
| * multiple entry fonts on Motif |
| */ |
| public FontData[] bestDataArray(FontData[] fonts, Display display) { |
| |
| FontData bestData = bestData(fonts, display); |
| if (bestData == null) |
| return null; |
| |
| FontData[] datas = new FontData[1]; |
| datas[0] = bestData; |
| return datas; |
| } |
| |
| /** |
| * Removes from the list all fonts that do not exist in this system. |
| * If none are valid, return the first irregardless. If the list is |
| * empty return <code>null</code>. |
| * |
| * @param fonts the fonts to check |
| * @param display the display to check against |
| * @return the list of fonts that have been found on this system |
| * @since 3.1 |
| */ |
| public FontData [] filterData(FontData [] fonts, Display display) { |
| ArrayList good = new ArrayList(fonts.length); |
| for (int i = 0; i < fonts.length; i++) { |
| FontData fd = fonts[i]; |
| |
| if (fd == null) |
| continue; |
| |
| FontData[] fixedFonts = display.getFontList(fd.getName(), false); |
| if (isFixedFont(fixedFonts, fd)) { |
| good.add(fd); |
| } |
| |
| FontData[] scalableFonts = display.getFontList(fd.getName(), true); |
| if (scalableFonts.length > 0) { |
| good.add(fd); |
| } |
| } |
| |
| |
| //None of the provided datas are valid. Return the |
| //first one as it is at least the first choice. |
| if (good.isEmpty() && fonts.length > 0) { |
| good.add(fonts[0]); |
| } |
| else if (fonts.length == 0) { |
| return null; |
| } |
| |
| return (FontData[]) good.toArray(new FontData[good.size()]); |
| } |
| |
| |
| /** |
| * Creates a new font with the given font datas or <code>null</code> |
| * if there is no data. |
| * @return FontRecord for the new Font or <code>null</code>. |
| */ |
| private FontRecord createFont(String symbolicName, FontData[] fonts) { |
| Display display = Display.getCurrent(); |
| if (display == null) |
| return null; |
| |
| FontData[] validData = filterData(fonts, display); |
| if (validData.length == 0) { |
| //Nothing specified |
| return null; |
| } |
| |
| //Do not fire the update from creation as it is not a property change |
| put(symbolicName, validData, false); |
| Font newFont = new Font(display, validData); |
| return new FontRecord(newFont, validData); |
| } |
| |
| /** |
| * Calculates the default font and returns the result |
| */ |
| Font calculateDefaultFont() { |
| Display current = Display.getCurrent(); |
| if (current == null) { |
| Shell shell = new Shell(); |
| Font font = new Font(null, shell.getFont().getFontData()); |
| shell.dispose(); |
| return font; |
| } |
| return new Font(current, current.getSystemFont().getFontData()); |
| } |
| |
| /** |
| * Returns the default font data. Creates it if necessary. |
| * @return Font |
| */ |
| public Font defaultFont() { |
| return defaultFontRecord().getBaseFont(); |
| } |
| |
| /** |
| * Returns the default font record. |
| */ |
| private FontRecord defaultFontRecord() { |
| |
| FontRecord record = (FontRecord) stringToFontRecord |
| .get(JFaceResources.DEFAULT_FONT); |
| if (record == null) { |
| Font defaultFont = calculateDefaultFont(); |
| record = createFont(JFaceResources.DEFAULT_FONT, defaultFont |
| .getFontData()); |
| stringToFontRecord.put(JFaceResources.DEFAULT_FONT, record); |
| } |
| return record; |
| } |
| |
| /** |
| * Returns the default font data. Creates it if necessary. |
| */ |
| private FontData[] defaultFontData() { |
| return defaultFontRecord().baseData; |
| } |
| |
| /** |
| * Returns the font data associated with the given symbolic font name. |
| * Returns the default font data if there is no special value associated |
| * with that name. |
| * |
| * @param symbolicName symbolic font name |
| * @return the font |
| */ |
| public FontData[] getFontData(String symbolicName) { |
| |
| Assert.isNotNull(symbolicName); |
| Object result = stringToFontData.get(symbolicName); |
| if (result == null) |
| return defaultFontData(); |
| |
| return (FontData[]) result; |
| } |
| |
| /** |
| * Returns the font associated with the given symbolic font name. |
| * Returns the default font if there is no special value associated |
| * with that name. |
| * |
| * @param symbolicName symbolic font name |
| * @return the font |
| */ |
| public Font get(String symbolicName) { |
| |
| return getFontRecord(symbolicName).getBaseFont(); |
| } |
| |
| /** |
| * Returns the bold font associated with the given symbolic font name. |
| * Returns the bolded default font if there is no special value associated |
| * with that name. |
| * |
| * @param symbolicName symbolic font name |
| * @return the font |
| * @since 3.0 |
| */ |
| public Font getBold(String symbolicName) { |
| |
| return getFontRecord(symbolicName).getBoldFont(); |
| } |
| |
| /** |
| * Returns the italic font associated with the given symbolic font name. |
| * Returns the italic default font if there is no special value associated |
| * with that name. |
| * |
| * @param symbolicName symbolic font name |
| * @return the font |
| * @since 3.0 |
| */ |
| public Font getItalic(String symbolicName) { |
| |
| return getFontRecord(symbolicName).getItalicFont(); |
| } |
| |
| /** |
| * Return the font record for the key. |
| * @param symbolicName The key for the record. |
| * @return FontRecird |
| */ |
| private FontRecord getFontRecord(String symbolicName) { |
| Assert.isNotNull(symbolicName); |
| Object result = stringToFontRecord.get(symbolicName); |
| if (result != null) |
| return (FontRecord) result; |
| |
| result = stringToFontData.get(symbolicName); |
| |
| FontRecord fontRecord; |
| |
| if (result == null) |
| fontRecord = defaultFontRecord(); |
| else |
| fontRecord = createFont(symbolicName, (FontData[]) result); |
| |
| if (fontRecord == null) // create may have failed. Ensure we have a valid font. |
| fontRecord = defaultFontRecord(); |
| |
| stringToFontRecord.put(symbolicName, fontRecord); |
| return fontRecord; |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.resource.ResourceRegistry#getKeySet() |
| */ |
| public Set getKeySet() { |
| return Collections.unmodifiableSet(stringToFontData.keySet()); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.resource.ResourceRegistry#hasValueFor(java.lang.String) |
| */ |
| public boolean hasValueFor(String fontKey) { |
| return stringToFontData.containsKey(fontKey); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.resource.ResourceRegistry#clearCaches() |
| */ |
| protected void clearCaches() { |
| |
| Iterator iterator = stringToFontRecord.values().iterator(); |
| while (iterator.hasNext()) { |
| Object next = iterator.next(); |
| ((FontRecord) next).dispose(); |
| } |
| |
| disposeFonts(staleFonts.iterator()); |
| stringToFontRecord.clear(); |
| staleFonts.clear(); |
| } |
| |
| /** |
| * Dispose of all of the fonts in this iterator. |
| * @param Iterator over Collection of Font |
| */ |
| private void disposeFonts(Iterator iterator) { |
| while (iterator.hasNext()) { |
| Object next = iterator.next(); |
| ((Font) next).dispose(); |
| } |
| } |
| |
| /** |
| * Hook a dispose listener on the SWT display. |
| */ |
| private void hookDisplayDispose(Display display) { |
| display.disposeExec(displayRunnable); |
| } |
| |
| /** |
| * Checks whether the given font is in the list of fixed fonts. |
| */ |
| private boolean isFixedFont(FontData[] fixedFonts, FontData fd) { |
| // Can't use FontData.equals() since some values aren't |
| // set if a fontdata isn't used. |
| int height = fd.getHeight(); |
| String name = fd.getName(); |
| for (int i = 0; i < fixedFonts.length; i++) { |
| FontData fixed = fixedFonts[i]; |
| if (fixed.getHeight() == height && fixed.getName().equals(name)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Converts a String into a FontData object. |
| */ |
| private FontData makeFontData(String value) throws MissingResourceException { |
| try { |
| return StringConverter.asFontData(value.trim()); |
| } catch (DataFormatException e) { |
| throw new MissingResourceException( |
| "Wrong font data format. Value is: \"" + value + "\"", getClass().getName(), value); //$NON-NLS-2$//$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Adds (or replaces) a font to this font registry under the given |
| * symbolic name. |
| * <p> |
| * A property change event is reported whenever the mapping from |
| * a symbolic name to a font changes. The source of the event is |
| * this registry; the property name is the symbolic font name. |
| * </p> |
| * |
| * @param symbolicName the symbolic font name |
| * @param fontData an Array of FontData |
| */ |
| public void put(String symbolicName, FontData[] fontData) { |
| put(symbolicName, fontData, true); |
| } |
| |
| /** |
| * Adds (or replaces) a font to this font registry under the given |
| * symbolic name. |
| * <p> |
| * A property change event is reported whenever the mapping from |
| * a symbolic name to a font changes. The source of the event is |
| * this registry; the property name is the symbolic font name. |
| * </p> |
| * |
| * @param symbolicName the symbolic font name |
| * @param fontData an Array of FontData |
| * @param update - fire a font mapping changed if true. False |
| * if this method is called from the get method as no setting |
| * has changed. |
| */ |
| private void put(String symbolicName, FontData[] fontData, boolean update) { |
| |
| Assert.isNotNull(symbolicName); |
| Assert.isNotNull(fontData); |
| |
| FontData[] existing = (FontData[]) stringToFontData.get(symbolicName); |
| if (Arrays.equals(existing, fontData)) |
| return; |
| |
| FontRecord oldFont = (FontRecord) stringToFontRecord |
| .remove(symbolicName); |
| stringToFontData.put(symbolicName, fontData); |
| if (update) |
| fireMappingChanged(symbolicName, existing, fontData); |
| |
| if (oldFont != null) |
| oldFont.addAllocatedFontsToStale(defaultFontRecord().getBaseFont()); |
| } |
| |
| /** |
| * Reads the resource bundle. This puts FontData[] objects |
| * in the mapping table. These will lazily be turned into |
| * real Font objects when requested. |
| */ |
| private void readResourceBundle(ResourceBundle bundle, String bundleName) |
| throws MissingResourceException { |
| Enumeration keys = bundle.getKeys(); |
| while (keys.hasMoreElements()) { |
| String key = (String) keys.nextElement(); |
| int pos = key.lastIndexOf('.'); |
| if (pos == -1) { |
| stringToFontData.put(key, new FontData[] { makeFontData(bundle |
| .getString(key)) }); |
| } else { |
| String name = key.substring(0, pos); |
| int i = 0; |
| try { |
| i = Integer.parseInt(key.substring(pos + 1)); |
| } catch (NumberFormatException e) { |
| //Panic the file can not be parsed. |
| throw new MissingResourceException( |
| "Wrong key format ", bundleName, key); //$NON-NLS-1$ |
| } |
| FontData[] elements = (FontData[]) stringToFontData.get(name); |
| if (elements == null) { |
| elements = new FontData[8]; |
| stringToFontData.put(name, elements); |
| } |
| if (i > elements.length) { |
| FontData[] na = new FontData[i + 8]; |
| System.arraycopy(elements, 0, na, 0, elements.length); |
| elements = na; |
| stringToFontData.put(name, elements); |
| } |
| elements[i] = makeFontData(bundle.getString(key)); |
| } |
| } |
| } |
| } |