001 /* UIDefaults.java -- database for all settings and interface bindings.
002 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.awt.Color;
042 import java.awt.Dimension;
043 import java.awt.Font;
044 import java.awt.Insets;
045 import java.beans.PropertyChangeListener;
046 import java.beans.PropertyChangeSupport;
047 import java.lang.reflect.Method;
048 import java.util.Hashtable;
049 import java.util.LinkedList;
050 import java.util.ListIterator;
051 import java.util.Locale;
052 import java.util.MissingResourceException;
053 import java.util.ResourceBundle;
054
055 import javax.swing.border.Border;
056 import javax.swing.plaf.ComponentUI;
057 import javax.swing.plaf.InputMapUIResource;
058
059 /**
060 * UIDefaults is a database where all settings and interface bindings are
061 * stored into. A PLAF implementation fills one of these (see for example
062 * plaf/basic/BasicLookAndFeel.java) with "ButtonUI" -> new BasicButtonUI().
063 *
064 * @author Ronald Veldema (rveldema@cs.vu.nl)
065 */
066 public class UIDefaults extends Hashtable<Object, Object>
067 {
068
069 /** Our ResourceBundles. */
070 private LinkedList bundles;
071
072 /** The default locale. */
073 private Locale defaultLocale;
074
075 /** We use this for firing PropertyChangeEvents. */
076 private PropertyChangeSupport propertyChangeSupport;
077
078 /**
079 * Used for lazy instantiation of UIDefaults values so that they are not
080 * all loaded when a Swing application starts up, but only the values that
081 * are really needed. An <code>ActiveValue</code> is newly instantiated
082 * every time when the value is requested, as opposed to the normal
083 * {@link LazyValue} that is only instantiated once.
084 */
085 public static interface ActiveValue
086 {
087 Object createValue(UIDefaults table);
088 }
089
090 public static class LazyInputMap implements LazyValue
091 {
092 Object[] bind;
093 public LazyInputMap(Object[] bindings)
094 {
095 bind = bindings;
096 }
097 public Object createValue(UIDefaults table)
098 {
099 InputMapUIResource im = new InputMapUIResource();
100 for (int i = 0; 2 * i + 1 < bind.length; ++i)
101 {
102 Object curr = bind[2 * i];
103 if (curr instanceof KeyStroke)
104 im.put((KeyStroke) curr, bind[2 * i + 1]);
105 else
106 im.put(KeyStroke.getKeyStroke((String) curr),
107 bind[2 * i + 1]);
108 }
109 return im;
110 }
111 }
112
113 /**
114 * Used for lazy instantiation of UIDefaults values so that they are not
115 * all loaded when a Swing application starts up, but only the values that
116 * are really needed. A <code>LazyValue</code> is only instantiated once,
117 * as opposed to the {@link ActiveValue} that is newly created every time
118 * it is requested.
119 */
120 public static interface LazyValue
121 {
122 Object createValue(UIDefaults table);
123 }
124
125 public static class ProxyLazyValue implements LazyValue
126 {
127 LazyValue inner;
128 public ProxyLazyValue(String s)
129 {
130 final String className = s;
131 inner = new LazyValue()
132 {
133 public Object createValue(UIDefaults table)
134 {
135 try
136 {
137 return Class
138 .forName(className)
139 .getConstructor(new Class[] {})
140 .newInstance(new Object[] {});
141 }
142 catch (Exception e)
143 {
144 return null;
145 }
146 }
147 };
148 }
149
150 public ProxyLazyValue(String c, String m)
151 {
152 final String className = c;
153 final String methodName = m;
154 inner = new LazyValue()
155 {
156 public Object createValue(UIDefaults table)
157 {
158 try
159 {
160 return Class
161 .forName(className)
162 .getMethod(methodName, new Class[] {})
163 .invoke(null, new Object[] {});
164 }
165 catch (Exception e)
166 {
167 return null;
168 }
169 }
170 };
171 }
172
173 public ProxyLazyValue(String c, Object[] os)
174 {
175 final String className = c;
176 final Object[] objs = os;
177 final Class[] clss = new Class[objs.length];
178 for (int i = 0; i < objs.length; ++i)
179 {
180 clss[i] = objs[i].getClass();
181 }
182 inner = new LazyValue()
183 {
184 public Object createValue(UIDefaults table)
185 {
186 try
187 {
188 return Class
189 .forName(className)
190 .getConstructor(clss)
191 .newInstance(objs);
192 }
193 catch (Exception e)
194 {
195 return null;
196 }
197 }
198 };
199 }
200
201 public ProxyLazyValue(String c, String m, Object[] os)
202 {
203 final String className = c;
204 final String methodName = m;
205 final Object[] objs = os;
206 final Class[] clss = new Class[objs.length];
207 for (int i = 0; i < objs.length; ++i)
208 {
209 clss[i] = objs[i].getClass();
210 }
211 inner = new LazyValue()
212 {
213 public Object createValue(UIDefaults table)
214 {
215 try
216 {
217 return Class
218 .forName(className)
219 .getMethod(methodName, clss)
220 .invoke(null, objs);
221 }
222 catch (Exception e)
223 {
224 return null;
225 }
226 }
227 };
228 }
229
230 public Object createValue(UIDefaults table)
231 {
232 return inner.createValue(table);
233 }
234 }
235
236 /** Our serialVersionUID for serialization. */
237 private static final long serialVersionUID = 7341222528856548117L;
238
239 /**
240 * Constructs a new empty UIDefaults instance.
241 */
242 public UIDefaults()
243 {
244 bundles = new LinkedList();
245 defaultLocale = Locale.getDefault();
246 propertyChangeSupport = new PropertyChangeSupport(this);
247 }
248
249 /**
250 * Constructs a new UIDefaults instance and loads the specified entries.
251 * The entries are expected to come in pairs, that means
252 * <code>entries[0]</code> is a key, <code>entries[1]</code> is a value,
253 * <code>entries[2]</code> a key and so forth.
254 *
255 * @param entries the entries to initialize the UIDefaults instance with
256 */
257 public UIDefaults(Object[] entries)
258 {
259 this();
260
261 for (int i = 0; (2 * i + 1) < entries.length; ++i)
262 put(entries[2 * i], entries[2 * i + 1]);
263 }
264
265 /**
266 * Returns the entry for the specified <code>key</code> in the default
267 * locale.
268 *
269 * @return the entry for the specified <code>key</code>
270 */
271 public Object get(Object key)
272 {
273 return this.get(key, getDefaultLocale());
274 }
275
276 /**
277 * Returns the entry for the specified <code>key</code> in the Locale
278 * <code>loc</code>.
279 *
280 * @param key the key for which we return the value
281 * @param loc the locale
282 */
283 public Object get(Object key, Locale loc)
284 {
285 Object obj = null;
286
287 if (super.containsKey(key))
288 {
289 obj = super.get(key);
290 }
291 else if (key instanceof String)
292 {
293 String keyString = (String) key;
294 ListIterator i = bundles.listIterator(0);
295 while (i.hasNext())
296 {
297 String bundle_name = (String) i.next();
298 ResourceBundle res =
299 ResourceBundle.getBundle(bundle_name, loc);
300 if (res != null)
301 {
302 try
303 {
304 obj = res.getObject(keyString);
305 break;
306 }
307 catch (MissingResourceException me)
308 {
309 // continue, this bundle has no such key
310 }
311 }
312 }
313 }
314
315 // now we've found the object, resolve it.
316 // nb: LazyValues aren't supported in resource bundles, so it's correct
317 // to insert their results in the locale-less hashtable.
318
319 if (obj == null)
320 return null;
321
322 if (obj instanceof LazyValue)
323 {
324 Object resolved = ((LazyValue) obj).createValue(this);
325 super.remove(key);
326 super.put(key, resolved);
327 return resolved;
328 }
329 else if (obj instanceof ActiveValue)
330 {
331 return ((ActiveValue) obj).createValue(this);
332 }
333
334 return obj;
335 }
336
337 /**
338 * Puts a key and value into this UIDefaults object.<br>
339 * In contrast to
340 * {@link java.util.Hashtable}s <code>null</code>-values are accepted
341 * here and treated like #remove(key).
342 * <br>
343 * This fires a PropertyChangeEvent with key as name and the old and new
344 * values.
345 *
346 * @param key the key to put into the map
347 * @param value the value to put into the map
348 *
349 * @return the old value for key or <code>null</code> if <code>key</code>
350 * had no value assigned
351 */
352 public Object put(Object key, Object value)
353 {
354 Object old = checkAndPut(key, value);
355
356 if (key instanceof String && old != value)
357 firePropertyChange((String) key, old, value);
358 return old;
359 }
360
361 /**
362 * Puts a set of key-value pairs into the map.
363 * The entries are expected to come in pairs, that means
364 * <code>entries[0]</code> is a key, <code>entries[1]</code> is a value,
365 * <code>entries[2]</code> a key and so forth.
366 * <br>
367 * If a value is <code>null</code> it is treated like #remove(key).
368 * <br>
369 * This unconditionally fires a PropertyChangeEvent with
370 * <code>'UIDefaults'</code> as name and <code>null</code> for
371 * old and new value.
372 *
373 * @param entries the entries to be put into the map
374 */
375 public void putDefaults(Object[] entries)
376 {
377 for (int i = 0; (2 * i + 1) < entries.length; ++i)
378 {
379 checkAndPut(entries[2 * i], entries[2 * i + 1]);
380 }
381 firePropertyChange("UIDefaults", null, null);
382 }
383
384 /**
385 * Checks the value for <code>null</code> and put it into the Hashtable, if
386 * it is not <code>null</code>. If the value is <code>null</code> then
387 * remove the corresponding key.
388 *
389 * @param key the key to put into this UIDefauls table
390 * @param value the value to put into this UIDefaults table
391 *
392 * @return the old value for <code>key</code>
393 */
394 private Object checkAndPut(Object key, Object value)
395 {
396 Object old;
397
398 if (value != null)
399 old = super.put(key, value);
400 else
401 old = super.remove(key);
402
403 return old;
404 }
405
406 /**
407 * Returns a font entry for the default locale.
408 *
409 * @param key the key to the requested entry
410 *
411 * @return the font entry for <code>key</code> or null if no such entry
412 * exists
413 */
414 public Font getFont(Object key)
415 {
416 Object o = get(key);
417 return o instanceof Font ? (Font) o : null;
418 }
419
420 /**
421 * Returns a font entry for a specic locale.
422 *
423 * @param key the key to the requested entry
424 * @param locale the locale to the requested entry
425 *
426 * @return the font entry for <code>key</code> or null if no such entry
427 * exists
428 */
429 public Font getFont(Object key, Locale locale)
430 {
431 Object o = get(key, locale);
432 return o instanceof Font ? (Font) o : null;
433 }
434
435 /**
436 * Returns a color entry for the default locale.
437 *
438 * @param key the key to the requested entry
439 *
440 * @return the color entry for <code>key</code> or null if no such entry
441 * exists
442 */
443 public Color getColor(Object key)
444 {
445 Object o = get(key);
446 return o instanceof Color ? (Color) o : null;
447 }
448
449 /**
450 * Returns a color entry for a specic locale.
451 *
452 * @param key the key to the requested entry
453 * @param locale the locale to the requested entry
454 *
455 * @return the color entry for <code>key</code> or null if no such entry
456 * exists
457 */
458 public Color getColor(Object key, Locale locale)
459 {
460 Object o = get(key, locale);
461 return o instanceof Color ? (Color) o : null;
462 }
463
464 /**
465 * Returns an icon entry for the default locale.
466 *
467 * @param key the key to the requested entry
468 *
469 * @return the icon entry for <code>key</code> or null if no such entry
470 * exists
471 */
472 public Icon getIcon(Object key)
473 {
474 Object o = get(key);
475 return o instanceof Icon ? (Icon) o : null;
476 }
477
478 /**
479 * Returns an icon entry for a specic locale.
480 *
481 * @param key the key to the requested entry
482 * @param locale the locale to the requested entry
483 *
484 * @return the icon entry for <code>key</code> or null if no such entry
485 * exists
486 */
487 public Icon getIcon(Object key, Locale locale)
488 {
489 Object o = get(key, locale);
490 return o instanceof Icon ? (Icon) o : null;
491 }
492
493 /**
494 * Returns a border entry for the default locale.
495 *
496 * @param key the key to the requested entry
497 *
498 * @return the border entry for <code>key</code> or null if no such entry
499 * exists
500 */
501 public Border getBorder(Object key)
502 {
503 Object o = get(key);
504 return o instanceof Border ? (Border) o : null;
505 }
506
507 /**
508 * Returns a border entry for a specic locale.
509 *
510 * @param key the key to the requested entry
511 * @param locale the locale to the requested entry
512 *
513 * @return the border entry for <code>key</code> or null if no such entry
514 * exists
515 */
516 public Border getBorder(Object key, Locale locale)
517 {
518 Object o = get(key, locale);
519 return o instanceof Border ? (Border) o : null;
520 }
521
522 /**
523 * Returns a string entry for the default locale.
524 *
525 * @param key the key to the requested entry
526 *
527 * @return the string entry for <code>key</code> or null if no such entry
528 * exists
529 */
530 public String getString(Object key)
531 {
532 Object o = get(key);
533 return o instanceof String ? (String) o : null;
534 }
535
536 /**
537 * Returns a string entry for a specic locale.
538 *
539 * @param key the key to the requested entry
540 * @param locale the locale to the requested entry
541 *
542 * @return the string entry for <code>key</code> or null if no such entry
543 * exists
544 */
545 public String getString(Object key, Locale locale)
546 {
547 Object o = get(key, locale);
548 return o instanceof String ? (String) o : null;
549 }
550
551 /**
552 * Returns an integer entry for the default locale.
553 *
554 * @param key the key to the requested entry
555 *
556 * @return the integer entry for <code>key</code> or null if no such entry
557 * exists
558 */
559 public int getInt(Object key)
560 {
561 Object o = get(key);
562 return o instanceof Integer ? ((Integer) o).intValue() : 0;
563 }
564
565 /**
566 * Returns an integer entry for a specic locale.
567 *
568 * @param key the key to the requested entry
569 * @param locale the locale to the requested entry
570 *
571 * @return the integer entry for <code>key</code> or null if no such entry
572 * exists
573 */
574 public int getInt(Object key, Locale locale)
575 {
576 Object o = get(key, locale);
577 return o instanceof Integer ? ((Integer) o).intValue() : 0;
578 }
579
580 /**
581 * Returns a boolean entry for the default locale.
582 *
583 * @param key the key to the requested entry
584 *
585 * @return The boolean entry for <code>key</code> or <code>false</code> if no
586 * such entry exists.
587 */
588 public boolean getBoolean(Object key)
589 {
590 return Boolean.TRUE.equals(get(key));
591 }
592
593 /**
594 * Returns a boolean entry for a specic locale.
595 *
596 * @param key the key to the requested entry
597 * @param locale the locale to the requested entry
598 *
599 * @return the boolean entry for <code>key</code> or null if no such entry
600 * exists
601 */
602 public boolean getBoolean(Object key, Locale locale)
603 {
604 return Boolean.TRUE.equals(get(key, locale));
605 }
606
607 /**
608 * Returns an insets entry for the default locale.
609 *
610 * @param key the key to the requested entry
611 *
612 * @return the insets entry for <code>key</code> or null if no such entry
613 * exists
614 */
615 public Insets getInsets(Object key)
616 {
617 Object o = get(key);
618 return o instanceof Insets ? (Insets) o : null;
619 }
620
621 /**
622 * Returns an insets entry for a specic locale.
623 *
624 * @param key the key to the requested entry
625 * @param locale the locale to the requested entry
626 *
627 * @return the boolean entry for <code>key</code> or null if no such entry
628 * exists
629 */
630 public Insets getInsets(Object key, Locale locale)
631 {
632 Object o = get(key, locale);
633 return o instanceof Insets ? (Insets) o : null;
634 }
635
636 /**
637 * Returns a dimension entry for the default locale.
638 *
639 * @param key the key to the requested entry
640 *
641 * @return the dimension entry for <code>key</code> or null if no such entry
642 * exists
643 */
644 public Dimension getDimension(Object key)
645 {
646 Object o = get(key);
647 return o instanceof Dimension ? (Dimension) o : null;
648 }
649
650 /**
651 * Returns a dimension entry for a specic locale.
652 *
653 * @param key the key to the requested entry
654 * @param locale the locale to the requested entry
655 *
656 * @return the boolean entry for <code>key</code> or null if no such entry
657 * exists
658 */
659 public Dimension getDimension(Object key, Locale locale)
660 {
661 Object o = get(key, locale);
662 return o instanceof Dimension ? (Dimension) o : null;
663 }
664
665 /**
666 * Returns the ComponentUI class that renders a component. <code>id</code>
667 * is the ID for which the String value of the classname is stored in
668 * this UIDefaults map.
669 *
670 * @param id the ID of the UI class
671 * @param loader the ClassLoader to use
672 *
673 * @return the UI class for <code>id</code>
674 */
675 public Class<? extends ComponentUI> getUIClass(String id, ClassLoader loader)
676 {
677 String className = (String) get(id);
678 if (className == null)
679 return null;
680 try
681 {
682 if (loader == null)
683 loader = ClassLoader.getSystemClassLoader();
684 return (Class<? extends ComponentUI>) loader.loadClass (className);
685 }
686 catch (Exception e)
687 {
688 return null;
689 }
690 }
691
692 /**
693 * Returns the ComponentUI class that renders a component. <code>id</code>
694 * is the ID for which the String value of the classname is stored in
695 * this UIDefaults map.
696 *
697 * @param id the ID of the UI class
698 *
699 * @return the UI class for <code>id</code>
700 */
701 public Class<? extends ComponentUI> getUIClass(String id)
702 {
703 return getUIClass (id, null);
704 }
705
706 /**
707 * If a key is requested in #get(key) that has no value, this method
708 * is called before returning <code>null</code>.
709 *
710 * @param msg the error message
711 */
712 protected void getUIError(String msg)
713 {
714 System.err.println ("UIDefaults.getUIError: " + msg);
715 }
716
717 /**
718 * Returns the {@link ComponentUI} for the specified {@link JComponent}.
719 *
720 * @param target the component for which the ComponentUI is requested
721 *
722 * @return the {@link ComponentUI} for the specified {@link JComponent}
723 */
724 public ComponentUI getUI(JComponent target)
725 {
726 String classId = target.getUIClassID ();
727 Class cls = getUIClass (classId);
728 if (cls == null)
729 {
730 getUIError ("failed to locate UI class:" + classId);
731 return null;
732 }
733
734 Method factory;
735
736 try
737 {
738 factory = cls.getMethod ("createUI", new Class[] { JComponent.class } );
739 }
740 catch (NoSuchMethodException nme)
741 {
742 getUIError ("failed to locate createUI method on " + cls.toString ());
743 return null;
744 }
745
746 try
747 {
748 return (ComponentUI) factory.invoke (null, new Object[] { target });
749 }
750 catch (java.lang.reflect.InvocationTargetException ite)
751 {
752 getUIError ("InvocationTargetException ("+ ite.getTargetException()
753 +") calling createUI(...) on " + cls.toString ());
754 return null;
755 }
756 catch (Exception e)
757 {
758 getUIError ("exception calling createUI(...) on " + cls.toString ());
759 return null;
760 }
761 }
762
763 /**
764 * Adds a {@link PropertyChangeListener} to this UIDefaults map.
765 * Registered PropertyChangeListener are notified when values
766 * are beeing put into this UIDefaults map.
767 *
768 * @param listener the PropertyChangeListener to add
769 */
770 public void addPropertyChangeListener(PropertyChangeListener listener)
771 {
772 propertyChangeSupport.addPropertyChangeListener(listener);
773 }
774
775 /**
776 * Removes a PropertyChangeListener from this UIDefaults map.
777 *
778 * @param listener the PropertyChangeListener to remove
779 */
780 public void removePropertyChangeListener(PropertyChangeListener listener)
781 {
782 propertyChangeSupport.removePropertyChangeListener(listener);
783 }
784
785 /**
786 * Returns an array of all registered PropertyChangeListeners.
787 *
788 * @return all registered PropertyChangeListeners
789 */
790 public PropertyChangeListener[] getPropertyChangeListeners()
791 {
792 return propertyChangeSupport.getPropertyChangeListeners();
793 }
794
795 /**
796 * Fires a PropertyChangeEvent.
797 *
798 * @param property the property name
799 * @param oldValue the old value
800 * @param newValue the new value
801 */
802 protected void firePropertyChange(String property,
803 Object oldValue, Object newValue)
804 {
805 propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
806 }
807
808 /**
809 * Adds a ResourceBundle for localized values.
810 *
811 * @param name the name of the ResourceBundle to add
812 */
813 public void addResourceBundle(String name)
814 {
815 bundles.addFirst(name);
816 }
817
818 /**
819 * Removes a ResourceBundle.
820 *
821 * @param name the name of the ResourceBundle to remove
822 */
823 public void removeResourceBundle(String name)
824 {
825 bundles.remove(name);
826 }
827
828 /**
829 * Sets the current locale to <code>loc</code>.
830 *
831 * @param loc the Locale to be set
832 */
833 public void setDefaultLocale(Locale loc)
834 {
835 defaultLocale = loc;
836 }
837
838 /**
839 * Returns the current default locale.
840 *
841 * @return the current default locale
842 */
843 public Locale getDefaultLocale()
844 {
845 return defaultLocale;
846 }
847 }