I. Introduction

Dans cet article, nous étudierons comment mettre en place un système d'autocomplétion similaire à ce qui existe de base dans la plateforme Eclipse pour correspondre aux besoins spécifiques d'une application. Le principe d'autocomplétion dans Eclipse peut se découper en deux principales catégories : l'autocomplétion dans les éditeurs ou l'autocomplétion dans les composants SWT/JFace tels que les champs texte, comme le montrent les captures d'écran ci-dessous :

Autocomplétion dans un éditeur Java
Autocomplétion dans un éditeur Java
Autocomplétion sur un champ texte
Autocomplétion sur un champ texte

Cet article nécessite de posséder des connaissances sur :

II. L'autocomplétion sur des composants SWT

Dans cette partie nous détaillerons la mise en place de l'autocomplétion sur des champs texte SWT. Ce mécanisme est le plus simple à mettre en place tout en permettant de fournir rapidement aux utilisateurs de l'application une liste de propositions. Ce type d'assistant peut se mettre en place soit sur des champs texte soit sur des listes déroulantes. Cet assistant est déclenché soit par une combinaison de touches (par exemple le célèbre Ctrl+Espace), soit par une liste de caractères prédéfinis. Tout d'abord, nous allons mettre en œuvre ce mécanisme sur un exemple très simple.

II-A. Un premier exemple simple

Créez un nouveau plugin, avec une unique vue contenant un champ texte. Le contenu de la vue est donné ci-dessous. L'intégralité du comportement de l'assistant de contenu est défini dans la méthode « constructFieldAssist » :

ContentAssistView.java
CacherSélectionnez
  1. package com.abernard.contentassist.swt; 
  2.  
  3. import org.eclipse.core.runtime.Status; 
  4. import org.eclipse.jface.bindings.keys.KeyStroke; 
  5. import org.eclipse.jface.bindings.keys.ParseException; 
  6. import org.eclipse.jface.fieldassist.ContentProposalAdapter; 
  7. import org.eclipse.jface.fieldassist.ControlDecoration; 
  8. import org.eclipse.jface.fieldassist.FieldDecorationRegistry; 
  9. import org.eclipse.jface.fieldassist.SimpleContentProposalProvider; 
  10. import org.eclipse.jface.fieldassist.TextContentAdapter; 
  11. import org.eclipse.swt.widgets.Composite; 
  12. import org.eclipse.ui.part.ViewPart; 
  13. import org.eclipse.ui.statushandlers.StatusManager; 
  14. import org.eclipse.swt.graphics.Image; 
  15. import org.eclipse.swt.layout.GridLayout; 
  16. import org.eclipse.swt.widgets.Label; 
  17. import org.eclipse.swt.SWT; 
  18. import org.eclipse.swt.widgets.Text; 
  19. import org.eclipse.swt.layout.GridData; 
  20.  
  21. import com.abernard.contentassist.Activator; 
  22.  
  23. /** 
  24.  * Cette vue met en oeuvre un assistant de contenu basique sur un composant SWT. 
  25.  * @author A. BERNARD 
  26.  * 
  27.  */ 
  28. public class ContentAssistView extends ViewPart { 
  29.  
  30.     private Text textAssist; 
  31.  
  32.     @Override 
  33.     public void createPartControl(Composite parent) { 
  34. 	parent.setLayout(new GridLayout(2, false)); 
  35.  
  36. 	Label label = new Label(parent, SWT.NONE); 
  37. 	label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 
  38. 	label.setText("Enter your text here:"); 
  39.  
  40. 	// Definition du champ texte ou l'assistant de contenu sera propose 
  41. 	textAssist = new Text(parent, SWT.BORDER); 
  42. 	textAssist.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 
  43.  
  44. 	// Creation d'un indicateur sur le champ texte 
  45. 	ControlDecoration deco = new ControlDecoration(textAssist, SWT.TOP 
  46. 		| SWT.LEFT); 
  47. 	// L'image utilisee est l'image standard pour les decorateurs informatifs 
  48. 	Image image = FieldDecorationRegistry.getDefault() 
  49. 		.getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION) 
  50. 		.getImage(); 
  51. 	deco.setDescriptionText("Utilisez CTRL + ESPACE pour voir la liste des mots-cles."); 
  52. 	deco.setImage(image); 
  53. 	deco.setShowOnlyOnFocus(true); // Ne montrer l'indicateur que lorsque le champ texte a le 
  54. 		focus 
  55.  
  56. 	// Construction de l'assistant de contenu 
  57. 	constructFieldAssist(); 
  58.  
  59.     } 
  60.  
  61.     /** 
  62.      * Construit l'assistant de contenu sur le champ texte 
  63.      */ 
  64.     private void constructFieldAssist() { 
  65. 	// Definition des caracteres d'activation automatique 
  66. 	char[] autoActivationCharacters = new char[] { '_' }; 
  67. 	try { 
  68. 	    // Definition du raccourci clavier d'activation 
  69. 	    KeyStroke keyStroke = KeyStroke.getInstance("Ctrl+Space"); 
  70. 	    // Creation de l'assistant de contenu 
  71. 	    SimpleContentProposalProvider provider = new SimpleContentProposalProvider(new String[] 
  72. 		{ 
  73. 		    "Item_1", "Deuxieme_Item", "Item_n3" }); 
  74. 	    // Filtre les propositions suivant le contenu du champ texte  
  75. 	    provider.setFiltering(true); 
  76. 	    // Construit l'assistant de contenu 
  77. 	    ContentProposalAdapter adapter = new ContentProposalAdapter(textAssist, 
  78. 		    new TextContentAdapter(), provider, keyStroke, autoActivationCharacters); 
  79. 	    // Avec cette commande, le champ texte sera integralement remplace par le contenu 
  80. 		choisi 
  81. 	    adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); 
  82. 	} catch (ParseException e1) { 
  83. 	    StatusManager.getManager().handle(new Status(Status.ERROR, Activator.PLUGIN_ID,  
  84. 		    "Unable to create key stroke for content assist", e1), StatusManager.SHOW); 
  85. 	}  
  86.  
  87.     } 
  88.  
  89.     @Override 
  90.     public void setFocus() { 
  91. 	textAssist.setFocus(); 
  92.     } 
  93.  
  94. } 

