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

Last change on this file since 5527 was 5527, checked in by mdewsnip, 21 years ago

Partway through tooltip assignment. Made lots of little changes, registered a whole lot more components, removed dead code, fixed help links (with SimpleMenuBars). Lots more of the same to come.

  • Property svn:keywords set to Author Date Id Revision
File size: 22.7 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 = false;
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 Gatherer.println("Having loaded new dictionary: " + language);
134 // Refresh all registered component
135 Gatherer.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 System.err.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
409 /** Register a text component. */
410 public void register(JTextComponent component, String[] args, boolean already_registered)
411 {
412 if (component != null) {
413 // Determine the key
414 String key = "";
415 if (!already_registered) {
416 key = component.getText();
417 }
418 else {
419 key = args[args.length - 1];
420 }
421
422 // Update the component using the AWTEvent queue
423 String value = get(key, args);
424 String tooltip = get(key + "_Tooltip", (String[])null);
425 ChangeTask task = new JTextComponentChangeTask(component, key, value, tooltip);
426 SwingUtilities.invokeLater(task);
427 // Register as necessary
428 if (!already_registered) {
429 args = ArrayTools.add(args, key);
430 put(component, args);
431 }
432 }
433 }
434
435 /** Register a tree component. */
436 public void register(JTree component, String[] args, boolean already_registered) {
437 if(component != null) {
438 // Retrieve the tooltip using the components name
439 String key = component.getName();
440 String tooltip = get(key + "_Tooltip", (String[])null);
441 ChangeTask task = new JTreeChangeTask(component, key, tooltip);
442 SwingUtilities.invokeLater(task);
443 // 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!
444 ArrayList nodes = new ArrayList();
445 nodes.add(component.getModel().getRoot());
446 while(nodes.size() > 0) {
447 DictionaryTreeNode node = (DictionaryTreeNode) nodes.remove(0);
448 // Update
449 String value = get(node.getKey(), (String[])null);
450 task = new JTreeChangeTask(component, node.getKey(), node, value);
451 SwingUtilities.invokeLater(task);
452 // Add children to nodes
453 for(int i = 0; i < node.getChildCount(); i++) {
454 nodes.add(node.getChildAt(i));
455 }
456 }
457 }
458 }
459 /** Register a titled border component. */
460 public void register(TitledBorder component, String[] args, boolean already_registered) {
461 if(component != null) {
462 // Determine the key
463 String key = "";
464 if(!already_registered) {
465 key = component.getTitle();
466 }
467 else {
468 key = args[args.length - 1];
469 }
470 // Update the component using the AWTEvent queue
471 String value = get(key, args);
472 ChangeTask task = new TitledBorderChangeTask(component, key, value);
473 SwingUtilities.invokeLater(task);
474 // Register as necessary
475 if(!already_registered) {
476 args = ArrayTools.add(args, key);
477 put(component, args);
478 }
479 }
480 }
481 /** 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. */
482 private String get(String[] args, int index) {
483 return get(args[index], args);
484 }
485
486 private abstract class ChangeTask
487 implements Runnable {
488 protected String key;
489 protected String value;
490 public ChangeTask(String key, String value) {
491 this.key = key;
492 this.value = value;
493 }
494 public void run() {
495 }
496 }
497 /** Update the text and tooltip for this button. */
498 private class AbstractButtonChangeTask
499 extends ChangeTask {
500 private AbstractButton component;
501 private String tooltip;
502 public AbstractButtonChangeTask(AbstractButton component, String key, String value, String tooltip) {
503 super(key, value);
504 this.component = component;
505 this.tooltip = tooltip;
506 }
507 public void run() {
508 component.setText(value);
509 if(!tooltip.equals(key+"_Tooltip")) {
510 component.setToolTipText(tooltip);
511 }
512 else {
513 component.setToolTipText(null);
514 }
515 }
516 }
517 /** Update the text associated with a combobox. If the index used is -1 then we are setting the tooltip for this combobox. */
518 private class JComboBoxChangeTask
519 extends ChangeTask {
520 private int index;
521 private JComboBox component;
522 public JComboBoxChangeTask(JComboBox component, String key, int index, String value) {
523 super(key, value);
524 this.component = component;
525 this.index = index;
526 }
527 public void run() {
528 if(index != -1) {
529 try {
530 MutableComboBoxEntry entry = (MutableComboBoxEntry)component.getItemAt(index);
531 entry.setText(value);
532 }
533 catch (Exception error) {
534 }
535 }
536 else {
537 if(!value.equals(key+"_Tooltip")) {
538 component.setToolTipText(value);
539 }
540 else {
541 component.setToolTipText(null);
542 }
543 }
544 }
545 }
546 /** Update the title of this dialog. */
547 private class JDialogChangeTask
548 extends ChangeTask {
549 private JDialog component;
550 public JDialogChangeTask(JDialog component, String key, String value) {
551 super(key, value);
552 this.component = component;
553 }
554 public void run() {
555 component.setTitle(value);
556 }
557 }
558 /** Update the title of this frame. */
559 private class JFrameChangeTask
560 extends ChangeTask {
561 private JFrame component;
562 public JFrameChangeTask(JFrame component, String key, String value) {
563 super(key, value);
564 this.component = component;
565 }
566 public void run() {
567 component.setTitle(value);
568 }
569 }
570 /** Update the text of this label. */
571 private class JLabelChangeTask
572 extends ChangeTask {
573 private JLabel component;
574 public JLabelChangeTask(JLabel component, String key, String value) {
575 super(key, value);
576 this.component = component;
577 }
578 public void run() {
579 component.setText(value);
580 }
581 }
582 /** Updates a tabbed panes tab title and tooltip. */
583 private class JTabbedPaneChangeTask
584 extends ChangeTask {
585 private int index;
586 private JTabbedPane component;
587 private String tooltip;
588 public JTabbedPaneChangeTask(JTabbedPane component, String key, int index, String value, String tooltip) {
589 super(key, value);
590 this.component = component;
591 this.index = index;
592 this.tooltip = tooltip;
593 }
594 public void run() {
595 component.setTitleAt(index, value);
596 if(!tooltip.equals(key+"_Tooltip")) {
597 component.setToolTipTextAt(index, tooltip);
598 }
599 else {
600 component.setToolTipTextAt(index, null);
601 }
602 }
603 }
604 /** Update the text and tooltip of this text component. */
605 private class JTextComponentChangeTask
606 extends ChangeTask {
607 private JTextComponent component;
608 private String tooltip;
609 public JTextComponentChangeTask(JTextComponent component, String key, String value, String tooltip) {
610 super(key, value);
611 this.component = component;
612 this.tooltip = tooltip;
613 }
614 public void run() {
615 component.setText(value);
616 if(!tooltip.equals(key+"_Tooltip")) {
617 component.setToolTipText(tooltip);
618 }
619 else {
620 component.setToolTipText(null);
621 }
622 }
623 }
624 /** 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. */
625 private class JTreeChangeTask
626 extends ChangeTask {
627 private DictionaryTreeNode node;
628 private JTree component;
629 public JTreeChangeTask(JTree component, String key, String value) {
630 super(key, value);
631 this.component = component;
632 }
633 public JTreeChangeTask(JTree component, String key, DictionaryTreeNode node, String value) {
634 super(key, value);
635 this.component = component;
636 this.node = node;
637 }
638 public void run() {
639 if(value != null) {
640 node.setText(value);
641 ((DefaultTreeModel)component.getModel()).nodeChanged((TreeNode)node);
642 }
643 // Set the tool tip
644 else {
645 if(!value.equals(key+"_Tooltip")) {
646 component.setToolTipText(value);
647 }
648 else {
649 component.setToolTipText(null);
650 }
651 }
652 }
653 }
654 /** Update the title of this titled border. */
655 private class TitledBorderChangeTask
656 extends ChangeTask {
657 private TitledBorder component;
658 public TitledBorderChangeTask(TitledBorder component, String key, String value) {
659 super(key, value);
660 this.component = component;
661 }
662 public void run() {
663 component.setTitle(value);
664 component.getParent().repaint();
665 }
666 }
667}
668
669
670
671
Note: See TracBrowser for help on using the repository browser.