Contrairement a la majorite des fonctionnalites de GEF, le copier/coller se fait, comme son nom l'indique, en deux temps. Dans un premier temps, nous allons creer une commande qui representera la copie, ainsi qu'une action qui construira cette dite-commande lorsque le contexte est favorable. Dans un second temps, nous ferrons pareil pour le coller. Il nous faudra ensuite integrer ses actions dans notre application.

C'est parti et nous commencons donc par cette fameuse commande. Elle herite evidemment de Command et va memoriser dans une ArrayList des elements a copier, si ceux-ci sont utilisables dans ce contexte. Dans notre cas, nous avons decide de pouvoir copier, non seulement, les employes mais egalement les services.

public class CopyNodeCommand extends Command
{
        private ArrayList<Node> list = new ArrayList<Node>();
       
        public boolean addElement(Node node) {
                if (!list.contains(node)) {
                        return list.add(node);
                }
                return false;
        }
       
        @Override
        public boolean canExecute() {
                if (list == null || list.isEmpty())
                        return false;
                Iterator<Node> it = list.iterator();
                while (it.hasNext()) {
                        if (!isCopyableNode(it.next()))
                                return false;
                }
                return true;
        }
       
        @Override
        public void execute() {
                if (canExecute())
                        Clipboard.getDefault().setContents(list);
        }

        @Override
        public boolean canUndo() {
                return false;
        }
       
        public boolean isCopyableNode(Node node) {
                if (node instanceof Service || node instanceof Employe)
                        return true;
                return false;
        }
}

Comme le code ci-dessus le montre, on applique notre selection (notre ArrayList) au mecanisme interne de GEF via la methode statique Clipboard.getDefault().setContents(list). Nous verrons plus tard comment recuperer ces informations.

Maintenant que nous avons notre commande, il nous faut definir une action que nous integrerons a l'application. Celle-ci va construire notre commande, si le contexte nous est favorable. Nous avons essaye de simplifier l'action au maximum, et ainsi abstraire la complexite (si tant est que ca soit complique) dans la commande.

public class CopyNodeAction extends SelectionAction
{
        public CopyNodeAction(IWorkbenchPart part) {
                super(part);
                // force calculateEnabled() to be called in every context
                setLazyEnablementCalculation(true);
        }

        @Override
        protected void init() {
                super.init();
                ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
                setText("Copy");
                setId(ActionFactory.COPY.getId());
                setHoverImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
                setImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
                setDisabledImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED));
                setEnabled(false);
        }
       
        private Command createCopyCommand(List<Object> selectedObjects) {
                if (selectedObjects == null || selectedObjects.isEmpty()) {
                        return null;
                }

                CopyNodeCommand cmd = new CopyNodeCommand();
                Iterator<Object> it = selectedObjects.iterator();
                while (it.hasNext()) {
                        EditPart ep = (EditPart)it.next();
                        Node node = (Node)ep.getModel();
                        if (!cmd.isCopyableNode(node))
                                return null;
                        cmd.addElement(node);
                }
                return cmd;
        }
       
        @Override
        protected boolean calculateEnabled() {
                Command cmd = createCopyCommand(getSelectedObjects());
                if (cmd == null)
                        return false;
                return cmd.canExecute();
        }
       
        @Override
        public void run() {
                Command cmd = createCopyCommand(getSelectedObjects());
                if (cmd != null && cmd.canExecute()) {
                        cmd.execute();
                }              
        }
       
}