Nous pouvons visualiser le résultat en lançant notre plugin dans une application Eclipse :

Assistant basique
Assistant basique

Nous pouvons aussi constater que lorsqu'on entre le caractère '_' les éléments apparaissent, filtrés :

Assistant basique avec filtre
Assistant basique avec filtre

Détaillons ensemble le contenu de la méthode « constructFieldAssist ».
En premier lieu, le tableau autoActivationChars nous permet de définir les caractères d'activation automatique. Nous définissons aussi un objet de type « KeyStroke » qui permet de définir le raccourci de déclenchement de l'assistant de contenu.
Nous instancions ensuite un « SimpleContentProposalProvider » qui permet de définir très simplement une liste de contenus à partir d'une liste de mots. Nous verrons par la suite comment définir un provider plus complexe.
Après cela nous créons le « ContentProposalAdapter » qui va mettre en place l'assistant de contenu proprement dit.
Enfin, nous devons indiquer par quel moyen le contenu validé sera inséré dans le champ texte : soit par le remplacement du contenu existant (ContentProposalAdapter.PROPOSAL_REPLACE), soit par insertion (ContentProposalAdapter.PROPOSAL_INSERT), soit pas de modification du champ texte (ContentProposalAdapter.PROPOSAL_IGNORE). Ce dernier cas est réservé aux champs dont le traitement de l'insertion est plus complexe et nécessite des actions spécifiques. On pourra être notifié de l'acceptation de l'assistant un ajoutant un « IContentProposalListener ». Ceci sera détaillé dans le prochain exemple.
En effet, le comportement basique que nous venons de décrire manque de raffinement pour des interfaces plus complexes, où l'information proposée nécessite de donner plus d'informations.

II-B. Un exemple plus complexe

Dans ce deuxième exemple, nous allons insérer des éléments de manière plus complexe. Mettons par exemple que nous voulons proposer une liste d'avions qui possèdent tous un certain nombre de propriétés : le nom de l'avion, l'avionneur, le nombre de membres d'équipage navigant, ainsi que le nombre de moteurs. À la manière d'Eclipse, nous souhaitons qu'un cadre affiche les informations détaillées sur les différents éléments disponibles à côté de la liste affichée.
La première étape consiste à définir des objets qui implémentent l'interface « IContentProposal ». C'est cette interface qui est utilisée par le mécanisme d'autocomplétion pour afficher les éléments dans la liste. Nous définissons par exemple notre propre implémentation, donnée ci-dessous, de cette interface pour nos avions :

PlaneProposal.java
CacherSélectionnez
  1. package com.abernard.contentassist; 
  2.  
  3. import org.eclipse.jface.fieldassist.IContentProposal; 
  4.  
  5. /** 
  6.  * Cette classe donne une description des elements a afficher dans  
  7.  * l'assistant de contenu.  
  8.  * @author A. BERNARD 
  9.  * 
  10.  */ 
  11. public class PlaneProposal implements IContentProposal { 
  12.  
  13.     private String name; 
  14.     private int crew; 
  15.     private int engines; 
  16.     private String manufacturer; 
  17.  
  18.     /** Constructeur 
  19.      * @param name le nom de l'avion 
  20.      * @param manufacturer l'avionneur 
  21.      * @param crew le nombre de membres d'équipage 
  22.      * @param engines le nombre de moteurs 
  23.      */ 
  24.     public PlaneProposal(String name, String manufacturer, int crew, int engines) { 
  25. 	this.name = name;  
  26. 	this.manufacturer = manufacturer; 
  27. 	this.crew = crew; 
  28. 	this.engines = engines; 
  29.  
  30.     } 
  31.  
  32.     @Override 
  33.     public String getContent() { 
  34. 	// Cette methode doit retourner le contenu a placer dans le champ texte 
  35. 	return name; 
  36.     } 
  37.  
  38.     @Override 
  39.     public int getCursorPosition() { 
  40. 	// Cette methode retourne la position que doit avoir le curseur apres insertion 
  41. 	return name.length(); 
  42.     } 
  43.  
  44.     @Override 
  45.     public String getLabel() { 
  46. 	// Le texte a afficher dans la liste des elements disponibles 
  47. 	return name + " (" + manufacturer + ")"; 
  48.     } 
  49.  
  50.     @Override 
  51.     public String getDescription() { 
  52. 	// Cette description est affichee dans l'encart a cote de l'assistant 
  53. 	return manufacturer + " " + name + " : \nEquipage : " + crew  
  54. 		+ "\nNombre de moteurs: " + engines; 
  55.     } 
  56.  
  57.     /** 
  58.      * Donne l'avionneur 
  59.      * @return 
  60.      */ 
  61.     public String getManufacturer() { 
  62. 	return manufacturer; 
  63.     } 
  64.  
  65.     /** 
  66.      * Donne le nombre de membres d'equipage 
  67.      * @return 
  68.      */ 
  69.     public int getCrew() { 
  70. 	return crew; 
  71.     } 
  72.  
  73.     /** 
  74.      * Donne le nombre de moteurs 
  75.      * @return 
  76.      */ 
  77.     public int getEngines() { 
  78. 	return engines; 
  79.     } 
  80.  
  81. } 

Nous pouvons ensuite définir l'interface graphique où nous mettrons notre assistant de contenu en place. Cette interface comporte simplement quatre champs texte destinés à afficher les différentes informations sur l'avion qui aura été sélectionné. Seul le premier champ pour le nom de l'avion proposera l'autocomplétion. En tout premier lieu, nous créons la liste des avions disponibles en instanciant pour chaque avion l'objet de type « PlaneProposal » idoine.

