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 }