Dans un premier temps, il est a noter, dans la methode Init(), que nous allons charger les icones disponibles dans Eclipse. Ces icones seront visibles dans le menu contextuel mais egalement dans la toolbar (et, si nous en avions definis un, dans le menu globale de l'application).

Dans un second temps, notez que dans la methode run(), nous appelons directement la methode execute() de notre commande et non la methode execute() a qui nous pourrions passer la commande. Cela nous permet d'eviter d'integrer l'action de copier a la Command Stack de GEF; en effet, nous ne considerons pas qu'il soit pertinent de pouvoir undo/reo un copier (mais ca ne sera pas le cas pour le coller).

Copier, c'est sympa mais pouvoir coller ce que l'on vient de copier, c'est mieux, evidemment. Nous allons proceder de facon relativement similaire. Nous creeons une commande et une action, qui construira notre commande si le contexte est favorable a cela.

public class PasteNodeCommand extends Command
{
        private HashMap<Node, Node> list = new HashMap<Node, Node>();
       
        @Override
        public boolean canExecute() {
                ArrayList<Node> bList = (ArrayList<Node>) Clipboard.getDefault().getContents();
                if (bList == null || bList.isEmpty())
                        return false;
               
                Iterator<Node> it = bList.iterator();
                while (it.hasNext()) {
                        Node node = (Node)it.next();
                        if (isPastableNode(node)) {
                                list.put(node, null);
                        }
                }
                return true;
        }
       
        @Override
        public void execute() {
                if (!canExecute())
                        return ;
               
                Iterator<Node> it = list.keySet().iterator();
                while (it.hasNext()) {
                        Node node = (Node)it.next();
                        try {
                                if (node instanceof Service) {
                                        Service srv = (Service) node;         
                                        Service clone = (Service) srv.clone();
                                        list.put(node, clone);
                                }
                                else if (node instanceof Employe) {
                                        Employe emp = (Employe) node;
                                        Employe clone = (Employe) emp.clone();
                                        list.put(node, clone);
                                }
                        }
                        catch (CloneNotSupportedException e) {
                                e.printStackTrace();
                        }
                }
               
                redo();
        }
       
        @Override
        public void redo() {
                Iterator<Node> it = list.values().iterator();
                while (it.hasNext()) {
                        Node node = it.next();
                        if (isPastableNode(node)) {
                                node.getParent().addChild(node);
                        }
                }
        }
       
        @Override
        public boolean canUndo() {
                return !(list.isEmpty());
        }
       
        @Override
        public void undo() {
                Iterator<Node> it = list.values().iterator();
                while (it.hasNext()) {
                        Node node = it.next();
                        if (isPastableNode(node)) {
                                node.getParent().removeChild(node);
                        }
                }
        }
       
        public boolean isPastableNode(Node node) {
                if (node instanceof Service || node instanceof Employe)
                        return true;
                return false;
        }
}

On recupere le contenu du Clipboard, que nous avons defini dans la commande de copie. Ensuite, une HashMap est utilisee pour memoriser, en tant que clefs, l'objet copie source, et, en tant que valeurs, le clone genere a partir de la clef. On utilise ensuite la valeur pour recuperer son pere (qui est le meme pere que celui de la clef d'ailleurs car nos methodes clone() rappatrient egalement la "paternite", voir ci-dessous). On a besoin de memoriser les objets cres afin de pouvoir s'integrer au mecanisme d'undo/redo de GEF. Passons maintenant a l'action correspondante.

public class PasteNodeAction extends SelectionAction
{
        public PasteNodeAction(IWorkbenchPart part) {
                super(part);
                // force calculateEnabled() to be called in every context
                setLazyEnablementCalculation(true);
        }

        protected void init()
        {
                super.init();
                ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
                setText("Paste");
                setId(ActionFactory.PASTE.getId());
                setHoverImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE));
                setImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE));
                setDisabledImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE_DISABLED));
                setEnabled(false);
        }
       
        private Command createPasteCommand() {
                return new PasteNodeCommand();
        }

        @Override
        protected boolean calculateEnabled() {
                Command command = createPasteCommand();
        return command != null && command.canExecute();
        }

        @Override
        public void run() {
                Command command = createPasteCommand();
                if (command != null && command.canExecute())
                        execute(command);
        }
}

Rien de bien passionant ici. On cree une action qui construit notre commande. Passons maintenant a quelques details. Si vous etes attentifs, vous aurez remarque l'appel a la methode clone(). Celle-ci est generique et doit donc etre personnalise pour etre en mesure de retourner un nouvel objet (nouvelle instance) en tout point similaire a l'instance appelante. Deux clones ont, en toute logique, le meme pere, ce qui assure que la commande de coller fonctionnera correctement.

public class Employe extends Node {
        @Override
        public Object clone() throws CloneNotSupportedException {
                Employe emp = new Employe();
                emp.setName(this.getName());
                emp.setParent(this.getParent());
                emp.setPrenom(this.prenom);
                emp.setLayout(new Rectangle(getLayout().x + 10, getLayout().y + 10,
                                                                        getLayout().width, getLayout().height));
                return emp;
        }
}