ContentAssistAdvanced.java
CacherSélectionnez
  1. package com.abernard.contentassist.swt; 
  2.  
  3. import java.util.ArrayList; 
  4. import java.util.List; 
  5.  
  6. import org.eclipse.core.runtime.Status; 
  7. import org.eclipse.jface.bindings.keys.KeyStroke; 
  8. import org.eclipse.jface.bindings.keys.ParseException; 
  9. import org.eclipse.jface.fieldassist.ContentProposalAdapter; 
  10. import org.eclipse.jface.fieldassist.ControlDecoration; 
  11. import org.eclipse.jface.fieldassist.FieldDecorationRegistry; 
  12. import org.eclipse.jface.fieldassist.IContentProposal; 
  13. import org.eclipse.jface.fieldassist.IContentProposalListener; 
  14. import org.eclipse.jface.fieldassist.IContentProposalProvider; 
  15. import org.eclipse.jface.fieldassist.TextContentAdapter; 
  16. import org.eclipse.swt.SWT; 
  17. import org.eclipse.swt.graphics.Image; 
  18. import org.eclipse.swt.layout.GridData; 
  19. import org.eclipse.swt.layout.GridLayout; 
  20. import org.eclipse.swt.widgets.Composite; 
  21. import org.eclipse.swt.widgets.Label; 
  22. import org.eclipse.swt.widgets.Text; 
  23. import org.eclipse.ui.part.ViewPart; 
  24. import org.eclipse.ui.statushandlers.StatusManager; 
  25.  
  26. import com.abernard.contentassist.Activator; 
  27. import com.abernard.contentassist.PlaneProposal; 
  28.  
  29. /** 
  30.  * Cette vue met en oeuvre un assistant de contenu plus avance sur un composant  
  31.  * SWT. 
  32.  * @author A. BERNARD 
  33.  * 
  34.  */ 
  35. public class ContentAssistAdvanced extends ViewPart { 
  36.  
  37.     private Text textAssist; 
  38.     private List<PlaneProposal> elements; 
  39.     private Text textManufacturer; 
  40.     private Text textCrew; 
  41.     private Text textEngines; 
  42.  
  43.     /** 
  44.      * Constructeur. Initialise la liste des elements disponibles 
  45.      */ 
  46.     public ContentAssistAdvanced() { 
  47. 	elements = new ArrayList<PlaneProposal>(); 
  48. 	elements.add(new PlaneProposal("A320", "Airbus", 2, 2)); 
  49. 	elements.add(new PlaneProposal("Rafale", "Dassault", 1, 1)); 
  50. 	elements.add(new PlaneProposal("Falcon 7x", "Dassault", 3, 3)); 
  51. 	elements.add(new PlaneProposal("B777", "Boeing", 4, 2)); 
  52. 	elements.add(new PlaneProposal("A380", "Airbus", 2, 4)); 
  53.     } 
  54.  
  55.     @Override 
  56.     public void createPartControl(Composite parent) { 
  57. 	parent.setLayout(new GridLayout(4, false)); 
  58.  
  59. 	Label labelPlane = new Label(parent, SWT.NONE); 
  60. 	labelPlane.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 
  61. 	labelPlane.setText("Plane name:"); 
  62.  
  63. 	// Definition du champ texte ou l'assistant de contenu sera propose 
  64. 	textAssist = new Text(parent, SWT.BORDER); 
  65. 	textAssist.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 
  66. 	// Contruction de l'assistant de contenu 
  67. 	constructFieldAssist(); 
  68.  
  69. 	// L'image utilisee est l'image standard pour les decorateurs informatifs 
  70. 	Image image = FieldDecorationRegistry.getDefault() 
  71. 		.getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION) 
  72. 		.getImage(); 
  73.  
  74.  
  75. 	// Creation d'un indicateur sur le champ texte 
  76. 	ControlDecoration deco = new ControlDecoration(textAssist, SWT.TOP 
  77. 		| SWT.LEFT); 
  78. 	deco.setDescriptionText("Utilisez CTRL + ESPACE pour voir la liste des mots-cles."); 
  79. 	deco.setImage(image); 
  80. 	deco.setShowOnlyOnFocus(true); 
  81.  
  82. 	// Creation des autres champs d'affichage 
  83. 	Label labelManufacturer = new Label(parent, SWT.NONE); 
  84. 	labelManufacturer.setText("Manufacturer:"); 
  85.  
  86. 	textManufacturer = new Text(parent, SWT.READ_ONLY); 
  87. 	textManufacturer.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 
  88.  
  89. 	Label labelCrew = new Label(parent, SWT.NONE); 
  90. 	labelCrew.setText("Crew:"); 
  91.  
  92. 	textCrew = new Text(parent, SWT.READ_ONLY); 
  93. 	textCrew.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 
  94.  
  95. 	Label labelEngines = new Label(parent, SWT.NONE); 
  96. 	labelEngines.setText("Engines:"); 
  97.  
  98. 	textEngines = new Text(parent, SWT.READ_ONLY); 
  99. 	textEngines.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 
  100.  
  101.  
  102.     } 
  103.  
  104.     /** 
  105.      * Construit l'assistant de contenu sur le champ texte 
  106.      */ 
  107.     private void constructFieldAssist() { 
  108. 	// Definition des caracteres d'activation automatique 
  109. 	char[] autoActivationCharacters = new char[] { 'A', 'B' }; 
  110. 	try { 
  111. 	    // Definition du raccourci clavier d'activation 
  112. 	    KeyStroke keyStroke = KeyStroke.getInstance("Ctrl+Space"); 
  113. 	    // Creation de l'assistant de contenu 
  114. 	    ContentProposalAdapter adapter = new ContentProposalAdapter(textAssist,  
  115. 		    new TextContentAdapter(), new PlaneContentProposalProvider(true),  
  116. 		    keyStroke, autoActivationCharacters); 
  117. 	    // Avec cette commande, le champ texte sera integralement remplace par le contenu 
  118. 		choisi 
  119. 	    adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); 
  120. 	    adapter.addContentProposalListener(new IContentProposalListener() { 
  121. 		@Override 
  122. 		public void proposalAccepted(IContentProposal proposal) { 
  123. 		    // On peut dans cette methode effectuer des traitements supplementaires 
  124. 		    //	 apres que la validation du contenu. 
  125. 		    if (proposal instanceof PlaneProposal) { 
  126. 			PlaneProposal planeProposal  = (PlaneProposal) proposal; 
  127. 			textManufacturer.setText(planeProposal.getManufacturer()); 
  128. 			textCrew.setText(Integer.toString(planeProposal.getCrew())); 
  129. 			textEngines.setText(Integer.toString(planeProposal.getEngines())); 
  130. 		    } 
  131. 		} 
  132. 	    }); 
  133. 	} catch (ParseException e1) { 
  134. 	    StatusManager.getManager().handle(new Status(Status.ERROR, Activator.PLUGIN_ID,  
  135. 		    "Unable to create key stroke for content assist", e1), StatusManager.SHOW); 
  136. 	}  
  137.  
  138.     } 
  139.  
  140.     @Override 
  141.     public void setFocus() { 
  142. 	textAssist.setFocus(); 
  143.  
  144.     } 
  145.  
  146.  
  147.     /** 
  148.      * Cette classe fournit les elements a afficher a l'assistant de contenu 
  149.      * @author A. BERNARD 
  150.      * 
  151.      */ 
  152.     public class PlaneContentProposalProvider implements IContentProposalProvider { 
  153.  
  154. 	private boolean filterProposals = false; 
  155.  
  156. 	/** 
  157. 	 * Constructeur 
  158. 	 * @param filter <code>true</code> si les elements doivent etre filtres  
  159. 	 *  
  160. 	 */ 
  161. 	public PlaneContentProposalProvider(boolean filter) { 
  162. 	    this.filterProposals = filter; 
  163. 	} 
  164.  
  165. 	@Override 
  166. 	public IContentProposal[] getProposals(String contents, int position) { 
  167. 	    if (filterProposals) { 
  168. 		// Effectuer un filtrage s'il est active: les elements a afficher 
  169. 		//   sont ceux qui correspondent au texte deja entre 
  170. 		ArrayList<IContentProposal> list = new ArrayList<IContentProposal>(); 
  171. 		for (IContentProposal proposal : elements) { 
  172. 		    if (proposal.getContent().startsWith(contents)) { 
  173. 			list.add(proposal); 
  174. 		    } 
  175. 		} 
  176. 		return list.toArray(new IContentProposal[list.size()]); 
  177. 	    } 
  178. 	    return elements.toArray(new IContentProposal[elements.size()]); 
  179. 	} 
  180.     } 
  181.  
  182. } 

