Laf-Widget : How To Change Existing LAF

# 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.