Dans le cas d'un Service, nous voulons que la methode clone() nous retourne un nouveau Service ayant precisement les meme employes que son modele; il nous fallait donc egalement cloner les fils de l'objet. A noter le petit tweak sur la layout des Employes clones, qui nous permet de garder precisement la meme disposition a l'interieur de nos Services.

public class Service extends Node {
        @Override
        public Object clone() throws CloneNotSupportedException {
                Service srv = new Service();
                srv.setColor(this.color);
                srv.setEtage(this.etage);
                srv.setName(this.getName());
                srv.setParent(this.getParent());
                srv.setLayout(new Rectangle(
                                getLayout().x + 10, getLayout().y + 10,
                                getLayout().width, getLayout().height));
               
                Iterator<Node> it = this.getChildrenArray().iterator();
                while (it.hasNext()) {
                        Node node = it.next();
                        if (node instanceof Employe) {
                                Employe child = (Employe)node;
                                Node clone = (Node)child.clone();
                                srv.addChild(clone);
                                clone.setLayout(child.getLayout());
                        }
                }
                return srv;
        }
}

Maintenant que nos actions (et leurs commandes relatives) sont implementees, il nous faut maintenant les integrer a notre application. Tout d'abord, on ajoute les actions dans la toolbar via MyGraphicalEditorActionBarContributor.

public class MyGraphicalEditorActionBarContributor extends ActionBarContributor
{
        protected void buildActions() {
                IWorkbenchWindow iww = getPage().getWorkbenchWindow();
                // ...
            addRetargetAction((RetargetAction)ActionFactory.COPY.create(iww));
            addRetargetAction((RetargetAction)ActionFactory.PASTE.create(iww));
                // ...
        }
        // ...
        public void contributeToToolBar(IToolBarManager toolBarManager)
        {          
                // ...
                toolBarManager.add(getAction(ActionFactory.COPY.getId()));
                toolBarManager.add(getAction(ActionFactory.PASTE.getId()));
                // ...
        }
}

Ensuite, il nous faut ajouter nos actions par les control de notre editeur. Cela se fait, bien sur, dans MyGraphicalEditor comme suit:

public class MyGraphicalEditor extends GraphicalEditorWithPalette
{
        // ...
        protected class OutlinePage extends ContentOutlinePage {
                public void createControl(Composite parent) {
                        // ...
                        IActionBars bars = getSite().getActionBars();
                        ActionRegistry ar = getActionRegistry();
                        // ...
                        bars.setGlobalActionHandler(ActionFactory.COPY.getId(), ar.getAction(ActionFactory.COPY.getId()));
                        bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), ar.getAction(ActionFactory.PASTE.getId()));
                        // ...
                }
                // ...
        }
        // ... 
        public void createActions() {
                super.createActions();
               
                ActionRegistry registry = getActionRegistry();
                // ...
                action = new CopyNodeAction(this);
                registry.registerAction(action);
                getSelectionActions().add(action.getId());
               
                action = new PasteNodeAction(this);
            registry.registerAction(action);
            getSelectionActions().add(action.getId());
        }
}

Ne partez pas, c'est pas encore fini ! Derniere petite chose a faire, s'assurer que les raccourcis clavier vont bien etre actifs et surtout marches. Pour cela, il nous faut enregistrer les actions dans l'ApplicationActionBarAdvisor.

public class ApplicationActionBarAdvisor extends ActionBarAdvisor
{
    public ApplicationActionBarAdvisor(IActionBarConfigurer configurer) {
        super(configurer);
    }

    protected IWorkbenchAction makeAction(IWorkbenchWindow window, ActionFactory af) {
        IWorkbenchAction action = af.create(window);
        register(action);
        return action;
    }
   
    protected void makeActions(IWorkbenchWindow window) {
        makeAction(window, ActionFactory.UNDO);
        makeAction(window, ActionFactory.REDO);
        makeAction(window, ActionFactory.COPY);
        makeAction(window, ActionFactory.PASTE);
    }

    protected void fillMenuBar(IMenuManager menuBar) {
    }
}

Oupla ! Il ne nous reste plus qu'a tester le tout.


Vous pouvez telecharger l'archive du tuto ICI
Tutorial Redige par Romain Meson