source: trunk/gli/src/org/greenstone/gatherer/Dictionary.java@ 5296

Last change on this file since 5296 was 5296, checked in by jmt12, 21 years ago

Added keylist functionality

  • Property svn:keywords set to Author Date Id Revision
File size: 22.6 KB
Line 
1package org.greenstone.gatherer;
2/**
3 *#########################################################################
4 *
5 * A component of the Gatherer application, part of the Greenstone digital
6 * library suite from the New Zealand Digital Library Project at the
7 * University of Waikato, New Zealand.
8 *
9 * <BR><BR>
10 *
11 * Author: John Thompson, Greenstone Digital Library, University of Waikato
12 *
13 * <BR><BR>
14 *
15 * Copyright (C) 1999 New Zealand Digital Library Project
16 *
17 * <BR><BR>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * <BR><BR>
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * <BR><BR>
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
36 *########################################################################
37 */
38import java.io.*;
39import java.util.*;
40import javax.swing.*;
41import javax.swing.plaf.*;
42import javax.swing.text.*;
43import javax.swing.tree.*;
44import org.greenstone.gatherer.gui.border.TitledBorder;
45import org.greenstone.gatherer.util.DictionaryTreeNode;
46import org.greenstone.gatherer.util.MutableComboBoxEntry;
47import org.greenstone.gatherer.util.ArrayTools;
48import org.greenstone.gatherer.util.Utility;
49/** Extends the ResourceBundle class to allow for the automatic insertion of arguments. Note that the key names beginning Farg are reserved for formatting. <BR>
50 * <BR>
51 * Property files usable by this class have the Backus-Naur form: <BR>
52 * <BR>
53 * FileLine ::= Comment | Mapping <BR>
54 * Comment ::= '#' SString <BR>
55 * Mapping ::= NZString ':' SString ( Argument SString )* <BR>
56 * NZString ::= ( Char | Int ) SString <BR>
57 * Argument ::= '{' Int '}' <BR>
58 * SString ::= String . ['"','#',...] -> ['\"','\#',...] <BR>
59 * <BR>
60 * In order to add a new dictionary Locale, simply copy the existing dictionary.properties files, replace the values (Strings after the ':') with the new language specific ones being careful to maintain formatting and Gatherer placed arguments, then save the new dictionary as: <br>
61 * <BR>
62 * dictionary_<I>locale</I>.properties<BR>
63 * <BR>
64 * where locale is made of two two-letter codes seperated by an underscore. The first code is in lower-case and defines the language. The second is in upper-case and defines the country. For example the default dictionary could also correctly be called:
65 * <BR>
66 * dictionary_en_NZ.properties<BR>
67 * @author John Thompson, Greenstone Digital Library, University of Waikato
68 * @version 2.3
69 */
70public class Dictionary
71 extends HashMap {
72
73 static final private boolean KEY_LIST_DEBUG = true;
74 static final private String KEY_LIST_FILENAME = "keylist.txt";
75
76 /** A String which more explicitly states the Locale of this dictionary. */
77 public String language = null;
78 /** A static reference to ourself. */
79 static public Dictionary self;
80 /** The font used when displaying various html text. */
81 private FontUIResource font = null;
82 /** A reference to remind us of the current locale. */
83 private Locale locale = null;
84 /** The ResourceBundle which contains the raw key-value mappings. Loaded from a file named "dictionary<I>locale</I>.properties*/
85 private ResourceBundle dictionary = null;
86
87 private TreeSet key_list = null;;
88
89 /** Constructs the Dictionary class by first checking if a Locale has been set. If not the default locale is used, and a ResourceBundle is created. Finally a single important String, Language, is made available outside the class so a more read-able version of the Locale of this Dictionary is present.
90 * @param locale The <strong>Locale</strong> used to load the desired dictionary resource bundle.
91 */
92 public Dictionary(Locale locale, FontUIResource font) {
93 super();
94 this.self = this;
95
96 if(KEY_LIST_DEBUG) {
97 // Reload the keylist
98 File file = new File(KEY_LIST_FILENAME);
99 key_list = new TreeSet();
100 try {
101 BufferedReader br = new BufferedReader(new FileReader(file));
102 String line;
103 while((line = br.readLine()) != null) {
104 key_list.add(line);
105 }
106 br.close();
107 br = null;
108 }
109 catch(Exception error) {
110 error.printStackTrace();
111 }
112 }
113
114 // Initialize.
115 this.font = font;
116 if(locale == null) {
117 this.locale = Locale.getDefault();
118 }
119 else {
120 this.locale = locale;
121 Locale.setDefault(locale);
122 }
123 dictionary = ResourceBundle.getBundle(Utility.DICTIONARY, this.locale);
124 // Now quickly read in language name.
125 language = dictionary.getString("Language");
126 }
127 /** Change the currently loaded dictionary and update registered (ie dynamic) components as possible. */
128 public void changeDictionary(Locale locale) {
129 this.locale = locale;
130 // Load new dictionary
131 dictionary = ResourceBundle.getBundle(Utility.DICTIONARY, locale);
132 language = dictionary.getString("Language");
133 System.err.println("Having loaded new dictionary: " + language);
134 // Refresh all registered component
135 System.err.println("Updating components");
136 for(Iterator keys = keySet().iterator(); keys.hasNext(); ) {
137 Object component = keys.next();
138 String[] args = (String[]) get(component);
139 if(component instanceof AbstractButton) {
140 register((AbstractButton)component, args, true);
141 }
142 else if(component instanceof JComboBox) {
143 register((JComboBox)component, args, true);
144 }
145 else if(component instanceof JDialog) {
146 register((JDialog)component, args, true);
147 }
148 else if(component instanceof JFrame) {
149 register((JFrame)component, args, true);
150 }
151 else if(component instanceof JLabel) {
152 register((JLabel)component, args, true);
153 }
154 else if(component instanceof JTabbedPane) {
155 register((JTabbedPane)component, args, true);
156 }
157 else if(component instanceof JTextComponent) {
158 register((JTextComponent)component, args, true);
159 }
160 else if(component instanceof JTree) {
161 register((JTree)component, args, true);
162 }
163 else if(component instanceof TitledBorder) {
164 register((TitledBorder)component, args, true);
165 }
166 args = null;
167 component = null;
168 }
169 }
170 /** Remove the component from our registered components list. */
171 public void deregister(Object component) {
172 remove(component);
173 }
174
175 public void destroy() {
176 if(key_list != null) {
177 try {
178 FileOutputStream fos = new FileOutputStream(KEY_LIST_FILENAME);
179 for(Iterator iter = key_list.iterator(); iter.hasNext(); ) {
180 String value = (String) iter.next();
181 fos.write(value.getBytes());
182 fos.write('\n');
183 value = null;
184 }
185 fos.close();
186 fos = null;
187 }
188 catch(Exception error) {
189 error.printStackTrace();
190 }
191 }
192 }
193
194 /** Overloaded to call get with both a key and an empty argument array.
195 * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
196 * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
197 */
198 public String get(String key) {
199 return get(key, (String[])null);
200 }
201 /** Convienence method with transforms the second string argument into a string array. */
202 public String get(String key, String arg) {
203 String[] args = new String[1];
204 args[0] = arg;
205 return get(key, args);
206 }
207 /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
208 * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
209 * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
210 * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
211 * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
212 */
213 public String get(String key, String args[]) {
214 if(key_list != null) {
215 synchronized(this) {
216 key_list.add(key);
217 }
218 }
219 try {
220 String initial = dictionary.getString(key);
221 // If the string contains arguments we have to insert them.
222 String complete = "";
223 // While we still have initial string left.
224 while(initial.length() > 0 && initial.indexOf('{') != -1 && initial.indexOf('}') != -1) {
225 // Remove preamble
226 int opening = initial.indexOf('{');
227 int closing = initial.indexOf('}');
228 complete = complete + initial.substring(0, opening);
229 // Parse arg_num
230 String arg_str = initial.substring(opening + 1, closing);
231 int arg_num = Integer.parseInt(arg_str);
232 if(closing + 1 < initial.length()) {
233 initial = initial.substring(closing + 1);
234 }
235 else {
236 initial = "";
237 }
238 // Insert argument
239 if(args != null && 0 <= arg_num && arg_num < args.length) {
240 complete = complete + args[arg_num];
241 }
242 else if(arg_num >= 32) {
243 String f_subargs[] = new String[1];
244 if(font != null) {
245 f_subargs[0] = font.getFontName();
246 }
247 else {
248 f_subargs[0] = "Arial";
249 }
250 complete = complete + get("Farg" + arg_num, f_subargs);
251 }
252 }
253 return complete + initial;
254 }
255 catch (Exception e) {
256 if(!key.endsWith("_Tooltip")) {
257 Gatherer.println("Missing value for key: " + key);
258 }
259 //e.printStackTrace();
260 return key;
261 }
262 }
263 /** Retrieve the two letter code of the current language we are using, according to the stored locale.
264 * @return A <strong>String</strong> containing the two letter ISO639 language code.
265 */
266 public String getLanguage() {
267 return locale.getLanguage();
268 }
269 /** Register an abstract button component. */
270 public void register(AbstractButton component, String[] args, boolean already_registered) {
271 if(component != null) {
272 // Determine the key
273 String key = "";
274 if(!already_registered) {
275 key = component.getText();
276 }
277 else {
278 key = args[args.length - 1];
279 }
280 // Update the component using the AWTEvent queue
281 String value = get(key, args);
282 String tooltip = get(key + "_Tooltip", (String[])null);
283 ChangeTask task = new AbstractButtonChangeTask(component, key, value, tooltip);
284 SwingUtilities.invokeLater(task);
285 // Register as necessary
286 if(!already_registered) {
287 args = ArrayTools.add(args, key);
288 put(component, args);
289 }
290 }
291 }
292 /** Register a combobox component. */
293 public void register(JComboBox component, String[] args, boolean already_registered) {
294 if(component != null) {
295 // If not already registered then args will be null.
296 if(!already_registered) {
297 args = new String[component.getItemCount()];
298 }
299 // Retrieve the tooltip. The key is mostly derived from the comboboxes name.
300 String key = component.getName();
301 String tooltip = get(key + "_Tooltip", (String[])null);
302 ChangeTask task = new JComboBoxChangeTask(component, key, -1, tooltip);
303 SwingUtilities.invokeLater(task);
304 // Iterate through the combobox, updating values and recording the original key of each item in args.
305 for(int i = 0; i < args.length; i++) {
306 if(args[i] == null) {
307 args[i] = component.getItemAt(i).toString();
308 }
309 String value = get(args[i], (String[])null);
310 task = new JComboBoxChangeTask(component, key, i, value);
311 SwingUtilities.invokeLater(task);
312 }
313 // Register if necessary
314 if(!already_registered) {
315 put(component, args);
316 }
317 }
318 }
319 /** Register a dialog component. */
320 public void register(JDialog component, String[] args, boolean already_registered) {
321 if(component != null) {
322 // Determine the key
323 String key = "";
324 if(!already_registered) {
325 key = component.getTitle();
326 }
327 else {
328 key = args[args.length - 1];
329 }
330 // Update the component using the AWTEvent queue
331 String value = get(key, args);
332 ChangeTask task = new JDialogChangeTask(component, key, value);
333 SwingUtilities.invokeLater(task);
334 // Register as necessary
335 if(!already_registered) {
336 args = ArrayTools.add(args, key);
337 put(component, args);
338 }
339 }
340 }
341 /** Register a frame component. */
342 public void register(JFrame component, String[] args, boolean already_registered) {
343 if(component != null) {
344 // Determine the key
345 String key = "";
346 if(!already_registered) {
347 key = component.getTitle();
348 }
349 else {
350 key = args[args.length - 1];
351 }
352 // Update the component using the AWTEvent queue
353 String value = get(key, args);
354 ChangeTask task = new JFrameChangeTask(component, key, value);
355 SwingUtilities.invokeLater(task);
356 // Register as necessary
357 if(!already_registered) {
358 args = ArrayTools.add(args, key);
359 put(component, args);
360 }
361 }
362 }
363 /** Register a label component. */
364 public void register(JLabel component, String[] args, boolean already_registered) {
365 if(component != null) {
366 // Determine the key
367 String key = "";
368 if(!already_registered) {
369 key = component.getText();
370 }
371 else {
372 key = args[args.length - 1];
373 }
374 // Update the component using the AWTEvent queue
375 String value = get(key, args);
376 ChangeTask task = new JLabelChangeTask(component, key, value);
377 SwingUtilities.invokeLater(task);
378 // Register as necessary
379 if(!already_registered) {
380 args = ArrayTools.add(args, key);
381 put(component, args);
382 }
383 }
384 }
385 /** Register a tab pane component. */
386 public void register(JTabbedPane component, String[] args, boolean already_registered) {
387 if(component != null) {
388 // If not already registered then args will be null.
389 if(!already_registered) {
390 args = new String[component.getTabCount()];
391 }
392 // Iterate through the tabbed panes tabs, updating values and recording the original key of each item in args.
393 for(int i = 0; i < args.length; i++) {
394 if(args[i] == null) {
395 args[i] = component.getTitleAt(i);
396 }
397 String value = get(args[i], (String[])null);
398 String tooltip = get(args[i] + "_Tooltip", (String[])null);
399 ChangeTask task = new JTabbedPaneChangeTask(component, args[i], i, value, tooltip);
400 SwingUtilities.invokeLater(task);
401 }
402 // Register if necessary
403 if(!already_registered) {
404 put(component, args);
405 }
406 }
407 }
408 /** Register a text component. */
409 public void register(JTextComponent component, String[] args, boolean already_registered) {
410 if(component != null) {
411 // Determine the key
412 String key = "";
413 if(!already_registered) {
414 key = component.getText();
415 }
416 else {
417 key = args[args.length - 1];
418 }
419 // Update the component using the AWTEvent queue
420 String value = get(key, args);
421 String tooltip = get(key + "_Tooltip", (String[])null);
422 ChangeTask task = new JTextComponentChangeTask(component, key, value, tooltip);
423 SwingUtilities.invokeLater(task);
424 // Register as necessary
425 if(!already_registered) {
426 args = ArrayTools.add(args, key);
427 put(component, args);
428 }
429 }
430 }
431 /** Register a tree component. */
432 public void register(JTree component, String[] args, boolean already_registered) {
433 if(component != null) {
434 // Retrieve the tooltip using the components name
435 String key = component.getName();
436 String tooltip = get(key + "_Tooltip", (String[])null);
437 ChangeTask task = new JTreeChangeTask(component, key, tooltip);
438 SwingUtilities.invokeLater(task);
439 // A tree can never be previously registered. In otherwords the keys are harvested each time. Thus for a tree to remain consistant its up to the implementer to implement DictionaryTreeNode for the tree nodes!
440 ArrayList nodes = new ArrayList();
441 nodes.add(component.getModel().getRoot());
442 while(nodes.size() > 0) {
443 DictionaryTreeNode node = (DictionaryTreeNode) nodes.remove(0);
444 // Update
445 String value = get(node.getKey(), (String[])null);
446 task = new JTreeChangeTask(component, node.getKey(), node, value);
447 SwingUtilities.invokeLater(task);
448 // Add children to nodes
449 for(int i = 0; i < node.getChildCount(); i++) {
450 nodes.add(node.getChildAt(i));
451 }
452 }
453 }
454 }
455 /** Register a titled border component. */
456 public void register(TitledBorder component, String[] args, boolean already_registered) {
457 if(component != null) {
458 // Determine the key
459 String key = "";
460 if(!already_registered) {
461 key = component.getTitle();
462 }
463 else {
464 key = args[args.length - 1];
465 }
466 // Update the component using the AWTEvent queue
467 String value = get(key, args);
468 ChangeTask task = new TitledBorderChangeTask(component, key, value);
469 SwingUtilities.invokeLater(task);
470 // Register as necessary
471 if(!already_registered) {
472 args = ArrayTools.add(args, key);
473 put(component, args);
474 }
475 }
476 }
477 /** A get method called internally by components that have been previously been registered, which means that arg[index] is the original key value. Index is usually the last entry in the array, however this is not true for comboboxes, tabbed panes and trees. */
478 private String get(String[] args, int index) {
479 return get(args[index], args);
480 }
481
482 private abstract class ChangeTask
483 implements Runnable {
484 protected String key;
485 protected String value;
486 public ChangeTask(String key, String value) {
487 this.key = key;
488 this.value = value;
489 }
490 public void run() {
491 }
492 }
493 /** Update the text and tooltip for this button. */
494 private class AbstractButtonChangeTask
495 extends ChangeTask {
496 private AbstractButton component;
497 private String tooltip;
498 public AbstractButtonChangeTask(AbstractButton component, String key, String value, String tooltip) {
499 super(key, value);
500 this.component = component;
501 this.tooltip = tooltip;
502 }
503 public void run() {
504 component.setText(value);
505 if(!tooltip.equals(key+"_Tooltip")) {
506 component.setToolTipText(tooltip);
507 }
508 else {
509 component.setToolTipText(null);
510 }
511 }
512 }
513 /** Update the text associated with a combobox. If the index used is -1 then we are setting the tooltip for this combobox. */
514 private class JComboBoxChangeTask
515 extends ChangeTask {
516 private int index;
517 private JComboBox component;
518 public JComboBoxChangeTask(JComboBox component, String key, int index, String value) {
519 super(key, value);
520 this.component = component;
521 this.index = index;
522 }
523 public void run() {
524 if(index != -1) {
525 try {
526 MutableComboBoxEntry entry = (MutableComboBoxEntry)component.getItemAt(index);
527 entry.setText(value);
528 }
529 catch (Exception error) {
530 }
531 }
532 else {
533 if(!value.equals(key+"_Tooltip")) {
534 component.setToolTipText(value);
535 }
536 else {
537 component.setToolTipText(null);
538 }
539 }
540 }
541 }
542 /** Update the title of this dialog. */
543 private class JDialogChangeTask
544 extends ChangeTask {
545 private JDialog component;
546 public JDialogChangeTask(JDialog component, String key, String value) {
547 super(key, value);
548 this.component = component;
549 }
550 public void run() {
551 component.setTitle(value);
552 }
553 }
554 /** Update the title of this frame. */
555 private class JFrameChangeTask
556 extends ChangeTask {
557 private JFrame component;
558 public JFrameChangeTask(JFrame component, String key, String value) {
559 super(key, value);
560 this.component = component;
561 }
562 public void run() {
563 component.setTitle(value);
564 }
565 }
566 /** Update the text of this label. */
567 private class JLabelChangeTask
568 extends ChangeTask {
569 private JLabel component;
570 public JLabelChangeTask(JLabel component, String key, String value) {
571 super(key, value);
572 this.component = component;
573 }
574 public void run() {
575 component.setText(value);
576 }
577 }
578 /** Updates a tabbed panes tab title and tooltip. */
579 private class JTabbedPaneChangeTask
580 extends ChangeTask {
581 private int index;
582 private JTabbedPane component;
583 private String tooltip;
584 public JTabbedPaneChangeTask(JTabbedPane component, String key, int index, String value, String tooltip) {
585 super(key, value);
586 this.component = component;
587 this.index = index;
588 this.tooltip = tooltip;
589 }
590 public void run() {
591 component.setTitleAt(index, value);
592 if(!tooltip.equals(key+"_Tooltip")) {
593 component.setToolTipTextAt(index, tooltip);
594 }
595 else {
596 component.setToolTipTextAt(index, null);
597 }
598 }
599 }
600 /** Update the text and tooltip of this text component. */
601 private class JTextComponentChangeTask
602 extends ChangeTask {
603 private JTextComponent component;
604 private String tooltip;
605 public JTextComponentChangeTask(JTextComponent component, String key, String value, String tooltip) {
606 super(key, value);
607 this.component = component;
608 this.tooltip = tooltip;
609 }
610 public void run() {
611 component.setText(value);
612 if(!tooltip.equals(key+"_Tooltip")) {
613 component.setToolTipText(tooltip);
614 }
615 else {
616 component.setToolTipText(null);
617 }
618 }
619 }
620 /** Update the tooltip of a tree and its tree node's labels. Shouldn't really ever be used on a dynamic tree, but is quite useful for a 'contents' tree type control. */
621 private class JTreeChangeTask
622 extends ChangeTask {
623 private DictionaryTreeNode node;
624 private JTree component;
625 public JTreeChangeTask(JTree component, String key, String value) {
626 super(key, value);
627 this.component = component;
628 }
629 public JTreeChangeTask(JTree component, String key, DictionaryTreeNode node, String value) {
630 super(key, value);
631 this.component = component;
632 this.node = node;
633 }
634 public void run() {
635 if(value != null) {
636 node.setText(value);
637 ((DefaultTreeModel)component.getModel()).nodeChanged((TreeNode)node);
638 }
639 // Set the tool tip
640 else {
641 if(!value.equals(key+"_Tooltip")) {
642 component.setToolTipText(value);
643 }
644 else {
645 component.setToolTipText(null);
646 }
647 }
648 }
649 }
650 /** Update the title of this titled border. */
651 private class TitledBorderChangeTask
652 extends ChangeTask {
653 private TitledBorder component;
654 public TitledBorderChangeTask(TitledBorder component, String key, String value) {
655 super(key, value);
656 this.component = component;
657 }
658 public void run() {
659 component.setTitle(value);
660 component.getParent().repaint();
661 }
662 }
663}
664
665
666
667
Note: See TracBrowser for help on using the repository browser.