Afin de construire correctement la liste des avions à proposer à l'utilisateur, nous effectuons un filtrage sur les éléments à afficher. Ce filtrage est fait au travers de la classe « PlaneContentProposalProvider ». Grâce à la définition de la méthode « getDescription » de la classe « PlaneProposal », le cadre jaune affiché à côté de la liste affiche bien la description de nos éléments :

Description du contenu
Description du contenu

De plus, afin de pouvoir remplir les champs texte lorsque l'utilisateur valide une proposition, nous ajoutons un écouteur grâce à la méthode « addContentProposalListener(IContentProposalListener) » :

 
Sélectionnez
adapter.addContentProposalListener(new IContentProposalListener() {
		@Override
		public void proposalAccepted(IContentProposal proposal) {
		    // Ici le traitement particulier.
		}
	    });
Remplissage des champs texte
Remplissage des champs texte

Ces deux exemples nous ont permis de voir rapidement comment mettre en place un assistant de contenu sur des champs texte ou des listes déroulantes. Notons qu'il est possible de construire ses assistants sur n'importe quel type de composant SWT, en redéfinissant soi-même une implémentation de l'interface « IControlContentAdapter » et en l'utilisant en lieu et place du « TextContentAdapter ».
La mise en place de l'assistant de contenu sur des champs texte reste très simple. Celle pour des éditeurs est plus complexe, mais offre bien davantage de possibilités.

III. Autocomplétion dans les éditeurs

III-A. Éditeurs de texte classiques

De la même manière que l'autocomplétion pour les fichiers de code Java, il est possible de mettre en place un assistant de contenu dans un éditeur personnalisé. Nous expliquons ici comment mettre en place un tel assistant, toujours en se basant sur le même exemple que dans le paragraphe précédent : une liste d'avions. Dans cet exemple, nous nous baserons sur un éditeur de texte très classique qui hérite directement de la classe « AbstractDecoratedTextEditor ».

III-A-1. Mise en place de l'éditeur

Pour commencer à mettre en place l'autocomplétion, nous devons créer notre éditeur « squelette ». Pour cela, vérifier que vous avez dans les dépendances de votre plugin, les plugins suivants :

  • org.eclipse.jface.text ;
  • org.eclipse.ui.editors ;
  • org.eclipse.core.resources ;
  • org.eclipse.ui.ide.

Il faut ensuite créer l'éditeur. Il dérive directement de la classe « AbstractDecoratedTextEditor ». Dans cet éditeur, nous devons créer un objet de type « TextSourceViewerConfiguration » :

AircraftsEditor.java
CacherSélectionnez
  1. package com.abernard.contentassist.editor; 
  2.  
  3. import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor; 
  4.  
  5. /** 
  6.  * Cet editeur affiche du texte brut. Il propose une autocompletion sur une  
  7.  * liste de mots-cles. 
  8.  * @author A. BERNARD 
  9.  * 
  10.  */ 
  11. public class AircraftsEditor extends AbstractDecoratedTextEditor { 
  12.  
  13.     /** 
  14.      * Constructeur. Definit la configuration de l'editeur. 
  15.      */ 
  16.     public AircraftsEditor() { 
  17. 	super(); 
  18. 	this.setSourceViewerConfiguration(new AircraftSourceViewerConfiguration(getSharedColors(),  
  19. 		getPreferenceStore())); 
  20.     } 
  21.  
  22. } 
AircraftSourceViewerConfiguration.java
CacherSélectionnez
  1. package com.abernard.contentassist.editor; 
  2.  
  3.  
  4. import org.eclipse.jface.preference.IPreferenceStore; 
  5. import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter; 
  6. import org.eclipse.jface.text.source.ISharedTextColors; 
  7. import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; 
  8.  
  9. /** 
  10.  * Cette configuration de l'editeur permet d'activer l'autocompletion sur le  
  11.  * texte en cours de saisie. 
  12.  * @author A. BERNARD 
  13.  * 
  14.  */ 
  15. public class AircraftSourceViewerConfiguration extends 
  16. 	TextSourceViewerConfiguration { 
  17.  
  18.     /** 
  19.      * Constructeur. 
  20.      * @param sharedColors 
  21.      * @param preferenceStore 
  22.      */ 
  23.     public AircraftSourceViewerConfiguration(final ISharedTextColors sharedColors, 
  24. 	    IPreferenceStore preferenceStore) { 
  25. 	super(preferenceStore); 
  26.     } 
  27. } 

