CSS properties/tooltip-uppercase

Dans cette exemple, on apprendra à ajouter sa propre propriété CSS tooltip-uppercase qui permettra de mettre en majuscule le texte d'un Tooltip d'un Component Swing. Il sera ainsi possible de mettre en majuscule certain Component Swing à l'aide des Selectors. Plus précisemment cette exemple permettra de mettre en majuscule les textes des tooltip des JLabel. La feuille de style s'ecrira comme ceci :

JLabel {
    tooltip-uppercase:true;	
}
qui permettra de mettre en majuscule le texte des tooltip des JLabel d'une IHM Swing. Le code JLabel suivant
JLabel label = new JLabel();
label.setText("Jlabel text");
label.setToolTipText("bla bla bla...");

affiche l'interface suivante :

Lorsque le moteur CSS appliquera le style CSS tooltip-uppercase , ceci affichera :

Strategies

Il existe deux stratégies pour enregister sa propriétés CSS dans le moteur CSS :

  • Stratégie mapping nom propriété/ICSSPropertyHandler : cette stratégie indique explicitement qu'une propriété CSS est traitée par une interface ICSSPropertyHandler . Il faut ensuite indiquer l'implémentation ICSSPropertyHandler a utiliser pour traiter la propriété CSS (implémentation Swing, SWT...)
  • Lazy Stratégie : cette stratégie s'effectue par règle de nommage. Autrement dit la propriété CSS tooltip-uppercase est traitée par la classe CSSPropertyTooltipUpperCaseHandler qui est retrouvé dans un package que l'on a enregistré préalablement à l'aide de la méthode registerPackage d'une instance de CSSEngine

Dans notre exemple on utilisera la première stratégie qui parait plus rébarabative mais qui est plus optimisée, car la deuxième stratégie impose de rechercher la classe CSSPropertyXXXHandler dans le ClassLoader. De plus cette dernière stratégie n'est pas encore bien finalisée concernant la fonctionnalité de réinitialisation des widgets avant d'appliquer un nouveau style CSS (pour pouvoir appliquer des styles CSS au runtime), car ceci impose de retrouver toutes les propriétés CSS enregistrées dans le ClassLoader, fonctionnalité non développée aujourd'hui.

Dependencies

Avant de démarrer le développement d'une nouvelle propriété CSS, vous devez créer un projet (Eclipse, Netbeans...) qui font référence aux libraires jar fournit dans la distribution org.akrogen.tkui.css.swing*

Voici une copie d'écran du projet Eclipse utilisé pour ce tutorial :

Custom CSS Engine

La première étape est de créer sa classe CSSEngine . Vous pouvez directement utiliser CSSSwingEngineImpl (qui étend AbstractCSSSwingEngineImpl) et qui configure les propriétés CSS2. AbstractCSSSwingEngineImpl permet de configurer Element Provider pour gerer les selectors de type Swing (dans notre cas on ecrit le style avec JLabel {...}). Dans notyre cas nous étendons la classe CSSSwingEngineImpl pour bénéficier des propriétés CSS2 déja implementées.

Pour cela créer la classe MyCSSSwingEngine dans le package com.mycompany.myapp.css.swing.engine :

package com.mycompany.myapp.css.swing.engine;

import org.akrogen.tkui.css.swing.engine.CSSSwingEngineImpl;

public class MyCSSSwingEngineImpl extends CSSSwingEngineImpl {

	protected void initializeCSSPropertyHandlers() {
		super.initializeCSSPropertyHandlers();		
	}
}
La classe abstraite AbstractCSSSwingEngineImpl impose d'implementer la methode initializeCSSPropertyHandlers qui permet d'enregistrer les implémentations ICSSPropertyHandler , classes qui permettent d'appliquer une valeur d'une propriété sur une widget. En appelant super.initializeCSSPropertyHandlers() ceci permet d'enregister les propriétés CSS2 gérées par CSSSwingEngineImpl .

ICSSPropertyHandler Interface

