Tutorial: GEF (Graphical Editing Framework) [Part 13]
Par Jean Charles MAMMANA, lundi 20 août 2007 à 14:31 :: Programmation :: #31 :: rss
Apres avoir vu comment integrer le drag-and-drop, il est temps pour nous de nous pencher sur une autre fonctionnalite indispensable dans toute interface qui se respecte de nos jours : le copier/coller. C'est la troisieme et derniere methode, apres l'insertion via la palette et le drag-and-drop, permettant de creer graphiquement de nouveaux objets.
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;
}
}
{
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();
}
}
}
{
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;
}
}
{
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);
}
}
{
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;
}
}
@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;
}
}
@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()));
// ...
}
}
{
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());
}
}
{
// ...
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) {
}
}
{
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

Commentaires
1. Le dimanche 30 septembre 2007 à 14:03, par Xav
2. Le dimanche 30 septembre 2007 à 23:17, par Psykokwak
3. Le lundi 01 octobre 2007 à 00:11, par Xav
4. Le lundi 01 octobre 2007 à 09:59, par tups
5. Le mercredi 03 octobre 2007 à 11:01, par Rom One
6. Le mardi 09 octobre 2007 à 07:25, par Rom1
7. Le mercredi 17 octobre 2007 à 10:17, par Xav
8. Le mercredi 17 octobre 2007 à 11:40, par Psykokwak
9. Le mercredi 31 octobre 2007 à 11:58, par Rom1
10. Le mercredi 27 février 2008 à 17:20, par Makaveli
11. Le mercredi 18 juin 2008 à 21:01, par vince
12. Le mardi 16 septembre 2008 à 17:43, par revo
13. Le vendredi 07 août 2009 à 09:53, par Asari
14. Le vendredi 23 octobre 2009 à 21:26, par jogri13
Ajouter un commentaire