À ce stade, si vous lancez votre application Eclipse, vous devriez pouvoir ouvrir des fichiers dans l'éditeur défini.

Dans le fichier « plugin.xml », vous devez déclarer à quelle extension de fichier est associé votre éditeur. Nous avons choisi dans cet exemple l'extension totalement arbitraire « *.ac ».

III-A-2. Mise en place de l'autocomplétion

Nous pouvons maintenant mettre en place l'autocomplétion dans notre éditeur. Cela passe par deux étapes : en premier nous devons définir une classe qui implémente l'interface « IContentAssistProcessor », puis indiquer à notre classe « AircraftSourceViewerConfiguration » d'indiquer cette classe pour la gestion de l'autocomplétion. Créons tout d'abord la classe « AircraftCompletionProcessor » :

AircraftCompletionProcessor.java
CacherSélectionnez
  1. package com.abernard.contentassist.editor; 
  2.  
  3. import java.util.ArrayList; 
  4. import java.util.List; 
  5.  
  6. import org.eclipse.core.runtime.Status; 
  7. import org.eclipse.jface.text.BadLocationException; 
  8. import org.eclipse.jface.text.IDocument; 
  9. import org.eclipse.jface.text.ITextViewer; 
  10. import org.eclipse.jface.text.contentassist.CompletionProposal; 
  11. import org.eclipse.jface.text.contentassist.ContextInformation; 
  12. import org.eclipse.jface.text.contentassist.ContextInformationValidator; 
  13. import org.eclipse.jface.text.contentassist.ICompletionProposal; 
  14. import org.eclipse.jface.text.contentassist.IContentAssistProcessor; 
  15. import org.eclipse.jface.text.contentassist.IContextInformation; 
  16. import org.eclipse.jface.text.contentassist.IContextInformationValidator; 
  17. import org.eclipse.ui.statushandlers.StatusManager; 
  18.  
  19. import com.abernard.contentassist.Activator; 
  20. import com.abernard.contentassist.PlaneProposal; 
  21.  
  22.  
  23. /** 
  24.  * Cree la liste des elements a afficher dans l'assistant de contenu de l'editeur. 
  25.  * @author A. BERNARD 
  26.  * 
  27.  */ 
  28. public class AircraftCompletionProcessor implements IContentAssistProcessor { 
  29.  
  30.     private List<PlaneProposal> elements; 
  31.  
  32.     /** 
  33.      * Constructeur. Initialise la liste des elements disponibles 
  34.      */ 
  35.     public AircraftCompletionProcessor() { 
  36. 	elements = new ArrayList<PlaneProposal>(); 
  37. 	elements.add(new PlaneProposal("A320", "Airbus", 2, 2)); 
  38. 	elements.add(new PlaneProposal("Rafale", "Dassault", 1, 1)); 
  39. 	elements.add(new PlaneProposal("Falcon 7x", "Dassault", 3, 3)); 
  40. 	elements.add(new PlaneProposal("B777", "Boeing", 4, 2)); 
  41. 	elements.add(new PlaneProposal("A380", "Airbus", 2, 4)); 
  42.     } 
  43.  
  44.     @Override 
  45.     public char[] getCompletionProposalAutoActivationCharacters() { 
  46. 	return new char[] {'A', 'B'}; 
  47.     } 
  48.  
  49.     @Override 
  50.     public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { 
  51. 	IDocument document = viewer.getDocument(); 
  52. 	int currOffset = offset-1; 
  53. 	String currWord = ""; 
  54. 	if (currOffset >= 0) { 
  55. 	    try { 
  56. 		char currChar; 
  57. 		/* 
  58. 		 * Retrouver le debut du mot sur lequel on a declenche la completion 
  59. 		 * On se deplace d'offset en offset jusqu'a rencontrer un caractere  
  60. 		 * de type 'whitespace' (espace, ou retour a la ligne, ...). 
  61. 		 * Chaque caractere est ajoute au debut du mot en cours de lecture 
  62. 		 */ 
  63. 		while (currOffset >= 0 && !Character.isWhitespace(currChar = document 
  64. 			.getChar(currOffset))) { 
  65. 		    currWord = currChar + currWord; 
  66. 		    currOffset--; 
  67. 		} 
  68. 		// Une fois le mot reconstruit, retrouver les elements a proposer 
  69. 		List<PlaneProposal> availableSuggests = getAvailableElements(currWord); 
  70. 		// A partir des elements disponibles construire la liste des propositions 
  71. 		ICompletionProposal[] proposals = null; 
  72. 		if (availableSuggests.size() > 0) { 
  73. 		    proposals = buildProposals(availableSuggests, currWord, offset - 
  74. 			currWord.length()); 
  75. 		} 
  76. 		return proposals; 
  77. 	    } catch (BadLocationException e) { 
  78. 		StatusManager.getManager().handle(new Status(Status.ERROR, Activator.PLUGIN_ID, 
  79. 			e.getMessage(), e), StatusManager.LOG); 
  80. 		return null; 
  81. 	    } 
  82. 	} else { 
  83. 	    // Pas de texte dans le document, donc pas de completion disponible !  
  84. 	    return null; 
  85. 	} 
  86.     } 
  87.  
  88.     @Override 
  89.     public IContextInformation[] computeContextInformation( 
  90. 	    ITextViewer viewer, int offset) { 
  91. 	ContextInformation[] contextInfos = new ContextInformation[elements.size()]; 
  92. 	for (int i = 0; i < contextInfos.length; i++) { 
  93. 	    contextInfos[i] = new ContextInformation("Avion : " + elements.get(i).getContent(),  
  94. 		    elements.get(i).getContent()); 
  95. 	} 
  96.  
  97. 	return contextInfos; 
  98.     } 
  99.  
  100.     @Override 
  101.     public char[] getContextInformationAutoActivationCharacters() { 
  102. 	return null; 
  103.     } 
  104.  
  105.     @Override 
  106.     public String getErrorMessage() { 
  107. 	return "No completions found"; 
  108.     } 
  109.  
  110.  
  111.     @Override 
  112.     public IContextInformationValidator getContextInformationValidator() { 
  113. 	return new ContextInformationValidator(this); 
  114.     } 
  115.  
  116.     /** 
  117.      * Construit la liste des elements qui, peuvent etre inseres a cet endroit  
  118.      * dans l'editeur. 
  119.      * @param word le mot en cours d'ecriture dans l'editeur 
  120.      * @return la liste des elements disponibles 
  121.      */ 
  122.     private List<PlaneProposal> getAvailableElements(String word) { 
  123. 	List<PlaneProposal> availableItems = new ArrayList<PlaneProposal>(); 
  124. 	for (PlaneProposal proposal : elements) { 
  125. 	    if (proposal.getContent().startsWith(word)) { 
  126. 		availableItems.add(proposal); 
  127. 	    } 
  128. 	} 
  129. 	return availableItems; 
  130.     } 
  131.  
  132.     /** 
  133.      * Construit la liste des elements d'autocompletion a partir des elements  
  134.      * disponibles. 
  135.      * @param availableElements la liste des elements disponibles 
  136.      * @param replacedWord le mot a remplacer dans l'editeur 
  137.      * @param offset la position du curseur dans le document 
  138.      * @return la liste des suggestions d'autocompletion 
  139.      */ 
  140.     private ICompletionProposal[] buildProposals(List<PlaneProposal> availableElements, String 
  141. 	replacedWord, int offset) { 
  142. 	ICompletionProposal[] proposals = new ICompletionProposal[availableElements.size()]; 
  143. 	int index = 0; 
  144. 	// Create proposals from model elements. 
  145. 	for (PlaneProposal proposal : availableElements) { 
  146. 	    IContextInformation contextInfo = new ContextInformation(null, proposal.getContent()); 
  147. 	    proposals[index] = new CompletionProposal(proposal.getContent(), offset, 
  148. 		    replacedWord.length(), proposal.getContent().length(),  
  149. 		    null, proposal.getContent(),  
  150. 		    contextInfo, proposal.getDescription()); 
  151. 	    index++; 
  152. 	} 
  153. 	return proposals; 
  154.     } 
  155.  
  156. } 

