Certaines parties de code sont un peu fastidieuses à créer mais en réalité assez simples et logiques.
Vous pouvez vous réferer à Cette page pour des explications plus complètes sur la page de propriétés.

Ajout de la couleur des services dans le modèle

Tout d'abord, avant d'entrer dans le vif du sujet, ajoutons la possibilité de modifier la couleur d'un service après qu'elle ait été choisie au hasard.

Dans la version précédente du tutorial, la couleur était choisie au hasard dans la Figure des services, ce qui veut dire que nous n'avions aucun contrôle dessus. Nous allons stocker la couleur du service dans une variable du modèle, et nous allons créer les accesseurs pour la lire et la modifier, ainsi que lancer un changement de propriété de couleur lors de la modification pour avertir les vues qu'elles doivent se mettre à jour.
Dans ServiceFigure, on modifie le constructeur pour ne plus décider de la couleur de fond, et on fixe la couleur du bord à noir :
public ServiceFigure() {
        XYLayout layout = new XYLayout();
        setLayoutManager(layout);

        labelName.setForegroundColor(ColorConstants.darkGray);
        add(labelName, ToolbarLayout.ALIGN_CENTER);
        setConstraint(labelName, new Rectangle(5, 17, -1, -1));
       
        labelEtage.setForegroundColor(ColorConstants.black);
        add(labelEtage, ToolbarLayout.ALIGN_CENTER);
        setConstraint(labelEtage, new Rectangle(5, 5, -1, -1));
       
        setForegroundColor(ColorConstants.black);
       
        setBorder(new LineBorder(1));
        setOpaque(true);
}


Puis dans Service, on ajoute la propriété de couleur, toujours initialisée aléatoirement au démarrage :
private Color color;

// ...

private Color createRandomColor() {
        /** Just for Fun :) **/
        return new Color(null,
                        (new Double(Math.random() * 128)).intValue() + 128 ,
                        (new Double(Math.random() * 128)).intValue() + 128 ,
                        (new Double(Math.random() * 128)).intValue() + 128 );
}

public Service() {
        this.color = createRandomColor();
}

public Color getColor() {
        return color;
}

public void setColor(Color color) {
        Color oldColor = this.color;
        this.color = color;

        // mise-à-jour des vues
        getListeners().firePropertyChange(PROPERTY_COLOR, oldColor, color);
}


Définir une source de propriétés


La plate-forme Eclipse utilise une fenêtre de propriétés standard qui réagit automatiquement aux changements de sélection, et qui met à jour l'affichage des propriétés des objets sélectionnés. Notre sélection est déjà représentée directement par les objets du modèle, ce sont eux que nous allons donc définir comme source de propriétés.

