How to change existing LAF
Manual changes
A look-and-feel that wishes to use this project, needs to change the relevant
UI delegate classes to integrate with LafWidgetRepository. This can be
done either manually by changing the code or automatically by using supplied
Ant task or batch files.
In order to get an instance of LafWidgetRepository class, the
following static function is used:
/**
* Returns the widget repository.
*
* @return Widget repository.
*/
public static synchronized LafWidgetRepository getRepository()
The UI delegate class needs to call the following function in its installUI
method:
/**
* Returns a set of widgets that match the specified component. The
* component hierarchy is scanned bottom-up and all matching widget classes
* are used to instantiate new instance of widgets. In case the
* {@link #isCustomLafSupportSet} is <code>false</code>, only widgets
* that return <code>false</code> in
* {@link LafWidget#requiresCustomLafSupport()} are returned.
*
*
* @param jcomp
* UI component.
* @return Set of widgets that match the specified component.
*/
public synchronized Set getMatchingWidgets(JComponent jcomp)
The result Set contains instances of LafWidget interface. Each instance
implements some widget (behavioural trait). This interface defines the lifecycle of
the widget (and the associated component). The relevant functions mirror the usual
approach of Swing UI delegates, four function for install phase and four
functions for uninstall phase (see below). These functions should be called
from the main UI delegate in order to install / uninstall the relevant listeners,
components and default settings for each widget. Two additional functions in this
interface set the associated component and check whether the widget requires custom
LAF support (explained later):
public interface LafWidget {
/**
* Associates a component with <code>this</code> widget.
*
* @param jcomp
* Component.
*/
public void setComponent(JComponent jcomp);
/**
* Returns indication whether <code>this</code> widget requires custom LAF
* support. Some widgets such as {@link TabOverviewDialogWidget} or
* {@link TabHoverPreviewWidget} require custom implementation based on the
* internals of the specific LAF. Relevant functions in the base
* {@link LafWidgetSupport} support throw
* {@link UnsupportedOperationException}.
*
* @return <code>true</code> if <code>this</code> widget requires custom
* LAF support, <code>false</code> otherwise.
*/
public boolean requiresCustomLafSupport();
/**
* Installs UI on the associated component.
*/
public void installUI();
/**
* Installs default settings for the associated component.
*/
public void installDefaults();
/**
* Installs listeners for the associated component.
*/
public void installListeners();
/**
* Installs components for the associated component.
*/
public void installComponents();
/**
* Uninstalls UI on the associated component.
*/
public void uninstallUI();
/**
* Uninstalls default settings for the associated component.
*/
public void uninstallDefaults();
/**
* Uninstalls listeners for the associated component.
*/
public void uninstallListeners();
/**
* Uninstalls components for the associated component.
*/
public void uninstallComponents();
}
As stated above, the UI delegates in the look-and-feel should fetch the set of widgets
for the associated component (using getMatchingWidgets function on
LafWidgetRepository) and set it on the UI delegate instance. All
install / uninstall methods in the UI delegate class should
call the corresponding lifecycle methods on all the registered widgets. For example:
public class MyLafTabbedPaneUI extends MetalTabbedPaneUI {
protected Set lafWidgets;
public static ComponentUI createUI(JComponent x) {
return new MyLafTabbedPaneUI();
}
public void installUI(JComponent c) {
// Fetches all widgets that were registered for the
// specified component
this.lafWidgets = LafWidgetRepository.getRepository()
.getMatchingWidgets(c);
super.installUI(c);
// Some custom logic
// Installs all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installUI();
}
}
protected void installListeners() {
super.installListeners();
// Install custom listeners
// ...
// Installs listeners on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installListeners();
}
}
protected void installComponents() {
super.installComponents();
// Install custom components
// ...
// Installs components on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installComponents();
}
}
protected void installDefaults() {
super.installDefaults();
// Install custom defaults
// ...
// Installs defaults on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installDefaults();
}
}
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
// Some custom logic
// Uninstalls all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.uninstallUI();
}
}
protected void uninstallComponents() {
super.uninstallComponents();
// Uninstall custom components
// ...
// Uninstalls components on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.uninstallComponents();
}
}
protected void uninstallDefaults() {
super.uninstallDefaults();
// Uninstall custom defaults
// ...
// Uninstalls defaults on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.uninstallDefaults();
}
}
protected void uninstallListeners() {
super.uninstallListeners();
// Uninstall custom listeners
// ...
// Uninstalls listeners on all widgets
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.uninstallListeners();
}
}
}
Note that if a UI delegate doesn't override some install / uninstall
method, it needs to do so for proper lifecycle management of the widgets. In this case
you will need to call the super implementation and call relevant method on
all widgets. In addition, some look-and-feels do not provide custom UI delegates for some
components. If the relevant components have associated widgets, you will have to
create (and register) forwarding UI delegate (that looks as above) without any
custom logic (only integration with LafWidgetRepository).
Automatic changes
This project provides an Ant task and a collection of sample batch scripts that
allow automating the above tasks of adapting the existing look-and-feel UI delegates
and creating forwarding UI delegates. The automatic support uses
ASM bytecode manipulation framework in order
to take the existing look-and-feel classes and augment the compiled classes
with the code described in the previous section. Note that in this case you don't have
to make any changes to your code. You can even take an existing third-party LAF jar
(tested on InfoNode, Liquid, Looks, Napkin, Pagosoft and Squareness) and change
it locally.
Currently, the automatic support can handle any look-and-feel that extends
Basic, Metal or Windows core look-and-feels and follow the
conventional extension points. The supported look-and-feel must:
- Override LookAndFeel.initClassDefaults to specify LAF-specific
UI delegates. This rules out LAF augmentation on the
winlaf that uses custom mechanism to
specify UI delegates.
- Override various install and uninstall methods from
base UI delegate classes.
There are two types of automatic support (that match the relevant tasks in
the manual support) - delegate augmentation and LAF augmentation.
The LAF augmentation takes the main look-and-feel class and changes the
initClassDefaults function, adding entries for the missing UI delegates.
In addition, it creates forwarding UI delegates (see examples below). The
delegate augmentation takes an existing UI delegate that is not
using LafWidgetRepository and changes the relevant install /
uninstall methods (adding forwarding ones as necessary). Note that
when using automatic support, the LAF augmentation must be performed
before the delegate augmentation since the latter needs to augment
the generated forwarding delegates.
All the classes mentioned below are located in org.jvnet.lafwidget.ant
package. Note that in order to use the automatic support, you need
to use the asm-all-2.2.2.jar (available in CVS repository under /lib
or from the ASM project site).
The LAF augmentation is provided by the LafMainClassAugmenter.
Its main function gets the following parameters:
- -verbose (optional) specifies that the augmenter should output
the tracing statements on its execution to System.out.
- -main specifies the fully-qualified class name of the main
look-and-feel class.
- -dir specifies the root directory that contains all compiled
LAF classes. This directory can be created by using jar on the
LAF main jar.
- -delegates specifies semicolon-separated IDs of UI delegates
to generate. This parameter should contain UI IDs (see examples below) for
all controls that don't have custom UI delegates and have associated
widgets. See the list of all component UI IDs
that exist in Swing core.
The matching Ant task is defined in AugmentMainTask which has
the following attributes and elements:
- verbose (optional) attribute. If present, Ant output will contain
tracing statements of the augmentation process.
- mainlafclassname attribute. Specify the fully-qualified class
name of the main look-and-feel class.
- classpathset element. Specifies the root directory that contains
all compiled LAF classes.
- delegate element. Has a single name attribute
that holds the ID of UI delegate to generate. See the
list of all component UI IDs
that exist in Swing core.
The delegate augmentation is provided by the UiDelegateAugmenter.
Its main function gets the following parameters:
- -verbose (optional) specifies that the augmenter should output
the tracing statements on its execution to System.out.
- -pattern (optional) specifies the pattern that will be used
for deciding which classes are augmented. The default value is
.*UI\u002Eclass. It is recommended to use the default value
when the LAF augmentation is used (since the classnames of the
generated UI delegates begin with __Forwarding__).
- The last parameter can point to either a file or a directory.
The directory should be the root directory for classes.
The matching Ant task is defined in AugmentTask which has
the following attributes and elements:
- verbose (optional) attribute. If present, Ant output will contain
tracing statements of the augmentation process.
- pattern (optional) attribute. Specifies the pattern that will be used
for deciding which classes are augmented.
- classpathset element. Specifies the root directory that contains
all compiled LAF classes.
Examples of batch files (in CVS repository under /3rd):
- For JGoodies Looks
Plastic XP:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../looks-2.0.2.jar
"%JAVA_HOME%/bin/java" -classpath ../looks-2.0.2.jar;../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.jgoodies.looks.plastic.PlasticXPLookAndFeel -delegates EditorPaneUI;FormattedTextFieldUI;TextFieldUI;TextPaneUI -dir .
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/looks-2.0.2.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
- For Liquid:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../liquidlnf.jar
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.birosoft.liquid.LiquidLookAndFeel -delegates EditorPaneUI;FormattedTextFieldUI;TextAreaUI;TextPaneUI -dir .
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/liquidlnf.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
- For Pagosoft:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../PgsLookAndFeel.jar
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.pagosoft.plaf.PgsLookAndFeel -delegates InternalFrameUI -dir .
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/PgsLookAndFeel.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
- For Squareness:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../squareness.jar
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main net.beeger.squareness.SquarenessLookAndFeel -delegates MenuBarUI;EditorPaneUI;FormattedTextFieldUI;PasswordFieldUI;TextAreaUI;TextFieldUI;TextPaneUI -dir .
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/squareness.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
- For InfoNode:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../ilf-gpl.jar
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main net.infonode.gui.laf.InfoNodeLookAndFeel -delegates TabbedPaneUI;MenuBarUI;EditorPaneUI;FormattedTextFieldUI;PasswordFieldUI;TextAreaUI;TextFieldUI;TextPaneUI -dir .
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/ilf-gpl.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
- For Napkin:
mkdir TMP
cd TMP
"%JAVA_HOME%/bin/jar" xf ../napkinlaf.jar
"%JAVA_HOME%/bin/java" -classpath ../../drop/laf-widget-50.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
"%JAVA_HOME%/bin/jar" cfm ../augmented/napkinlaf.jar META-INF/manifest.mf .
cd ..
rmdir TMP /s /q
Sample usage of Ant tasks for LAF augmentation and
delegate augmentation can be found
in the build script of Substance:
<!-- Augment task definition -->
<taskdef name="delegate-augment" classname="org.jvnet.lafwidget.ant.AugmentTask" classpath="${substance.lib.dir}/laf-widget-50.jar;${substance.lib.dir}/asm-all-2.2.2.jar" />
<taskdef name="laf-augment" classname="org.jvnet.lafwidget.ant.AugmentMainTask" classpath="${substance.lib.dir}/laf-widget-50.jar;${substance.lib.dir}/asm-all-2.2.2.jar" />
<target name="jar-bin" description="create runtime jar">
<delete file="${substance.drop.dir}/substance.jar" />
<unjar src="${substance.lib.dir}/laf-plugin.jar" dest="${substance.output.dir}/" />
<unjar src="${substance.lib.dir}/laf-widget-50.jar" dest="${substance.output.dir}/" />
<laf-augment verbose="true" mainlafclassname="org.jvnet.substance.SubstanceLookAndFeel">
<delegate name="DesktopPaneUI" />
<delegate name="LabelUI" />
<delegate name="PopupMenuUI" />
<delegate name="ToolBarSeparatorUI" />
<delegate name="ToolTipUI" />
<delegate name="ViewportUI" />
<classpathset dir="${substance.output.dir}" />
</laf-augment>
<delegate-augment verbose="true" pattern=".*UI\u002Eclass">
<classpathset dir="${substance.output.dir}" />
</delegate-augment>
<jar compress="true" destfile="${substance.drop.dir}/substance.jar" manifest="${substance.src.dir}/META-INF/MANIFEST.MF">
<fileset dir="${substance.output.dir}/" excludes="test/** org/jvnet/lafwidget/ant/**" />
<fileset dir="${module.substance.basedir}/" includes="resources/**" />
</jar>
<copy file="${substance.drop.dir}/substance.jar" todir="${module.substance.basedir}/www/webstart" />
</target>
As noted above, the classpath for the build script must contain reference
to the asm-all-2.2.2.jar. In addition, the Ant excerpt above shows
how to take the classes from the laf-widget-50.jar and put them in the
resulting LAF jar (all classes except test and org.jvnet.lafwidget.ant
packages).
Important note - it is highly recommended to put all
unimplemented component UI IDs from this list
in the LAF augmentation. This is to ensure that the specific look-and-feel
will work correctly with all base and custom
widgets.