Dans cet exemple, nous utilisons la stratégie Stratégie mapping nom propriété/ICSSPropertyHandler ce qui impose de définir une interface qui traite un ensemble de propriété CSS. Dans notre cas nous souhaitons gérer les propriétés CSS concernant les tooltip. Pour cela créer l'interface ICSSPropertyTooltipHandler dans le package com.mycompany.myapp.css.properties qui hérite de l'interface ICSSPropertyHandler . Remarquee que le package utilisé est com.mycompany.myapp.css.properties et pas com.mycompany.myapp.css.swing.properties car cette interface peut aussi être utilisée par une implémentation autre que Swing (comme SWT).

package com.mycompany.myapp.css.properties;

import org.akrogen.tkui.css.core.dom.properties.ICSSPropertyHandler;

public interface ICSSPropertyTooltipHandler extends ICSSPropertyHandler {

}

ICSSPropertyHandler Implementation

Il faut ensuite créer l'implementation Swing qui va traiter les propriétés CSS tootip sur des Component Swing, pour cela créer la classe CSSPropertyTooltipHandlerImpl dans le package com.mycompany.myapp.css.swing.properties .

package com.mycompany.myapp.css.swing.properties;

import org.akrogen.tkui.css.core.dom.properties.ICSSPropertyHandler;
import org.akrogen.tkui.css.core.engine.CSSEngine;
import org.w3c.dom.css.CSSValue;

import com.mycompany.myapp.css.properties.ICSSPropertyTooltipHandler;

public class CSSPropertyTooltipHandlerImpl implements
		ICSSPropertyTooltipHandler {

	public static final ICSSPropertyHandler INSTANCE = new CSSPropertyTooltipHandlerImpl();

	public boolean applyCSSProperty(Object element, String property,
			CSSValue value, String pseudo, CSSEngine engine) throws Exception {
		System.out.println("Apply CSS property [name=" + property + ", value="
				+ value + "] to the element class=" + element.getClass());
		return false;
	}

	public String retrieveCSSProperty(Object element, String property,
			CSSEngine engine) throws Exception {
		System.out.println("CSSPropertyTooltipHandlerImpl#retrieveCSSProperty");
		return null;
	}
}

  • la méthode applyCSSProperty permet d'appliquer la propriété CSS de nom=property avec la valeur=value de type CSSValue sur l'element=element . Element est un object qui peut etre de n'importe quel type (Swing Component, SWT Widget, w3c Element...) Cette méthode retourne un boolean qui indique si la propriété CSS peut etre appliqué à l'objet element. Dans le cas de Swing, il faut que l'element soit de type Component ou CSSStylableElement (Element w3c qui wrappe un Component Swing) et dont le Component peut etre retrouvé à l'aide de la méthode getNativeWidget.
  • la méthode retrieveCSSProperty permet de retrouver la valeur de la propriété CSS de l'élement. Cette méthode est utilisée lorsque le style CSS initial doit être retrouvé lorsque le moteur CSS doit appliquer au runtime des styles CSS.
  • La variable static INSTANCE permet de fournir un singleton de la classe CSSPropertyTooltipHandlerImpl ce qui évite d'instancier un CSSPropertyTooltipHandlerImpl à chaque fois que l'on instanciera notre engine MyCSSSwingEngine .

Register ICSSPropertyHandler

A cette étape, il faut enregistrer l'implémentation CSSPropertyTooltipHandlerImpl dans notre moteur CSS. Veuillez modifier la classe MyCSSSwingEngineImpl comme ceci :

package com.mycompany.myapp.css.swing.engine;

import org.akrogen.tkui.css.swing.engine.CSSSwingEngineImpl;

import com.mycompany.myapp.css.properties.ICSSPropertyTooltipHandler;
import com.mycompany.myapp.css.swing.properties.CSSPropertyTooltipHandlerImpl;

public class MyCSSSwingEngineImpl extends CSSSwingEngineImpl {

	protected void initializeCSSPropertyHandlers() {
		super.initializeCSSPropertyHandlers();		
		super.registerCSSProperty("tooltip-uppercase", ICSSPropertyTooltipHandler.class);		
		super.registerCSSPropertyHandler(ICSSPropertyTooltipHandler.class,
				CSSPropertyTooltipHandlerImpl.INSTANCE);
	}
}
  • Le code
    super.registerCSSProperty("tooltip-uppercase", ICSSPropertyTooltipHandler.class);
    permet d'indiquer au moteur CSS que la propriété CSS tooltip-uppercase est géré par l'interface ICSSPropertyTooltipHandler .
  • Le code
    super.registerCSSPropertyHandler(ICSSPropertyTooltipHandler.class,
    				CSSPropertyTooltipHandlerImpl.INSTANCE);
    permet d'indiquer au moteur CSS que l'implémentation de ICSSPropertyTooltipHandler à utiliser est une instance de CSSPropertyTooltipHandlerImpl .

