1 | // Jomic - a viewer for comic book archives. |
2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | package net.sf.jomic.tools; |
17 | |
18 | import java.beans.PropertyChangeEvent; |
19 | import java.beans.PropertyChangeListener; |
20 | import java.text.DecimalFormat; |
21 | import java.text.MessageFormat; |
22 | import java.text.NumberFormat; |
23 | import java.util.Locale; |
24 | import java.util.ResourceBundle; |
25 | import java.util.StringTokenizer; |
26 | |
27 | import javax.swing.ButtonGroup; |
28 | import javax.swing.JButton; |
29 | import javax.swing.JMenu; |
30 | import javax.swing.JMenuItem; |
31 | import javax.swing.JRadioButtonMenuItem; |
32 | import javax.swing.UIManager; |
33 | |
34 | import net.sf.jomic.common.PropertyConstants; |
35 | |
36 | import org.apache.commons.logging.Log; |
37 | import org.apache.commons.logging.LogFactory; |
38 | |
39 | /** |
40 | * Tools for localization. |
41 | * |
42 | * @author Thomas Aglassinger |
43 | */ |
44 | public final class LocaleTools implements PropertyChangeListener |
45 | { |
46 | public static final String MENU_EDIT = "edit"; |
47 | public static final String MENU_FILE = "file"; |
48 | public static final String MENU_GO = "go"; |
49 | public static final String MENU_HELP = "help"; |
50 | public static final String MENU_VIEW = "view"; |
51 | |
52 | /** |
53 | * "1000" in terms of base 2. |
54 | */ |
55 | private static final int BASE2_KILO = 1024; |
56 | private static final int LOCALE_ELEMENT_COUNT = 3; |
57 | |
58 | private static /*@ spec_public nullable @*/ LocaleTools instance; |
59 | private Log logger; |
60 | private /*@ spec_public @*/ ResourceBundle bundle; |
61 | private NumberFormat byteFormat; |
62 | private ErrorTools errorTools; |
63 | private /*@ spec_public @*/ Locale locale; |
64 | |
65 | private LocaleTools() { |
66 | logger = LogFactory.getLog(LocaleTools.class); |
67 | errorTools = ErrorTools.instance(); |
68 | setLocaleHelper(Locale.getDefault()); |
69 | byteFormat = new DecimalFormat("0.##"); |
70 | } |
71 | |
72 | /** |
73 | * Set Locale to use with Jomic. |
74 | */ |
75 | //@ assignable bundle; |
76 | //@ assignable locale; |
77 | //@ ensures getLocale().equals(newLocale); |
78 | public void setLocale(Locale newLocale) { |
79 | setLocaleHelper(newLocale); |
80 | } |
81 | |
82 | /** |
83 | * Set Locale to use with Jomic. |
84 | */ |
85 | //@ assignable bundle; |
86 | //@ assignable locale; |
87 | public void setLocale(String displayName) { |
88 | Locale newLocale = getLocaleForLanguageCountryVariant(displayName); |
89 | |
90 | setLocale(newLocale); |
91 | } |
92 | |
93 | /** |
94 | * Get ResourceBundle for localization. |
95 | */ |
96 | public /*@ pure @*/ ResourceBundle getBundle() { |
97 | return bundle; |
98 | } |
99 | |
100 | /** |
101 | * Get text to be used for "Cancel" buttons. |
102 | */ |
103 | public /*@ pure @*/ String getCancelText() { |
104 | return UIManager.getString("OptionPane.cancelButtonText"); |
105 | } |
106 | |
107 | public /*@ pure @*/ Locale getLocale() { |
108 | return locale; |
109 | } |
110 | |
111 | /** |
112 | * Get locale specified by <code>languageCountryVariant</code>. |
113 | * |
114 | * @param languageCountryVariant text of the form "language_region_variant", for example "en" |
115 | * or "de_AT" |
116 | */ |
117 | public /*@ pure @*/ Locale getLocaleForLanguageCountryVariant(String languageCountryVariant) { |
118 | Locale result; |
119 | String[] strings = parseLanguageCountryVariant(languageCountryVariant); |
120 | |
121 | //@ assert strings.length == LOCALE_ELEMENT_COUNT; |
122 | result = new Locale(strings[0], strings[1], strings[2]); |
123 | return result; |
124 | } |
125 | |
126 | public /*@ pure @*/ String getMenuItemLabel(String subMenu, String itemKey) { |
127 | return getMessage("menus." + subMenu + "." + itemKey); |
128 | } |
129 | |
130 | public /*@ pure @*/ String getMenuLabel(String menuKey) { |
131 | return getMessage("menus." + menuKey); |
132 | } |
133 | |
134 | /** |
135 | * Get localized message (without options). |
136 | */ |
137 | //@ requires key.length() > 0; |
138 | public /*@ pure @*/ String getMessage(String key) { |
139 | return getMessage(key, null); |
140 | } |
141 | |
142 | /** |
143 | * Get localized message with several options referred to as {0}, {1}, and so on. |
144 | */ |
145 | //@ requires key.length() > 0; |
146 | public /*@ pure @*/ String getMessage(String key, /*@ nullable @*/ Object[] options) { |
147 | String result; |
148 | String pattern = bundle.getString(key); |
149 | |
150 | if (options == null) { |
151 | result = pattern; |
152 | } else { |
153 | MessageFormat messageFormat = new MessageFormat(pattern); |
154 | messageFormat.setLocale(getLocale()); |
155 | result = messageFormat.format(options); |
156 | } |
157 | return result; |
158 | } |
159 | |
160 | /** |
161 | * Get localized message with one option referred to as {0}. |
162 | */ |
163 | //@ requires key.length() > 0; |
164 | public /*@ pure @*/ String getMessage(String key, /*@ nullable @*/ Object option) { |
165 | Object[] options = new Object[]{option}; |
166 | |
167 | return getMessage(key, options); |
168 | } |
169 | |
170 | /** |
171 | * Get text to be used for "OK" buttons. |
172 | */ |
173 | public /*@ pure @*/ String getOkText() { |
174 | return UIManager.getString("OptionPane.okButtonText"); |
175 | } |
176 | |
177 | //@ ensures instance != null; |
178 | public static synchronized LocaleTools instance() { |
179 | if (instance == null) { |
180 | instance = new LocaleTools(); |
181 | } |
182 | return instance; |
183 | } |
184 | |
185 | //@ requires amount >= 0; |
186 | public /*@ pure @*/ String asByteText(long amount) { |
187 | String result; |
188 | String[] units = new String[]{"byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte", "exabyte"}; |
189 | int unitIndex = -1; |
190 | long unitDivider = 1; |
191 | double dividedAmount; |
192 | |
193 | do { |
194 | dividedAmount = ((double) amount) / unitDivider; |
195 | unitDivider *= BASE2_KILO; |
196 | unitIndex += 1; |
197 | } while ((dividedAmount >= BASE2_KILO) && (unitIndex < units.length)); |
198 | |
199 | String amountText = byteFormat.format(dividedAmount); |
200 | |
201 | result = getMessage("units." + units[unitIndex], amountText); |
202 | return result; |
203 | } |
204 | |
205 | public /*@ pure @*/ JButton createButton(String buttonKey) { |
206 | String label = getMessage(buttonKey); |
207 | JButton result = createButtonWithLabel(label); |
208 | |
209 | return result; |
210 | } |
211 | |
212 | public /*@ pure @*/ JButton createCancelButton() { |
213 | return createButtonWithLabel(getCancelText()); |
214 | } |
215 | |
216 | public /*@ pure @*/ JMenu createMenu(String menuKey) { |
217 | String label = getMenuLabel(menuKey); |
218 | JMenu result = new JMenu(label); |
219 | |
220 | return result; |
221 | } |
222 | |
223 | public /*@ pure @*/ JMenuItem createMenuItem(String subMenu, String itemKey) { |
224 | String label = getMenuItemLabel(subMenu, itemKey); |
225 | JMenuItem result = new JMenuItem(label); |
226 | |
227 | return result; |
228 | } |
229 | |
230 | public JRadioButtonMenuItem createRadioButtonMenuItem(String subMenu, ButtonGroup group, String itemKey) { |
231 | String label = getMenuItemLabel(subMenu, itemKey); |
232 | JRadioButtonMenuItem result = new JRadioButtonMenuItem(label); |
233 | |
234 | group.add(result); |
235 | |
236 | return result; |
237 | } |
238 | |
239 | /** |
240 | * Parse a string of the form "language_country_variant" (with any of the elements being |
241 | * optional), and return an array containing the elements. If no element has been specified, it |
242 | * will be empty (<code>""</code>). |
243 | * |
244 | * @param languageCountryVariant of the form "language_region_variant", for example "en" or |
245 | * "de_AT", or <code>null</code> to indicate the default Locale. |
246 | */ |
247 | //@ signals_only IllegalArgumentException; |
248 | //@ ensures \nonnullelements(\result); |
249 | public /*@ pure @*/ String[] parseLanguageCountryVariant(/*@ nullable @*/ String languageCountryVariant) { |
250 | String[] result = new String[LOCALE_ELEMENT_COUNT]; |
251 | String localeText; |
252 | |
253 | if (languageCountryVariant == null) { |
254 | localeText = Locale.getDefault().toString(); |
255 | } else { |
256 | localeText = languageCountryVariant; |
257 | } |
258 | |
259 | String language = ""; |
260 | String country = ""; |
261 | String variant = ""; |
262 | int wordIndex = 0; |
263 | StringTokenizer tokenizer = new StringTokenizer(localeText, "_", true); |
264 | |
265 | while (tokenizer.hasMoreTokens()) { |
266 | String token = tokenizer.nextToken(); |
267 | |
268 | if (token.equals("_")) { |
269 | wordIndex += 1; |
270 | if (wordIndex == LOCALE_ELEMENT_COUNT) { |
271 | // English message because we cannot expect the localization bundle to be loaded yet. |
272 | throw new IllegalArgumentException( |
273 | "locale specification must contain at most 2 underscores (_), but is: " |
274 | + languageCountryVariant); |
275 | } |
276 | } else if (wordIndex == 0) { |
277 | language = token; |
278 | } else if (wordIndex == 1) { |
279 | country = token; |
280 | } else if (wordIndex == 2) { |
281 | variant = token; |
282 | } |
283 | } |
284 | result[0] = language; |
285 | result[1] = country; |
286 | result[2] = variant; |
287 | return result; |
288 | } |
289 | |
290 | //@ also |
291 | //@ requires event.getPropertyName() != null; |
292 | public void propertyChange(PropertyChangeEvent event) { |
293 | try { |
294 | String propertyName = event.getPropertyName(); |
295 | |
296 | if (propertyName.equals(PropertyConstants.LOCALE)) { |
297 | String localeText = (String) event.getNewValue(); |
298 | |
299 | setLocale(new Locale(localeText)); |
300 | } |
301 | } catch (Throwable error) { |
302 | errorTools.showError(event, error); |
303 | } |
304 | } |
305 | |
306 | private /*@ pure @*/ JButton createButtonWithLabel(String label) { |
307 | JButton result = new JButton(label); |
308 | |
309 | return result; |
310 | } |
311 | |
312 | //@ assignable bundle; |
313 | //@ assignable locale; |
314 | private /*@ helper @*/ void setLocaleHelper(Locale newLocale) { |
315 | if (logger.isInfoEnabled()) { |
316 | logger.info("setting locale to " + newLocale.toString() |
317 | + " (" + newLocale.getDisplayName(Locale.ENGLISH) + ")"); |
318 | } |
319 | Locale.setDefault(newLocale); |
320 | locale = newLocale; |
321 | bundle = ResourceBundle.getBundle(LocaleTools.class.getPackage().getName() + ".bundle", locale); |
322 | } |
323 | } |