1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2007
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: org.eclipse.jdt.ui.prefs 1178 2006-11-06 12:48:02Z dmsmith $
21   */
22  package org.crosswire.common.swing;
23  
24  import java.awt.Font;
25  import java.io.IOException;
26  import java.net.URI;
27  
28  import org.crosswire.common.util.FileUtil;
29  import org.crosswire.common.util.Language;
30  import org.crosswire.common.util.Logger;
31  import org.crosswire.common.util.NetUtil;
32  import org.crosswire.common.util.PropertyMap;
33  import org.crosswire.common.util.ResourceUtil;
34  
35  /**
36   * Font Store maintains a persistent, hierarchical store of user font
37   * preferences. A font preference consists of the name of a resource and a font
38   * specification for that resource. The name of the resource may be any unique
39   * value that follows the rules for a property key. The font specification is
40   * the font itself or a string representation of the font that can be turned
41   * into a font with <code>Font.decode(String)</code>.
42   * <p>
43   * Many languages share the same script. Rather than setting a font spec for
44   * many resources with the same language, this class makes it possible to set a
45   * font spec for each language.
46   * </p>
47   * <p>
48   * Thus, the look up hierarchy begins with an exact match for the requested
49   * resource. If it does not work the lookup continues in the following order:
50   * the specified language's font, the fallback font, and the default font. Of
51   * course, if that does not work, use any font that Java thinks is appropriate,
52   * but use the size and style of the default font. Since scripts are shared by
53   * many languages, this FontStore supports the setting of Language defaults. If
54   * the requested language font does not exist a more general one will be
55   * provided.
56   * </p>
57   * <p>
58   * Note: Some languages are represented as transliterations and others have more
59   * than one script, which may or may not be supported by a single font.
60   * </p>
61   * 
62   * @see gnu.lgpl.License for license details.<br>
63   *      The copyright to this program is held by it's authors.
64   * @author DM Smith [dmsmith555 at yahoo dot com]
65   */
66  public class FontStore {
67  
68      /**
69       * Create an new FontStore with the given persistent store.
70       * 
71       * @param storeName
72       *            The name of the store, used as a file name and as a label
73       *            inside the fontStore.
74       * @param fontDir
75       *            The location where the fontStore can be stored.
76       */
77      public FontStore(String storeName, URI fontDir) {
78          if (fontDir == null) {
79              throw new IllegalArgumentException("fontStore cannot be null");
80          }
81          this.storeName = storeName;
82          this.fontStore = NetUtil.lengthenURI(fontDir, this.storeName + FileUtil.EXTENSION_PROPERTIES);
83          this.fontMap = new PropertyMap();
84      }
85  
86      /**
87       * @return the defaultFont
88       */
89      public String getDefaultFont() {
90          load();
91          defaultFont = fontMap.get(DEFAULT_KEY, DEFAULT_FONT);
92          return defaultFont;
93      }
94  
95      /**
96       * @param defaultFont
97       *            the defaultFont to set
98       */
99      public void setDefaultFont(String defaultFont) {
100         load();
101         this.defaultFont = defaultFont;
102         fontMap.put(DEFAULT_KEY, defaultFont);
103         store();
104     }
105 
106     /**
107      * Store a font specification for the resource.
108      * 
109      * @param resource
110      *            the resource
111      * @param font
112      *            the font
113      */
114     public void setFont(String resource, Font font) {
115         if (resource == null || font == null) {
116             return;
117         }
118         load();
119         fontMap.put(resource, GuiConvert.font2String(font));
120         store();
121     }
122 
123     /**
124      * Store a font specification for the language.
125      * 
126      * @param lang
127      *            the language
128      * @param font
129      *            the font
130      */
131     public void setFont(Language lang, Font font) {
132         if (lang == null || font == null) {
133             return;
134         }
135         load();
136         fontMap.put(new StringBuilder(LANG_KEY_PREFIX).append(lang.getCode()).toString(), GuiConvert.font2String(font));
137         store();
138     }
139 
140     /**
141      * Remove the font settings for a given key
142      * 
143      * @param key
144      *            the book initials or language code
145      */
146     public void resetFont(String key) {
147         load();
148         fontMap.remove(key);
149         store();
150     }
151 
152     /**
153      * Get a font for the specified resource. If it does not work try the
154      * following in order: the specified language's font, the fallback font, and
155      * the default font. Of course, if that does not work, use any font that
156      * Java thinks is appropriate, but use the size and style of the default
157      * font.
158      * 
159      * @param resource
160      *            the name of the resource for whom the font is stored.
161      * @param lang
162      *            the language of the resource
163      * @param fallback
164      *            the fontspec for the fallback font
165      * @return the requested font if possible. A fallback font otherwise.
166      */
167     public Font getFont(String resource, Language lang, String fallback) {
168         load();
169 
170         String fontSpec = null;
171         if (resource != null) {
172             fontSpec = fontMap.get(resource);
173         }
174 
175         if (fontSpec != null) {
176             Font obtainedFont = obtainFont(fontSpec);
177             if (obtainedFont != null) {
178                 return obtainedFont;
179             }
180             fontSpec = null;
181         }
182 
183         if (lang != null) {
184             fontSpec = fontMap.get(new StringBuilder(LANG_KEY_PREFIX).append(lang.getCode()).toString());
185         }
186 
187         if (fontSpec != null) {
188             Font obtainedFont = obtainFont(fontSpec);
189             if (obtainedFont != null) {
190                 return obtainedFont;
191             }
192         }
193 
194         fontSpec = fallback;
195         if (fontSpec != null) {
196             Font obtainedFont = obtainFont(fontSpec);
197             if (obtainedFont != null) {
198                 return obtainedFont;
199             }
200         }
201 
202         return GuiConvert.string2Font(defaultFont);
203     }
204 
205     /**
206      * @return the storeName
207      */
208     protected String getStoreName() {
209         return storeName;
210     }
211 
212     /**
213      * @param storeName
214      *            the storeName to set
215      */
216     protected void setStoreName(String storeName) {
217         this.storeName = storeName;
218     }
219 
220     /**
221      * @return the fontStore
222      */
223     protected URI getFontStore() {
224         return fontStore;
225     }
226 
227     /**
228      * @param fontStore
229      *            the fontStore to set
230      */
231     protected void setFontStore(URI fontStore) {
232         this.fontStore = fontStore;
233     }
234 
235     /**
236      * @return the loaded
237      */
238     protected boolean isLoaded() {
239         return loaded;
240     }
241 
242     /**
243      * @param loaded
244      *            the loaded to set
245      */
246     protected void setLoaded(boolean loaded) {
247         this.loaded = loaded;
248     }
249 
250     /**
251      * @return the fontMap
252      */
253     protected PropertyMap getFontMap() {
254         return fontMap;
255     }
256 
257     /**
258      * @param fontMap
259      *            the fontMap to set
260      */
261     protected void setFontMap(PropertyMap fontMap) {
262         this.fontMap = fontMap;
263     }
264 
265     /**
266      * Load the store, if it has not been loaded.
267      */
268     protected void load() {
269         if (loaded) {
270             return;
271         }
272 
273         try {
274             fontMap = ResourceUtil.getProperties(storeName);
275             loaded = true;
276         } catch (IOException e) {
277             log.error("Unable to load the font store: " + fontStore);
278             fontMap = new PropertyMap();
279         }
280     }
281 
282     /**
283      * Store the store, if it exists.
284      */
285     protected void store() {
286         load();
287 
288         try {
289             NetUtil.storeProperties(fontMap, fontStore, storeName);
290         } catch (IOException ex) {
291             log.error("Failed to save BibleDesktop UI Translation", ex);
292         }
293     }
294 
295     protected Font obtainFont(String fontSpec) {
296         if (fontSpec != null) {
297             // Creating a font never fails. Java just silently does
298             // substitution.
299             // Ensure that substitution does not happen.
300             Font obtainedFont = GuiConvert.string2Font(fontSpec);
301             String obtainedFontSpec = GuiConvert.font2String(obtainedFont);
302             if (obtainedFontSpec != null && obtainedFontSpec.equalsIgnoreCase(fontSpec)) {
303                 return obtainedFont;
304             }
305         }
306         return null;
307     }
308 
309     protected static final String DEFAULT_FONT = "Dialog-PLAIN-12";
310     protected static final String LANG_KEY_PREFIX = "lang.";
311     protected static final String DEFAULT_KEY = "default";
312 
313     private String storeName;
314     private String defaultFont;
315     private URI fontStore;
316     private boolean loaded;
317     private PropertyMap fontMap;
318 
319     private static final Logger log = Logger.getLogger(FontStore.class);
320 }
321