Notez que, par facilité, nous utilisons la classe « PlaneProposal » que nous avons créée précédemment mais que cela n'est pas nécessaire. N'importe quelle classe pourra convenir. Le constructeur ne sert ici qu'à créer la liste des éléments disponibles. Dans un cas réel d'utilisation, les éléments disponibles seront probablement chargés dynamiquement à l'appel de l'autocomplétion. C'est en effet dans la méthode « public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) » que les éléments à afficher dans la popup d'autocomplétion doivent être créés. Cette méthode est appelée à chaque fois que l'utilisateur appuie sur la combinaison de touche « Ctrl+Espace » pour afficher les éléments disponibles.
En premier lieu, notre implémentation tente de retrouver le début de mot que nous sommes en train d'écrire. Pour cela, on lit les caractères un à un jusqu'à tomber sur un caractère d'espacement (espace, tabulation, retour à la ligne, etc.). Si on est au début du fichier, il n'y pas d'éléments disponibles (cas où offset = 0). Une fois reconstitué le mot en cours de saisie (« currWord »), on filtre la liste des éléments disponibles afin de trouver ceux compatibles avec le mot, au sein de la méthode « private List<PlaneProposal> getAvailableElements(String word) ». Les éléments disponibles sont simplement ceux qui commencent par « currWord ».
De là, nous pouvons construire les éléments à afficher dans le popup d'autocomplétion, au sein de la méthode « private ICompletionProposal[] buildProposals(List<PlaneProposal> availableElements, String replacedWord, int offset) ». Pour chaque élément disponible, nous devons instancier un élément de type « CompletionProposal ».
Remarquez que nous créons en plus un élément de type « ContextInformation ». Cet élément sera affiché dans un tooltip lors de la validation de la proposition choisie : Image non disponible. Notre méthode principale retourne la liste de propositions ainsi construite, permettant à la plateforme d'afficher le popup d'autocomplétion.

Ceci fait, nous devons activer le mécanisme d'autocomplétion dans notre classe « AircraftSourceViewerConfiguration » :

AircraftSourceViewerConfiguration.java
CacherSélectionnez
  1. package com.abernard.contentassist.editor; 
  2.  
  3. import org.eclipse.jface.preference.IPreferenceStore; 
  4. import org.eclipse.jface.text.DefaultInformationControl; 
  5. import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter; 
  6. import org.eclipse.jface.text.IDocument; 
  7. import org.eclipse.jface.text.IInformationControl; 
  8. import org.eclipse.jface.text.IInformationControlCreator; 
  9. import org.eclipse.jface.text.TextPresentation; 
  10. import org.eclipse.jface.text.contentassist.ContentAssistant; 
  11. import org.eclipse.jface.text.contentassist.IContentAssistant; 
  12. import org.eclipse.jface.text.source.ISharedTextColors; 
  13. import org.eclipse.jface.text.source.ISourceViewer; 
  14. import org.eclipse.swt.SWT; 
  15. import org.eclipse.swt.custom.StyleRange; 
  16. import org.eclipse.swt.widgets.Display; 
  17. import org.eclipse.swt.widgets.Shell; 
  18. import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; 
  19.  
  20. /** 
  21.  * Cette configuration de l'editeur permet d'activer l'autocompletion sur le  
  22.  * texte en cours de saisie. 
  23.  * @author A. BERNARD 
  24.  * 
  25.  */ 
  26. public class AircraftSourceViewerConfiguration extends 
  27. 	TextSourceViewerConfiguration { 
  28.  
  29.     private IInformationPresenter presenter; 
  30.  
  31.     /** 
  32.      * Constructeur. 
  33.      * @param sharedColors 
  34.      * @param preferenceStore 
  35.      */ 
  36.     public AircraftSourceViewerConfiguration(final ISharedTextColors sharedColors, 
  37. 	    IPreferenceStore preferenceStore) { 
  38. 	super(preferenceStore); 
  39.  
  40. 	presenter = new DefaultInformationControl.IInformationPresenter() { 
  41. 	    @Override 
  42. 	    public String updatePresentation(Display display, String hoverInfo, 
  43. 		    TextPresentation presentation, int maxWidth, int maxHeight) { 
  44. 		// Mise en gras du "titre" (reference et avionneur) 
  45. 		int firstColon = (hoverInfo.indexOf(':') != -1 ? hoverInfo.indexOf(':') : 0); 
  46. 		StyleRange title = new StyleRange(0, firstColon, null, null, SWT.BOLD | 
  47. 			SWT.ITALIC); 
  48. 		presentation.addStyleRange(title); 
  49. 		return hoverInfo; 
  50. 	    } 
  51. 	}; 
  52.     } 
  53.  
  54.     @Override 
  55.     public IContentAssistant getContentAssistant(final ISourceViewer sourceViewer) { 
  56. 	ContentAssistant assistant = new ContentAssistant(); 
  57. 	assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); 
  58. 	AircraftCompletionProcessor processor = new AircraftCompletionProcessor(); 
  59. 	assistant.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE); 
  60. 	assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer)); 
  61. 	// Insere automatiquement la seule possibilite si elle est unique 
  62. 	assistant.enableAutoInsert(true); 
  63. 	// Autorise l'"autoactivation", i.e. le declenchement sur des caracteres particuliers 
  64. 	assistant.enableAutoActivation(true); 
  65. 	// Affiche la ligne de statut en bas de la popup de l'assistant 
  66. 	assistant.setStatusLineVisible(true); 
  67. 	assistant.setStatusMessage("Available planes to insert"); 
  68. 	return assistant; 
  69.     } 
  70.  
  71.  
  72.     @Override 
  73.     public IInformationControlCreator getInformationControlCreator(ISourceViewer sourceViewer) { 
  74. 	return new IInformationControlCreator() { 
  75. 	    @Override 
  76. 	    public IInformationControl createInformationControl(Shell parent) { 
  77. 		return new DefaultInformationControl(parent, presenter); 
  78. 	    } 
  79. 	}; 
  80.     } 
  81.  
  82. } 

