package com.limegroup.gnutella.gui; import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.FontUIResource; import javax.swing.plaf.metal.MetalTheme; import javax.swing.plaf.metal.DefaultMetalTheme; import javax.swing.plaf.metal.MetalLookAndFeel; import java.lang.reflect.InvocationTargetException; import com.limegroup.gnutella.gui.themes.LimeLookAndFeel; import com.limegroup.gnutella.gui.themes.LimePlasticTheme; import com.limegroup.gnutella.gui.themes.ThemeFileHandler; import com.limegroup.gnutella.gui.themes.ThemeSettings; import com.limegroup.gnutella.settings.ApplicationSettings; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.Expand; /** * Manages application resources, including the custom <tt>LookAndFeel</tt>, * the locale-specific <tt>String</tt> instances, and any <tt>Icon</tt> * instances needed by the application. */ //2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678| public final class ResourceManager { /** * Instance of this <tt>ResourceManager</tt>, following singleton. */ private static ResourceManager _instance; /** * The constant name of the native Windows library. */ private static final String WINDOWS_LIBRARY_NAME = "LimeWire20"; /** * Constant for the relative path of the gui directory. */ private static final String GUI_PATH = "com/limegroup/gnutella/gui/"; /** * Constant for the relative path of the resources directory. */ private static final String RESOURCES_PATH = GUI_PATH + "resources/"; /** * Constant for the relative path of the images directory. */ private static final String IMAGES_PATH = GUI_PATH + "images/"; /** * Boolean status that controls whever the shared <tt>Locale</tt> instance * needs to be loaded, and locale-specific options need to be setup. */ private static boolean _localeOptionsSet; /** * Static variable for the loaded <tt>Locale</tt> instance. */ private static Locale _locale; /** * The <tt>ResourceBundle</tt> instance to use for loading * locale-specific resources. */ private static ResourceBundle _resourceBundle; /** * Locale-specific option, set from the loaded resource bundle, and that * control the preferred appearence and layout of the GUI for the loaded * locale. Complex scripts (such as Chinese, Korean, Japanese) should * not be displayed with bold characters if they are small (below 12 * 12 points). */ private static boolean _useBold; /** * Locale-specific option, set from the loaded resource bundle, and that * control the preferred appearence and layout of the GUI for the loaded * locale. Semitic scripts (such as Hebrew, Arabic, Urdu, Farsi, Divehi) * and Thai should be used with a right-to-left directionality in the GUI * layout. Needed here to extend Swing with Java 1.1.8 (on Mac OS 8/9), * which does not handle java.awt.ComponentOrientation. */ private static boolean _useLeftToRight; /** * Boolean for whether or not the installer has been shared. */ private static boolean _installerShared = false; /** * Boolean for whether or not the font-size has been reduced. */ private static boolean _fontReduced = false; /** * The default MetalTheme. */ private static MetalTheme _defaultTheme = null; /** * Whether or not LimeWire was started in the 'brushed metal' * look. */ private final boolean BRUSHED_METAL; /** * Whether or not the WINDOWS_LIBRARY was able to load. */ private final boolean LOADED_TRAY_LIBRARY; /** Cache of theme images (name as String -> image as ImageIcon) */ private static final HashMap THEME_IMAGES = new HashMap(); /** * Statically initialize necessary resources. */ static { resetLocaleOptions(); } static void resetLocaleOptions() { _localeOptionsSet = false; setLocaleOptions(); } static void setLocaleOptions() { if (!_localeOptionsSet) { if(ApplicationSettings.LANGUAGE.getValue().equals("")) ApplicationSettings.LANGUAGE.setValue("en"); _locale = new Locale( ApplicationSettings.LANGUAGE.getValue(), ApplicationSettings.COUNTRY.getValue(), ApplicationSettings.LOCALE_VARIANT.getValue()); _resourceBundle = ResourceBundle.getBundle( "MessagesBundle", _locale); _useBold = ! Boolean.valueOf(getStringResource("DISABLE_BOLD_CHARACTERS")). booleanValue(); _useLeftToRight = ! Boolean.valueOf(getStringResource("LAYOUT_RIGHT_TO_LEFT")). booleanValue(); _localeOptionsSet = true; } } /** * Returns the <tt>Locale</tt> instance currently in use. * * @return the <tt>Locale</tt> instance currently in use */ static Locale getLocale() { return _locale; } /** * Determines whether or not the current locale language is English. * Note that the user setting may be empty, defaulting to the running * system locale which may be other than English. Here we check the * effective locale seen in the MessagesBundle. */ static boolean hasLocalizedTipsOfTheDay() { return Boolean.valueOf(getStringResource("HAS_TIPS_OF_THE_DAY")).booleanValue(); } /** * Returns the TOTD resource bundle. */ static ResourceBundle getTOTDResourceBundle() { return ResourceBundle.getBundle("totd/TOTD", _locale); } /** * Returns the XML resource bundle for the given schema. * @param String schema name * (not the URI but name returned by LimeXMLSchema.getDisplayString) * @return ResourceBundle */ static ResourceBundle getXMLResourceBundle(String name) { return ResourceBundle.getBundle("xml.display." + name, _locale); } /** * Determines whever or not bold style can safely be used in the current * locale for displaying text with a size lower than 1 pica (12 points). * * @return <tt>true</tt> if bold style can safely be used in this * locale (simple scripts), <tt>false</tt> otherwise (complex scripts) */ static final boolean useBold() { setLocaleOptions(); return _useBold; } /** * Determines whever the standard left-to-right orientation should be used * in the current locale. * * @return <tt>true</tt> if bold characters should be used in this * locale, <tt>false</tt> otherwise (Semitic scripts). */ static final boolean isLeftToRight() { setLocaleOptions(); return _useLeftToRight; } /** * Returns the locale-specific String from the resource manager. * * @return an internationalized <tt>String</tt> instance * corresponding with the <tt>resourceKey</tt> */ static final String getStringResource(final String resourceKey) { return _resourceBundle.getString(resourceKey); } /** * Serves as a single point of access for any icons that should be accessed * directly from the file system for themes. * * @param name The name of the image (excluding the extension) to locate. * @return a new <tt>ImageIcon</tt> instance for the specified file, * or <tt>null</tt> if the resource could not be loaded */ static final ImageIcon getThemeImage(final String name) { if(name == null) throw new NullPointerException("null image name"); ImageIcon icon = null; // First try to get theme image from cache icon = (ImageIcon)THEME_IMAGES.get(name); if(icon != null) return icon; File themeDir = ThemeSettings.THEME_DIR.getValue(); // Next try to get from themes. icon = getImageFromURL(new File(themeDir, name).getPath(), true); if(icon != null && icon.getImage() != null) { THEME_IMAGES.put(name, icon); return icon; } // Then try to get from com/limegroup/gnutella/images resources icon = getImageFromURL(IMAGES_PATH + name, false); if(icon != null && icon.getImage() != null) { THEME_IMAGES.put(name, icon); return icon; } // no resource? error. throw new MissingResourceException( "image: " + name + " doesn't exist.", null, null); } /** * Retrieves an icon from the specified path in the filesystem. */ static final ImageIcon getImageFromPath(String loc) { return getImageFromURL(loc, true); } /** * Retrieves an icon from a URL-style path. * * If 'file' is true, location is treated as a file, otherwise * it is treated as a resource. * * This tries, in order, the exact location, the location as a png, * and the location as a gif. */ private static final ImageIcon getImageFromURL(String location, boolean file) { // no theme, try backup image. URL img = toURL(location, file); if(img != null) return new ImageIcon(img); // try with png img = toURL(location + ".png", file); if(img != null) return new ImageIcon(img); // try with gif img = toURL(location + ".gif", file); if(img != null) return new ImageIcon(img); return null; } /** * Makes a URL out of a location, as either a file or a resource. */ private static final URL toURL(String location, boolean file) { if(file) { File f = new File(location); if(f.exists()) { try { return f.toURL(); } catch(MalformedURLException murl) { return null; } } else { return null; } } else { return getURL(location); } } /** * Returns a new <tt>URL</tt> instance for the specified file in the * "resources" directory. * * @param FILE_NAME the name of the resource file * @return a new <tt>URL</tt> instance for the desired file, or * <tt>null</tt> if the <tt>URL</tt> could not be loaded */ static URL getURLResource(final String FILE_NAME) { return ResourceManager.getURL(RESOURCES_PATH + FILE_NAME); } /** * Returns a new <tt>URL</tt> instance for the resource at the * specified local path. The path should be the full path within * the jar file, such as: <p> * * com/limegroup/gnutella/gui/images/searching.gif<p> * * @param PATH the path to the resource file within the jar * @return a new <tt>URL</tt> instance for the desired file, or * <tt>null</tt> if the <tt>URL</tt> could not be loaded */ private static URL getURL(final String PATH) { ClassLoader cl = ResourceManager.class.getClassLoader(); if (cl == null) { return ClassLoader.getSystemResource(PATH); } URL url = cl.getResource(PATH); if (url == null) { return ClassLoader.getSystemResource(PATH); } return url; } /** * Instance accessor following singleton. */ public static final ResourceManager instance() { if (_instance == null) _instance = new ResourceManager(); return _instance; } /** * Private constructor to ensure that a <tt>ResourceManager</tt> * cannot be constructed from outside this class. */ private ResourceManager() { GUIMediator.setSplashScreenString( GUIMediator.getStringResource("SPLASH_STATUS_RESOURCE_MANAGER")); if(!ThemeFileHandler.isCurrent() || !ThemeSettings.isValid()) { ThemeSettings.THEME_FILE.revertToDefault(); ThemeSettings.THEME_DIR.revertToDefault(); ThemeFileHandler.reload(); } String bMetal = System.getProperty("apple.awt.brushMetalLook"); BRUSHED_METAL = bMetal != null && bMetal.equalsIgnoreCase("true"); themeChanged(); try { validateLocaleAndFonts(); } catch(NullPointerException npe) { // ignore, can't do much about it -- internal ignorable error. } // The Windows library is not stored in any jar, and is // already in the appropriate location, so copying the resource // file is pointless (and doesn't work also). if (CommonUtils.isWindows()) { boolean loaded = false; try { System.loadLibrary(WINDOWS_LIBRARY_NAME); loaded = true; } catch(UnsatisfiedLinkError ule) {} LOADED_TRAY_LIBRARY = loaded; } else if (CommonUtils.isLinux()){ boolean loaded = false; try { System.loadLibrary("tray"); loaded = true; } catch (UnsatisfiedLinkError ule){} LOADED_TRAY_LIBRARY = loaded; }else { LOADED_TRAY_LIBRARY = false; } try { unpackWarFiles(); unpackVersionFile(); } catch(IOException e) { GUIMediator.showInternalError(e); } } /** * Validates the locale, determining if the current locale's resources * can be displayed using the current fonts. If not, then the locale * is reset to English. * * This prevents the UI from appearing as all boxes. */ public void validateLocaleAndFonts() { // OSX can always display everything, and if it can't, // we have no way of correcting things 'cause canDisplayUpTo // is broken on it. if(CommonUtils.isMacOSX()) return; String s = getStringResource("LOCALE_LANGUAGE_NAME"); if(!checkUIFonts("dialog", s)) { // if it couldn't display, revert the locale to english. ApplicationSettings.LANGUAGE.setValue("en"); ApplicationSettings.COUNTRY.setValue(""); ApplicationSettings.LOCALE_VARIANT.setValue(""); resetLocaleOptions(); } // Ensure that the Table.font can always display intl characters // since we can always get i18n stuff there, but only if we'd actually // be capable of displaying an intl character with the font... // unicode string == country name of simplified chinese String i18n = "\u4e2d\u56fd"; checkFont("TextField.font", "dialog", i18n, true); checkFont("Table.font", "dialog", i18n, true); checkFont("ProgressBar.font", "dialog", i18n, true); checkFont("TabbedPane.font", "dialog", i18n, true); } /** * Alters all Fonts in UIManager to use Dialog, to correctly display * foreign strings. */ private boolean checkUIFonts(String newFont, String testString) { String[] comps = new String[] { "TextField.font", "PasswordField.font", "TextArea.font", "TextPane.font", "EditorPane.font", "FormattedTextField.font", "Button.font", "CheckBox.font", "RadioButton.font", "ToggleButton.font", "ProgressBar.font", "ComboBox.font", "InternalFrame.titleFont", "DesktopIcon.font", "TitledBorder.font", "Label.font", "List.font", "TabbedPane.font", "Table.font", "TableHeader.font", "MenuBar.font", "Menu.font", "Menu.acceleratorFont", "MenuItem.font", "MenuItem.acceleratorFont", "PopupMenu.font", "CheckBoxMenuItem.font", "CheckBoxMenuItem.acceleratorFont", "RadioButtonMenuItem.font", "RadioButtonMenuItem.acceleratorFont", "Spinner.font", "Tree.font", "ToolBar.font", "OptionPane.messageFont", "OptionPane.buttonFont", "ToolTip.font", }; boolean displayable = false; for(int i = 0; i < comps.length; i++) displayable |= checkFont(comps[i], newFont, testString, false); // Then do it the automagic way. // note that this could work all the time (without requiring the above) // if Java 1.4 didn't introduce Locales, and it could even still work // if they offered a way to get all the keys of possible resources. for(Iterator i = UIManager.getDefaults().entrySet().iterator(); i.hasNext(); ) { Map.Entry next = (Map.Entry)i.next(); if(next.getValue() instanceof Font) { Font f = (Font)next.getValue(); if(f != null && !newFont.equalsIgnoreCase(f.getName())) { if(!GUIUtils.canDisplay(f, testString)) { f = new Font(newFont, f.getStyle(), f.getSize()); if(GUIUtils.canDisplay(f, testString)) { next.setValue(f); displayable = true; } } } } } return displayable; } /** * Updates the font of a given fontName to be newName. */ private boolean checkFont(String fontName, String newName, String testString, boolean force) { boolean displayable = true; Font f = UIManager.getFont(fontName); if(f != null && !newName.equalsIgnoreCase(f.getName())) { if(!GUIUtils.canDisplay(f, testString) || force) { f = new Font(newName, f.getStyle(), f.getSize()); if(GUIUtils.canDisplay(f, testString)) UIManager.put(fontName, f); else displayable = false; } } else if (f != null) { displayable = GUIUtils.canDisplay(f, testString); } else { displayable = false; } return displayable; } /** * Determines if the tray library has loaded. */ public boolean isTrayLibraryLoaded() { return LOADED_TRAY_LIBRARY; } /** * Determines if the brushed metal property is set. */ public boolean isBrushedMetalSet() { return BRUSHED_METAL; } /** * Updates to the current theme. */ public void themeChanged() { THEME_IMAGES.clear(); try { if(ThemeSettings.isOtherTheme()) { // in case this is using metal ... UIManager.put("swing.boldMetal", Boolean.FALSE); if(_defaultTheme != null) MetalLookAndFeel.setCurrentTheme(_defaultTheme); String other = ThemeSettings.getOtherLF(); UIManager.setLookAndFeel(other); } else if(ThemeSettings.isNativeTheme()) { if(CommonUtils.isWindows() && isPlasticWindowsAvailable()) { try { UIManager.setLookAndFeel("com.jgoodies.plaf.windows.ExtWindowsLookAndFeel"); } catch (NullPointerException npe) { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } } else UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); if(CommonUtils.isMacOSX()) { if(!_fontReduced) { _fontReduced = true; reduceFont("Label.font"); reduceFont("Table.font"); } UIManager.put("List.focusCellHighlightBorder", BorderFactory.createEmptyBorder(1, 1, 1, 1)); UIManager.put("ScrollPane.border", BorderFactory.createMatteBorder(1, 1, 1, 1, Color.lightGray)); } } else { if(isPlasticAvailable()) { if(_defaultTheme == null) _defaultTheme = getDefaultTheme(); LimePlasticTheme.installThisTheme(); UIManager.setLookAndFeel("com.jgoodies.plaf.plastic.PlasticXPLookAndFeel"); LimeLookAndFeel.installUIManagerDefaults(); } else { UIManager.setLookAndFeel(new LimeLookAndFeel()); } } UIManager.put("Tree.leafIcon", UIManager.getIcon("Tree.closedIcon")); // remove split pane borders UIManager.put("SplitPane.border", BorderFactory.createEmptyBorder()); if(!CommonUtils.isMacOSX()) UIManager.put("Table.focusRowHighlightBorder", UIManager.get("Table.focusCellHighlightBorder")); UIManager.put("Table.focusCellHighlightBorder", BorderFactory.createEmptyBorder(1, 1, 1, 1)); // Add a bolded text version of simple text. Font normal = UIManager.getFont("Table.font"); FontUIResource bold = new FontUIResource( normal.getName(), Font.BOLD, normal.getSize()); UIManager.put("Table.font.bold", bold); } catch (UnsupportedLookAndFeelException e) { throw new ExceptionInInitializerError(e); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } catch (InstantiationException e) { throw new ExceptionInInitializerError(e); } catch (IllegalAccessException e) { throw new ExceptionInInitializerError(e); } } /** * Gets the current theme (or defaults to MetalTheme) if possible. */ private MetalTheme getDefaultTheme() { MetalTheme theme = null; if(CommonUtils.isJava15OrLater()) { try { theme = (MetalTheme)MetalLookAndFeel.class.getMethod("getCurrentTheme", null).invoke(null, null); } catch(IllegalAccessException iae) { } catch(InvocationTargetException ite) { } catch(NoSuchMethodException nsme) { } } if(theme == null) theme = new DefaultMetalTheme(); return theme; } /** * Determines if the PlasticXP Theme is available. */ private boolean isPlasticAvailable() { try { Class plastic = Class.forName("com.jgoodies.plaf.plastic.PlasticXPLookAndFeel"); return plastic != null; } catch(ClassNotFoundException cnfe) { return false; } } /** * Determines if the Plastic Windows Theme is available. */ private boolean isPlasticWindowsAvailable() { try { Class plastic = Class.forName("com.jgoodies.plaf.windows.ExtWindowsLookAndFeel"); return plastic != null; } catch(ClassNotFoundException cnfe) { return false; } } /** * Updates the component to use the native UI resource. */ static ComponentUI getNativeUI(JComponent c) { ComponentUI ret = null; String name = UIManager.getSystemLookAndFeelClassName(); if(name != null) { try { Class clazz = Class.forName(name); LookAndFeel lf = (LookAndFeel)clazz.newInstance(); lf.initialize(); UIDefaults def = lf.getDefaults(); ret = def.getUI(c); } catch(ExceptionInInitializerError e) { } catch(ClassNotFoundException e) { } catch(LinkageError e) { } catch(IllegalAccessException e) { } catch(InstantiationException e) { } catch(SecurityException e) { } catch(ClassCastException e) { } } // if any of those failed, default to the current UI. if(ret == null) ret = UIManager.getUI(c); return ret; } /** * Reduces the size of a font in UIManager. */ private static void reduceFont(String name) { Font oldFont = UIManager.getFont(name); FontUIResource newFont = new FontUIResource(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() - 2); UIManager.put(name, newFont); } /** * Unpacks any war files in the current directory. */ private static void unpackWarFiles() throws IOException { //Unpack any .war files, and put into appropriate dirs. File currDir = CommonUtils.getCurrentDirectory(); String[] warFiles = (currDir).list( new FilenameFilter() { //the files to be accepted to be returned public boolean accept(File dir, String name) { return name.endsWith(".war"); } }); if (warFiles == null) { // no war files to expand -- don't worry about it return; } File destDir = CommonUtils.getUserSettingsDir(); if(!destDir.isDirectory()) { throw new IOException("settings dir not a directory: "+destDir); } if(!destDir.canWrite()) { throw new IOException("cannot write to the settings dir: "+destDir); } for (int i = 0; i < warFiles.length; i++) { if(warFiles[i].equals("xml.war")) { // force schemas to always be recopied. Expand.expandFile(new File(warFiles[i]), destDir, false, new String[] { "xml/schemas/" } ); } else { Expand.expandFile(new File(warFiles[i]), destDir); } } } /** * Unpacks the update.ver file. */ private void unpackVersionFile() throws IOException { File userHome = CommonUtils.getUserSettingsDir(); File verFile = new File("update.ver"); Expand.expandFile(verFile, userHome); } }