CSSEngine Test

A cette étape il est possible de tester si la propriété CSS tooltip-uppercase est enregistré correctement dans le moteur CSS et si elle s'applique bien à un JLabel. Créer la classe de test avec le code suivant :

package com.mycompany.myapp.css.test;

import java.io.StringReader;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.akrogen.tkui.css.core.engine.CSSEngine;

import com.mycompany.myapp.css.swing.engine.MyCSSSwingEngineImpl;

public class TestMyCSSSwingEngine {

    public static void main(String[] args) {
        try {
             CSSEngine engine = new MyCSSSwingEngineImpl();
             engine.parseStyleSheet(new StringReader("JLabel {tooltip-uppercase:true;}"));

             /*---  Start UI Swing ---*/
             JFrame frame = new JFrame();
             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

             JPanel panel = new JPanel();
             frame.getContentPane().add(panel);

             // Label
             JLabel label = new JLabel();
             label.setText("Jlabel text");
             label.setToolTipText("bla bla bla...");
             panel.add(label);

             /*--- End UI Swing  ---*/

             // Apply Styles
             engine.applyStyles(frame, true);

            frame.pack();
            frame.setVisible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Après avoir lancé le test, le texte du tooltip du JLabel n'est pas mis en majuscule mais vous devez voir dans le console le message affiché

Apply CSS property [name=tooltip-uppercase, value=true] to the element 
	class=class org.akrogen.tkui.css.swing.dom.SwingElement
qui provient de la méthode applyCSSProperty . Le handler est enregistré correctement. Comme vous pouvez le voir element est une instance SwingElement qui est un wrapper w3c Element (plus exactement CSSStylableElement ) sur le Component Swing JLabel.

CSSPropertyTooltipHandlerImpl logic

A cette étape CSSPropertyTooltipHandlerImpl est enregistré correctment dans le moteur CSS. Il faut maintenant traiter la mise en majuscule du tooltip.

Get Swing Component

Le paramètre element de la méthode applyCSSProperty est une instance de SwingElement. Pour mettre en majuscule, il faut récupérer le Component JLabel de SwingElement, pour cela modifier le code de la méthode applyCSSProperty de CSSPropertyTooltipHandlerImpl comme ceci :

public boolean applyCSSProperty(Object element, String property,
        CSSValue value, String pseudo, CSSEngine engine) throws Exception {
    Component component = SwingElementHelpers.getComponent(element);
    if (component != null) {
        System.out.println("Apply CSS property [name=" + property
                + ", value=" + value + "] to the component class="
                + component.getClass());
        return true;
    }
    return false;
}
La classe Helper SwingElementHelpers permet de retourner le Component Swing en fontion de l'instance element qui peut être soir un Component Swing soit une implémentation de CSSStylableElement .

Relancer le test, la console doit afficher le message suivant :

Apply CSS property [name=tooltip-uppercase, value=true] to the component 
    class=class javax.swing.JLabel
Ce test permet de montrer que l'instance component est un javax.swing.JLabel . La méthode applyCSSProperty retourne true si le component a pu être récupéré et false dans le cas contraire.

AbstractCSSPropertySwingHandler

TK-UI fournit la classe abstraite org.akrogen.tkui.css.swing.properties.AbstractCSSPropertySwingHandler qui permet de récuperer le Component Swing à partir d'un Object element et de gérer le retour de la méthode applyCSSproperty comme décrit ci dessus. Pour utiliser cette classe, modifier la classe CSSPropertyTooltipHandlerImpl comme ceci :

package com.mycompany.myapp.css.swing.properties;

import java.awt.Component;

import org.akrogen.tkui.css.core.dom.properties.ICSSPropertyHandler;
import org.akrogen.tkui.css.core.engine.CSSEngine;
import org.akrogen.tkui.css.swing.properties.AbstractCSSPropertySwingHandler;
import org.w3c.dom.css.CSSValue;

public class CSSPropertyTooltipHandlerImpl extends AbstractCSSPropertySwingHandler {

	public static final ICSSPropertyHandler INSTANCE = new CSSPropertyTooltipHandlerImpl();

	public void applyCSSProperty(Component component, String property,
	    CSSValue value, String pseudo, CSSEngine engine) throws Exception {
        System.out.println("Apply CSS property [name=" + property + ", value="
            + value + "] to the component class=" + component.getClass());
    }

	public String retrieveCSSProperty(Component element, String property,
	    CSSEngine engine) throws Exception {
        System.out.println("CSSPropertyTooltipHandlerImpl#retrieveCSSProperty");
        return null;
    }
}
Les méthodes applyCSSProperty et retrieveCSSProperty ont un Component Swing en paramètre au lieu d'un Object.

CSSValue/Get

A cette étape il faut récuperer la valeur true du CSSValue . Pour cela modifier le code de la méthode applyCSSProperty comme ceci :

public void applyCSSProperty(Component component, String property,
            CSSValue value, String pseudo, CSSEngine engine) throws Exception {
    boolean isUpperCase = false;
    if (value.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
        CSSPrimitiveValue primitiveValue = (CSSPrimitiveValue) value;
        isUpperCase = "true".equals(primitiveValue.getStringValue());
    }
    if (isUpperCase) {
        System.out.println("Apply CSS property [name=" + property
        + ", value=" + value + "] to the component class="
        + component.getClass());
    }
}
Le boolean isUpperCase est mis a jour. Relancer le test et le message doit s'afficher ce qui indique que le isUpperCase est a true.

CSSValue/Set

A cette étape il est maintenant possible de mettre en majuscule le tooltip du Component JLabel, pour cela modifier le code de la méthode applyCSSProperty de CSSPropertyTooltipHandlerImpl comme ceci :

public void applyCSSProperty(Component component, String property,
			CSSValue value, String pseudo, CSSEngine engine) throws Exception {
    boolean isUpperCase = false;
    if (value.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
        CSSPrimitiveValue primitiveValue = (CSSPrimitiveValue) value;
        isUpperCase = "true".equals(primitiveValue.getStringValue());
    }
    if (isUpperCase) {
        JComponent jComponent = (JComponent)component;
        String toolTipText = jComponent.getToolTipText();
        toolTipText = toolTipText.toUpperCase();
        jComponent.setToolTipText(toolTipText);
    }
}
Relancer le test, la tooltip du JLabel doit être mis en majuscule :

ICSSValueConverter Implementation

Il est possible d'utiliser un converter pour transformer une instance CSSValue en java.lang.Boolean en implémentant l'interface ICSSValueConverter . Ce concerter permet :

  • dans notre cas, de simplifier le code de conversion d'une instance CSSValue en java.lang.Boolean
  • de mettre en cache une instance java.lang.Boolean qui correspond à la valeur CSSValue (avec la valeur true). Dans notre cas, cette mise en cache n'a pas beaucoup d'interêt. Cette mise en cache est intéressante lorsque l'on souhaite convertir une instance CSSValue en instance java.awt.Color par exemple ce qui permet d'éviter d'instancier une multitude d'objets.
TK-UI fournit déja un converter CSSValueBooleanConverterImpl qui gère les java.lang.Boolean, mais nous allons crééer notre propre converter de Boolean pour apprendre à utiliser les converters.

Pour créer un converter, il est conseillé de partir de la classe AbstractCSSValueConverter qui implémente quelques méthodes dont la méthode getToType (qui indique le type renvoye du converter, dans notre cas ca sera java.lang.Boolean) qui est renseignée dans son constructeur.
Créer la classe MyCSSValueBooleanConverterImpl dans le package com.mycompany.myapp.css.converters comme ceci :

package com.mycompany.myapp.css.converters;

import org.akrogen.tkui.css.core.dom.properties.converters.AbstractCSSValueConverter;
import org.akrogen.tkui.css.core.dom.properties.converters.ICSSValueConverter;
import org.akrogen.tkui.css.core.dom.properties.converters.ICSSValueConverterConfig;
import org.akrogen.tkui.css.core.engine.CSSEngine;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.w3c.dom.css.CSSValue;

public class MyCSSValueBooleanConverterImpl extends AbstractCSSValueConverter {

    public static final ICSSValueConverter INSTANCE = new MyCSSValueBooleanConverterImpl();

    public MyCSSValueBooleanConverterImpl() {
        super(Boolean.class);
    }

    public Object convert(CSSValue value, CSSEngine engine, Object context)
            throws Exception {
        if (value.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
            CSSPrimitiveValue primitiveValue = (CSSPrimitiveValue) value;
            if ("true".equals(primitiveValue.getStringValue()))
                return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    public String convert(Object value, CSSEngine engine, Object context,
            ICSSValueConverterConfig config) throws Exception {
        if (value instanceof Boolean) {
            Boolean b = (Boolean) value;
            if (b.booleanValue())
                return "true";
        }
        return "false";
    }
}

ICSSValueConverter using

Pour utiliser cette classe converter vous pouvez l'utiliser directement dans la méthode applyCSSProperty comme ceci :

public void applyCSSProperty(Component component, String property,
            CSSValue value, String pseudo, CSSEngine engine) throws Exception {
    Boolean isUpperCase = (Boolean)MyCSSValueBooleanConverterImpl.INSTANCE.convert(value, engine, null);
    if (isUpperCase.booleanValue()) {
        JComponent jComponent = (JComponent)component;
        String toolTipText = jComponent.getToolTipText();
        toolTipText = toolTipText.toUpperCase();
        jComponent.setToolTipText(toolTipText);
    }
}

ICSSValueConverter/CSSEngine using

Il est cependant conseillé d'utiliser les converters en appelant la méthode convert du moteur CSS qui permet de rechercher dans un premier temps la valeur converti dans un cache (voir IResourcesRegistry ) avant d'appeler le converter adéquate.

Dans notre cas, cette mise en cache n'est pas très pertinente mais lorsque l'on souhaite manipulee des ressources comme Color, Font, la méthode convert de engien vérifie dans un premier temps dans le cache si une ressource a deja ete creée avec les valeurs de l'instance CSSValue avant d'appeler le converter qui lui creé une nouvelle instance d'une resource.

Pour utiliser notre converter à partir du CSSEngine, il faut l'enregistrer à l'aide de la methode registerCSSValueConverter comme ceci :

package com.mycompany.myapp.css.swing.engine;

import org.akrogen.tkui.css.swing.engine.CSSSwingEngineImpl;

import com.mycompany.myapp.css.converters.MyCSSValueBooleanConverterImpl;
import com.mycompany.myapp.css.properties.ICSSPropertyTooltipHandler;
import com.mycompany.myapp.css.swing.properties.CSSPropertyTooltipHandlerImpl;

public class MyCSSSwingEngineImpl extends CSSSwingEngineImpl {

    public MyCSSSwingEngineImpl() {
        super();
        super.registerCSSValueConverter(MyCSSValueBooleanConverterImpl.INSTANCE);
    }

    protected void initializeCSSPropertyHandlers() {
        super.initializeCSSPropertyHandlers();
        super.registerCSSProperty("tooltip-uppercase",
                ICSSPropertyTooltipHandler.class);
        super.registerCSSPropertyHandler(ICSSPropertyTooltipHandler.class,
                CSSPropertyTooltipHandlerImpl.INSTANCE);
    }
}

Vous pouvez ensuite appeler dans la méthode applyCSSProperty la méthode convert du CSS engine comme ceci :

public void applyCSSProperty(Component component, String property,
            CSSValue value, String pseudo, CSSEngine engine) throws Exception {
    Boolean isUpperCase = (Boolean)engine.convert(value, Boolean.class, null);
    if (isUpperCase.booleanValue()) {
        JComponent jComponent = (JComponent)component;
        String toolTipText = jComponent.getToolTipText();
        toolTipText = toolTipText.toUpperCase();
        jComponent.setToolTipText(toolTipText);
    }
}
Si vous avez activez les logs en debug (avec log4j) vous verrez le log suivant :
DEBUG [main] (SwingResourcesRegistry.java:59) - Cache Resource key=true
qui indique que le Boolean.TRUE a et mis en cache.