La déclaration de l'assistant de contenu est faite dans la méthode « public IContentAssistant getContentAssistant(final ISourceViewer sourceViewer) ». Il faut créer un élément de type « ContentAssistant », qui va déclencher les différents assistants disponibles suivant le type de contenu où l'on se trouve dans le document. Ainsi, il est nécessaire de configurer le partitionnement du document grâce à l'instruction « assistant.setDocumentPartitioning(Partioning) » et d'affecter ensuite nos différents éléments « IContentAssistProcessor » suivant le type de contenu grâce à l'instruction « assistant.setContentAssistProcessor(ContentAssistProcessor, ContentType) ». Dans notre cas, nous n'avons pas défini de partitionnement, notre assistant de contenu est donc configuré pour le type de contenu par défaut.
On peut ensuite configurer l'assistant grâce aux quelques méthodes utilisées dans notre exemple ici :

  • autoriser l'activation automatique sur certains caractères : « assistant.enableAutoActivation(true) » ;
  • insérer automatiquement la seule option disponible le cas échéant : « assistant.enableAutoInsert(true) » ;
  • afficher la barre de statut avec le texte idoine : « assistant.setStatusLineVisible(true) » accompagné de la méthode « assistant.setStatusMessage("texte") »;
  • permettre un assistant de contenu cyclique : « assistant.setRepeatedInvocationMode(true) ».

Enfin, nous devons aussi configurer la manière dont Eclipse va afficher les détails concernant les différentes propositions affichées (le cadre jaune affiché à côté de la liste). Pour cela, nous définissons notre variable « presenter » de type « IInformationPresenter ». Puis nous indiquons à la plateforme d'utiliser ce mode de présentation dans la méthode « getInformationControlCreator ».

Ce mode de rendu sera utilisé dans tous les tooltips affichés par votre éditeur, notamment par exemple pour les descriptions des erreurs de syntaxe dans les markers.

Nous pouvons lancer notre application et constater le résultat.

Notre assistant de contenu
Notre assistant de contenu

III-A-3. Mis en place des templates

Nous pouvons compléter notre assistant de contenu avec des templates. Les templates sont des éléments qui, activés par l'utilisateur, insèrent automatiquement dans l'éditeur un ensemble de contenu. La meilleure manière de définir les templates est de le faire via le fichier « plugin.xml ». Nous allons dans cet exemple créer un template qui insère tous les avions disponibles. La première étape est de définir une nouvelle extension pour le point d'extension « org.eclipse.ui.editors.templates ». Au sein de ce point d'extension, il faut définir un nouvel élément de type « contextType ». Dans notre cas, nous utilisons pour ce contexte celui par défaut de la plateforme.

Définition du contextType
Définition du contextType

Puis nous définissons le template proprement dit. Le template est associé au context type défini précédemment afin que la plateforme puisse déterminer dans quel cas proposer ce template. Dans ce template, le sous-élément « pattern » définit le contenu du template à proprement parler.

Définition du template
Définition du template

Pour afficher les templates, notre classe « AircraftCompletionProcessor » doit être une sous-classe de « TemplateCompletionProcessor ». Notre classe se trouve donc modifiée comme suit :

