package org.eclipse.jface.resource; /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ import java.util.*; import org.eclipse.jface.util.*; 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. *

* 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. *

*

* A special constructor is provided for populating a font registry * from a property files using the standard Java resource bundle mechanism. *

*

* Methods are provided for registering listeners that will be kept * apprised of changes to list of registed fonts. *

*

* Clients may instantiate this class (it was not designed to be subclassed). *

*/ public class FontRegistry { /** * List of property change listeners * (element type: org.eclipse.jface.util.IPropertyChangeListener). */ private ListenerList listeners = new ListenerList(); /** * Table of known fonts, keyed by symbolic font name * (key type: String, * value type: org.eclipse.swt.graphics.Font. */ private Map stringToFont = new HashMap(7); /** * Table of known font data, keyed by symbolic font name * (key type: String, * value type: org.eclipse.swt.graphics.FontData[]). */ 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; /** * Creates an empty font registry. *

* There must be an SWT Display created in the current * thread before calling this method. *

*/ public FontRegistry() { Display display = Display.getCurrent(); Assert.isNotNull(display); hookDisplayDispose(display); } /** * Creates a font registry and initializes its content from * a property file. *

* There must be an SWT Display created in the current * thread before calling this method. *

*

* The OS name (retrieved using System.getProperty("os.name")) * is converted to lowercase, purged of whitespace, and appended * as suffix (separated by an underscore '_') to the given * location string to yield the base name of a resource bundle * acceptable to ResourceBundle.getBundle. * The standard Java resource bundle mechanism is then used to locate * and open the appropriate properties file, taking into account * locale specific variations. *

*

* For example, on the Windows 2000 operating system the location string * "com.example.myapp.Fonts" yields the base name * "com.example.myapp.Fonts_windows2000". For the US English locale, * this further elaborates to the resource bundle name * "com.example.myapp.Fonts_windows2000_en_us". *

*

* If no appropriate OS-specific resource bundle is found, the * process is repeated using the location as the base bundle name. *

*

* The property file contains entries that look like this: *

	 *	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
	 * 
* Each entry maps a symbolic font names (the font registry keys) with * a ".n suffix to standard font names * on the right. The suffix indicated order of preference: * ".0" indicates the first choice, * ".1" indicates the second choice, and so on. *

* The following example shows how to use the font registry: *
	 *	FontRegistry registry = new FontRegistry("com.example.myapp.fonts");
	 *  Font font = registry.get("textfont");
	 *  control.setFont(font);
	 *  ...
	 * 
* * @param location the name of the resource bundle * @param 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 * @see FontRegistry(String,loader) */ 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; } } } /** * 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, ClassLoader loader) { 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, Locale.getDefault(), loader); readResourceBundle(bundle, WSLocation); } catch (MissingResourceException wsException) { try { bundle = ResourceBundle.getBundle( OSLocation, Locale.getDefault(), loader); readResourceBundle(bundle, WSLocation); } catch (MissingResourceException osException) { if (location != OSLocation) { bundle = ResourceBundle.getBundle( location, Locale.getDefault(), loader); readResourceBundle(bundle, WSLocation); } else throw osException; } } } /** * Creates an empty font registry. * * @param display the Display */ public FontRegistry(Display display) { Assert.isNotNull(display); hookDisplayDispose(display); } /** * Adds a property change listener to this registry. * * @param listener a property change listener */ public void addListener(IPropertyChangeListener listener) { listeners.add(listener); } /** * 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]; else //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. */ public FontData[] bestDataArray(FontData[] fonts, Display display) { FontData bestData = bestData(fonts, display); if (bestData == null) return null; else { FontData[] datas = new FontData[1]; datas[0] = bestData; return datas; } } /** * Creates a new font with the given font datas or nulk * if there is no data. */ private Font createFont(String symbolicName, FontData[] fonts) { Display display = Display.getCurrent(); FontData[] validData = bestDataArray(fonts, display); if (validData == null) { //Nothing specified return null; } else { //Do not fire the update from creation as it is not a property change put(symbolicName, validData, false); return new Font(display, validData); } } /** * Returns the default font. Creates it if necessary. */ Font defaultFont() { Display current = Display.getCurrent(); if (current == null) { Shell shell = new Shell(); Font font = shell.getFont(); shell.dispose(); return font; } else return current.getSystemFont(); } /** * Returns the default font data. Creates it if necessary. */ private FontData[] defaultFontData() { return defaultFont().getFontData(); } /** * Fires a PropertyChangeEvent. */ private void fireFontMappingChanged(String name) { final String finalName = name; final Object[] listeners = this.listeners.getListeners(); if (listeners.length > 0) { // FIXME: need to do this without dependency on org.eclipse.core.runtime // Platform.run(new SafeRunnable(JFaceResources.getString("FontRegistry.changeError")) { //$NON-NLS-1$ // public void run() { PropertyChangeEvent event = new PropertyChangeEvent(this, finalName, null, null); for (int i = 0; i < listeners.length; ++i) { ((IPropertyChangeListener) listeners[i]).propertyChange(event); } // } // }); } } /** * 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) { Assert.isNotNull(symbolicName); Object result = stringToFont.get(symbolicName); if (result != null) return (Font) result; result = stringToFontData.get(symbolicName); if (result == null) return defaultFont(); // Create the font and update the mapping so it can // be shared. Font font = createFont(symbolicName, (FontData[]) result); stringToFont.put(symbolicName, font); // Note, the font may be null if the create() failed. Put a mapping // in for this font to prevent repeated attempts to allocate the // same font. if (font == null) return defaultFont(); return font; } /** * Shut downs this resource registry and disposes of all registered fonts. */ private void handleDisplayDispose() { disposeFonts(stringToFont.values().iterator()); disposeFonts(staleFonts.iterator()); stringToFont.clear(); staleFonts.clear(); listeners.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(new Runnable() { public void run() { handleDisplayDispose(); } }); } /** * 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. *

* 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. *

* * @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. */ 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. *

* 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. *

* * @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; Font oldFont = (Font) stringToFont.remove(symbolicName); stringToFontData.put(symbolicName, fontData); if (update) fireFontMappingChanged(symbolicName); if (oldFont == defaultFont()) return; if (oldFont != null) staleFonts.add(oldFont); } /** * 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)); } } } /** * Removes the given listener from this registry. * Has no affect if the listener is not registered. * * @param listener a property change listener */ public void removeListener(IPropertyChangeListener listener) { listeners.remove(listener); } }