Il y a deux méthodes pour définir une source de propriétés :
  • Soit en implémentant directement l'interface IPropertySource dans l'objet du modèle
  • Soit en implémentant l'interface IAdaptable dans l'objet du modèle, et en retournant un objet implémentant IPropertySource dans la méthode getAdapter() lorsque le type IPropertySource est passé en argument. Cette méthode a l'avantage de mieux séparer le modèle et la source de propriétés, et c'est celle que nous allons implémenter dans Node, ce qui fait que tous les objets qui héritent de Node pourront avoir des propriétés (donc l'entreprise, les services et les employés).

    Node.java :
    public class Node implements IAdaptable
    {
            // mise en cache de la IPropertySource pour ne la créer qu'au premier appel de getAdapter()
            private IPropertySource propertySource = null;

            // ...
     
            @Override
            public Object getAdapter(Class adapter) {
                    if (adapter == IPropertySource.class) {
                            if (propertySource == null)
                                    propertySource = new NodePropertySource(this);
                            return propertySource;
                    }
                    return null;
            }
    }


    Ce code ne compile pas encore, c'est normal, il manque la classe NodePropertySource que nous allons créer maintenant et qui implémente IPropertySource. Nous allons découper sa description en plusieurs parties pour détailler chaque méthode. Mais avant, on va créer les identifiants uniques de propriétés, qui sont des champs statiques définis dans le modèle, de type quelconque (ici String).

    Définition des champs statiques dans Service :
            public static final String PROPERTY_COLOR = "ServiceColor";
            public static final String PROPERTY_FLOOR = "ServiceFloor";


    Ceux de Entreprise :
            public static final String PROPERTY_CAPITAL = "EntrepriseCapital";


    Puis Employe :
            public static final String PROPERTY_FIRSTNAME = "EmployePrenom";


    Profitons-en pour mettre à jour les listeners de propriétés dans les différents EditParts, afin que les vues se mettent à jour après tout changement de propriété éditable.
    Dans EntreprisePart : on ajoute la gestion de PROPERTY_RENAME et PROPERTY_CAPITAL.
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals(Node.PROPERTY_ADD)) refreshChildren();
                if (evt.getPropertyName().equals(Node.PROPERTY_REMOVE)) refreshChildren();
                if (evt.getPropertyName().equals(Node.PROPERTY_RENAME)) refreshVisuals();
                if (evt.getPropertyName().equals(Entreprise.PROPERTY_CAPITAL)) refreshVisuals();
            }


    La modification est similaire dans tous les EditPart du graphique et de l'arbre. Nous n'allons pas les énumérer ici mais elles sont bien-sûr incluses dans l'archive du tutoriel.

    Revenons maintenant a notre classe NodePropertySource.
    Tout d'abord, nous stockons l'objet du modèle pour pouvoir s'y référer plus tard.
    public class NodePropertySource implements IPropertySource {

            private Node node;
           
            public NodePropertySource(Node node) {
                    this.node = node;
            }


            /**
             * Returns the property value when this property source is used as a value. We can
             * return <tt>null</tt> here
             */

            @Override
            public Object getEditableValue() {
                    return null;
            }


    Dans ce tutoriel, nous avons choisi d'implémenter toutes les propriétés du modèle dans une seule classe, d'où les différents tests pour savoir si telle propriété est disponible ou non pour tel ou tel élément du modèle, mais il est tout à fait possible de créer une classe distincte pour chaque objet du modèle et de réimplémenter ces méthodes, ou encore d'implémenter IPropertySource directement dans tous les objets du modèle qui ont des propriétés.

    La méthode suivante renvoie des IPropertyDescriptor qui représentent chacun une propriété du modèle. Si la propriété est en lecture seule, un simple PropertyDescriptor suffit, le rôle des classes dérivées est de renvoyer à la feuille de propriété un CellEditor adapté pour éditer la cellule. Comme on ne veut pas pouvoir éditer le nom et le prénom des employés, on renvoie un PropertyDescriptor.
            @Override
            public IPropertyDescriptor[] getPropertyDescriptors() {
                    ArrayList<IPropertyDescriptor> properties = new ArrayList<IPropertyDescriptor>();
                    if (node instanceof Employe)
                            properties.add(new PropertyDescriptor(Node.PROPERTY_RENAME, "Name"));
                    else
                            properties.add(new TextPropertyDescriptor(Node.PROPERTY_RENAME, "Name"));
                    if (node instanceof Service) {
                            properties.add(new ColorPropertyDescriptor(Service.PROPERTY_COLOR, "Color"));
                            properties.add(new TextPropertyDescriptor(Service.PROPERTY_FLOOR, "Etage"));
                    }
                    else if (node instanceof Entreprise) {
                            properties.add(new TextPropertyDescriptor(Entreprise.PROPERTY_CAPITAL, "Capital"));
                    }              
                    else if (node instanceof Employe) {
                            properties.add(new PropertyDescriptor(Employe.PROPERTY_FIRSTNAME, "Prenom"));
                    }
                    return properties.toArray(new IPropertyDescriptor[0]);
            }


    Récupération de la valeur d'une propriété selon son identifiant.
            @Override
            public Object getPropertyValue(Object id) {
                    if (id.equals(Node.PROPERTY_RENAME))
                            return node.getName();
                    if (id.equals(Service.PROPERTY_COLOR))
                            // ColorCellEditor, renvoyé comme éditeur de cellule par ColorPropertyDescriptor,
                            // utilise la classe RGB de SWT pour lire et écrire une couleur.
                            return ((Service)node).getColor().getRGB();
                    if (id.equals(Service.PROPERTY_FLOOR))
                            return Integer.toString(((Service)node).getEtage());
                    if (id.equals(Entreprise.PROPERTY_CAPITAL))
                            return Integer.toString(((Entreprise)node).getCapital());
                    if (id.equals(Employe.PROPERTY_FIRSTNAME))
                            return (((Employe)node).getPrenom());
                    return null;
            }


            /**
             * Returns if the property with the given id has been changed since its initial default value.
             * We do not handle default properties, so we return <tt>false</tt>.
             */

            @Override
            public boolean isPropertySet(Object id) {
                    return false;
            }

            /**
             * Reset a property to its default value. Since we do not handle default properties, we do
             * nothing.
             */

            @Override
            public void resetPropertyValue(Object id) {
            }


    Enregistrement d'une valeur de propriété après que l'utilisateur l'ait éditée :
            @Override
            public void setPropertyValue(Object id, Object value) {
                    if (id.equals(Node.PROPERTY_RENAME))
                            node.setName((String)value);
                    else if (id.equals(Service.PROPERTY_COLOR)) {
                            Color newColor = new Color(null, (RGB)value);
                            ((Service)node).setColor(newColor);
                    }
                    else if (id.equals(Service.PROPERTY_FLOOR)) {
                            try {
                                    Integer floor = Integer.parseInt((String)value);
                                    ((Service)node).setEtage(floor);
                            }
                            catch (NumberFormatException e) { }
                    }
                    else if (id.equals(Entreprise.PROPERTY_CAPITAL)) {
                            try {
                                    Integer capital = Integer.parseInt((String)value);
                                    ((Entreprise)node).setCapital(capital);
                            }
                            catch (NumberFormatException e) { }
                    }
            }


    Intégration de la feuille de propriétés


    Voilà, Notre source de propriétés est prête à fournir les propriétés des objets du modèle. Seulement, nous n'avons pas encore intégré la feuille de propriétés dans la perspective Eclipse.
    Nous allons en fait procéder en deux étapes : D'abord on définit un "placeholder" (emplacement par défaut) pour cette feuille de propriété, puis on demande son affichage quand l'utilisateur double-clique sur un objet GEF.
    Commençons par définir l'emplacement de la fenêtre : ca se passe dans la définition de la perspective par défaut (Perspective.java) :
            public void createInitialLayout(IPageLayout layout) {
                    String editorArea = layout.getEditorArea();
                    layout.setEditorAreaVisible(true);
                   
                    IFolderLayout tabs = layout.createFolder(
                                    ID_TABS_FOLDER, IPageLayout.LEFT, 0.3f, editorArea);
                    tabs.addView(IPageLayout.ID_OUTLINE);
                    tabs.addPlaceholder(IPageLayout.ID_PROP_SHEET);
            }


    Dans la version précédente du tutoriel, l'outline était attaché directement à la fenêtre principale, maintenant nous avons un IFolderLayout (des onglets) auquel nous attachons les deux fenêtres, mais seul l'outline sera affiché au démarrage.
    Nous allons maintenant associer le double-clic sur un EditPart à l'ouverture ou la mise en avant de la feuille de propriétés, ce qui nous ramène quelques instants à GEF pour terminer cette partie du tutoriel.
    Le double-clic sur un GraphicalEditPart génère en fait une Request de type REQ_OPEN. C'est une requête qui n'est pas transmise aux EditPolicy et qui doit être traitée dans la méthode performRequest() de l'EditPart.
    Dans AppAbstractEditPart (Pour gérer le double-clic sur les éléments graphiques) :
            @Override
            public void performRequest(Request req) {
                    if (req.getType().equals(RequestConstants.REQ_OPEN)) {
                            try {
                                    IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                                    page.showView(IPageLayout.ID_PROP_SHEET);
                            }
                            catch (PartInitException e) {
                                    e.printStackTrace();
                            }
                    }
            }


    Cela suffit pour les EditPart qui héritent de AbstractGraphicalEditPart, car celui-ci renvoie un DragTracker pour gérer la sélection depuis la méthode getDragTracker(). Comme cette méthode ne renvoie rien dans AbstractTreeEditPart (pour l'arbre), il faut en plus l'implémenter pour gérer le double-clic dans l'arbre.

    Dans AppAbstractTreeEditPart :
            @Override
            public DragTracker getDragTracker(Request req) {
                    return new SelectEditPartTracker(this);
            }
           
            @Override
            public void performRequest(Request req) {
                    if (req.getType().equals(RequestConstants.REQ_OPEN)) {
                            try {
                                    IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                                    page.showView(IPageLayout.ID_PROP_SHEET);
                            }
                            catch (PartInitException e) {
                                    e.printStackTrace();
                            }
                    }
            }


    Voilà, le modèle est maintenant visualisable et éditable grâce à une feuille de propriétés.

    Vous pouvez télécharger le résultat ICI.
    Tutoriel rédigé par Jonathan Gramain.