AircraftCompletionProcessor.java
CacherSélectionnez
  1. package com.abernard.contentassist.editor; 
  2.  
  3. import java.io.IOException; 
  4.  
  5. import org.eclipse.jface.text.templates.Template; 
  6. import org.eclipse.jface.text.templates.TemplateCompletionProcessor; 
  7. import org.eclipse.jface.text.templates.TemplateContextType; 
  8. import org.eclipse.swt.graphics.Image; 
  9. import org.eclipse.ui.editors.text.templates.ContributionContextTypeRegistry; 
  10. import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; 
  11.  
  12. import com.abernard.contentassist.Activator; 
  13. import com.abernard.contentassist.Constants; 
  14. // ... autres imports 
  15.  
  16.  
  17. /** 
  18.  * Cree la liste des elements a afficher dans l'assistant de contenu de l'editeur. 
  19.  * @author A. BERNARD 
  20.  * 
  21.  */ 
  22. public class AircraftCompletionProcessor extends TemplateCompletionProcessor { 
  23.  
  24.     private List<PlaneProposal> elements; 
  25.     private ContributionTemplateStore fTemplateStore; 
  26.     private ContributionContextTypeRegistry fRegistry; 
  27.  
  28.     /** 
  29.      * Constructeur. Initialise la liste des elements disponibles 
  30.      */ 
  31.     public AircraftCompletionProcessor() { 
  32. 	// ... construction de la liste des elements comme precedemment 
  33.  
  34. 	fRegistry= new ContributionContextTypeRegistry(); 
  35. 	fRegistry.addContextType(Constants.TEMPLATE_CONTEXT_TYPE); 
  36.  
  37. 	fTemplateStore= new ContributionTemplateStore(fRegistry,  
  38. 		Activator.getDefault().getPreferenceStore(), 
  39. 		Constants.PREF_TEMPLATE); 
  40. 	try { 
  41. 	    fTemplateStore.load(); 
  42. 	} catch (IOException x) { 
  43. 	    Activator.getDefault().getLog().log(new Status(Status.ERROR,  
  44. 		    Activator.PLUGIN_ID, x.getMessage(), x)); 
  45. 	} 
  46.     } 
  47.  
  48.     @Override 
  49.     public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { 
  50. 	ICompletionProposal[] proposals = super.computeCompletionProposals(viewer, offset); 
  51. 	IDocument document = viewer.getDocument(); 
  52. 	int currOffset = offset-1; 
  53. 	String currWord = ""; 
  54. 	if (currOffset >= 0) { 
  55. 	    try { 
  56. 		char currChar; 
  57. 		/* 
  58. 		 * Retrouver le debut du mot sur lequel on a declenche la completion 
  59. 		 * On se deplace d'offset en offset jusqu'a rencontrer un caractere  
  60. 		 * de type 'whitespace' (espace, ou retour a la ligne, ...). 
  61. 		 * Chaque caractere est ajoute au debut du mot en cours de lecture 
  62. 		 */ 
  63. 		while (currOffset >= 0 && !Character.isWhitespace(currChar = document 
  64. 			.getChar(currOffset))) { 
  65. 		    currWord = currChar + currWord; 
  66. 		    currOffset--; 
  67. 		} 
  68. 		// Une fois le mot reconstruit, retrouver les elements a proposer 
  69. 		List<PlaneProposal> availableSuggests = getAvailableElements(currWord); 
  70. 		// A partir des elements disponibles construire la liste des propositions 
  71. 		List<ICompletionProposal> proposalsList = new ArrayList<ICompletionProposal>(); 
  72. 		proposalsList.addAll(Arrays.asList(proposals)); 
  73. 		if (availableSuggests.size() > 0) { 
  74. 		    proposalsList.addAll(buildProposals(availableSuggests,  
  75. 			    currWord, offset - currWord.length())); 
  76. 		} 
  77. 		proposals = proposalsList.toArray(new ICompletionProposal[proposalsList.size()]); 
  78. 		return proposals; 
  79. 	    } catch (BadLocationException e) { 
  80. 		StatusManager.getManager().handle(new Status(Status.ERROR, Activator.PLUGIN_ID, 
  81. 			e.getMessage(), e), StatusManager.LOG); 
  82. 		return null; 
  83. 	    } 
  84. 	} else { 
  85. 	    // Pas de texte dans le document, donc pas de completion disponible !  
  86. 	    return proposals; 
  87. 	} 
  88.     } 
  89.  
  90.  
  91.     @Override 
  92.     protected TemplateContextType getContextType(ITextViewer viewer, 
  93. 	    IRegion region) { 
  94. 	return fRegistry.getContextType(Constants.TEMPLATE_CONTEXT_TYPE); 
  95.     } 
  96.  
  97.     @Override 
  98.     protected Image getImage(Template template) { 
  99. 	return Activator.getDefault().getImageRegistry().get(Constants.IMG_TEMPLATE); 
  100.     } 
  101.  
  102.     @Override 
  103.     protected Template[] getTemplates(String contextTypeId) { 
  104. 	return fTemplateStore.getTemplates(contextTypeId); 
  105.     } 
  106.  
  107.     // ... autres methodes implementees precedemment 
  108.  
  109. } 

La première étape consiste à indiquer quels templates Eclipse charger dans le contexte de notre éditeur. C'est pour cela que nous initialisons les attributs fTemplatesStore et fRegistry dans le constructeur de la classe. Le lien est fait entre le type de contexte que nous avons défini pour notre template dans le fichier « plugin.xml » et les préférences d'Eclipse. Il ne reste ensuite qu'à implémenter les trois méthodes « getContextType », « getImage » (optionnelle) et »getTemplates ».
À chaque appel de l'assistant de contenu, les templates idoines sont recherchés au sein des templates existant, en faisant tout simplement un appel à « super.computeCompletionProposals(viewer, offset) ». Nous les ajoutons ensuite à la liste des éléments affichés à l'utilisateur. En lançant notre application, nous pouvons observer la présence de notre template au sein de la liste de propositions, ainsi que l'insertion de ce template au sein de notre document.

Notre template dans l'assistant de contenu
Notre template dans l'assistant de contenu

III-B. Utilisation de Xtext

Le système que nous avons décrit dans ce paragraphe utilise les API bas niveau d'Eclipse. Si l'on ajoute à la gestion de l'assistant de contenu la gestion de la coloration syntaxique, de la vérification de syntaxe et de toutes les préférences associées, la mise en place de tous ces éléments peut devenir lourde sur des éditeurs complexes. Certains frameworks, tel Xtext permettent de mettre en place ces éléments de manière plus simple. Vous trouverez dans cet articlePersonnalisation de l'IDE généré par Xtext comment mettre en place l'autocomplétion dans Xtext.

IV. Conclusion

Nous avons vu dans cet article deux méthodes pour mettre en place un assistant de contenu dans une application Eclipse RCP : sur un composant SWT ou au sein d'un éditeur de texte. Bien que différentes par leur complexité et les possibilités qu'elles offrent, ces deux méthodes ont le même but : proposer à l'utilisateur de votre application un moyen simple et intuitif de connaître les différentes possibilités qui s'offrent à lui. Ces systèmes sont très appréciés et apporteront rapidement une réelle plus-value à vos interfaces.

V. Liens utiles

VI. Remerciements

Je tiens à remercier l'équipe de Developpez.com, particulièrement Marc et Mickaël, ainsi que Claude LELOUP pour sa relecture orthographique attentive.