/*
* JetS3t : Java S3 Toolkit
* Project hosted at http://bitbucket.org/jmurty/jets3t/
*
* Copyright 2006-2010 James Murty
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jets3t.gui.skins;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jets3t.gui.HyperlinkActivatedListener;
import org.jets3t.gui.JHtmlLabel;
import java.awt.Component;
/**
* Manages the creation of skinned GUI elements.
* Skinned elements are created using the following process:
* <ol>
* <li>Instantiate a skin-specific class in the skin's package
* <code>org.jets3t.gui.skins.<i><skinName></i></code></li>
* <li>If a skin-specific class is not available or cannot be created,
* instantiate a generic GUI class instead</li>
* </ol>
* <p>
* Skinned classes are specially-named extensions to standard Swing classes, which must have a
* constructor of the form <br><code>public SkinnedJButton(Properties skinProperties, String itemName)</code>.
* This constructor allows skinned GUI elements to change their look or behaviour based on any
* skin-specific properties that are provided, or based on the name of a specific GUI element.
* <p>
* The skinned class names supported by this factory include:
* <table summary="The skinned class names supported by this factory">
* <tr><th>Class name</th><th>Extends</th></tr>
* <tr><td>SkinnedJButton</td><td>javax.swing.JButton</td></tr>
* <tr><td>SkinnedJHtmlLabel</td><td>org.jets3t.gui.JHtmlLabel</td></tr>
* <tr><td>SkinnedJPanel</td><td>javax.swing.JPanel</td></tr>
* <tr><td>SkinnedLookAndFeel</td><td>javax.swing.plaf.metal.MetalLookAndFeel</td></tr>
* </table>
*
* @author James Murty
*
*/
public class SkinsFactory {
private static final Log log = LogFactory.getLog(SkinsFactory.class);
public static final String NO_SKIN = "noskin";
/**
* The name of the chosen skin.
*/
private String skinName = null;
/**
* Properties that apply specifically to the chosen skin.
*/
private Properties skinProperties = new Properties();
/**
* Track component class names that are not available.
*/
private static Map unavailableClassMap = new HashMap();
/**
* Construct the factory and find skin-specific properties in the provided properties set.
*
* @param properties
* A set of properties that may contain skin-specific properties.
*/
private SkinsFactory(Properties properties) {
// Gracefully handle missing properties.
if (properties == null) {
properties = new Properties();
}
this.skinName = properties.getProperty("skin.name");
if (this.skinName == null) {
this.skinName = NO_SKIN;
}
// Find skin-specific properties.
String skinPropertyPrefix = "skin." + this.skinName.toLowerCase(Locale.getDefault()) + ".";
Iterator iter = properties.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String propertyName = (String) entry.getKey();
String propertyValue = (String) entry.getValue();
if (propertyName.toLowerCase(Locale.getDefault()).startsWith(skinPropertyPrefix)) {
String skinPropertyName = propertyName.substring(skinPropertyPrefix.length());
this.skinProperties.put(skinPropertyName, propertyValue);
}
}
}
/**
* Provides a skin factory initialised with skin-specific properties from the provided
* properties set. Skin-specific properties are identified as those properties with the
* prefix <code>skin.<i><skinName></i>.</code>
*
* @param properties
* a set of properties that may contain skin-specific properties.
*
* @return
* the skins factory initialised with skin settings.
*/
public static SkinsFactory getInstance(Properties properties) {
return new SkinsFactory(properties);
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedLookAndFeel</code> class implementation for the current skin, or the default
* system LookAndFeel if no skin-specific implementation is available.
*/
public LookAndFeel createSkinnedMetalTheme(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedLookAndFeel"), itemName);
if (instance != null) {
return (LookAndFeel) instance;
} else {
return UIManager.getLookAndFeel();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJButton</code> class implementation for the current skin, or a default
* JButton if no skin-specific implementation is available.
*/
public JButton createSkinnedJButton(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJButton"), itemName);
if (instance != null) {
return (JButton) instance;
} else {
return new JButton();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJRadioButton</code> class implementation for the current skin, or a default
* JRadioButton if no skin-specific implementation is available.
*/
public JRadioButton createSkinnedJRadioButton(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJRadioButton"), itemName);
if (instance != null) {
return (JRadioButton) instance;
} else {
return new JRadioButton();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJComboBox</code> class implementation for the current skin, or a default
* JComboBox if no skin-specific implementation is available.
*/
public JComboBox createSkinnedJComboBox(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJComboBox"), itemName);
if (instance != null) {
return (JComboBox) instance;
} else {
return new JComboBox();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJComboBox</code> class implementation for the current skin, or a default
* JComboBox if no skin-specific implementation is available.
*/
public JCheckBox createSkinnedJCheckBox(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJCheckBox"), itemName);
if (instance != null) {
return (JCheckBox) instance;
} else {
return new JCheckBox();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJPanel</code> class implementation for the current skin, or a default
* JPanel if no skin-specific implementation is available.
*/
public JPanel createSkinnedJPanel(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJPanel"), itemName);
if (instance != null) {
return (JPanel) instance;
} else {
return new JPanel();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJTable</code> class implementation for the current skin, or a default
* JPanel if no skin-specific implementation is available.
*/
public JTable createSkinnedJTable(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJTable"), itemName);
if (instance != null) {
return (JTable) instance;
} else {
return new JTable();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @param view
* the client's viewport view to be used.
*
* @return
* a <code>SkinnedJScrollPane</code> class implementation for the current skin, or a default
* JScrollPane if no skin-specific implementation is available.
*/
public JScrollPane createSkinnedJScrollPane(String itemName, Object view) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJScrollPane"), itemName);
if (instance != null) {
JScrollPane scrollPane = (JScrollPane) instance;
scrollPane.setViewportView((Component) view);
return scrollPane;
} else {
return new JScrollPane((Component) view);
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJScrollPane</code> class implementation for the current skin, or a default
* JScrollPane if no skin-specific implementation is available.
*/
public JScrollPane createSkinnedJScrollPane(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJScrollPane"), itemName);
if (instance != null) {
return (JScrollPane) instance;
} else {
return new JScrollPane();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJLabel</code> class implementation for the current skin, or a default
* JHtmlLabel if no skin-specific implementation is available.
*/
public JHtmlLabel createSkinnedJHtmlLabel(String itemName, HyperlinkActivatedListener hyperlinkListener) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJHtmlLabel"), itemName);
if (instance != null) {
JHtmlLabel label = (JHtmlLabel) instance;
label.setHyperlinkeActivatedListener(hyperlinkListener);
return label;
} else {
return new JHtmlLabel(hyperlinkListener);
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJLabel</code> class implementation for the current skin, or a default
* JHtmlLabel if no skin-specific implementation is available.
*/
public JHtmlLabel createSkinnedJHtmlLabel(String itemName) {
return createSkinnedJHtmlLabel(itemName, null);
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJPasswordField</code> class implementation for the current skin, or a default
* JPasswordField if no skin-specific implementation is available.
*/
public JPasswordField createSkinnedJPasswordField(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJPasswordField"), itemName);
if (instance != null) {
return (JPasswordField) instance;
} else {
return new JPasswordField();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJTextField</code> class implementation for the current skin, or a default
* JTextField if no skin-specific implementation is available.
*/
public JTextField createSkinnedJTextField(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJTextField"), itemName);
if (instance != null) {
return (JTextField) instance;
} else {
return new JTextField();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJTextArea</code> class implementation for the current skin, or a default
* JTextArea if no skin-specific implementation is available.
*/
public JTextArea createSkinnedJTextArea(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJTextArea"), itemName);
if (instance != null) {
return (JTextArea) instance;
} else {
return new JTextArea();
}
}
public JPopupMenu createSkinnedJPopupMenu(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJPopupMenu"), itemName);
if (instance != null) {
return (JPopupMenu) instance;
} else {
return new JPopupMenu();
}
}
public JMenuItem createSkinnedJMenuItem(String itemName) {
Object instance = instantiateClass(buildSkinnedClassName("SkinnedJMenuItem"), itemName);
if (instance != null) {
return (JMenuItem) instance;
} else {
return new JMenuItem();
}
}
/**
* @param itemName
* the name of this specific item in the GUI, which may be used to determine how the skinned
* item should look or behave.
*
* @return
* a <code>SkinnedJProgressBar</code> class implementation for the current skin, or a default
* JProgressBar if no skin-specific implementation is available.
*/
public JProgressBar createSkinnedJProgressBar(String itemName, int min, int max) {
JProgressBar jProgressBar = (JProgressBar) instantiateClass(
buildSkinnedClassName("SkinnedJProgressBar"), itemName);
if (jProgressBar != null) {
jProgressBar.setMinimum(min);
jProgressBar.setMaximum(max);
return jProgressBar;
} else {
jProgressBar = new JProgressBar(min, max);
return jProgressBar;
}
}
private String buildSkinnedClassName(String className) {
if (NO_SKIN.equals(skinName)) {
return null;
} else {
String skinnedClassName =
this.getClass().getPackage().getName() + "." + this.skinName + "." + className;
return skinnedClassName;
}
}
private Object instantiateClass(String className, String itemName) {
if (className == null) {
return null;
}
if (unavailableClassMap.get(className) != null) {
// This class name is not available, don't waste time trying to load it.
return null;
}
try {
Class myClass = Class.forName(className);
Constructor constructor = myClass.getConstructor(
new Class[] { Properties.class, String.class });
Object instance = constructor.newInstance(new Object[] { skinProperties, itemName });
return instance;
} catch (ClassNotFoundException e) {
log.debug("Class does not exist, will use default. Skinned class name: " + className);
} catch (Exception e) {
log.warn("Unable to instantiate skinned class '" + className + "'", e);
}
unavailableClassMap.put(className, Boolean.TRUE);
return null;
}
}